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 |
No files matched your search
@@ -50,6 +50,7 @@ set(ENTITY_SOURCES_BASE
|
|||||||
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
|
source/game/entities/concrete/dust.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,6 +75,8 @@ set(APP_SOURCES
|
|||||||
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
|
||||||
@@ -88,6 +91,7 @@ set(APP_SOURCES
|
|||||||
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
|
||||||
@@ -112,12 +116,19 @@ set(APP_SOURCES
|
|||||||
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
|
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/map/paint_layer.cpp
|
||||||
source/game/levels/level_manager.cpp
|
source/game/levels/level_manager.cpp
|
||||||
# Mòdul editor (només activa contingut en Debug via `#ifdef _DEBUG`).
|
# Mòdul editor (només activa contingut en Debug via `#ifdef _DEBUG`).
|
||||||
# En Release el fitxer compila a un object buit i l'enllaçador no
|
# En Release els fitxers compilen a object buit i l'enllaçador no
|
||||||
# arrossega cap símbol del joc final.
|
# arrossega cap símbol del joc final.
|
||||||
source/game/editor/editor.cpp
|
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
|
||||||
@@ -345,6 +356,7 @@ 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
|
||||||
@@ -368,6 +380,7 @@ if(BUILD_TESTS)
|
|||||||
add_executable(test_whip_attack
|
add_executable(test_whip_attack
|
||||||
tests/test_whip_attack.cpp
|
tests/test_whip_attack.cpp
|
||||||
tests/whip_attack_player_stubs.cpp
|
tests/whip_attack_player_stubs.cpp
|
||||||
|
tests/hole_player_stubs.cpp
|
||||||
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
|
||||||
@@ -444,4 +457,21 @@ if(BUILD_TESTS)
|
|||||||
add_test(NAME game_fsm
|
add_test(NAME game_fsm
|
||||||
COMMAND test_game_fsm
|
COMMAND test_game_fsm
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
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()
|
||||||
@@ -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,5 +1,6 @@
|
|||||||
name: door
|
name: door
|
||||||
type: interactable
|
type: interactable
|
||||||
|
editable: true
|
||||||
sprite:
|
sprite:
|
||||||
path: door.gif
|
path: door.gif
|
||||||
size: [48, 32]
|
size: [48, 32]
|
||||||
|
|||||||
|
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
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
name: mummy
|
name: mummy
|
||||||
type: dynamic_hazard
|
type: dynamic_hazard
|
||||||
|
editable: true
|
||||||
sprite:
|
sprite:
|
||||||
path: mummy.gif
|
path: mummy.gif
|
||||||
size: [16, 32]
|
size: [16, 32]
|
||||||
@@ -18,26 +19,35 @@ collision:
|
|||||||
# jugador amb jump_z > height no col·lisiona amb la momia (la salta
|
# 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,
|
# 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.
|
# així que 24 deixa marge per esquivar amb un salt no perfecte.
|
||||||
height: 24
|
height: 12
|
||||||
flags:
|
flags:
|
||||||
collides: true
|
collides: true
|
||||||
interactable: false
|
interactable: false
|
||||||
# Strip de 12 frames en graella 4×3 (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ó
|
# Estat HURT: post-fuetada, fugint en línia recta. Si la direcció
|
||||||
# preferida (la del whip) està bloquejada, prova perpendiculars; si totes
|
# preferida (la del whip) està bloquejada, prova perpendiculars; si totes
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
name: torch
|
name: torch
|
||||||
type: decoration
|
type: decoration
|
||||||
|
editable: true
|
||||||
sprite:
|
sprite:
|
||||||
path: torch.gif
|
path: torch.gif
|
||||||
size: [16, 16]
|
size: [16, 16]
|
||||||
|
|||||||
@@ -44,9 +44,18 @@ keys:
|
|||||||
- id: editor_toggle
|
- id: editor_toggle
|
||||||
code: "9"
|
code: "9"
|
||||||
desc: "Entra/surt del mode editor (només Debug)"
|
desc: "Entra/surt del mode editor (només Debug)"
|
||||||
- id: editor_picker
|
- id: editor_tile_picker
|
||||||
code: "T"
|
code: "T"
|
||||||
desc: "Obri/tanca el tilepicker dins de l'editor"
|
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
|
- id: prev_room
|
||||||
code: "["
|
code: "["
|
||||||
desc: "Salta a la sala anterior del levels.yaml (només Debug)"
|
desc: "Salta a la sala anterior del levels.yaml (només Debug)"
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ input:
|
|||||||
editor_on: "Editor ON"
|
editor_on: "Editor ON"
|
||||||
editor_off: "Editor OFF"
|
editor_off: "Editor OFF"
|
||||||
editor_save_error: "Editor: error guardant el mapa"
|
editor_save_error: "Editor: error guardant el mapa"
|
||||||
|
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:
|
hud:
|
||||||
p1: "P1"
|
p1: "P1"
|
||||||
@@ -71,8 +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_p1: "Comandament P1"
|
||||||
gamepad_p2: "Comandament P2"
|
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"
|
||||||
@@ -104,3 +112,7 @@ menu:
|
|||||||
render_info_bottom: "Baix"
|
render_info_bottom: "Baix"
|
||||||
gamepad_none: "Cap"
|
gamepad_none: "Cap"
|
||||||
gamepad_disconnected: "Desconnectat"
|
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"
|
||||||
@@ -27,6 +27,9 @@ input:
|
|||||||
editor_on: "Editor ON"
|
editor_on: "Editor ON"
|
||||||
editor_off: "Editor OFF"
|
editor_off: "Editor OFF"
|
||||||
editor_save_error: "Editor: map save failed"
|
editor_save_error: "Editor: map save failed"
|
||||||
|
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:
|
hud:
|
||||||
p1: "P1"
|
p1: "P1"
|
||||||
@@ -57,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"
|
||||||
@@ -69,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"
|
||||||
@@ -98,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"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 1
|
pyramid: 1
|
||||||
room: 1
|
room: 1
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -49,24 +49,24 @@ geometry:
|
|||||||
# 16 px centrals (just aquesta columna) són el "forat" del mur trasser per
|
# 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
|
# 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.
|
# mur visible. El spawn dels jugadors també s'alinea amb aquesta columna.
|
||||||
door_col: 10
|
door_col: 11
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
@@ -82,8 +82,6 @@ data:
|
|||||||
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
# d'obertura (262/263). La momia al corredor central. L'antorxa al costat
|
||||||
# superior esquerre per verificar render d'una entitat decorativa.
|
# superior esquerre per verificar render d'una entitat decorativa.
|
||||||
entities:
|
entities:
|
||||||
- type: torch
|
|
||||||
position: [4, 2]
|
|
||||||
- type: torch
|
- type: torch
|
||||||
position: [8, 2]
|
position: [8, 2]
|
||||||
- type: torch
|
- type: torch
|
||||||
@@ -130,11 +128,14 @@ entities:
|
|||||||
sarc_position: [2, 4]
|
sarc_position: [2, 4]
|
||||||
- type: sarcophagus
|
- type: sarcophagus
|
||||||
sarc_position: [3, 4]
|
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
|
- 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]
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 1
|
pyramid: 1
|
||||||
room: 2
|
room: 2
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 1
|
pyramid: 1
|
||||||
room: 3
|
room: 3
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 1
|
pyramid: 1
|
||||||
room: 4
|
room: 4
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 1
|
pyramid: 1
|
||||||
room: 5
|
room: 5
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 1
|
pyramid: 1
|
||||||
room: 6
|
room: 6
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_1.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 2
|
pyramid: 2
|
||||||
room: 1
|
room: 1
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_2.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 2
|
pyramid: 2
|
||||||
room: 2
|
room: 2
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_2.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 2
|
pyramid: 2
|
||||||
room: 3
|
room: 3
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_2.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 2
|
pyramid: 2
|
||||||
room: 4
|
room: 4
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_2.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 2
|
pyramid: 2
|
||||||
room: 5
|
room: 5
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_2.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 2
|
pyramid: 2
|
||||||
room: 6
|
room: 6
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_2.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 3
|
pyramid: 3
|
||||||
room: 1
|
room: 1
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_3.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 3
|
pyramid: 3
|
||||||
room: 2
|
room: 2
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_3.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 3
|
pyramid: 3
|
||||||
room: 3
|
room: 3
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_3.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 3
|
pyramid: 3
|
||||||
room: 4
|
room: 4
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_3.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 3
|
pyramid: 3
|
||||||
room: 5
|
room: 5
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_3.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 3
|
pyramid: 3
|
||||||
room: 6
|
room: 6
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_3.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 4
|
pyramid: 4
|
||||||
room: 1
|
room: 1
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_4.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 4
|
pyramid: 4
|
||||||
room: 2
|
room: 2
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_4.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 4
|
pyramid: 4
|
||||||
room: 3
|
room: 3
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_4.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 4
|
pyramid: 4
|
||||||
room: 4
|
room: 4
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_4.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 4
|
pyramid: 4
|
||||||
room: 5
|
room: 5
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_4.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 4
|
pyramid: 4
|
||||||
room: 6
|
room: 6
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_4.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 5
|
pyramid: 5
|
||||||
room: 1
|
room: 1
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_5.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 5
|
pyramid: 5
|
||||||
room: 2
|
room: 2
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_5.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 5
|
pyramid: 5
|
||||||
room: 3
|
room: 3
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_5.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 5
|
pyramid: 5
|
||||||
room: 4
|
room: 4
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_5.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 5
|
pyramid: 5
|
||||||
room: 5
|
room: 5
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_5.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Mapa de la piràmide: graella 21×15 de tiles 16×16 sobre tomb.gif.
|
# 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
|
# 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
|
# 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.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
pyramid: 5
|
pyramid: 5
|
||||||
room: 6
|
room: 6
|
||||||
tileset: data/tilesets/tomb.gif
|
tileset: data/tilesets/pyramid_5.gif
|
||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
@@ -52,21 +52,21 @@ geometry:
|
|||||||
door_col: 10
|
door_col: 10
|
||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13]
|
||||||
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
- [11,30,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,31,13]
|
||||||
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
- [11,13,27,28,15,22,28,28,28,28,28,28,28,28,28,28,15,28, 6,11,13]
|
||||||
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
- [11,13, 8, 8,16, 8,17, 8,10, 8,17, 8, 8, 8,17, 8, 8, 8, 8,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
- [11,13,17, 8, 9, 2, 8,10, 8, 8, 8, 8,10, 8, 8,26, 8, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
- [11,13,17, 8, 8,26, 8, 8, 9, 8, 8, 8, 8, 8,10, 8,16, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2,16, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
- [11,13,17, 8,10, 8, 8,10, 2, 8, 8,26, 8, 8, 8, 8,10, 8,17,11,13]
|
||||||
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
- [11,13, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8,11,13]
|
||||||
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
- [11,13, 8,10,16,16,17, 8, 8, 8,17, 8, 9, 8,17, 8, 2, 8, 8,11,13]
|
||||||
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
- [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
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ origin: [-8, 0]
|
|||||||
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
# estigui definit; canviar aquí l'índex és tot el que cal. Omet el camp
|
||||||
# per desactivar el pintat per a aquest jugador.
|
# per desactivar el pintat per a aquest jugador.
|
||||||
player1_paint_tile: 0
|
player1_paint_tile: 0
|
||||||
player2_paint_tile: 4
|
player2_paint_tile: 1
|
||||||
|
|
||||||
# Física del contacte entre jugadors. Quan un jugador està quiet i l'altre
|
# 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
|
# el topa, el segon l'empenta a aquesta fracció de la velocitat normal (i
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -1,143 +1,104 @@
|
|||||||
# player animation
|
|
||||||
sprite:
|
sprite:
|
||||||
path: morcus.gif
|
path: morcus.gif
|
||||||
size: [16, 32]
|
size: [16, 32]
|
||||||
|
|
||||||
# Geometria de col·lisió del jugador (independent de l'animació):
|
# Geometria de col·lisió. `size` és la caixa contra murs/sarcs;
|
||||||
# size → costat del quadrat de col·lisió (16×16)
|
# `hit_size`, la caixa contra altres actors (més petita); `height`,
|
||||||
# base_size → costat del quadrat visual inferior del sprite (els "peus");
|
# l'alçada que un salt ha de superar per passar per damunt.
|
||||||
# s'usa per centrar la caixa de col·lisió sobre ell.
|
|
||||||
collision:
|
collision:
|
||||||
size: 16
|
size: 16
|
||||||
base_size: 16
|
base_size: 16
|
||||||
# Caixa de contacte actor-actor (jugador↔jugador, jugador↔momia/trampa).
|
|
||||||
# Més petita que `size` perquè els contactes d'acció siguin precisos;
|
|
||||||
# `size` es reserva per a la col·lisió amb murs/sarcòfags i per enfilar
|
|
||||||
# els passadissos. Centrada en el mateix punt que la caixa base.
|
|
||||||
hit_size: 8
|
hit_size: 8
|
||||||
# Alçada del cos en píxels. Funció DOBLE segons amb què col·lisione:
|
|
||||||
# · Contra entitats (mummy, rolling_rock, sarc): si `jump_z > height`
|
|
||||||
# d'aquella entitat, no hi ha col·lisió. Igual que als YAMLs
|
|
||||||
# d'entitats.
|
|
||||||
# · Contra altres jugadors: la col·lisió és binària per estat aeri.
|
|
||||||
# Si un està en l'aire i l'altre no → cap col·lisió. Si tots dos
|
|
||||||
# estan en l'aire, llavors `body_height` decideix si els rangs
|
|
||||||
# verticals solapen i s'aplica el rebot mid-aire. `0` = sense cos
|
|
||||||
# (ni rebot quan tots dos volen).
|
|
||||||
height: 12
|
height: 12
|
||||||
|
|
||||||
# Animacions en mapa (clau = nom). Camps per animació:
|
|
||||||
# frames → llista d'índexs dins de l'spritesheet.
|
|
||||||
# frame_ms → durada de cada frame en ms. Escalar = uniforme; llista =
|
|
||||||
# per-frame (mateixa mida que `frames`).
|
|
||||||
# loop → true per defecte. `loop: false` = un sol tret (es queda al
|
|
||||||
# darrer frame i `animationIsCompleted()` passa a true).
|
|
||||||
# loop_from → rebota a aquest índex en passar del final (implica loop).
|
|
||||||
animations:
|
animations:
|
||||||
stand_down: { frames: [0] }
|
# Les variants `*_west` reaprofiten els frames de l'est i declaren
|
||||||
stand_up: { frames: [4] }
|
# `flip_h: true` perquè el motor d'animació apliqui el flip al sprite —
|
||||||
stand: { frames: [8] }
|
# el codi del joc no toca mai `flip_` directament.
|
||||||
jump_down: { frames: [12] }
|
stand_south: { frames: [0] }
|
||||||
jump_up: { frames: [13] }
|
stand_north: { frames: [4] }
|
||||||
jump: { frames: [14] }
|
stand_east: { frames: [8] }
|
||||||
stun_down: { frames: [16] }
|
stand_west: { frames: [8], flip_h: true }
|
||||||
stun_up: { frames: [17] }
|
|
||||||
stun: { frames: [18] }
|
|
||||||
|
|
||||||
walk_down:
|
jump_south: { frames: [12] }
|
||||||
frames: [0, 1, 2, 3]
|
jump_north: { frames: [13] }
|
||||||
frame_ms: 100
|
jump_east: { frames: [14] }
|
||||||
|
jump_west: { frames: [14], flip_h: true }
|
||||||
|
|
||||||
walk_up:
|
stun_south: { frames: [16] }
|
||||||
frames: [4, 5, 6, 7]
|
stun_north: { frames: [17] }
|
||||||
frame_ms: 100
|
stun_east: { frames: [18] }
|
||||||
|
stun_west: { frames: [18], flip_h: true }
|
||||||
|
|
||||||
walk:
|
walk_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
frames: [8, 9, 10, 11]
|
walk_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
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_down:
|
hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
frames: [0, 1, 2, 3]
|
hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
frame_ms: 100
|
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
||||||
|
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
hurt_up:
|
attack_south: { frames: [20, 21, 22, 23], frame_ms: 50 }
|
||||||
frames: [4, 5, 6, 7]
|
attack_north: { frames: [24, 25, 26, 27], frame_ms: 50 }
|
||||||
frame_ms: 100
|
attack_east: { frames: [28, 29, 30, 31], frame_ms: 50 }
|
||||||
|
attack_west: { frames: [28, 29, 30, 31], frame_ms: 50, flip_h: true }
|
||||||
|
|
||||||
hurt:
|
recover_south: { frames: [23] }
|
||||||
frames: [8, 9, 10, 11]
|
recover_north: { frames: [27] }
|
||||||
frame_ms: 100
|
recover_east: { frames: [31] }
|
||||||
|
recover_west: { frames: [31], flip_h: true }
|
||||||
|
|
||||||
# Placeholder mentre no arribe l'art real (el GIF de morcus encara no té
|
# Animació de mort: 3 voltes horàries començant pel sud (south →
|
||||||
# frames d'atac específics — reaprofitem `walk_*`). El bloc està declarat
|
# west → north → east, repetit 3×). Reaprofita els frames stand_*; el
|
||||||
# perquè `Player::animationName` el busque sense fallback i la mecànica
|
# west és l'east amb `flip_h: true` declarat per-frame (mapping form,
|
||||||
# de l'atac estiga lligada amb morcus tan bon punt s'afegisquen els
|
# no hereta del nivell d'anim). `frame_ms` per-frame creix linealment
|
||||||
# frames; ara mateix visualment el sprite caminarà mentre llança el
|
# per donar la sensació de desaccelerar fins a aturar-se. `loop: false`
|
||||||
# látigo (acceptat per al placeholder).
|
# → es queda al darrer frame i marca completed.
|
||||||
attack_down:
|
death_spinning:
|
||||||
frames: [0, 1, 2, 3]
|
frames:
|
||||||
frame_ms: 50
|
- 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
|
||||||
|
|
||||||
attack_up:
|
# Mort per caiguda a un agujero (DeathCause::HOLE). `loop: false` perquè
|
||||||
frames: [4, 5, 6, 7]
|
# `animationIsCompleted()` dispari el respawn timer determinísticament.
|
||||||
frame_ms: 50
|
death_hole:
|
||||||
|
frames: [15]
|
||||||
|
frame_ms: 60
|
||||||
|
loop: false
|
||||||
|
|
||||||
attack:
|
# Inèrcia de frenada en soltar la direcció. `threshold_ms` filtra
|
||||||
frames: [8, 9, 10, 11]
|
# tocs curts (passets d'un tile sense rellisquera).
|
||||||
frame_ms: 50
|
|
||||||
|
|
||||||
# Cooldown post-swing (recovery): igual que a sam.yaml, placeholder als
|
|
||||||
# frames de `stand_*`. Quan arriben els frames reals només cal canviar
|
|
||||||
# els índexs ací.
|
|
||||||
recover_down:
|
|
||||||
frames: [0]
|
|
||||||
|
|
||||||
recover_up:
|
|
||||||
frames: [4]
|
|
||||||
|
|
||||||
recover:
|
|
||||||
frames: [8]
|
|
||||||
|
|
||||||
# Inèrcia només de frenada. L'acceleració és sempre instantània (soltar
|
|
||||||
# el teclat + prémer una direcció = velocitat plena); en canvi, quan es
|
|
||||||
# solta la direcció el jugador decau linealment a 0 en `brake_time_ms`.
|
|
||||||
# El `threshold_ms` filtra els tocs curts: si el temps acumulat de
|
|
||||||
# moviment continu no ha arribat al llindar, la parada és instantània
|
|
||||||
# (permet "passets" d'un tile sense rellisquera). L'acumulador es
|
|
||||||
# reseteja només quan no hi ha ni input ni velocitat residual, així
|
|
||||||
# els canvis de direcció enmig del coast no rearmen el llindar.
|
|
||||||
# `brake_time_ms: 0` o absència del bloc = inèrcia desactivada.
|
|
||||||
inertia:
|
inertia:
|
||||||
brake_time_ms: 150
|
brake_time_ms: 150
|
||||||
threshold_ms: 300
|
threshold_ms: 300
|
||||||
|
|
||||||
# Gir de 180°: mentre dura, el facing queda fixat a la direcció
|
# Gir de 180°: durant `duration_ms` el facing es fixa a la direcció
|
||||||
# intermèdia (efecte purament visual; el moviment continua sense
|
# intermèdia indicada (purament visual).
|
||||||
# alterar-se). Els girs de 90° no passen per cap intermedi.
|
|
||||||
turn:
|
turn:
|
||||||
duration_ms: 80
|
duration_ms: 80
|
||||||
# Direcció intermèdia per a cada gir de 180° (sentit horari).
|
|
||||||
transitions:
|
transitions:
|
||||||
west_to_east: down # passa per baix (stand_down)
|
west_to_east: south
|
||||||
east_to_west: up # passa per dalt (stand_up)
|
east_to_west: north
|
||||||
north_to_south: east # passa per la dreta (stand, sense flip)
|
north_to_south: east
|
||||||
south_to_north: west # passa per l'esquerra (stand, amb flip)
|
south_to_north: west
|
||||||
|
|
||||||
# Salt vertical (px verticals "visuals", no afecta la posició al món).
|
# Salt vertical (px de z, no toca la posició al món).
|
||||||
# impulse → velocitat vertical inicial (px/s) en deixar el sòl.
|
# `shadow_y_offset`: puja la sombra per compensar la franja
|
||||||
# gravity → acceleració descendent constant (px/s²).
|
# transparent sota els peus del sprite.
|
||||||
# landing_stun_ms → temps en què el jugador queda paralitzat en aterrar
|
|
||||||
# (no es pot moure ni tornar a saltar).
|
|
||||||
# shadow_y_offset → desplaçament vertical de la sombra (px, positiu =
|
|
||||||
# puja). Compensa la franja transparent que els
|
|
||||||
# sprites del jugador tenen sota els peus reals
|
|
||||||
# perquè camine "pel mig del passadís" (3 px aquí).
|
|
||||||
# sound → fitxer .wav reproduït al despegue (relatiu a
|
|
||||||
# data/sounds/). Buit o absent = sense so.
|
|
||||||
# land_sound → fitxer .wav reproduït en aterrar.
|
|
||||||
# L'alçada del cos viu al bloc `collision:` (`height`), igual que la resta
|
|
||||||
# d'entitats: és una propietat de col·lisió, no de la física del salt.
|
|
||||||
# Amb impulse=240 i gravity=600: pic ≈ 48 px, temps total en l'aire ≈ 0.8 s.
|
|
||||||
jump:
|
jump:
|
||||||
impulse: 200
|
impulse: 200
|
||||||
gravity: 600
|
gravity: 600
|
||||||
@@ -146,22 +107,27 @@ jump:
|
|||||||
sound: jump.wav
|
sound: jump.wav
|
||||||
land_sound: land.wav
|
land_sound: land.wav
|
||||||
|
|
||||||
# Estat HURT: el jugador queda projectat sense control per un impacte
|
# Projecció post-impacte: surt cap a la direcció amb més recorregut
|
||||||
# (algú que cau al damunt; en el futur també fuetada de l'altre jugador).
|
# lliure i frena fins a 0 en `brake_time_ms`.
|
||||||
# `enterHurt` tria automàticament la direcció amb més recorregut lliure
|
|
||||||
# entre els 4 cardinals, posa la velocitat inicial a `speed` i la fa
|
|
||||||
# decaure linealment fins a 0 en `brake_time_ms`. Si pel camí xoca amb
|
|
||||||
# un mur, para en sec. En tornar a velocitat 0, recupera l'estat
|
|
||||||
# GROUNDED i el control. Mentre dura, no col·lisiona amb altres jugadors.
|
|
||||||
# Amb speed=120 i brake_time=500ms ⇒ recorregut ≈ 30 px (≈2 tiles).
|
|
||||||
hurt:
|
hurt:
|
||||||
speed: 120
|
speed: 120
|
||||||
brake_time_ms: 500
|
brake_time_ms: 500
|
||||||
|
sound: hurt.wav
|
||||||
|
|
||||||
# Configuració del látigo. Mateixos valors que Sam de moment;
|
# Atac amb làtigo. `range_tiles` és la longitud abans de retallar
|
||||||
# diferenciar quan equilibrem cada personatge.
|
# contra murs/sarcs; `cooldown_ms` allarga ATTACKING amb una
|
||||||
|
# recuperació en què la hitbox ja no fa dany.
|
||||||
whip:
|
whip:
|
||||||
range_tiles: 3
|
range_tiles: 3
|
||||||
swing_ms: 200
|
swing_ms: 200
|
||||||
cooldown_ms: 200
|
cooldown_ms: 200
|
||||||
damage: 1
|
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: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -1,143 +1,104 @@
|
|||||||
# player animation
|
|
||||||
sprite:
|
sprite:
|
||||||
path: sam.gif
|
path: sam.gif
|
||||||
size: [16, 32]
|
size: [16, 32]
|
||||||
|
|
||||||
# Geometria de col·lisió del jugador (independent de l'animació):
|
# Geometria de col·lisió. `size` és la caixa contra murs/sarcs;
|
||||||
# size → costat del quadrat de col·lisió (16×16)
|
# `hit_size`, la caixa contra altres actors (més petita); `height`,
|
||||||
# base_size → costat del quadrat visual inferior del sprite (els "peus");
|
# l'alçada que un salt ha de superar per passar per damunt.
|
||||||
# s'usa per centrar la caixa de col·lisió sobre ell.
|
|
||||||
collision:
|
collision:
|
||||||
size: 16
|
size: 16
|
||||||
base_size: 16
|
base_size: 16
|
||||||
# Caixa de contacte actor-actor (jugador↔jugador, jugador↔momia/trampa).
|
|
||||||
# Més petita que `size` perquè els contactes d'acció siguin precisos;
|
|
||||||
# `size` es reserva per a la col·lisió amb murs/sarcòfags i per enfilar
|
|
||||||
# els passadissos. Centrada en el mateix punt que la caixa base.
|
|
||||||
hit_size: 8
|
hit_size: 8
|
||||||
# Alçada del cos en píxels. Funció DOBLE segons amb què col·lisione:
|
|
||||||
# · Contra entitats (mummy, rolling_rock, sarc): si `jump_z > height`
|
|
||||||
# d'aquella entitat, no hi ha col·lisió. Igual que als YAMLs
|
|
||||||
# d'entitats.
|
|
||||||
# · Contra altres jugadors: la col·lisió és binària per estat aeri.
|
|
||||||
# Si un està en l'aire i l'altre no → cap col·lisió. Si tots dos
|
|
||||||
# estan en l'aire, llavors `body_height` decideix si els rangs
|
|
||||||
# verticals solapen i s'aplica el rebot mid-aire. `0` = sense cos
|
|
||||||
# (ni rebot quan tots dos volen).
|
|
||||||
height: 12
|
height: 12
|
||||||
|
|
||||||
# Animacions en mapa (clau = nom). Camps per animació:
|
|
||||||
# frames → llista d'índexs dins de l'spritesheet.
|
|
||||||
# frame_ms → durada de cada frame en ms. Escalar = uniforme; llista =
|
|
||||||
# per-frame (mateixa mida que `frames`).
|
|
||||||
# loop → true per defecte. `loop: false` = un sol tret (es queda al
|
|
||||||
# darrer frame i `animationIsCompleted()` passa a true).
|
|
||||||
# loop_from → rebota a aquest índex en passar del final (implica loop).
|
|
||||||
animations:
|
animations:
|
||||||
stand_down: { frames: [1] }
|
# Les variants `*_west` reaprofiten els frames de l'est i declaren
|
||||||
stand_up: { frames: [5] }
|
# `flip_h: true` perquè el motor d'animació apliqui el flip al sprite —
|
||||||
stand: { frames: [9] }
|
# el codi del joc no toca mai `flip_` directament.
|
||||||
jump_down: { frames: [12] }
|
stand_south: { frames: [0] }
|
||||||
jump_up: { frames: [13] }
|
stand_north: { frames: [4] }
|
||||||
jump: { frames: [14] }
|
stand_east: { frames: [8] }
|
||||||
stun_down: { frames: [16] }
|
stand_west: { frames: [8], flip_h: true }
|
||||||
stun_up: { frames: [17] }
|
|
||||||
stun: { frames: [18] }
|
|
||||||
|
|
||||||
walk_down:
|
jump_south: { frames: [12] }
|
||||||
frames: [0, 1, 2, 3]
|
jump_north: { frames: [13] }
|
||||||
frame_ms: 100
|
jump_east: { frames: [14] }
|
||||||
|
jump_west: { frames: [14], flip_h: true }
|
||||||
|
|
||||||
walk_up:
|
stun_south: { frames: [16] }
|
||||||
frames: [4, 5, 6, 7]
|
stun_north: { frames: [17] }
|
||||||
frame_ms: 100
|
stun_east: { frames: [18] }
|
||||||
|
stun_west: { frames: [18], flip_h: true }
|
||||||
|
|
||||||
walk:
|
walk_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
frames: [8, 9, 10, 11]
|
walk_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
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_down:
|
hurt_south: { frames: [0, 1, 2, 3], frame_ms: 100 }
|
||||||
frames: [0, 1, 2, 3]
|
hurt_north: { frames: [4, 5, 6, 7], frame_ms: 100 }
|
||||||
frame_ms: 100
|
hurt_east: { frames: [8, 9, 10, 11], frame_ms: 100 }
|
||||||
|
hurt_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true }
|
||||||
|
|
||||||
hurt_up:
|
attack_south: { frames: [20, 21, 22, 23], frame_ms: 50 }
|
||||||
frames: [4, 5, 6, 7]
|
attack_north: { frames: [24, 25, 26, 27], frame_ms: 50 }
|
||||||
frame_ms: 100
|
attack_east: { frames: [28, 29, 30, 31], frame_ms: 50 }
|
||||||
|
attack_west: { frames: [28, 29, 30, 31], frame_ms: 50, flip_h: true }
|
||||||
|
|
||||||
hurt:
|
recover_south: { frames: [23] }
|
||||||
frames: [8, 9, 10, 11]
|
recover_north: { frames: [27] }
|
||||||
frame_ms: 100
|
recover_east: { frames: [31] }
|
||||||
|
recover_west: { frames: [31], flip_h: true }
|
||||||
|
|
||||||
# Animacions del braç llançant el látigo. Placeholder: reaprofiten els
|
# Animació de mort: 3 voltes horàries començant pel sud (south →
|
||||||
# frames de `walk_*` perquè els sprites reals encara no existeixen
|
# west → north → east, repetit 3×). Reaprofita els frames stand_*; el
|
||||||
# (TODO: dibuixar-ne d'específics quan arribe l'art). El sprite del
|
# west és l'east amb `flip_h: true` declarat per-frame (mapping form,
|
||||||
# látigo viu al `WhipAttack` i es renderitza superposat per z-order.
|
# no hereta del nivell d'anim). `frame_ms` per-frame creix linealment
|
||||||
attack_down:
|
# per donar la sensació de desaccelerar fins a aturar-se. `loop: false`
|
||||||
frames: [28, 29, 30, 31]
|
# → es queda al darrer frame i marca completed.
|
||||||
frame_ms: 50
|
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
|
||||||
|
|
||||||
attack_up:
|
# Mort per caiguda a un agujero (DeathCause::HOLE). `loop: false` perquè
|
||||||
frames: [24, 25, 26, 27]
|
# `animationIsCompleted()` dispari el respawn timer determinísticament.
|
||||||
frame_ms: 50
|
death_hole:
|
||||||
|
frames: [15]
|
||||||
|
frame_ms: 60
|
||||||
|
loop: false
|
||||||
|
|
||||||
attack:
|
# Inèrcia de frenada en soltar la direcció. `threshold_ms` filtra
|
||||||
frames: [20, 21, 22, 23]
|
# tocs curts (passets d'un tile sense rellisquera).
|
||||||
frame_ms: 50
|
|
||||||
|
|
||||||
# Animacions de cooldown post-swing (recovery): mentre dura
|
|
||||||
# `whip_cooldown_ms` el jugador continua bloquejat però l'animació ja
|
|
||||||
# no és la d'atac. Placeholder: reaprofiten els frames de `stand_*`
|
|
||||||
# (la silueta no canvia respecte d'ells); quan arriben els frames
|
|
||||||
# reals només cal canviar els índexs ací, sense tocar el codi.
|
|
||||||
recover_down:
|
|
||||||
frames: [1]
|
|
||||||
|
|
||||||
recover_up:
|
|
||||||
frames: [5]
|
|
||||||
|
|
||||||
recover:
|
|
||||||
frames: [9]
|
|
||||||
|
|
||||||
# Inèrcia només de frenada. L'acceleració és sempre instantània (soltar
|
|
||||||
# el teclat + prémer una direcció = velocitat plena); en canvi, quan es
|
|
||||||
# solta la direcció el jugador decau linealment a 0 en `brake_time_ms`.
|
|
||||||
# El `threshold_ms` filtra els tocs curts: si el temps acumulat de
|
|
||||||
# moviment continu no ha arribat al llindar, la parada és instantània
|
|
||||||
# (permet "passets" d'un tile sense rellisquera). L'acumulador es
|
|
||||||
# reseteja només quan no hi ha ni input ni velocitat residual, així
|
|
||||||
# els canvis de direcció enmig del coast no rearmen el llindar.
|
|
||||||
# `brake_time_ms: 0` o absència del bloc = inèrcia desactivada.
|
|
||||||
inertia:
|
inertia:
|
||||||
brake_time_ms: 150
|
brake_time_ms: 150
|
||||||
threshold_ms: 300
|
threshold_ms: 300
|
||||||
|
|
||||||
# Gir de 180°: mentre dura, el facing queda fixat a la direcció
|
# Gir de 180°: durant `duration_ms` el facing es fixa a la direcció
|
||||||
# intermèdia (efecte purament visual; el moviment continua sense
|
# intermèdia indicada (purament visual).
|
||||||
# alterar-se). Els girs de 90° no passen per cap intermedi.
|
|
||||||
turn:
|
turn:
|
||||||
duration_ms: 80
|
duration_ms: 80
|
||||||
# Direcció intermèdia per a cada gir de 180° (sentit horari).
|
|
||||||
transitions:
|
transitions:
|
||||||
west_to_east: down # passa per baix (stand_down)
|
west_to_east: south
|
||||||
east_to_west: up # passa per dalt (stand_up)
|
east_to_west: north
|
||||||
north_to_south: east # passa per la dreta (stand, sense flip)
|
north_to_south: east
|
||||||
south_to_north: west # passa per l'esquerra (stand, amb flip)
|
south_to_north: west
|
||||||
|
|
||||||
# Salt vertical (px verticals "visuals", no afecta la posició al món).
|
# Salt vertical (px de z, no toca la posició al món).
|
||||||
# impulse → velocitat vertical inicial (px/s) en deixar el sòl.
|
# `shadow_y_offset`: puja la sombra per compensar la franja
|
||||||
# gravity → acceleració descendent constant (px/s²).
|
# transparent sota els peus del sprite.
|
||||||
# landing_stun_ms → temps en què el jugador queda paralitzat en aterrar
|
|
||||||
# (no es pot moure ni tornar a saltar).
|
|
||||||
# shadow_y_offset → desplaçament vertical de la sombra (px, positiu =
|
|
||||||
# puja). Compensa la franja transparent que els
|
|
||||||
# sprites del jugador tenen sota els peus reals
|
|
||||||
# perquè camine "pel mig del passadís" (3 px aquí).
|
|
||||||
# sound → fitxer .wav reproduït al despegue (relatiu a
|
|
||||||
# data/sounds/). Buit o absent = sense so.
|
|
||||||
# land_sound → fitxer .wav reproduït en aterrar.
|
|
||||||
# L'alçada del cos viu al bloc `collision:` (`height`), igual que la resta
|
|
||||||
# d'entitats: és una propietat de col·lisió, no de la física del salt.
|
|
||||||
# Amb impulse=240 i gravity=600: pic ≈ 48 px, temps total en l'aire ≈ 0.8 s.
|
|
||||||
jump:
|
jump:
|
||||||
impulse: 200
|
impulse: 200
|
||||||
gravity: 600
|
gravity: 600
|
||||||
@@ -146,27 +107,27 @@ jump:
|
|||||||
sound: jump.wav
|
sound: jump.wav
|
||||||
land_sound: land.wav
|
land_sound: land.wav
|
||||||
|
|
||||||
# Estat HURT: el jugador queda projectat sense control per un impacte
|
# Projecció post-impacte: surt cap a la direcció amb més recorregut
|
||||||
# (algú que cau al damunt; en el futur també fuetada de l'altre jugador).
|
# lliure i frena fins a 0 en `brake_time_ms`.
|
||||||
# `enterHurt` tria automàticament la direcció amb més recorregut lliure
|
|
||||||
# entre els 4 cardinals, posa la velocitat inicial a `speed` i la fa
|
|
||||||
# decaure linealment fins a 0 en `brake_time_ms`. Si pel camí xoca amb
|
|
||||||
# un mur, para en sec. En tornar a velocitat 0, recupera l'estat
|
|
||||||
# GROUNDED i el control. Mentre dura, no col·lisiona amb altres jugadors.
|
|
||||||
# Amb speed=120 i brake_time=500ms ⇒ recorregut ≈ 30 px (≈2 tiles).
|
|
||||||
hurt:
|
hurt:
|
||||||
speed: 120
|
speed: 120
|
||||||
brake_time_ms: 500
|
brake_time_ms: 500
|
||||||
|
sound: hurt.wav
|
||||||
|
|
||||||
# Configuració del látigo. `range_tiles` és la longitud de la hitbox
|
# Atac amb làtigo. `range_tiles` és la longitud abans de retallar
|
||||||
# abans de retallar contra murs/sarcs (3 tiles ≈ 48 px). `swing_ms` és
|
# contra murs/sarcs; `cooldown_ms` allarga ATTACKING amb una
|
||||||
# la durada de l'animació de l'atac, durant la qual la hitbox del whip
|
# recuperació en què la hitbox ja no fa dany.
|
||||||
# està viva. `cooldown_ms` allarga ATTACKING amb una recuperació en què
|
|
||||||
# el jugador segueix bloquejat però la hitbox ja ha desaparegut: castiga
|
|
||||||
# l'ús abusiu del látigo. `damage` queda llegit per a futur (els
|
|
||||||
# objectius reben `enterHurt` directament; cap entitat té HP encara).
|
|
||||||
whip:
|
whip:
|
||||||
range_tiles: 3
|
range_tiles: 3
|
||||||
swing_ms: 200
|
swing_ms: 200
|
||||||
cooldown_ms: 200
|
cooldown_ms: 200
|
||||||
damage: 1
|
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
|
||||||
@@ -11,18 +11,17 @@ room_intro:
|
|||||||
text_hold_ms: 2000 # text quiet al centre
|
text_hold_ms: 2000 # text quiet al centre
|
||||||
text_exit_ms: 400 # text centre → fora per l'esquerra
|
text_exit_ms: 400 # text centre → fora per l'esquerra
|
||||||
line_gap_px: -4 # px extra entre les dues línies (piràmide / sala)
|
line_gap_px: -4 # px extra entre les dues línies (piràmide / sala)
|
||||||
player_spawn_delay_ms: 2400 # ms des de l'inici d'INTRO al spawn aeri.
|
# Delays d'spawn aeri **mesurats des de la fi de la música d'INTRO**
|
||||||
player2_spawn_delay_ms: 2700 # només mode BOTH. Trenca la simultaneïtat.
|
# (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
|
# 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
|
# 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
|
# "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.
|
# post-mort cauen al so sec. Buit o absent = sense reverb.
|
||||||
land_reverb: stone_hall
|
land_reverb: stone_hall
|
||||||
# So puntual disparat quan apareix el text d'INTRO (phase 2,
|
|
||||||
# text_enter). Es resol contra `data/sounds/<text_sound>`. Buit o
|
|
||||||
# absent = sense so. Es preloda al ctor de GameScene perquè el
|
|
||||||
# primer dispar no faça disc.
|
|
||||||
text_sound: intro_room.wav
|
|
||||||
|
|
||||||
room_outro:
|
room_outro:
|
||||||
text_enter_ms: 400
|
text_enter_ms: 400
|
||||||
|
|||||||
|
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 |
@@ -65,6 +65,22 @@ hud:
|
|||||||
flash_ms: 250 # durada del flash quan un jugador rep un hit
|
flash_ms: 250 # durada del flash quan un jugador rep un hit
|
||||||
shake_ms: 300 # durada del shake que acompanya el flash
|
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
|
# 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
|
# easing i torna a arrancar amb una altra corba. Els timings es poden
|
||||||
# afinar independentment, i les corbes permeten estils diferents entrant
|
# afinar independentment, i les corbes permeten estils diferents entrant
|
||||||
@@ -75,3 +91,10 @@ pause:
|
|||||||
exit_ms: 250
|
exit_ms: 250
|
||||||
enter_curve: outCubic
|
enter_curve: outCubic
|
||||||
exit_curve: inCubic
|
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"
|
||||||
@@ -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_;
|
||||||
|
};
|
||||||
@@ -89,6 +89,9 @@ namespace GlobalInputs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onNextShader() {
|
void onNextShader() {
|
||||||
|
// F5/F6 només tenen sentit quan els shaders estan actius. Si no,
|
||||||
|
// ignorem la pulsació en silenci — l'usuari ha de prémer F4 abans.
|
||||||
|
if (!Options::video().shader.enabled) { return; }
|
||||||
const std::string NEW_NAME = (Options::video().shader.current_shader == "crtpi") ? "postfx" : "crtpi";
|
const std::string NEW_NAME = (Options::video().shader.current_shader == "crtpi") ? "postfx" : "crtpi";
|
||||||
Options::setActiveShaderName(NEW_NAME);
|
Options::setActiveShaderName(NEW_NAME);
|
||||||
OptionsApply::reapplyShader();
|
OptionsApply::reapplyShader();
|
||||||
@@ -98,6 +101,7 @@ namespace GlobalInputs {
|
|||||||
// Busca l'entrada actual al vector de presets i passa a la següent
|
// Busca l'entrada actual al vector de presets i passa a la següent
|
||||||
// (wrap-around). Si no en troba cap s'evita el canvi silenciosament.
|
// (wrap-around). Si no en troba cap s'evita el canvi silenciosament.
|
||||||
void onNextPreset() {
|
void onNextPreset() {
|
||||||
|
if (!Options::video().shader.enabled) { return; }
|
||||||
const bool IS_CRTPI = Options::video().shader.current_shader == "crtpi";
|
const bool IS_CRTPI = Options::video().shader.current_shader == "crtpi";
|
||||||
std::string new_preset;
|
std::string new_preset;
|
||||||
if (IS_CRTPI) {
|
if (IS_CRTPI) {
|
||||||
@@ -154,6 +158,14 @@ namespace GlobalInputs {
|
|||||||
Options::setVideoRenderInfoPosition(static_cast<Defaults::RenderInfoPosition>(NEXT));
|
Options::setVideoRenderInfoPosition(static_cast<Defaults::RenderInfoPosition>(NEXT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug helper: dispara una notificació prou llarga perquè
|
||||||
|
// `wrapToWidth` la parteixi en (almenys) dues línies. Útil per a
|
||||||
|
// verificar visualment el padding asimètric, el line_gap i la caixa
|
||||||
|
// multilinia després de tocar la geometria a `data/ui.yaml`.
|
||||||
|
void onDebugLongNotification() {
|
||||||
|
Notification::notify("Notificació de prova llarga per a comprovar que el text es parteix correctament en dues línies dins la caixa.");
|
||||||
|
}
|
||||||
|
|
||||||
void onExit() {
|
void onExit() {
|
||||||
if (exit_window_s > 0.0F) {
|
if (exit_window_s > 0.0F) {
|
||||||
quit_requested = true;
|
quit_requested = true;
|
||||||
@@ -193,6 +205,10 @@ namespace GlobalInputs {
|
|||||||
onCycleRenderInfo();
|
onCycleRenderInfo();
|
||||||
} else if (SC == KeyConfig::get().scancode("exit")) {
|
} else if (SC == KeyConfig::get().scancode("exit")) {
|
||||||
onExit();
|
onExit();
|
||||||
|
} else if (SC == SDL_SCANCODE_KP_2) {
|
||||||
|
// Tecla de debug fora del KeyConfig: dispara una notificació
|
||||||
|
// multilinia per a inspeccionar el layout de la caixa.
|
||||||
|
onDebugLongNotification();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/rendering/text.hpp"
|
#include "core/rendering/text.hpp"
|
||||||
|
#include "core/ui/ui_config.hpp"
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -67,26 +68,39 @@ void Notifier::init(std::shared_ptr<Text> text, const Palette& ui_palette) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Notifier::update(int delta_ms) {
|
void Notifier::update(int delta_ms) {
|
||||||
const float DT = static_cast<float>(delta_ms) / 1000.0F;
|
const auto& geo = UiConfig::get().notifier();
|
||||||
switch (status_) {
|
switch (status_) {
|
||||||
case Status::RISING:
|
case Status::RISING: {
|
||||||
y_ += SLIDE_SPEED * DT;
|
slide_elapsed_ms_ += delta_ms;
|
||||||
if (y_ >= static_cast<float>(MARGIN_TOP)) {
|
if (slide_elapsed_ms_ >= geo.slide_ms) {
|
||||||
y_ = static_cast<float>(MARGIN_TOP);
|
y_ = slide_y_on_;
|
||||||
status_ = Status::ACTIVE;
|
status_ = Status::ACTIVE;
|
||||||
|
} else {
|
||||||
|
const float T = static_cast<float>(slide_elapsed_ms_) / static_cast<float>(geo.slide_ms);
|
||||||
|
const float K = geo.slide_in_curve(T);
|
||||||
|
y_ = slide_y_off_ + ((slide_y_on_ - slide_y_off_) * K);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case Status::ACTIVE:
|
case Status::ACTIVE:
|
||||||
remaining_ms_ -= delta_ms;
|
remaining_ms_ -= delta_ms;
|
||||||
if (remaining_ms_ <= 0) { status_ = Status::VANISHING; }
|
if (remaining_ms_ <= 0) {
|
||||||
break;
|
slide_elapsed_ms_ = 0;
|
||||||
case Status::VANISHING:
|
status_ = Status::VANISHING;
|
||||||
y_ -= SLIDE_SPEED * DT;
|
|
||||||
if (y_ <= static_cast<float>(-current_box_h_)) {
|
|
||||||
y_ = static_cast<float>(-current_box_h_ - MARGIN_TOP);
|
|
||||||
status_ = Status::HIDDEN;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Status::VANISHING: {
|
||||||
|
slide_elapsed_ms_ += delta_ms;
|
||||||
|
if (slide_elapsed_ms_ >= geo.slide_ms) {
|
||||||
|
y_ = slide_y_off_;
|
||||||
|
status_ = Status::HIDDEN;
|
||||||
|
} else {
|
||||||
|
const float T = static_cast<float>(slide_elapsed_ms_) / static_cast<float>(geo.slide_ms);
|
||||||
|
const float K = geo.slide_out_curve(T);
|
||||||
|
y_ = slide_y_on_ + ((slide_y_off_ - slide_y_on_) * K);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -95,6 +109,7 @@ void Notifier::update(int delta_ms) {
|
|||||||
void Notifier::render() {
|
void Notifier::render() {
|
||||||
if (status_ == Status::HIDDEN || text_ == nullptr || current_lines_.empty()) { return; }
|
if (status_ == Status::HIDDEN || text_ == nullptr || current_lines_.empty()) { return; }
|
||||||
|
|
||||||
|
const auto& geo = UiConfig::get().notifier();
|
||||||
const int BOX_X = (GameCanvas::WIDTH - current_box_w_) / 2;
|
const int BOX_X = (GameCanvas::WIDTH - current_box_w_) / 2;
|
||||||
const int BOX_Y = static_cast<int>(y_);
|
const int BOX_Y = static_cast<int>(y_);
|
||||||
|
|
||||||
@@ -102,8 +117,17 @@ void Notifier::render() {
|
|||||||
// (ARGB directe al canvas, sense tocar cap surface paletada).
|
// (ARGB directe al canvas, sense tocar cap surface paletada).
|
||||||
Screen::get().fillRect(BOX_X, BOX_Y, current_box_w_, current_box_h_, ui_palette_[current_bg_color_]);
|
Screen::get().fillRect(BOX_X, BOX_Y, current_box_w_, current_box_h_, ui_palette_[current_bg_color_]);
|
||||||
|
|
||||||
const int LINE_STEP = Rendering::Hud::FONT_SIZE_PX + LINE_GAP;
|
// Vora externa: 4 línies d'1 px del color del text, paral·leles a cada
|
||||||
int line_y = BOX_Y + PADDING_V;
|
// costat i una mica fora de la caixa. Cada segment es queda dins del
|
||||||
|
// rang del costat corresponent perquè les cantonades no es cobrisquen.
|
||||||
|
const std::uint32_t BORDER_ARGB = ui_palette_[current_text_color_];
|
||||||
|
Screen::get().fillRect(BOX_X, BOX_Y - geo.border_px, current_box_w_, geo.border_px, BORDER_ARGB);
|
||||||
|
Screen::get().fillRect(BOX_X, BOX_Y + current_box_h_, current_box_w_, geo.border_px, BORDER_ARGB);
|
||||||
|
Screen::get().fillRect(BOX_X - geo.border_px, BOX_Y, geo.border_px, current_box_h_, BORDER_ARGB);
|
||||||
|
Screen::get().fillRect(BOX_X + current_box_w_, BOX_Y, geo.border_px, current_box_h_, BORDER_ARGB);
|
||||||
|
|
||||||
|
const int LINE_STEP = Rendering::Hud::FONT_SIZE_PX + geo.line_gap;
|
||||||
|
int line_y = BOX_Y + geo.padding_top;
|
||||||
for (const auto& line : current_lines_) {
|
for (const auto& line : current_lines_) {
|
||||||
text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||||
GameCanvas::CENTER_X,
|
GameCanvas::CENTER_X,
|
||||||
@@ -120,18 +144,26 @@ void Notifier::notify(const std::string& text, Uint8 text_color, Uint8 bg_color,
|
|||||||
// gestionar-ne una cua, canviar ací per una FIFO quan calga.
|
// gestionar-ne una cua, canviar ací per una FIFO quan calga.
|
||||||
if (text_ == nullptr) { return; }
|
if (text_ == nullptr) { return; }
|
||||||
|
|
||||||
|
const auto& geo = UiConfig::get().notifier();
|
||||||
|
const int MAX_TEXT_WIDTH = GameCanvas::WIDTH - (2 * SIDE_MARGIN) - (2 * geo.padding_h);
|
||||||
current_lines_ = wrapToWidth(text, MAX_TEXT_WIDTH, *text_, 1);
|
current_lines_ = wrapToWidth(text, MAX_TEXT_WIDTH, *text_, 1);
|
||||||
|
|
||||||
// Amplada = max de les línies + padding. Alçada = N*glif + (N-1)*gap + padding.
|
// Amplada = max de les línies + padding. Alçada = N*glif + (N-1)*gap + padding asimètric.
|
||||||
const int MAX_LINE_W = std::accumulate(current_lines_.begin(), current_lines_.end(), 0, [this](int acc, const std::string& s) { return std::max(acc, text_->length(s, 1)); });
|
const int MAX_LINE_W = std::accumulate(current_lines_.begin(), current_lines_.end(), 0, [this](int acc, const std::string& s) { return std::max(acc, text_->length(s, 1)); });
|
||||||
current_box_w_ = MAX_LINE_W + (2 * PADDING_H);
|
current_box_w_ = MAX_LINE_W + (2 * geo.padding_h);
|
||||||
|
|
||||||
const int N = static_cast<int>(current_lines_.size());
|
const int N = static_cast<int>(current_lines_.size());
|
||||||
current_box_h_ = (N * Rendering::Hud::FONT_SIZE_PX) + ((N - 1) * LINE_GAP) + (2 * PADDING_V);
|
current_box_h_ = (N * Rendering::Hud::FONT_SIZE_PX) + ((N - 1) * geo.line_gap) + geo.padding_top + geo.padding_bottom;
|
||||||
|
|
||||||
current_text_color_ = text_color;
|
current_text_color_ = text_color;
|
||||||
current_bg_color_ = bg_color;
|
current_bg_color_ = bg_color;
|
||||||
remaining_ms_ = time_ms;
|
remaining_ms_ = time_ms;
|
||||||
y_ = static_cast<float>(-current_box_h_ - MARGIN_TOP);
|
|
||||||
|
// Posicions extremes en y (top-left de la caixa). Off = totalment fora
|
||||||
|
// del canvas per dalt; On = posició final amb la vora respectada.
|
||||||
|
slide_y_off_ = static_cast<float>(-current_box_h_ - geo.border_px - geo.margin_top);
|
||||||
|
slide_y_on_ = static_cast<float>(geo.margin_top + geo.border_px);
|
||||||
|
slide_elapsed_ms_ = 0;
|
||||||
|
y_ = slide_y_off_;
|
||||||
status_ = Status::RISING;
|
status_ = Status::RISING;
|
||||||
}
|
}
|
||||||
@@ -43,15 +43,11 @@ class Notifier {
|
|||||||
|
|
||||||
void notify(const std::string& text, Uint8 text_color, Uint8 bg_color, int time_ms);
|
void notify(const std::string& text, Uint8 text_color, Uint8 bg_color, int time_ms);
|
||||||
|
|
||||||
static constexpr int PADDING_H = 6;
|
// Marge lateral entre la caixa i les vores esquerra/dreta del canvas.
|
||||||
static constexpr int PADDING_V = 3;
|
// Limita l'amplada disponible al text en multilinia (combinat amb el
|
||||||
static constexpr int LINE_GAP = 1; // Separació vertical entre línies multilinia.
|
// padding horitzontal llegit d'UiConfig). No està exposat al YAML
|
||||||
static constexpr int MARGIN_TOP = 2; // Separació amb la vora superior del canvas.
|
// perquè en pantalles 240 px no té sentit retallar més.
|
||||||
static constexpr float SLIDE_SPEED = 120.0F;
|
static constexpr int SIDE_MARGIN = 10;
|
||||||
// Amplada màxima (sense comptar padding) que pot ocupar el text abans que
|
|
||||||
// `wrapToWidth` el parteixi en línies. Deixem marge respecte a la vora del
|
|
||||||
// canvas perquè la caixa no toqui els laterals.
|
|
||||||
static constexpr int MAX_TEXT_WIDTH = GameCanvas::WIDTH - 20 - (2 * PADDING_H);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class Status : std::uint8_t { HIDDEN,
|
enum class Status : std::uint8_t { HIDDEN,
|
||||||
@@ -72,4 +68,11 @@ class Notifier {
|
|||||||
Uint8 current_text_color_{0};
|
Uint8 current_text_color_{0};
|
||||||
Uint8 current_bg_color_{0};
|
Uint8 current_bg_color_{0};
|
||||||
int remaining_ms_{0};
|
int remaining_ms_{0};
|
||||||
|
|
||||||
|
// Animació d'entrada/eixida per temps. `slide_elapsed_ms_` avança
|
||||||
|
// amb el delta dins de RISING/VANISHING; els extrems s'avaluen a
|
||||||
|
// `notify()` perquè depenen de la mida de la caixa.
|
||||||
|
int slide_elapsed_ms_{0};
|
||||||
|
float slide_y_off_{0.0F};
|
||||||
|
float slide_y_on_{0.0F};
|
||||||
};
|
};
|
||||||
@@ -7,36 +7,19 @@
|
|||||||
|
|
||||||
#include "core/rendering/surface.hpp"
|
#include "core/rendering/surface.hpp"
|
||||||
#include "core/resources/resource_cache.hpp"
|
#include "core/resources/resource_cache.hpp"
|
||||||
#include "external/fkyaml_node.hpp"
|
|
||||||
|
// El parser pure de YAML d'animacions (parseAnimationsFromYaml + helpers)
|
||||||
|
// viu a `animated_sprite_parser.cpp` perquè és testable sense Surface ni
|
||||||
|
// Resource::Cache. Aquesta unitat es queda amb el runtime de la classe i
|
||||||
|
// amb el que sí toca el cache de surfaces (parseSpriteBlock).
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
auto convertYamlFramesToRects(const fkyaml::node& frames_node, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> std::vector<SDL_FRect> {
|
|
||||||
std::vector<SDL_FRect> frames;
|
|
||||||
SDL_FRect rect = {.x = 0.0F, .y = 0.0F, .w = frame_width, .h = frame_height};
|
|
||||||
for (const auto& frame_index_node : frames_node) {
|
|
||||||
const int NUM_TILE = frame_index_node.get_value<int>();
|
|
||||||
if (NUM_TILE <= max_tiles) {
|
|
||||||
rect.x = static_cast<float>(NUM_TILE % frames_per_row) * frame_width;
|
|
||||||
rect.y = static_cast<float>(NUM_TILE / frames_per_row) * frame_height;
|
|
||||||
frames.emplace_back(rect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return frames;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SheetParams {
|
|
||||||
float frame_width{0.0F};
|
|
||||||
float frame_height{0.0F};
|
|
||||||
int frames_per_row{1};
|
|
||||||
int max_tiles{1};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Llegeix el bloc `sprite: { path, size: [w, h] }` — mateix format que
|
// Llegeix el bloc `sprite: { path, size: [w, h] }` — mateix format que
|
||||||
// les plantilles d'entitat. La surface es resol via Resource::Cache amb
|
// les plantilles d'entitat. La surface es resol via Resource::Cache amb
|
||||||
// el basename del fitxer.
|
// el basename del fitxer.
|
||||||
auto parseSpriteBlock(const fkyaml::node& yaml, std::shared_ptr<Surface>& surface) -> SheetParams {
|
auto parseSpriteBlock(const fkyaml::node& yaml, std::shared_ptr<Surface>& surface) -> AnimatedSprite::SheetParams {
|
||||||
SheetParams params;
|
AnimatedSprite::SheetParams params;
|
||||||
if (!yaml.contains("sprite")) { return params; }
|
if (!yaml.contains("sprite")) { return params; }
|
||||||
const auto& s = yaml["sprite"];
|
const auto& s = yaml["sprite"];
|
||||||
if (s.contains("path")) {
|
if (s.contains("path")) {
|
||||||
@@ -57,62 +40,18 @@ namespace {
|
|||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
// `frame_ms` pot ser un escalar (temps uniforme) o una llista (per-frame).
|
// Tradueix els flags lògics flip_h/flip_v al `SDL_FlipMode` que admet
|
||||||
// Convertim sempre a segons perquè `animate()` treballa amb delta_time
|
// `Surface::render`. Important: a SDL3, `SDL_FlipMode` és un enum
|
||||||
// en segons. Si falta o val 0, `speeds` queda buit i l'animació es
|
// exclusiu (NONE/HORIZONTAL/VERTICAL), no un bitmask combinable. Per
|
||||||
// considera estàtica (no avança).
|
// tant, V+H simultani al mateix frame no es pot expressar amb una
|
||||||
void parseFrameTimes(const fkyaml::node& anim_node, AnimatedSprite::AnimationData& animation) {
|
// única crida — prioritzem H (suficient per als casos pràctics
|
||||||
if (!anim_node.contains("frame_ms")) { return; }
|
// d'animació, com `walk_west` o `death_spin`). Si en algun moment fa
|
||||||
const auto& fm = anim_node["frame_ms"];
|
// falta combinar, caldrà ampliar `Surface::render` per a doble-flipar
|
||||||
if (fm.is_sequence()) {
|
// a baix nivell.
|
||||||
for (const auto& v : fm) {
|
auto toFlipMode(bool flip_h, bool flip_v) -> SDL_FlipMode {
|
||||||
animation.speeds.push_back(static_cast<float>(v.get_value<int>()) / 1000.0F);
|
if (flip_h) { return SDL_FLIP_HORIZONTAL; }
|
||||||
}
|
if (flip_v) { return SDL_FLIP_VERTICAL; }
|
||||||
} else {
|
return SDL_FLIP_NONE;
|
||||||
const float SEC = static_cast<float>(fm.get_value<int>()) / 1000.0F;
|
|
||||||
if (SEC > 0.0F) { animation.speeds.assign(animation.frames.size(), SEC); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ordre de prioritat per a la política de loop:
|
|
||||||
// 1) `loop_from: N` → rebota al frame N en passar del final (implica loop).
|
|
||||||
// 2) `loop: false` → animació d'un sol tret (es queda al darrer frame).
|
|
||||||
// 3) Res → loop complet des del frame 0 (default).
|
|
||||||
void parseLoopPolicy(const fkyaml::node& anim_node, AnimatedSprite::AnimationData& animation) {
|
|
||||||
if (anim_node.contains("loop_from")) {
|
|
||||||
animation.loop_from = anim_node["loop_from"].get_value<int>();
|
|
||||||
} else if (anim_node.contains("loop") && !anim_node["loop"].get_value<bool>()) {
|
|
||||||
animation.loop_from = -1;
|
|
||||||
} else {
|
|
||||||
animation.loop_from = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto parseAnimations(const fkyaml::node& yaml, const SheetParams& params) -> std::unordered_map<std::string, AnimatedSprite::AnimationData> {
|
|
||||||
std::unordered_map<std::string, AnimatedSprite::AnimationData> animations;
|
|
||||||
if (!yaml.contains("animations") || !yaml["animations"].is_mapping()) {
|
|
||||||
return animations;
|
|
||||||
}
|
|
||||||
const auto& anims_node = yaml["animations"];
|
|
||||||
for (auto it = anims_node.begin(); it != anims_node.end(); ++it) {
|
|
||||||
const auto NAME = it.key().get_value<std::string>();
|
|
||||||
const auto& anim_node = *it;
|
|
||||||
|
|
||||||
AnimatedSprite::AnimationData animation;
|
|
||||||
if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) {
|
|
||||||
animation.frames = convertYamlFramesToRects(
|
|
||||||
anim_node["frames"],
|
|
||||||
params.frame_width,
|
|
||||||
params.frame_height,
|
|
||||||
params.frames_per_row,
|
|
||||||
params.max_tiles);
|
|
||||||
}
|
|
||||||
parseFrameTimes(anim_node, animation);
|
|
||||||
parseLoopPolicy(anim_node, animation);
|
|
||||||
|
|
||||||
animations.emplace(NAME, std::move(animation));
|
|
||||||
}
|
|
||||||
return animations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -122,7 +61,7 @@ AnimatedSprite::AnimatedSprite(const AnimationResource& cached_data) {
|
|||||||
try {
|
try {
|
||||||
auto yaml = fkyaml::node::deserialize(yaml_content);
|
auto yaml = fkyaml::node::deserialize(yaml_content);
|
||||||
auto params = parseSpriteBlock(yaml, surface_);
|
auto params = parseSpriteBlock(yaml, surface_);
|
||||||
animations_ = parseAnimations(yaml, params);
|
animations_ = parseAnimationsFromYaml(yaml, params);
|
||||||
|
|
||||||
setWidth(params.frame_width);
|
setWidth(params.frame_width);
|
||||||
setHeight(params.frame_height);
|
setHeight(params.frame_height);
|
||||||
@@ -132,7 +71,7 @@ AnimatedSprite::AnimatedSprite(const AnimationResource& cached_data) {
|
|||||||
// abans del primer `update()`, així que el valor real el posa ell.
|
// abans del primer `update()`, així que el valor real el posa ell.
|
||||||
if (!animations_.empty()) {
|
if (!animations_.empty()) {
|
||||||
const auto& first = animations_.begin()->second;
|
const auto& first = animations_.begin()->second;
|
||||||
if (!first.frames.empty()) { setClip(first.frames[0]); }
|
if (!first.frames.empty()) { setClip(first.frames[0].rect); }
|
||||||
}
|
}
|
||||||
} catch (const fkyaml::exception& e) {
|
} catch (const fkyaml::exception& e) {
|
||||||
std::cerr << "YAML parsing error in animation " << cached_data.name << ": " << e.what() << '\n';
|
std::cerr << "YAML parsing error in animation " << cached_data.name << ": " << e.what() << '\n';
|
||||||
@@ -154,11 +93,20 @@ void AnimatedSprite::animate(float delta_time) {
|
|||||||
|
|
||||||
const float TOTAL = std::accumulate(anim.speeds.begin(), anim.speeds.end(), 0.0F);
|
const float TOTAL = std::accumulate(anim.speeds.begin(), anim.speeds.end(), 0.0F);
|
||||||
|
|
||||||
|
// Helper local per a propagar el frame actual (clip + flip) al sprite.
|
||||||
|
// El motor sempre sobreescriu el flip — si el YAML no declara `flip_h`/
|
||||||
|
// `flip_v`, el frame té els flags a false i el resultat és `SDL_FLIP_NONE`.
|
||||||
|
auto apply_current_frame = [&](int frame_index) {
|
||||||
|
const auto& frame = anim.frames[frame_index];
|
||||||
|
setClip(frame.rect);
|
||||||
|
setFlip(toFlipMode(frame.flip_h, frame.flip_v));
|
||||||
|
};
|
||||||
|
|
||||||
if (anim.accumulated_time >= TOTAL) {
|
if (anim.accumulated_time >= TOTAL) {
|
||||||
if (anim.loop_from < 0) {
|
if (anim.loop_from < 0) {
|
||||||
anim.current_frame = static_cast<int>(anim.frames.size()) - 1;
|
anim.current_frame = static_cast<int>(anim.frames.size()) - 1;
|
||||||
anim.completed = true;
|
anim.completed = true;
|
||||||
setClip(anim.frames[anim.current_frame]);
|
apply_current_frame(anim.current_frame);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
float loop_start = 0.0F;
|
float loop_start = 0.0F;
|
||||||
@@ -172,13 +120,13 @@ void AnimatedSprite::animate(float delta_time) {
|
|||||||
cursor += anim.speeds[i];
|
cursor += anim.speeds[i];
|
||||||
if (anim.accumulated_time < cursor) {
|
if (anim.accumulated_time < cursor) {
|
||||||
anim.current_frame = i;
|
anim.current_frame = i;
|
||||||
setClip(anim.frames[i]);
|
apply_current_frame(i);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anim.current_frame = static_cast<int>(anim.frames.size()) - 1;
|
anim.current_frame = static_cast<int>(anim.frames.size()) - 1;
|
||||||
setClip(anim.frames[anim.current_frame]);
|
apply_current_frame(anim.current_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto AnimatedSprite::animationIsCompleted() -> bool {
|
auto AnimatedSprite::animationIsCompleted() -> bool {
|
||||||
@@ -200,7 +148,10 @@ void AnimatedSprite::setCurrentAnimation(const std::string& name) {
|
|||||||
anim.current_frame = 0;
|
anim.current_frame = 0;
|
||||||
anim.accumulated_time = 0.0F;
|
anim.accumulated_time = 0.0F;
|
||||||
anim.completed = false;
|
anim.completed = false;
|
||||||
if (!anim.frames.empty()) { setClip(anim.frames[0]); }
|
if (!anim.frames.empty()) {
|
||||||
|
setClip(anim.frames[0].rect);
|
||||||
|
setFlip(toFlipMode(anim.frames[0].flip_h, anim.frames[0].flip_v));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimatedSprite::update(float delta_time) {
|
void AnimatedSprite::update(float delta_time) {
|
||||||
|
|||||||
@@ -8,14 +8,41 @@
|
|||||||
|
|
||||||
#include "core/rendering/sprite/moving_sprite.hpp"
|
#include "core/rendering/sprite/moving_sprite.hpp"
|
||||||
#include "core/resources/resource_types.hpp"
|
#include "core/resources/resource_types.hpp"
|
||||||
|
#include "external/fkyaml_node.hpp"
|
||||||
|
|
||||||
class AnimatedSprite : public MovingSprite {
|
class AnimatedSprite : public MovingSprite {
|
||||||
public:
|
public:
|
||||||
|
// Un frame d'animació = rectangle dins el spritesheet + flags de flip
|
||||||
|
// associats. El flip s'aplica al sprite a `animate()` quan l'animació
|
||||||
|
// declara que ella controla el flip (vegeu `AnimationData::controls_flip`).
|
||||||
|
struct Frame {
|
||||||
|
SDL_FRect rect{.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
|
||||||
|
bool flip_h{false};
|
||||||
|
bool flip_v{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Paràmetres geomètrics del spritesheet, derivats del bloc `sprite:`
|
||||||
|
// del YAML. Es passen al parser d'animacions per a calcular els
|
||||||
|
// rectangles de cada frame a partir dels seus índexs.
|
||||||
|
struct SheetParams {
|
||||||
|
float frame_width{0.0F};
|
||||||
|
float frame_height{0.0F};
|
||||||
|
int frames_per_row{1};
|
||||||
|
int max_tiles{1};
|
||||||
|
};
|
||||||
|
|
||||||
// Dades d'una animació concreta dins d'un spritesheet. Les claus del
|
// Dades d'una animació concreta dins d'un spritesheet. Les claus del
|
||||||
// mapa (`animations_`) són els noms; aquí només es guarden els fotogrames
|
// mapa (`animations_`) són els noms; aquí només es guarden els fotogrames
|
||||||
// i l'estat de reproducció.
|
// i l'estat de reproducció.
|
||||||
|
//
|
||||||
|
// El flip és sempre propietat del motor: a cada `animate()` s'escriu el
|
||||||
|
// flip del frame actual al sprite (default `SDL_FLIP_NONE` quan el YAML
|
||||||
|
// no declara res). El codi del joc no pot — ni ha de — tocar el flip
|
||||||
|
// manualment; ho expressa al YAML declarant `flip_h: true` a l'animació
|
||||||
|
// simètrica (p. ex. `walk_west`) o, per a casos puntuals, per frame
|
||||||
|
// amb la forma mapping `{ index, flip_h?, flip_v? }`.
|
||||||
struct AnimationData {
|
struct AnimationData {
|
||||||
std::vector<SDL_FRect> frames;
|
std::vector<Frame> frames;
|
||||||
// Durada de cada frame en segons (convertit des de `frame_ms` al YAML).
|
// Durada de cada frame en segons (convertit des de `frame_ms` al YAML).
|
||||||
// Si té un sol valor, val per a tots els frames; altrament ha de tenir
|
// Si té un sol valor, val per a tots els frames; altrament ha de tenir
|
||||||
// la mateixa mida que `frames`.
|
// la mateixa mida que `frames`.
|
||||||
@@ -40,6 +67,12 @@ class AnimatedSprite : public MovingSprite {
|
|||||||
void resetAnimation();
|
void resetAnimation();
|
||||||
void animate(float delta_time);
|
void animate(float delta_time);
|
||||||
|
|
||||||
|
// Helper de parsing exposat per a testabilitat. NO toca el cache de
|
||||||
|
// recursos — només el bloc `animations:` del YAML i els paràmetres de
|
||||||
|
// full ja calculats. El ctor d'`AnimatedSprite` el crida internament.
|
||||||
|
static auto parseAnimationsFromYaml(const fkyaml::node& yaml, const SheetParams& params)
|
||||||
|
-> std::unordered_map<std::string, AnimationData>;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, AnimationData> animations_;
|
std::unordered_map<std::string, AnimationData> animations_;
|
||||||
// Nom de l'animació activa. Buit fins que algú crida `setCurrentAnimation`.
|
// Nom de l'animació activa. Buit fins que algú crida `setCurrentAnimation`.
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
#include "core/rendering/sprite/animated_sprite.hpp"
|
||||||
|
|
||||||
|
// Implementació del parser pure de YAML d'animacions. Viu en una unitat
|
||||||
|
// separada perquè NO depèn de Resource::Cache ni de Surface — només de
|
||||||
|
// fkyaml — i això permet als tests stand-alone enllaçar només aquest .cpp
|
||||||
|
// per a verificar el parsing sense haver d'arrossegar tot l'stack de
|
||||||
|
// recursos i renderitzat.
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Construeix el rectangle d'un frame a partir del seu índex dins del
|
||||||
|
// spritesheet. Centralitzat per a no duplicar la fórmula als dos
|
||||||
|
// formats (escalar / mapping) que admet `frames:` al YAML.
|
||||||
|
auto rectFromIndex(int index, const AnimatedSprite::SheetParams& params) -> SDL_FRect {
|
||||||
|
return SDL_FRect{
|
||||||
|
.x = static_cast<float>(index % params.frames_per_row) * params.frame_width,
|
||||||
|
.y = static_cast<float>(index / params.frames_per_row) * params.frame_height,
|
||||||
|
.w = params.frame_width,
|
||||||
|
.h = params.frame_height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Llig un node `frames` (seqüència) i el converteix a la nova
|
||||||
|
// representació `vector<Frame>`. Cada element pot ser:
|
||||||
|
// - escalar (int) → hereta `default_flip_h`/`default_flip_v` de l'animació.
|
||||||
|
// - mapping `{ index: N, flip_h?: bool, flip_v?: bool }` → fully
|
||||||
|
// explícit. Els flags absents defaulten a false (NO hereta de l'anim).
|
||||||
|
auto convertYamlFrames(const fkyaml::node& frames_node, const AnimatedSprite::SheetParams& params, bool default_flip_h, bool default_flip_v) -> std::vector<AnimatedSprite::Frame> {
|
||||||
|
std::vector<AnimatedSprite::Frame> frames;
|
||||||
|
for (const auto& frame_node : frames_node) {
|
||||||
|
if (frame_node.is_mapping()) {
|
||||||
|
if (!frame_node.contains("index")) { continue; }
|
||||||
|
const int NUM_TILE = frame_node["index"].get_value<int>();
|
||||||
|
if (NUM_TILE > params.max_tiles) { continue; }
|
||||||
|
AnimatedSprite::Frame frame;
|
||||||
|
frame.rect = rectFromIndex(NUM_TILE, params);
|
||||||
|
if (frame_node.contains("flip_h")) { frame.flip_h = frame_node["flip_h"].get_value<bool>(); }
|
||||||
|
if (frame_node.contains("flip_v")) { frame.flip_v = frame_node["flip_v"].get_value<bool>(); }
|
||||||
|
frames.emplace_back(frame);
|
||||||
|
} else {
|
||||||
|
const int NUM_TILE = frame_node.get_value<int>();
|
||||||
|
if (NUM_TILE > params.max_tiles) { continue; }
|
||||||
|
frames.emplace_back(AnimatedSprite::Frame{
|
||||||
|
.rect = rectFromIndex(NUM_TILE, params),
|
||||||
|
.flip_h = default_flip_h,
|
||||||
|
.flip_v = default_flip_v,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `frame_ms` pot ser un escalar (temps uniforme) o una llista (per-frame).
|
||||||
|
// Convertim sempre a segons perquè `animate()` treballa amb delta_time
|
||||||
|
// en segons. Si falta o val 0, `speeds` queda buit i l'animació es
|
||||||
|
// considera estàtica (no avança).
|
||||||
|
void parseFrameTimes(const fkyaml::node& anim_node, AnimatedSprite::AnimationData& animation) {
|
||||||
|
if (!anim_node.contains("frame_ms")) { return; }
|
||||||
|
const auto& fm = anim_node["frame_ms"];
|
||||||
|
if (fm.is_sequence()) {
|
||||||
|
for (const auto& v : fm) {
|
||||||
|
animation.speeds.push_back(static_cast<float>(v.get_value<int>()) / 1000.0F);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const float SEC = static_cast<float>(fm.get_value<int>()) / 1000.0F;
|
||||||
|
if (SEC > 0.0F) { animation.speeds.assign(animation.frames.size(), SEC); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordre de prioritat per a la política de loop:
|
||||||
|
// 1) `loop_from: N` → rebota al frame N en passar del final (implica loop).
|
||||||
|
// 2) `loop: false` → animació d'un sol tret (es queda al darrer frame).
|
||||||
|
// 3) Res → loop complet des del frame 0 (default).
|
||||||
|
void parseLoopPolicy(const fkyaml::node& anim_node, AnimatedSprite::AnimationData& animation) {
|
||||||
|
if (anim_node.contains("loop_from")) {
|
||||||
|
animation.loop_from = anim_node["loop_from"].get_value<int>();
|
||||||
|
} else if (anim_node.contains("loop") && !anim_node["loop"].get_value<bool>()) {
|
||||||
|
animation.loop_from = -1;
|
||||||
|
} else {
|
||||||
|
animation.loop_from = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
auto AnimatedSprite::parseAnimationsFromYaml(const fkyaml::node& yaml, const SheetParams& params)
|
||||||
|
-> std::unordered_map<std::string, AnimationData> {
|
||||||
|
std::unordered_map<std::string, AnimationData> animations;
|
||||||
|
if (!yaml.contains("animations") || !yaml["animations"].is_mapping()) {
|
||||||
|
return animations;
|
||||||
|
}
|
||||||
|
const auto& anims_node = yaml["animations"];
|
||||||
|
for (auto it = anims_node.begin(); it != anims_node.end(); ++it) {
|
||||||
|
const auto NAME = it.key().get_value<std::string>();
|
||||||
|
const auto& anim_node = *it;
|
||||||
|
|
||||||
|
// Flags de flip a nivell d'animació (defaults a false). Els frames
|
||||||
|
// declarats com a int els hereten; els declarats com a mapping no.
|
||||||
|
bool anim_flip_h = false;
|
||||||
|
bool anim_flip_v = false;
|
||||||
|
if (anim_node.contains("flip_h")) { anim_flip_h = anim_node["flip_h"].get_value<bool>(); }
|
||||||
|
if (anim_node.contains("flip_v")) { anim_flip_v = anim_node["flip_v"].get_value<bool>(); }
|
||||||
|
|
||||||
|
AnimationData animation;
|
||||||
|
if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) {
|
||||||
|
animation.frames = convertYamlFrames(anim_node["frames"], params, anim_flip_h, anim_flip_v);
|
||||||
|
}
|
||||||
|
parseFrameTimes(anim_node, animation);
|
||||||
|
parseLoopPolicy(anim_node, animation);
|
||||||
|
|
||||||
|
animations.emplace(NAME, std::move(animation));
|
||||||
|
}
|
||||||
|
return animations;
|
||||||
|
}
|
||||||
@@ -15,12 +15,18 @@ namespace Resource::Helper {
|
|||||||
Pack g_pack;
|
Pack g_pack;
|
||||||
bool g_pack_loaded = false;
|
bool g_pack_loaded = false;
|
||||||
bool g_fallback_enabled = true;
|
bool g_fallback_enabled = true;
|
||||||
|
// Inverteix la prioritat (disc abans que pack). Activat en Debug
|
||||||
|
// per defecte; veure setPreferDisk al header.
|
||||||
|
bool g_prefer_disk = false;
|
||||||
|
|
||||||
auto readFromDisk(const std::string& filepath) -> std::vector<std::uint8_t> {
|
auto readFromDisk(const std::string& filepath, bool verbose = true) -> std::vector<std::uint8_t> {
|
||||||
const auto FULL = resolve(filepath);
|
const auto FULL = resolve(filepath);
|
||||||
std::ifstream file(FULL, std::ios::binary | std::ios::ate);
|
std::ifstream file(FULL, std::ios::binary | std::ios::ate);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
std::cerr << "Resource::Helper: cannot open " << FULL << '\n';
|
// Silenciem si veníem de prefer_disk: aleshores caurem
|
||||||
|
// al pack i no és cap error real que el fitxer no estigui
|
||||||
|
// al disc.
|
||||||
|
if (verbose) { std::cerr << "Resource::Helper: cannot open " << FULL << '\n'; }
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
std::streamsize size = file.tellg();
|
std::streamsize size = file.tellg();
|
||||||
@@ -37,6 +43,11 @@ namespace Resource::Helper {
|
|||||||
|
|
||||||
auto initializeResourceSystem(const std::string& pack_file, bool enable_fallback) -> bool {
|
auto initializeResourceSystem(const std::string& pack_file, bool enable_fallback) -> bool {
|
||||||
g_fallback_enabled = enable_fallback;
|
g_fallback_enabled = enable_fallback;
|
||||||
|
// En el flux Debug típic (fallback actiu, source tree disponible)
|
||||||
|
// preferim sempre disc: el desenvolupador edita YAML/GIF a `data/`
|
||||||
|
// i ho veu reflectit al següent reload sense re-packejar. En
|
||||||
|
// Release el fallback està desactivat i aquest flag és inert.
|
||||||
|
g_prefer_disk = enable_fallback;
|
||||||
g_pack_loaded = g_pack.loadPack(pack_file);
|
g_pack_loaded = g_pack.loadPack(pack_file);
|
||||||
|
|
||||||
if (g_pack_loaded) {
|
if (g_pack_loaded) {
|
||||||
@@ -76,17 +87,45 @@ namespace Resource::Helper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto loadFile(const std::string& filepath) -> std::vector<std::uint8_t> {
|
auto loadFile(const std::string& filepath) -> std::vector<std::uint8_t> {
|
||||||
if (g_pack_loaded) {
|
// Helper local: lookup al pack normalitzant la clau (els callers
|
||||||
// El pack està indexat relatiu a `data/` (la carpeta que
|
// passen "data/foo.yaml" però l'índex del pack és relatiu a la
|
||||||
// s'empaqueta), però els callers passen "data/foo.yaml" amb
|
// carpeta `data/`).
|
||||||
// prefix. Fem el strip ací perquè no hagin de tenir-ho en compte.
|
const auto FROM_PACK = [&filepath]() -> std::vector<std::uint8_t> {
|
||||||
|
if (!g_pack_loaded) { return {}; }
|
||||||
constexpr std::string_view PREFIX = "data/";
|
constexpr std::string_view PREFIX = "data/";
|
||||||
std::string key = filepath;
|
std::string key = filepath;
|
||||||
if (key.starts_with(PREFIX)) { key.erase(0, PREFIX.size()); }
|
if (key.starts_with(PREFIX)) { key.erase(0, PREFIX.size()); }
|
||||||
if (g_pack.hasResource(key)) { return g_pack.getResource(key); }
|
if (g_pack.hasResource(key)) { return g_pack.getResource(key); }
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (g_prefer_disk && g_fallback_enabled) {
|
||||||
|
// En Debug volem que el disc guanyi: així les edicions del
|
||||||
|
// editor in-game (i les manuals del repo) es vegen al pròxim
|
||||||
|
// reload sense rebuild del pack. Si el fitxer no és al disc,
|
||||||
|
// caiem al pack — útil per a recursos que només s'han
|
||||||
|
// empaquetat (cas estrany). El silenci a `readFromDisk` evita
|
||||||
|
// emplenar el log amb "cannot open" en aquest camí esperat.
|
||||||
|
auto bytes = readFromDisk(filepath, false);
|
||||||
|
if (!bytes.empty()) { return bytes; }
|
||||||
|
return FROM_PACK();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Política original: pack primer; disc com a fallback si el
|
||||||
|
// recurs no està empaquetat (típic d'arrencada en Debug abans
|
||||||
|
// del primer pack).
|
||||||
|
auto packed = FROM_PACK();
|
||||||
|
if (!packed.empty()) { return packed; }
|
||||||
if (g_fallback_enabled) { return readFromDisk(filepath); }
|
if (g_fallback_enabled) { return readFromDisk(filepath); }
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPreferDisk(bool prefer) {
|
||||||
|
g_prefer_disk = prefer;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto preferDisk() -> bool {
|
||||||
|
return g_prefer_disk;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Resource::Helper
|
} // namespace Resource::Helper
|
||||||
@@ -32,7 +32,17 @@ namespace Resource::Helper {
|
|||||||
// Carrega un recurs pel seu camí relatiu (p.ex. "fonts/hud.gif",
|
// Carrega un recurs pel seu camí relatiu (p.ex. "fonts/hud.gif",
|
||||||
// "input/keys.yaml"). Prova primer el pack; si no hi és i el fallback
|
// "input/keys.yaml"). Prova primer el pack; si no hi és i el fallback
|
||||||
// està actiu, cau a disc (data root + ruta). Retorna vector buit si
|
// està actiu, cau a disc (data root + ruta). Retorna vector buit si
|
||||||
// no el troba.
|
// no el troba. Si `preferDisk()` és true (Debug), inverteix la
|
||||||
|
// prioritat: prova disc abans del pack.
|
||||||
auto loadFile(const std::string& filepath) -> std::vector<std::uint8_t>;
|
auto loadFile(const std::string& filepath) -> std::vector<std::uint8_t>;
|
||||||
|
|
||||||
|
// Inverteix la prioritat de `loadFile` (disc abans que pack). En Debug
|
||||||
|
// s'activa per defecte al `initializeResourceSystem` perquè cada
|
||||||
|
// canvi al YAML/GIF al source tree es vegi sense re-packejar — és el
|
||||||
|
// que necessita l'editor in-game per veure les seues edicions
|
||||||
|
// immediatament al recarregar la sala. En Release no té efecte
|
||||||
|
// (el fallback està desactivat).
|
||||||
|
void setPreferDisk(bool prefer);
|
||||||
|
[[nodiscard]] auto preferDisk() -> bool;
|
||||||
|
|
||||||
} // namespace Resource::Helper
|
} // namespace Resource::Helper
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include "core/audio/audio.hpp"
|
#include "core/audio/audio.hpp"
|
||||||
#include "core/audio/sound_effects_config.hpp"
|
#include "core/audio/sound_effects_config.hpp"
|
||||||
|
#include "core/input/gamepad_bindings.hpp"
|
||||||
#include "core/input/gamepad_manager.hpp"
|
#include "core/input/gamepad_manager.hpp"
|
||||||
#include "core/input/key_config.hpp"
|
#include "core/input/key_config.hpp"
|
||||||
#include "core/input/mouse.hpp"
|
#include "core/input/mouse.hpp"
|
||||||
@@ -28,6 +29,9 @@
|
|||||||
#include "core/system/event_buffer.hpp"
|
#include "core/system/event_buffer.hpp"
|
||||||
#include "core/ui/ui_config.hpp"
|
#include "core/ui/ui_config.hpp"
|
||||||
#include "game/debug_config.hpp"
|
#include "game/debug_config.hpp"
|
||||||
|
#ifdef _DEBUG
|
||||||
|
#include "game/editor/editor_config.hpp"
|
||||||
|
#endif
|
||||||
#include "game/levels/level_manager.hpp"
|
#include "game/levels/level_manager.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "game/scenes/game_scene.hpp"
|
#include "game/scenes/game_scene.hpp"
|
||||||
@@ -82,6 +86,11 @@ Director::Director() {
|
|||||||
std::cerr << "Director: no s'ha pogut carregar ni desar config.yaml a " << system_folder_ << '\n';
|
std::cerr << "Director: no s'ha pogut carregar ni desar config.yaml a " << system_folder_ << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vinculacions de mando per dispositiu — fitxer aïllat de `config.yaml`
|
||||||
|
// perquè és per (name, path) del mando, no per perfil/usuari. Replica
|
||||||
|
// el patró de coffee_crisis_arcade_edition (`gamepad_configs.json`).
|
||||||
|
GamepadBindings::get().load(system_folder_ + "gamepad_configs.yaml");
|
||||||
|
|
||||||
// Carrega els presets de PostFX des de shaders/postfx.yaml; si no existeix
|
// Carrega els presets de PostFX des de shaders/postfx.yaml; si no existeix
|
||||||
// es crea amb presets per defecte (off, crt, ntsc).
|
// es crea amb presets per defecte (off, crt, ntsc).
|
||||||
Options::setPostFXFile(system_folder_ + Defaults::Video::POSTFX_FILE);
|
Options::setPostFXFile(system_folder_ + Defaults::Video::POSTFX_FILE);
|
||||||
@@ -113,6 +122,12 @@ Director::Director() {
|
|||||||
// data/ui.yaml cau al valor compilat a Defaults::UI.
|
// data/ui.yaml cau al valor compilat a Defaults::UI.
|
||||||
UiConfig::get().load("data/ui.yaml");
|
UiConfig::get().load("data/ui.yaml");
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
// Tema visual de l'editor de tilemap (Debug-only). Claus absents
|
||||||
|
// cauen als valors compilats a Defaults::Editor.
|
||||||
|
EditorConfig::get().load("data/editor/editor.yaml");
|
||||||
|
#endif
|
||||||
|
|
||||||
// Recursos compartits d'UI (paleta + fonts). Es carreguen abans que els
|
// Recursos compartits d'UI (paleta + fonts). Es carreguen abans que els
|
||||||
// overlays perquè tots els reaprofiten sense duplicar la lògica. Els
|
// overlays perquè tots els reaprofiten sense duplicar la lògica. Els
|
||||||
// noms de font viuen al fitxer data/ui.yaml via UiConfig.
|
// noms de font viuen al fitxer data/ui.yaml via UiConfig.
|
||||||
@@ -132,9 +147,20 @@ Director::Director() {
|
|||||||
Resource::Cache::get().loadSurface(ROOM_TEXT_FONT_GIF_KEY, UiConfig::get().roomTextFont().gif);
|
Resource::Cache::get().loadSurface(ROOM_TEXT_FONT_GIF_KEY, UiConfig::get().roomTextFont().gif);
|
||||||
Resource::Cache::get().getSurface(ROOM_TEXT_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui"));
|
Resource::Cache::get().getSurface(ROOM_TEXT_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui"));
|
||||||
|
|
||||||
// Música de la piràmide 1 (primer i únic track de moment). Carreguem ací
|
// Pistes de música del cicle de sala (intro/playing/outro). Es preloden
|
||||||
// perquè forma part del preload d'assets, com la font i la paleta.
|
// ací perquè formen part del preload d'assets, com la font i la paleta;
|
||||||
Resource::Cache::get().loadMusic("pyramid_1.ogg", "data/music/pyramid_1.ogg");
|
// la lògica de quina sona en cada estat viu a `GameScene`.
|
||||||
|
Resource::Cache::get().loadMusic("room_intro.ogg", "data/music/room_intro.ogg");
|
||||||
|
Resource::Cache::get().loadMusic("playing.ogg", "data/music/playing.ogg");
|
||||||
|
Resource::Cache::get().loadMusic("room_outro.ogg", "data/music/room_outro.ogg");
|
||||||
|
|
||||||
|
// Sons del menú de servei. Els noms venen de `data/ui.yaml` (amb
|
||||||
|
// fallback a `Defaults::UI::MENU_*_SOUND`); el ServiceMenu els
|
||||||
|
// dispara via Audio::Group::INTERFACE.
|
||||||
|
const auto& menu_select = UiConfig::get().menuSelectSound();
|
||||||
|
const auto& menu_accept = UiConfig::get().menuAcceptSound();
|
||||||
|
Resource::Cache::get().loadSound(menu_select, "data/sounds/" + menu_select);
|
||||||
|
Resource::Cache::get().loadSound(menu_accept, "data/sounds/" + menu_accept);
|
||||||
|
|
||||||
// Catàleg de presets d'efectes per als sons. Es carrega abans d'Audio
|
// Catàleg de presets d'efectes per als sons. Es carrega abans d'Audio
|
||||||
// perquè els primers playSoundWith* (al començament del joc) ja
|
// perquè els primers playSoundWith* (al començament del joc) ja
|
||||||
@@ -293,12 +319,29 @@ void Director::processEvents() {
|
|||||||
if (QUIT_REQUESTED) { current_scene_.reset(); }
|
if (QUIT_REQUESTED) { current_scene_.reset(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Resol el botó SERVICE configurat per al mando origen d'aquest event.
|
||||||
|
// Retorna -1 si no en troba cap. La cerca passa per `GamepadManager`
|
||||||
|
// per obtenir el (name, path) i d'ahí a `GamepadBindings`.
|
||||||
|
auto serviceButtonForGamepadEvent(SDL_JoystickID jid) -> int {
|
||||||
|
for (const auto& info : GamepadManager::get().getAll()) {
|
||||||
|
if (info.pad == nullptr) { continue; }
|
||||||
|
if (SDL_GetJoystickID(SDL_GetGamepadJoystick(info.pad)) != jid) { continue; }
|
||||||
|
return GamepadBindings::get().getButton(info.name, info.path, GamepadBindings::Action::SERVICE);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void Director::processGamepadEvents() {
|
void Director::processGamepadEvents() {
|
||||||
// Dues responsabilitats: (1) passar ADDED/REMOVED al GamepadManager
|
// Dues responsabilitats: (1) passar ADDED/REMOVED al GamepadManager
|
||||||
// perquè obri/tanqui mandos i emeti les notificacions hot-plug; (2)
|
// perquè obri/tanqui mandos i emeti les notificacions hot-plug; (2)
|
||||||
// tractar el botó BACK com a toggle del menú quan està tancat (el
|
// tractar el botó SERVICE (per defecte BACK; remapejable per
|
||||||
// paral·lel de F9 per al teclat). Quan el menú està obert, el BACK
|
// `GamepadBindings`) com a toggle del menú quan està tancat (el
|
||||||
// passa pel pipeline del `ServiceMenu::handleEvent()` via
|
// paral·lel de F9 per al teclat). Quan el menú està obert, el botó
|
||||||
|
// SERVICE passa pel pipeline del `ServiceMenu::handleEvent()` via
|
||||||
// `processMenuInput()` i allà es converteix en `close()`.
|
// `processMenuInput()` i allà es converteix en `close()`.
|
||||||
for (auto it = EventBuffer::events.begin(); it != EventBuffer::events.end();) {
|
for (auto it = EventBuffer::events.begin(); it != EventBuffer::events.end();) {
|
||||||
bool consumed = false;
|
bool consumed = false;
|
||||||
@@ -306,10 +349,12 @@ void Director::processGamepadEvents() {
|
|||||||
GamepadManager::get().handleEvent(*it);
|
GamepadManager::get().handleEvent(*it);
|
||||||
consumed = true;
|
consumed = true;
|
||||||
} else if (it->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN &&
|
} else if (it->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN &&
|
||||||
it->gbutton.button == SDL_GAMEPAD_BUTTON_BACK &&
|
|
||||||
!ServiceMenu::get().isOpen()) {
|
!ServiceMenu::get().isOpen()) {
|
||||||
ServiceMenu::get().toggle();
|
const int SERVICE_BTN = serviceButtonForGamepadEvent(it->gbutton.which);
|
||||||
consumed = true;
|
if (SERVICE_BTN >= 0 && static_cast<int>(it->gbutton.button) == SERVICE_BTN) {
|
||||||
|
ServiceMenu::get().toggle();
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (consumed) {
|
if (consumed) {
|
||||||
it = EventBuffer::events.erase(it);
|
it = EventBuffer::events.erase(it);
|
||||||
|
|||||||
@@ -96,11 +96,24 @@ UiConfig::UiConfig()
|
|||||||
pause_exit_ms_(Defaults::UI::PAUSE_EXIT_MS),
|
pause_exit_ms_(Defaults::UI::PAUSE_EXIT_MS),
|
||||||
pause_enter_curve_(curveByName(Defaults::UI::PAUSE_ENTER_CURVE)),
|
pause_enter_curve_(curveByName(Defaults::UI::PAUSE_ENTER_CURVE)),
|
||||||
pause_exit_curve_(curveByName(Defaults::UI::PAUSE_EXIT_CURVE)),
|
pause_exit_curve_(curveByName(Defaults::UI::PAUSE_EXIT_CURVE)),
|
||||||
|
menu_select_sound_(Defaults::UI::MENU_SELECT_SOUND),
|
||||||
|
menu_accept_sound_(Defaults::UI::MENU_ACCEPT_SOUND),
|
||||||
hud_font_{.name = Defaults::UI::Font::HUD_NAME, .gif = Defaults::UI::Font::HUD_GIF, .fnt = Defaults::UI::Font::HUD_FNT, .kerning = Defaults::UI::Font::HUD_KERNING},
|
hud_font_{.name = Defaults::UI::Font::HUD_NAME, .gif = Defaults::UI::Font::HUD_GIF, .fnt = Defaults::UI::Font::HUD_FNT, .kerning = Defaults::UI::Font::HUD_KERNING},
|
||||||
placeholder_font_{.name = Defaults::UI::Font::PLACEHOLDER_NAME, .gif = Defaults::UI::Font::PLACEHOLDER_GIF, .fnt = Defaults::UI::Font::PLACEHOLDER_FNT, .kerning = Defaults::UI::Font::PLACEHOLDER_KERNING},
|
placeholder_font_{.name = Defaults::UI::Font::PLACEHOLDER_NAME, .gif = Defaults::UI::Font::PLACEHOLDER_GIF, .fnt = Defaults::UI::Font::PLACEHOLDER_FNT, .kerning = Defaults::UI::Font::PLACEHOLDER_KERNING},
|
||||||
room_text_font_{.name = Defaults::UI::Font::ROOM_TEXT_NAME, .gif = Defaults::UI::Font::ROOM_TEXT_GIF, .fnt = Defaults::UI::Font::ROOM_TEXT_FNT, .kerning = Defaults::UI::Font::ROOM_TEXT_KERNING},
|
room_text_font_{.name = Defaults::UI::Font::ROOM_TEXT_NAME, .gif = Defaults::UI::Font::ROOM_TEXT_GIF, .fnt = Defaults::UI::Font::ROOM_TEXT_FNT, .kerning = Defaults::UI::Font::ROOM_TEXT_KERNING},
|
||||||
menu_title_font_{.name = Defaults::UI::Font::MENU_TITLE_NAME, .gif = Defaults::UI::Font::MENU_TITLE_GIF, .fnt = Defaults::UI::Font::MENU_TITLE_FNT, .kerning = Defaults::UI::Font::MENU_TITLE_KERNING},
|
menu_title_font_{.name = Defaults::UI::Font::MENU_TITLE_NAME, .gif = Defaults::UI::Font::MENU_TITLE_GIF, .fnt = Defaults::UI::Font::MENU_TITLE_FNT, .kerning = Defaults::UI::Font::MENU_TITLE_KERNING},
|
||||||
menu_items_font_{.name = Defaults::UI::Font::MENU_ITEMS_NAME, .gif = Defaults::UI::Font::MENU_ITEMS_GIF, .fnt = Defaults::UI::Font::MENU_ITEMS_FNT, .kerning = Defaults::UI::Font::MENU_ITEMS_KERNING} {
|
menu_items_font_{.name = Defaults::UI::Font::MENU_ITEMS_NAME, .gif = Defaults::UI::Font::MENU_ITEMS_GIF, .fnt = Defaults::UI::Font::MENU_ITEMS_FNT, .kerning = Defaults::UI::Font::MENU_ITEMS_KERNING},
|
||||||
|
notifier_{
|
||||||
|
.padding_h = Defaults::UI::Notifier::PADDING_H,
|
||||||
|
.padding_top = Defaults::UI::Notifier::PADDING_TOP,
|
||||||
|
.padding_bottom = Defaults::UI::Notifier::PADDING_BOTTOM,
|
||||||
|
.line_gap = Defaults::UI::Notifier::LINE_GAP,
|
||||||
|
.margin_top = Defaults::UI::Notifier::MARGIN_TOP,
|
||||||
|
.border_px = Defaults::UI::Notifier::BORDER_PX,
|
||||||
|
.slide_ms = Defaults::UI::Notifier::SLIDE_MS,
|
||||||
|
.slide_in_curve = curveByName(Defaults::UI::Notifier::SLIDE_IN_CURVE),
|
||||||
|
.slide_out_curve = curveByName(Defaults::UI::Notifier::SLIDE_OUT_CURVE),
|
||||||
|
} {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto UiConfig::get() -> UiConfig& {
|
auto UiConfig::get() -> UiConfig& {
|
||||||
@@ -185,6 +198,25 @@ void UiConfig::load(const std::string& file_path) {
|
|||||||
readCurve(p, "exit_curve", pause_exit_curve_);
|
readCurve(p, "exit_curve", pause_exit_curve_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (yaml.contains("sounds")) {
|
||||||
|
const auto& s = yaml["sounds"];
|
||||||
|
readField(s, "menu_select", menu_select_sound_);
|
||||||
|
readField(s, "menu_accept", menu_accept_sound_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yaml.contains("notifier")) {
|
||||||
|
const auto& n = yaml["notifier"];
|
||||||
|
readField(n, "padding_h", notifier_.padding_h);
|
||||||
|
readField(n, "padding_top", notifier_.padding_top);
|
||||||
|
readField(n, "padding_bottom", notifier_.padding_bottom);
|
||||||
|
readField(n, "line_gap", notifier_.line_gap);
|
||||||
|
readField(n, "margin_top", notifier_.margin_top);
|
||||||
|
readField(n, "border_px", notifier_.border_px);
|
||||||
|
readField(n, "slide_ms", notifier_.slide_ms);
|
||||||
|
readCurve(n, "slide_in_curve", notifier_.slide_in_curve);
|
||||||
|
readCurve(n, "slide_out_curve", notifier_.slide_out_curve);
|
||||||
|
}
|
||||||
|
|
||||||
std::cout << "UiConfig: carregat des de " << file_path << '\n';
|
std::cout << "UiConfig: carregat des de " << file_path << '\n';
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
std::cerr << "UiConfig: error parsejant " << file_path << ": " << e.what() << '\n';
|
std::cerr << "UiConfig: error parsejant " << file_path << ": " << e.what() << '\n';
|
||||||
|
|||||||
@@ -20,6 +20,27 @@ class UiConfig {
|
|||||||
int kerning;
|
int kerning;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Signatura d'una funció d'easing: t ∈ [0, 1] → valor ∈ [0, 1].
|
||||||
|
// Permet triar corbes des de data/ui.yaml per nom (cadena) i guardar
|
||||||
|
// el punter a la corresponent funció de `utils/easing.hpp`.
|
||||||
|
using CurveFn = auto (*)(float) -> float;
|
||||||
|
|
||||||
|
// Geometria del Notifier (popup superior). Carregada des de
|
||||||
|
// data/ui.yaml secció `notifier:`; els valors per defecte vénen de
|
||||||
|
// Defaults::UI::Notifier. El moviment és per temps (slide_ms) i no per
|
||||||
|
// velocitat, perquè una caixa més gran no triga més a entrar/eixir.
|
||||||
|
struct Notifier {
|
||||||
|
int padding_h;
|
||||||
|
int padding_top;
|
||||||
|
int padding_bottom;
|
||||||
|
int line_gap;
|
||||||
|
int margin_top;
|
||||||
|
int border_px;
|
||||||
|
int slide_ms;
|
||||||
|
CurveFn slide_in_curve;
|
||||||
|
CurveFn slide_out_curve;
|
||||||
|
};
|
||||||
|
|
||||||
static auto get() -> UiConfig&;
|
static auto get() -> UiConfig&;
|
||||||
|
|
||||||
UiConfig(const UiConfig&) = delete;
|
UiConfig(const UiConfig&) = delete;
|
||||||
@@ -51,11 +72,6 @@ class UiConfig {
|
|||||||
[[nodiscard]] auto roomText() const -> Uint8 { return room_text_; }
|
[[nodiscard]] auto roomText() const -> Uint8 { return room_text_; }
|
||||||
[[nodiscard]] auto roomTextOutline() const -> Uint8 { return room_text_outline_; }
|
[[nodiscard]] auto roomTextOutline() const -> Uint8 { return room_text_outline_; }
|
||||||
|
|
||||||
// Signatura d'una funció d'easing: t ∈ [0, 1] → valor ∈ [0, 1].
|
|
||||||
// Permet triar corbes des de data/ui.yaml per nom (cadena) i guardar
|
|
||||||
// el punter a la corresponent funció de `utils/easing.hpp`.
|
|
||||||
using CurveFn = auto (*)(float) -> float;
|
|
||||||
|
|
||||||
// Timings (ms).
|
// Timings (ms).
|
||||||
[[nodiscard]] auto notificationMs() const -> int { return notification_ms_; }
|
[[nodiscard]] auto notificationMs() const -> int { return notification_ms_; }
|
||||||
[[nodiscard]] auto exitMs() const -> int { return exit_ms_; }
|
[[nodiscard]] auto exitMs() const -> int { return exit_ms_; }
|
||||||
@@ -76,6 +92,11 @@ class UiConfig {
|
|||||||
// retorna Easing::linear i deixa un warning al log.
|
// retorna Easing::linear i deixa un warning al log.
|
||||||
[[nodiscard]] static auto curveByName(const std::string& name) -> CurveFn;
|
[[nodiscard]] static auto curveByName(const std::string& name) -> CurveFn;
|
||||||
|
|
||||||
|
// Sons del menú de servei. Claus de Resource::Cache::loadSound (els
|
||||||
|
// fitxers viuen a `data/sounds/`).
|
||||||
|
[[nodiscard]] auto menuSelectSound() const -> const std::string& { return menu_select_sound_; }
|
||||||
|
[[nodiscard]] auto menuAcceptSound() const -> const std::string& { return menu_accept_sound_; }
|
||||||
|
|
||||||
// Fonts de UI.
|
// Fonts de UI.
|
||||||
[[nodiscard]] auto hudFont() const -> const Font& { return hud_font_; }
|
[[nodiscard]] auto hudFont() const -> const Font& { return hud_font_; }
|
||||||
[[nodiscard]] auto placeholderFont() const -> const Font& { return placeholder_font_; }
|
[[nodiscard]] auto placeholderFont() const -> const Font& { return placeholder_font_; }
|
||||||
@@ -83,6 +104,9 @@ class UiConfig {
|
|||||||
[[nodiscard]] auto menuTitleFont() const -> const Font& { return menu_title_font_; }
|
[[nodiscard]] auto menuTitleFont() const -> const Font& { return menu_title_font_; }
|
||||||
[[nodiscard]] auto menuItemsFont() const -> const Font& { return menu_items_font_; }
|
[[nodiscard]] auto menuItemsFont() const -> const Font& { return menu_items_font_; }
|
||||||
|
|
||||||
|
// Geometria del Notifier.
|
||||||
|
[[nodiscard]] auto notifier() const -> const Notifier& { return notifier_; }
|
||||||
|
|
||||||
// Flags visuals.
|
// Flags visuals.
|
||||||
[[nodiscard]] auto hudBgEnabled() const -> bool { return hud_bg_enabled_; }
|
[[nodiscard]] auto hudBgEnabled() const -> bool { return hud_bg_enabled_; }
|
||||||
|
|
||||||
@@ -121,11 +145,16 @@ class UiConfig {
|
|||||||
CurveFn pause_enter_curve_;
|
CurveFn pause_enter_curve_;
|
||||||
CurveFn pause_exit_curve_;
|
CurveFn pause_exit_curve_;
|
||||||
|
|
||||||
|
std::string menu_select_sound_;
|
||||||
|
std::string menu_accept_sound_;
|
||||||
|
|
||||||
Font hud_font_;
|
Font hud_font_;
|
||||||
Font placeholder_font_;
|
Font placeholder_font_;
|
||||||
Font room_text_font_;
|
Font room_text_font_;
|
||||||
Font menu_title_font_;
|
Font menu_title_font_;
|
||||||
Font menu_items_font_;
|
Font menu_items_font_;
|
||||||
|
|
||||||
|
Notifier notifier_;
|
||||||
|
|
||||||
bool hud_bg_enabled_{false};
|
bool hud_bg_enabled_{false};
|
||||||
};
|
};
|
||||||
@@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class Entity;
|
||||||
|
class EntityManager;
|
||||||
|
class Surface;
|
||||||
|
class Text;
|
||||||
class Tilemap;
|
class Tilemap;
|
||||||
|
|
||||||
// Mode editor modal del GameScene. Només disponible en Debug — en Release
|
// Mode editor modal del GameScene. Només disponible en Debug — en Release
|
||||||
@@ -11,55 +20,176 @@ class Tilemap;
|
|||||||
// L'editor NO gestiona la tecla de toggle pròpia (`editor_toggle`): això
|
// L'editor NO gestiona la tecla de toggle pròpia (`editor_toggle`): això
|
||||||
// ho fa GameScene per poder decidir la coexistència amb la pausa F11
|
// ho fa GameScene per poder decidir la coexistència amb la pausa F11
|
||||||
// (mútuament excloents). L'editor s'encarrega de:
|
// (mútuament excloents). L'editor s'encarrega de:
|
||||||
// - Estat propi (actiu/no actiu, brush, picker obert).
|
// - Estat propi (mode actiu, brush per mode, picker obert).
|
||||||
// - Input del ratolí i tecles internes (`T`, `ESC`).
|
// - Input del ratolí i tecles internes (`T`, `E`, `H`, `Supr`, `ESC`).
|
||||||
// - Render del border blau + brush preview + picker.
|
// - Render del border blau + brush preview + picker.
|
||||||
// - Persistència del tilemap al YAML del repo en sortir.
|
// - Persistència del tilemap i de les entitats al YAML del repo en sortir.
|
||||||
//
|
//
|
||||||
// GameScene, quan `active()` és true, salta el tick de jugadors/entitats
|
// Modes del cursor (`Mode`):
|
||||||
// i no els pinta; el tilemap sí es pinta perquè és el que s'està editant.
|
// - TILE : el clic esquerre pinta el `brush_tile_` al tilemap.
|
||||||
|
// - ENTITY : el clic esquerre col·loca una instància de `brush_entity_`.
|
||||||
|
// - HAND : selecciona/mou/borra entitats existents.
|
||||||
|
//
|
||||||
|
// Pickers (`Picker`):
|
||||||
|
// - TILE_PICKER (`T`) : selecciona índex de tile.
|
||||||
|
// - ENTITY_PICKER (`E`) : selecciona tipus d'entitat (nom registrat al
|
||||||
|
// factory).
|
||||||
|
// Mútuament excloents: obrir un tanca l'altre.
|
||||||
class Editor {
|
class Editor {
|
||||||
public:
|
public:
|
||||||
// Entra al mode editor: notifica i marca `active=true`. La congelació
|
// Entra al mode editor: notifica i marca `active=true`. La congelació
|
||||||
// d'entitats / jugador la gestiona GameScene consultant `active()`.
|
// d'entitats / jugador la gestiona GameScene consultant `active()`.
|
||||||
void enter();
|
void enter();
|
||||||
|
|
||||||
// Surt del mode editor: persisteix el tilemap al YAML (writer surgical)
|
// Surt del mode editor: persisteix el tilemap i les entitats al YAML
|
||||||
// i notifica. Si la persistència falla, emet la notificació d'error.
|
// (writers surgicals) i notifica. Si la persistència falla, emet la
|
||||||
void exit(Tilemap& tilemap);
|
// notificació d'error.
|
||||||
|
void exit(Tilemap& tilemap, const EntityManager& entities);
|
||||||
|
|
||||||
// Helper: enter() si no actiu, exit() si actiu. Útil des de GameScene
|
// Helper: enter() si no actiu, exit() si actiu. Útil des de GameScene
|
||||||
// quan detecta la tecla de toggle.
|
// quan detecta la tecla de toggle.
|
||||||
void toggle(Tilemap& tilemap);
|
void toggle(Tilemap& tilemap, const EntityManager& entities);
|
||||||
|
|
||||||
[[nodiscard]] auto active() const -> bool { return active_; }
|
[[nodiscard]] auto active() const -> bool { return active_; }
|
||||||
|
|
||||||
// Processa input propi (brush, picker). No-op si no actiu.
|
// Cert quan, en mode editor, GameScene ha de pintar les entitats
|
||||||
void update(int delta_ms, Tilemap& tilemap);
|
// de la sala. Falses en mode TILE (volem veure el tilemap pelat
|
||||||
|
// per pintar-lo) i quan hi ha un picker obert (la caixa del picker
|
||||||
|
// ja tapa la pantalla i el soroll visual addicional sobra).
|
||||||
|
[[nodiscard]] auto shouldShowEntities() const -> bool {
|
||||||
|
return active_ && mode_ != Mode::TILE && picker_ == Picker::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
// Pinta el border blau i, més endavant, brush preview + picker.
|
// Processa input propi (brush, picker). No-op si no actiu.
|
||||||
// No-op si no actiu.
|
// `entities` cal perquè els modes ENTITY i HAND modifiquen el manager
|
||||||
void render(const Tilemap& tilemap) const;
|
// en directe (col·loca/mou/borra entitats); en TILE no s'usa.
|
||||||
|
void update(int delta_ms, Tilemap& tilemap, EntityManager& entities);
|
||||||
|
|
||||||
|
// Pinta el border blau i, segons mode i picker, brush preview, marc
|
||||||
|
// d'entitat seleccionada o el picker. No-op si no actiu.
|
||||||
|
// `entities` és necessari per al marc d'entitat hovered/seleccionada
|
||||||
|
// en Mode::HAND; en altres modes no s'usa.
|
||||||
|
void render(const Tilemap& tilemap, const EntityManager& entities) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Les branques d'update / render segons el mode (picker obert o
|
// Mode del cursor: determina què fa el clic esquerre sobre el mapa.
|
||||||
// pintura al mapa) queden separades per claredat; no formen part
|
enum class Mode : std::uint8_t { TILE,
|
||||||
// de l'API pública perquè només té sentit cridar-les des d'`update`.
|
ENTITY,
|
||||||
void updateMap(Tilemap& tilemap, int canvas_x, int canvas_y);
|
HAND };
|
||||||
void updatePicker(const Tilemap& tilemap, int canvas_x, int canvas_y);
|
|
||||||
void renderPicker(const Tilemap& tilemap) const;
|
// Quin picker està obert (si n'hi ha). Mútuament excloents.
|
||||||
|
enum class Picker : std::uint8_t { NONE,
|
||||||
|
TILE,
|
||||||
|
ENTITY };
|
||||||
|
|
||||||
|
// Consum de hotkeys pròpies (T, E, H, ESC). Modifica `mode_` / `picker_`.
|
||||||
|
// Es crida al principi d'`update` perquè altres handlers no vegin
|
||||||
|
// tecles ja interpretades pel propi editor.
|
||||||
|
void handleHotkeys();
|
||||||
|
|
||||||
|
// Aplica una tecla i retorna true si l'editor l'ha consumit. Aïllat
|
||||||
|
// per mantenir baixa la complexitat cognitiva de `handleHotkeys` —
|
||||||
|
// l'iteració del buffer queda neta i la lògica de transició viu en
|
||||||
|
// un sol lloc.
|
||||||
|
auto applyHotkey(int scancode) -> bool;
|
||||||
|
|
||||||
|
// Branques d'update segons (mode, picker). Només té sentit cridar-les
|
||||||
|
// des d'`update`.
|
||||||
|
void updateTileBrush(Tilemap& tilemap, int canvas_x, int canvas_y);
|
||||||
|
void updateEntityBrush(EntityManager& entities, int canvas_x, int canvas_y);
|
||||||
|
void updateHand(const Tilemap& tilemap, EntityManager& entities, int canvas_x, int canvas_y);
|
||||||
|
void updateTilePicker(const Tilemap& tilemap, int canvas_x, int canvas_y);
|
||||||
|
void updateEntityPicker(int canvas_x, int canvas_y);
|
||||||
|
|
||||||
|
// Intent de col·locació segons brush_entity_ a la cel·la del cursor.
|
||||||
|
// Construeix un node spawn sintètic, fa el snap concret per tipus
|
||||||
|
// (sarc → rejilla 4×5; door → fila del mur i, de moment, columna
|
||||||
|
// canònica) i comprova solapaments. No-op si la cel·la està ocupada
|
||||||
|
// o el snap no és vàlid. Retorna true si s'ha col·locat.
|
||||||
|
auto tryPlaceEntity(EntityManager& entities) -> bool;
|
||||||
|
|
||||||
|
// Mou l'entitat seleccionada a la cel·la del cursor: borra l'antiga
|
||||||
|
// i crida `addSpawn` amb la nova posició. Reusa la mateixa lògica
|
||||||
|
// de snap/solapament que la col·locació nova, excloent l'entitat
|
||||||
|
// que es mou del check de solapament. Cas especial per a la porta:
|
||||||
|
// el sprite ocupa 3 cols (col-1, col, col+1) sobre el mur superior;
|
||||||
|
// per tant la columna central vàlida és [1, cols-2] — `tilemap` és
|
||||||
|
// necessari per saber `cols`.
|
||||||
|
auto tryMoveSelected(const Tilemap& tilemap, EntityManager& entities) const -> bool;
|
||||||
|
|
||||||
|
// Sub-handlers de `updateHand`. Aïllats per mantenir baixa la
|
||||||
|
// complexitat cognitiva del bucle d'esdeveniments.
|
||||||
|
void handleHandLeftClick(const Tilemap& tilemap, EntityManager& entities);
|
||||||
|
void handleHandRightClick();
|
||||||
|
|
||||||
|
// Borra l'entitat seleccionada. La validació de constraints
|
||||||
|
// (1 puerta, ≥1 sarc) es fa al pas 8.
|
||||||
|
void deleteSelected(EntityManager& entities);
|
||||||
|
|
||||||
|
// Render del marc d'una entitat: 1 px sobre el seu pixel_rect.
|
||||||
|
static void renderEntityFrame(const Entity& e, std::uint32_t argb);
|
||||||
|
|
||||||
|
// HUD d'edició: franja superior amb mode, brush, cursor i comptadors
|
||||||
|
// de constraints. Substitueix el HUD del joc mentre l'editor és actiu.
|
||||||
|
void renderEditorHud(const EntityManager& entities) const;
|
||||||
|
|
||||||
|
// Carrega la font 8bithud del cache. Lazy: al primer enter().
|
||||||
|
void ensureHudFont();
|
||||||
|
|
||||||
|
void renderTilePicker(const Tilemap& tilemap) const;
|
||||||
|
void renderEntityPicker(const Tilemap& tilemap) const;
|
||||||
|
|
||||||
bool active_{false};
|
bool active_{false};
|
||||||
|
Mode mode_{Mode::TILE};
|
||||||
|
Picker picker_{Picker::NONE};
|
||||||
|
|
||||||
// Estat de l'edició (brush + cursor + picker). Es poblen al pas
|
// Cursor en cel·les del tilemap. -1/-1 = fora del mapa.
|
||||||
// següent; els declarem ara perquè `update()` pugui mutar-los i les
|
int cursor_col_{-1};
|
||||||
// signatures queden estables entre hitos.
|
|
||||||
int brush_{-1}; // Índex de tile al tileset; -1 = sense brush.
|
|
||||||
int cursor_col_{-1}; // Cel·la del mapa sota el cursor (-1 = fora).
|
|
||||||
int cursor_row_{-1};
|
int cursor_row_{-1};
|
||||||
bool picker_open_{false};
|
|
||||||
int picker_scroll_{0}; // Fila superior visible del picker (linealitzat).
|
// Brush actiu per Mode::TILE (índex al tileset; -1 = sense brush).
|
||||||
int picker_hover_{-1}; // Índex del tile sota el cursor dins del picker.
|
int brush_tile_{-1};
|
||||||
|
// Estat del TilePicker.
|
||||||
|
int tile_picker_scroll_{0};
|
||||||
|
int tile_picker_hover_{-1};
|
||||||
|
|
||||||
|
// Brush actiu per Mode::ENTITY (nom registrat al factory; "" = cap).
|
||||||
|
std::string brush_entity_;
|
||||||
|
// Estat de l'EntityPicker.
|
||||||
|
int entity_picker_scroll_{0};
|
||||||
|
int entity_picker_hover_{-1}; // Índex a la llista de noms.
|
||||||
|
|
||||||
|
// Entrada editable del picker. Cada item recorda el nom (clau de
|
||||||
|
// factory + YAML), el surface del sprite (frame 0 com a icona) i la
|
||||||
|
// mida de cada frame al GIF — necessària per clipar el primer frame
|
||||||
|
// quan el GIF és un strip multi-frame.
|
||||||
|
struct EditableEntity {
|
||||||
|
std::string name;
|
||||||
|
std::shared_ptr<Surface> sprite;
|
||||||
|
int frame_w{0};
|
||||||
|
int frame_h{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Construïda lazy al primer enter() i preservada entre sessions:
|
||||||
|
// el cost és I/O + parse de N plantilles YAML, però `EntityLoader`
|
||||||
|
// ja les memoritza, així que rellegir només resoldria per sortida i
|
||||||
|
// entrada al editor — no val la pena.
|
||||||
|
void ensureEditableEntities();
|
||||||
|
|
||||||
|
std::vector<EditableEntity> editable_entities_;
|
||||||
|
|
||||||
|
// Font compartida amb el Notifier/HUD: el surface el carrega Director
|
||||||
|
// a l'arrencada, ací només prenem la referència del cache. shared_ptr
|
||||||
|
// perquè `Text` accepta el surface per shared_ptr i així no copiem.
|
||||||
|
std::shared_ptr<Text> hud_font_;
|
||||||
|
|
||||||
|
// Selecció en Mode::HAND. Treballem amb índexs sobre el snapshot de
|
||||||
|
// `EntityManager::getEntities()` perquè és el que ens dóna l'API
|
||||||
|
// pública. Després de cada mutació (move/delete) ho recalculem; com
|
||||||
|
// que l'editor congela el joc, el snapshot només canvia per accions
|
||||||
|
// pròpies.
|
||||||
|
int hand_hover_idx_{-1};
|
||||||
|
int hand_selected_idx_{-1};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _DEBUG
|
#endif // _DEBUG
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
#include "game/editor/editor_config.hpp"
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "core/resources/resource_helper.hpp"
|
||||||
|
#include "external/fkyaml_node.hpp"
|
||||||
|
#include "utils/defaults.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void readField(const fkyaml::node& node, const char* key, T& dst) {
|
||||||
|
if (node.contains(key)) { dst = node[key].get_value<T>(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Llegeix un color ARGB. Accepta enter (0xFF2244FF al YAML) o cadena
|
||||||
|
// ("0xFF2244FF" / "#FF2244FF"). std::stoul amb base=0 detecta el prefix
|
||||||
|
// "0x" automàticament; si el valor és invàlid, deixem `dst` intacte i
|
||||||
|
// avisem perquè l'error no passi en silenci.
|
||||||
|
void readArgb(const fkyaml::node& node, const char* key, std::uint32_t& dst) {
|
||||||
|
if (!node.contains(key)) { return; }
|
||||||
|
try {
|
||||||
|
const auto& v = node[key];
|
||||||
|
if (v.is_integer()) {
|
||||||
|
dst = static_cast<std::uint32_t>(v.get_value<std::uint64_t>());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto s = v.get_value<std::string>();
|
||||||
|
if (!s.empty() && s.front() == '#') { s.erase(0, 1); }
|
||||||
|
dst = static_cast<std::uint32_t>(std::stoul(s, nullptr, 0));
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "EditorConfig: color '" << key << "' invàlid (" << e.what() << ") — ignorat\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
EditorConfig::EditorConfig()
|
||||||
|
: picker_margin_(Defaults::Editor::PICKER_MARGIN),
|
||||||
|
picker_spacing_(Defaults::Editor::PICKER_SPACING),
|
||||||
|
border_argb_(Defaults::Editor::BORDER_ARGB),
|
||||||
|
picker_bg_argb_(Defaults::Editor::PICKER_BG_ARGB),
|
||||||
|
picker_hover_argb_(Defaults::Editor::PICKER_HOVER_ARGB) {
|
||||||
|
}
|
||||||
|
|
||||||
|
auto EditorConfig::get() -> EditorConfig& {
|
||||||
|
static EditorConfig instance_;
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorConfig::load(const std::string& file_path) {
|
||||||
|
auto bytes = Resource::Helper::loadFile(file_path);
|
||||||
|
if (bytes.empty()) {
|
||||||
|
std::cerr << "EditorConfig: no s'ha pogut obrir " << file_path << " — s'usaran els defaults compilats\n";
|
||||||
|
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("picker")) {
|
||||||
|
const auto& p = yaml["picker"];
|
||||||
|
readField(p, "margin", picker_margin_);
|
||||||
|
readField(p, "spacing", picker_spacing_);
|
||||||
|
}
|
||||||
|
if (yaml.contains("colors")) {
|
||||||
|
const auto& c = yaml["colors"];
|
||||||
|
readArgb(c, "border", border_argb_);
|
||||||
|
readArgb(c, "picker_bg", picker_bg_argb_);
|
||||||
|
readArgb(c, "picker_hover", picker_hover_argb_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "EditorConfig: carregat des de " << file_path << '\n';
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "EditorConfig: error parsejant " << file_path << ": " << e.what() << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // _DEBUG
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Configuració visual de l'editor de tilemap (Debug-only). Carregada des
|
||||||
|
// de data/editor/editor.yaml; les claus absents cauen als valors compilats
|
||||||
|
// a Defaults::Editor. Mateix patró que UiConfig (Meyers singleton + load).
|
||||||
|
class EditorConfig {
|
||||||
|
public:
|
||||||
|
static auto get() -> EditorConfig&;
|
||||||
|
|
||||||
|
EditorConfig(const EditorConfig&) = delete;
|
||||||
|
EditorConfig(EditorConfig&&) = delete;
|
||||||
|
auto operator=(const EditorConfig&) -> EditorConfig& = delete;
|
||||||
|
auto operator=(EditorConfig&&) -> EditorConfig& = delete;
|
||||||
|
|
||||||
|
void load(const std::string& file_path);
|
||||||
|
|
||||||
|
[[nodiscard]] auto pickerMargin() const -> int { return picker_margin_; }
|
||||||
|
[[nodiscard]] auto pickerSpacing() const -> int { return picker_spacing_; }
|
||||||
|
[[nodiscard]] auto borderArgb() const -> std::uint32_t { return border_argb_; }
|
||||||
|
[[nodiscard]] auto pickerBgArgb() const -> std::uint32_t { return picker_bg_argb_; }
|
||||||
|
[[nodiscard]] auto pickerHoverArgb() const -> std::uint32_t { return picker_hover_argb_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
EditorConfig();
|
||||||
|
~EditorConfig() = default;
|
||||||
|
|
||||||
|
int picker_margin_;
|
||||||
|
int picker_spacing_;
|
||||||
|
std::uint32_t border_argb_;
|
||||||
|
std::uint32_t picker_bg_argb_;
|
||||||
|
std::uint32_t picker_hover_argb_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _DEBUG
|
||||||
@@ -4,9 +4,14 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
AnimationComponent::AnimationComponent(std::vector<int> frames, float fps, bool loop)
|
AnimationComponent::AnimationComponent(std::vector<int> frames, float fps, bool loop)
|
||||||
|
: AnimationComponent(std::move(frames), fps, loop, false, false) {}
|
||||||
|
|
||||||
|
AnimationComponent::AnimationComponent(std::vector<int> frames, float fps, bool loop, bool flip_h, bool flip_v)
|
||||||
: frames_(std::move(frames)),
|
: frames_(std::move(frames)),
|
||||||
fps_(fps > 0.0F ? fps : 1.0F),
|
fps_(fps > 0.0F ? fps : 1.0F),
|
||||||
loop_(loop) {
|
loop_(loop),
|
||||||
|
flip_h_(flip_h),
|
||||||
|
flip_v_(flip_v) {
|
||||||
// Una animació d'1 sol frame amb loop=false és una animació que ja s'ha
|
// Una animació d'1 sol frame amb loop=false és una animació que ja s'ha
|
||||||
// acabat de reproduir: no hi ha cap transició per mostrar. Marcant-la
|
// acabat de reproduir: no hi ha cap transició per mostrar. Marcant-la
|
||||||
// com a completada des de l'inici permet als consumidors (p.ex. Door)
|
// com a completada des de l'inici permet als consumidors (p.ex. Door)
|
||||||
@@ -46,3 +51,9 @@ auto AnimationComponent::currentFrame() const -> int {
|
|||||||
if (frames_.empty()) { return 0; }
|
if (frames_.empty()) { return 0; }
|
||||||
return frames_[static_cast<std::size_t>(current_)];
|
return frames_[static_cast<std::size_t>(current_)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto AnimationComponent::flipMode() const -> SDL_FlipMode {
|
||||||
|
if (flip_h_) { return SDL_FLIP_HORIZONTAL; }
|
||||||
|
if (flip_v_) { return SDL_FLIP_VERTICAL; }
|
||||||
|
return SDL_FLIP_NONE;
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -13,10 +15,18 @@
|
|||||||
// algun dia aterri l'art definitiu, el renderer consultarà `currentFrame()`
|
// algun dia aterri l'art definitiu, el renderer consultarà `currentFrame()`
|
||||||
// i traduirà l'índex al rectangle del sprite corresponent. De moment els
|
// i traduirà l'índex al rectangle del sprite corresponent. De moment els
|
||||||
// placeholders tenen un únic frame i `currentFrame()` retorna sempre 0.
|
// placeholders tenen un únic frame i `currentFrame()` retorna sempre 0.
|
||||||
|
//
|
||||||
|
// El flip (H/V) és propietat de l'animació, no del codi del joc: el YAML
|
||||||
|
// declara `flip_h`/`flip_v` al bloc d'animació i el component els exposa
|
||||||
|
// via `flipMode()`. Les entitats apliquen aquest valor al seu `flip_` quan
|
||||||
|
// canvien d'animació, sense decidir-lo per direcció. Així una mòmia que va
|
||||||
|
// cap a l'oest reprodueix `walk_west` (frames de l'est + `flip_h: true`)
|
||||||
|
// sense que el codi de la mòmia toqui flip directament.
|
||||||
class AnimationComponent {
|
class AnimationComponent {
|
||||||
public:
|
public:
|
||||||
AnimationComponent() = default;
|
AnimationComponent() = default;
|
||||||
AnimationComponent(std::vector<int> frames, float fps, bool loop);
|
AnimationComponent(std::vector<int> frames, float fps, bool loop);
|
||||||
|
AnimationComponent(std::vector<int> frames, float fps, bool loop, bool flip_h, bool flip_v);
|
||||||
|
|
||||||
// Avanç del temps. Si `frames_` està buit o té un sol element, no fa
|
// Avanç del temps. Si `frames_` està buit o té un sol element, no fa
|
||||||
// res. En mode loop, `current_frame_` rebota a 0 en passar del final;
|
// res. En mode loop, `current_frame_` rebota a 0 en passar del final;
|
||||||
@@ -37,10 +47,19 @@ class AnimationComponent {
|
|||||||
[[nodiscard]] auto completed() const -> bool { return completed_; }
|
[[nodiscard]] auto completed() const -> bool { return completed_; }
|
||||||
[[nodiscard]] auto loops() const -> bool { return loop_; }
|
[[nodiscard]] auto loops() const -> bool { return loop_; }
|
||||||
|
|
||||||
|
[[nodiscard]] auto flipH() const -> bool { return flip_h_; }
|
||||||
|
[[nodiscard]] auto flipV() const -> bool { return flip_v_; }
|
||||||
|
// Combinació dels dos flags al `SDL_FlipMode` que entén el render.
|
||||||
|
// SDL3 no admet bitmask: si fos VH simultani, prioritzem H (cap cas
|
||||||
|
// pràctic actual el necessita combinat).
|
||||||
|
[[nodiscard]] auto flipMode() const -> SDL_FlipMode;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<int> frames_;
|
std::vector<int> frames_;
|
||||||
float fps_{1.0F};
|
float fps_{1.0F};
|
||||||
bool loop_{true};
|
bool loop_{true};
|
||||||
|
bool flip_h_{false};
|
||||||
|
bool flip_v_{false};
|
||||||
int current_{0};
|
int current_{0};
|
||||||
float accumulator_{0.0F};
|
float accumulator_{0.0F};
|
||||||
bool completed_{false};
|
bool completed_{false};
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ FloorTrap::FloorTrap(const fkyaml::node& yaml) {
|
|||||||
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("floor_trap");
|
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("floor_trap");
|
||||||
collides_ = false;
|
collides_ = false;
|
||||||
interactable_ = false;
|
interactable_ = false;
|
||||||
|
// Trigger d'àrea: la trampa no bloqueja el pas però vol rebre
|
||||||
|
// `onPlayerContact` quan el jugador la solapa. Tanca el gap latent
|
||||||
|
// que la deixava muda (`checkPlayerCollisions` filtrava pel `collides`).
|
||||||
|
triggers_on_overlap_ = true;
|
||||||
|
|
||||||
if (yaml.contains("cycle")) {
|
if (yaml.contains("cycle")) {
|
||||||
const auto& c = yaml["cycle"];
|
const auto& c = yaml["cycle"];
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#include "game/entities/concrete/hole.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "game/entities/entity_factory.hpp"
|
||||||
|
#include "game/entities/player.hpp"
|
||||||
|
|
||||||
|
Hole::Hole(const fkyaml::node& yaml) {
|
||||||
|
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("hole");
|
||||||
|
// Sense xoc físic ni interacció — només trigger d'àrea. El bucle de
|
||||||
|
// contacte ens admet via `triggers_on_overlap_=true`. Damage queda a
|
||||||
|
// 0 (no publiquem `player_hit`); la mort la dispara `integrateJump`
|
||||||
|
// del jugador quan supera `HOLE_FALL_DEPTH`.
|
||||||
|
collides_ = false;
|
||||||
|
interactable_ = false;
|
||||||
|
triggers_on_overlap_ = true;
|
||||||
|
// Decal de sòl: `Entity::getFeetY()` torna sentinel mínim si aquest
|
||||||
|
// flag està actiu — així el forat es pinta abans que qualsevol actor.
|
||||||
|
is_floor_decal_ = true;
|
||||||
|
setDamage(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Hole::onPlayerContact(Player* player) {
|
||||||
|
if (player == nullptr) { return; }
|
||||||
|
// Només mossega quan el jugador trepitja el forat de peu (GROUNDED).
|
||||||
|
// En l'aire, el filtre d'altura del bucle ja l'ha rebutjat (height_=0
|
||||||
|
// i jump_z_>0 prou per superar-ho); però mantenim el gat per defensa
|
||||||
|
// davant de futurs canvis al filtre.
|
||||||
|
if (player->getState() != Player::State::GROUNDED) { return; }
|
||||||
|
|
||||||
|
// Re-verificació estricta: la hit-box interna del jugador (8×8) ha
|
||||||
|
// d'estar COMPLETAMENT continguda dins del rect del forat (16×16). El
|
||||||
|
// bucle de col·lisió del manager ja ens ha cridat per qualsevol solape;
|
||||||
|
// ací filtrem perquè la caiguda només es dispari quan els "dos peus"
|
||||||
|
// estan dins del forat, no quan només la vora frega — sinó es percep
|
||||||
|
// com un imant que succiona el jugador al centre.
|
||||||
|
const SDL_FRect HIT = player->getHitBox();
|
||||||
|
const SDL_FRect BOX = getCollisionRect();
|
||||||
|
const bool CONTAINED = HIT.x >= BOX.x && HIT.y >= BOX.y && HIT.x + HIT.w <= BOX.x + BOX.w && HIT.y + HIT.h <= BOX.y + BOX.h;
|
||||||
|
if (!CONTAINED) { return; }
|
||||||
|
|
||||||
|
// Snap al centre XY del forat + Y de la base del tile (per al clip
|
||||||
|
// rect del sprite mentre cau). `enterHoleFall` rep coordenades
|
||||||
|
// absolutes en el canvas — el player s'encarrega de convertir a la
|
||||||
|
// seua convenció (cantó superior-esquerra del sprite).
|
||||||
|
const float CENTER_X = BOX.x + (BOX.w * 0.5F);
|
||||||
|
const float CENTER_Y = BOX.y + (BOX.h * 0.5F);
|
||||||
|
const float FLOOR_Y = BOX.y + BOX.h;
|
||||||
|
player->enterHoleFall(CENTER_X, CENTER_Y, FLOOR_Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_ENTITY("hole", Hole)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "external/fkyaml_node.hpp"
|
||||||
|
#include "game/entities/hazard.hpp"
|
||||||
|
|
||||||
|
// Forat al sòl: hazard estàtic de 16×16 que NO bloqueja el pas però
|
||||||
|
// detecta quan el jugador hi cau a sobre. Característiques:
|
||||||
|
// - `collides_=false` + `triggers_on_overlap_=true`: rep `onPlayerContact`
|
||||||
|
// pel bucle de `EntityManager::checkPlayerCollisions` sense bloquejar
|
||||||
|
// el moviment del jugador.
|
||||||
|
// - `height_=0`: el filtre per altura del bucle (`jump_z > height`) NO
|
||||||
|
// s'activa amb height a zero, així que el bucle de contacte SÍ crida
|
||||||
|
// `onPlayerContact` també per a jugadors AIRBORNE. Qui rebutja els
|
||||||
|
// estats distints de GROUNDED (saltant, caient, HURT, DYING...) és el
|
||||||
|
// guard explícit dins de `Hole::onPlayerContact`. El filtre d'altura
|
||||||
|
// és una optimització ortogonal del bucle, no la defensa principal.
|
||||||
|
// - Damage 0: el contacte no publica `player_hit`. En lloc d'això,
|
||||||
|
// `onPlayerContact` invoca `Player::enterHoleFall`, que transiciona
|
||||||
|
// el jugador a AIRBORNE amb subestat `falling_through_hole_`.
|
||||||
|
// - Verificació amb hit-box interna: `onPlayerContact` re-verifica
|
||||||
|
// l'overlap amb `Player::getHitBox()` (8×8 centrada al sprite),
|
||||||
|
// així només es dispara quan el centre del jugador entra al tile.
|
||||||
|
// - Z-order: `is_floor_decal_=true` (heretat) — `Entity::getFeetY()`
|
||||||
|
// retorna sentinel mínim, així el forat es pinta abans que qualsevol
|
||||||
|
// actor i les momies passen per davant. L'efecte "el sòl es traga el
|
||||||
|
// jugador" no es fa amb Y-sort: el `Player::render` aplica un clip
|
||||||
|
// rect mentre `falling_through_hole_=true` que retalla el sprite per
|
||||||
|
// sota de la base del tile del forat.
|
||||||
|
class Hole : public StaticHazard {
|
||||||
|
public:
|
||||||
|
explicit Hole(const fkyaml::node& yaml);
|
||||||
|
|
||||||
|
// No publica `player_hit`. En el seu lloc, dispara
|
||||||
|
// `Player::enterHoleFall(center_x, center_y, floor_y)` perquè el
|
||||||
|
// jugador armi el clip rect a la Y de la base del forat.
|
||||||
|
void onPlayerContact(Player* player) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(hole-feature): afegir reset de `falling_through_hole_` i `hole_clip_bottom_y_` a `Player::disable()` per simetria.
|
||||||
|
// TODO(hole-feature): afegir tests dedicats del comportament de la mòmia amb forats (no requereixen Player real).
|
||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "game/entities/entity_factory.hpp"
|
#include "game/entities/entity_factory.hpp"
|
||||||
|
#include "game/entities/entity_manager.hpp"
|
||||||
#include "game/entities/event_bus.hpp"
|
#include "game/entities/event_bus.hpp"
|
||||||
#include "game/entities/player.hpp"
|
#include "game/entities/player.hpp"
|
||||||
#include "game/map/map.hpp"
|
#include "game/map/map.hpp"
|
||||||
@@ -26,11 +28,15 @@ namespace {
|
|||||||
// Llegeix un bloc d'animació del YAML amb fallbacks tolerants (mateix
|
// Llegeix un bloc d'animació del YAML amb fallbacks tolerants (mateix
|
||||||
// patró que Door/Sarcophagus). Si la clau no apareix, retorna una
|
// patró que Door/Sarcophagus). Si la clau no apareix, retorna una
|
||||||
// animació d'1 frame loopant, neutra — la momia es podrà moure però
|
// animació d'1 frame loopant, neutra — la momia es podrà moure però
|
||||||
// visualment es quedarà estàtica.
|
// visualment es quedarà estàtica. Llegeix `flip_h`/`flip_v` (default
|
||||||
|
// false) i els passa al component perquè el motor sàpiga aplicar-los
|
||||||
|
// al sprite quan aquesta animació estigui activa.
|
||||||
auto buildAnimation(const fkyaml::node& yaml, const std::string& key) -> AnimationComponent {
|
auto buildAnimation(const fkyaml::node& yaml, const std::string& key) -> AnimationComponent {
|
||||||
std::vector<int> frames = {0};
|
std::vector<int> frames = {0};
|
||||||
float fps = 8.0F;
|
float fps = 8.0F;
|
||||||
bool loop = true;
|
bool loop = true;
|
||||||
|
bool flip_h = false;
|
||||||
|
bool flip_v = false;
|
||||||
if (yaml.contains("animations") && yaml["animations"].contains(key)) {
|
if (yaml.contains("animations") && yaml["animations"].contains(key)) {
|
||||||
const auto& anim = yaml["animations"][key];
|
const auto& anim = yaml["animations"][key];
|
||||||
if (anim.contains("frames")) {
|
if (anim.contains("frames")) {
|
||||||
@@ -43,8 +49,10 @@ namespace {
|
|||||||
}
|
}
|
||||||
if (anim.contains("fps")) { fps = anim["fps"].get_value<float>(); }
|
if (anim.contains("fps")) { fps = anim["fps"].get_value<float>(); }
|
||||||
if (anim.contains("loop")) { loop = anim["loop"].get_value<bool>(); }
|
if (anim.contains("loop")) { loop = anim["loop"].get_value<bool>(); }
|
||||||
|
if (anim.contains("flip_h")) { flip_h = anim["flip_h"].get_value<bool>(); }
|
||||||
|
if (anim.contains("flip_v")) { flip_v = anim["flip_v"].get_value<bool>(); }
|
||||||
}
|
}
|
||||||
return {frames, fps, loop};
|
return {frames, fps, loop, flip_h, flip_v};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Busca si el segment [prev, prev+delta] travessa una lane del patró
|
// Busca si el segment [prev, prev+delta] travessa una lane del patró
|
||||||
@@ -73,35 +81,41 @@ namespace {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Mummy::Mummy(const fkyaml::node& yaml)
|
Mummy::Mummy(const fkyaml::node& yaml)
|
||||||
: anim_walk_down_(buildAnimation(yaml, "walk_down")),
|
: anim_walk_south_(buildAnimation(yaml, "walk_south")),
|
||||||
anim_walk_up_(buildAnimation(yaml, "walk_up")),
|
anim_walk_north_(buildAnimation(yaml, "walk_north")),
|
||||||
anim_walk_lateral_(buildAnimation(yaml, "walk")) {
|
anim_walk_east_(buildAnimation(yaml, "walk_east")),
|
||||||
|
anim_walk_west_(buildAnimation(yaml, "walk_west")) {
|
||||||
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("mummy");
|
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("mummy");
|
||||||
collides_ = true;
|
collides_ = true;
|
||||||
interactable_ = false;
|
interactable_ = false;
|
||||||
|
|
||||||
// Velocitat i dany des del bloc `stats` (si existeix).
|
// Velocitat i dany des del bloc `stats` (si existeix). El flag
|
||||||
|
// `kills_outright` també hi viu: per defecte true (la mòmia mata
|
||||||
|
// d'un sol toc); posat a false al YAML, el contacte només publica
|
||||||
|
// `player_hit` sense disparar el cicle DYING.
|
||||||
if (yaml.contains("stats")) {
|
if (yaml.contains("stats")) {
|
||||||
const auto& s = yaml["stats"];
|
const auto& s = yaml["stats"];
|
||||||
if (s.contains("speed")) { setSpeed(s["speed"].get_value<float>()); }
|
if (s.contains("speed")) { setSpeed(s["speed"].get_value<float>()); }
|
||||||
if (s.contains("damage")) { setDamage(s["damage"].get_value<int>()); }
|
if (s.contains("damage")) { setDamage(s["damage"].get_value<int>()); }
|
||||||
|
if (s.contains("kills_outright")) { kills_outright_ = s["kills_outright"].get_value<bool>(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direcció inicial llegida del bloc `ai:`. Per defecte dreta, per
|
// Direcció inicial llegida del bloc `ai:`. Per defecte est, per
|
||||||
// coherència amb el que fa RollingRock. Valors acceptats: right, left,
|
// coherència amb el que fa RollingRock. Valors acceptats:
|
||||||
// up, down. Qualsevol altre s'interpreta com a "sense direcció" (la
|
// east, west, north, south. Qualsevol altre s'interpreta com a
|
||||||
// momia no es mou fins que step() no triï una direcció).
|
// "sense direcció" (la momia no es mou fins que step() no triï una
|
||||||
std::string initial = "right";
|
// direcció).
|
||||||
|
std::string initial = "east";
|
||||||
if (yaml.contains("ai") && yaml["ai"].contains("initial_direction")) {
|
if (yaml.contains("ai") && yaml["ai"].contains("initial_direction")) {
|
||||||
initial = yaml["ai"]["initial_direction"].get_value<std::string>();
|
initial = yaml["ai"]["initial_direction"].get_value<std::string>();
|
||||||
}
|
}
|
||||||
if (initial == "right") {
|
if (initial == "east") {
|
||||||
changeDirection(1.0F, 0.0F);
|
changeDirection(1.0F, 0.0F);
|
||||||
} else if (initial == "left") {
|
} else if (initial == "west") {
|
||||||
changeDirection(-1.0F, 0.0F);
|
changeDirection(-1.0F, 0.0F);
|
||||||
} else if (initial == "up") {
|
} else if (initial == "north") {
|
||||||
changeDirection(0.0F, -1.0F);
|
changeDirection(0.0F, -1.0F);
|
||||||
} else if (initial == "down") {
|
} else if (initial == "south") {
|
||||||
changeDirection(0.0F, 1.0F);
|
changeDirection(0.0F, 1.0F);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,22 +138,26 @@ Mummy::Mummy(const fkyaml::node& yaml)
|
|||||||
|
|
||||||
void Mummy::changeDirection(float dx, float dy) {
|
void Mummy::changeDirection(float dx, float dy) {
|
||||||
setDirection(dx, dy);
|
setDirection(dx, dy);
|
||||||
// Prevalença lateral: si hi ha component horitzontal s'usa l'anim
|
// Una animació per cada cardinal — el flip viu al YAML
|
||||||
// lateral + flip segons el signe. Només direccions purament verticals
|
// (`walk_west: flip_h: true`) i el codi només selecciona quina
|
||||||
// disparen walk_up/walk_down. Quan la direcció queda (0,0) (cul-de-sac
|
// animació mostrar. Prevalença lateral en diagonals; quan la direcció
|
||||||
// amb NONE) no toquem l'animació: la momia es quedarà al frame actual
|
// queda (0,0) (cul-de-sac amb NONE) no toquem l'animació: la mòmia es
|
||||||
// fins que step() li torni a assignar direcció.
|
// quedarà al frame actual fins que step() li torni a assignar direcció.
|
||||||
if (dx != 0.0F) {
|
if (dx > 0.0F) {
|
||||||
anim_ = anim_walk_lateral_;
|
anim_ = anim_walk_east_;
|
||||||
flip_ = (dx < 0.0F) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
|
} else if (dx < 0.0F) {
|
||||||
|
anim_ = anim_walk_west_;
|
||||||
} else if (dy < 0.0F) {
|
} else if (dy < 0.0F) {
|
||||||
anim_ = anim_walk_up_;
|
anim_ = anim_walk_north_;
|
||||||
flip_ = SDL_FLIP_NONE;
|
|
||||||
} else if (dy > 0.0F) {
|
} else if (dy > 0.0F) {
|
||||||
anim_ = anim_walk_down_;
|
anim_ = anim_walk_south_;
|
||||||
flip_ = SDL_FLIP_NONE;
|
}
|
||||||
|
if (anim_.has_value()) {
|
||||||
|
anim_->reset();
|
||||||
|
// Propaguem el flip que l'animació declara al YAML al camp
|
||||||
|
// `flip_` que el render llegeix (entity_manager_loadlevel.cpp).
|
||||||
|
flip_ = anim_->flipMode();
|
||||||
}
|
}
|
||||||
if (anim_.has_value()) { anim_->reset(); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mummy::chooseDirection(const std::vector<Direction>& allowed) -> Direction {
|
auto Mummy::chooseDirection(const std::vector<Direction>& allowed) -> Direction {
|
||||||
@@ -181,7 +199,15 @@ void Mummy::snapToPlayfieldEdge(float dx, float dy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Mummy::step(float delta_time) {
|
void Mummy::step(float delta_time) {
|
||||||
// Desplaçament nominal en aquest frame segons la direcció actual.
|
// Detecció reactiva al canvi de tile: si la mòmia ha entrat aquest
|
||||||
|
// tick a un tile nou amb un decal trigger (Hole), dispara mitja volta
|
||||||
|
// abans de calcular el desplaçament — així el frame ja consumeix la
|
||||||
|
// nova direcció sense esperar al següent. La primera crida només
|
||||||
|
// registra el tile de spawn (lazy init).
|
||||||
|
reactToCurrentTile();
|
||||||
|
|
||||||
|
// Desplaçament nominal en aquest frame segons la direcció actual
|
||||||
|
// (potser ja invertida per `reactToCurrentTile`).
|
||||||
const float DX = dir_x_ * speed_ * delta_time;
|
const float DX = dir_x_ * speed_ * delta_time;
|
||||||
const float DY = dir_y_ * speed_ * delta_time;
|
const float DY = dir_y_ * speed_ * delta_time;
|
||||||
|
|
||||||
@@ -195,6 +221,9 @@ void Mummy::step(float delta_time) {
|
|||||||
// centre; el remanent queda per al pròxim frame, ja amb la (potser
|
// centre; el remanent queda per al pròxim frame, ja amb la (potser
|
||||||
// nova) direcció aplicada. Així la caixa 16×16 queda sempre alineada
|
// nova) direcció aplicada. Així la caixa 16×16 queda sempre alineada
|
||||||
// a la graella de corredors i els girs no xoquen per drift sub-píxel.
|
// a la graella de corredors i els girs no xoquen per drift sub-píxel.
|
||||||
|
// L'evitació de forats en pasillos rectes la cobreix
|
||||||
|
// `reactToCurrentTile` (al començament d'aquest mètode), no aquesta
|
||||||
|
// branca — aquí només es decideix entre direccions geomètriques.
|
||||||
if (const auto SNAP = detectLaneCrossing(DX, DY); SNAP.has_value()) {
|
if (const auto SNAP = detectLaneCrossing(DX, DY); SNAP.has_value()) {
|
||||||
if (DX != 0.0F) {
|
if (DX != 0.0F) {
|
||||||
pixel_rect_.x += *SNAP;
|
pixel_rect_.x += *SNAP;
|
||||||
@@ -217,14 +246,86 @@ void Mummy::step(float delta_time) {
|
|||||||
// Bloqueig per la vora del playfield: amarrem la caja a la vora exacta.
|
// Bloqueig per la vora del playfield: amarrem la caja a la vora exacta.
|
||||||
if (!Map::insidePlayfield(CANDIDATE)) { snapToPlayfieldEdge(DX, DY); }
|
if (!Map::insidePlayfield(CANDIDATE)) { snapToPlayfieldEdge(DX, DY); }
|
||||||
|
|
||||||
// Cal girar. Preferim perpendiculars; només fem U-turn si no hi ha cap
|
// Cal girar. Cascada de tres nivells:
|
||||||
// altra opció (comportament típic d'Oh Mummy!: no es recula sense motiu).
|
// Nivell 1 — perpendiculars sense decal (preferides; el comportament
|
||||||
const auto POOLS = collectDirectionPools(speed_ * delta_time);
|
// "natural" d'Oh Mummy!: girar abans que recular).
|
||||||
const auto& pool = !POOLS.perp.empty() ? POOLS.perp : POOLS.uturn;
|
// Nivell 2 — mitja volta sense decal (rescat: callejón sense salida o
|
||||||
applyChoice(chooseDirection(pool));
|
// tots els girs porten a forat). Preferim recular abans
|
||||||
|
// que caure al forat — un forat al costat NO ha de fer
|
||||||
|
// que la mòmia salti per damunt seu cap a una perpendicular
|
||||||
|
// amb forat només perquè la mitja volta era l'única neta.
|
||||||
|
// Nivell 3 — pool sense filtre de decals (cas degenerat: rodejada de
|
||||||
|
// forats). Permet caure al forat com a últim recurs; les
|
||||||
|
// mòmies no moren pels forats, així que és comportament
|
||||||
|
// tolerable en sales mal dissenyades.
|
||||||
|
// TODO(refactor): mateixa cascada a `decideAtIntersection` amb la
|
||||||
|
// variant "afegir recta a Nivell 1". Factoritzable
|
||||||
|
// quan tinga sentit (3+ callsites).
|
||||||
|
auto pools = collectDirectionPools(speed_ * delta_time, /*avoid_blocked=*/true);
|
||||||
|
const std::vector<Direction>* chosen = nullptr;
|
||||||
|
if (!pools.perp.empty()) {
|
||||||
|
chosen = &pools.perp;
|
||||||
|
} else if (!pools.uturn.empty()) {
|
||||||
|
chosen = &pools.uturn;
|
||||||
|
}
|
||||||
|
DirectionPools pools_unfiltered;
|
||||||
|
if (chosen == nullptr) {
|
||||||
|
pools_unfiltered = collectDirectionPools(speed_ * delta_time, /*avoid_blocked=*/false);
|
||||||
|
if (!pools_unfiltered.perp.empty()) {
|
||||||
|
chosen = &pools_unfiltered.perp;
|
||||||
|
} else if (!pools_unfiltered.uturn.empty()) {
|
||||||
|
chosen = &pools_unfiltered.uturn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyChoice((chosen != nullptr) ? chooseDirection(*chosen) : Direction::NONE);
|
||||||
// No avancem aquest frame: la direcció nova s'aplicarà al pròxim step.
|
// No avancem aquest frame: la direcció nova s'aplicarà al pròxim step.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Mummy::reactToCurrentTile() {
|
||||||
|
// Calculem el tile que toca la **vora davantera** de la BOX en la
|
||||||
|
// direcció de marxa, no el centre. Així la mitja volta es dispara
|
||||||
|
// tan aviat com el primer píxel del cos de la mòmia entra al tile-
|
||||||
|
// forat ("asoma el peu"), no quan ja hi ha mig cos a dins. Per a una
|
||||||
|
// direcció positiva en un eix, la vora davantera és el costat major
|
||||||
|
// de l'AABB (`x + w - 1`, exclusive→inclusive convention en píxels);
|
||||||
|
// per a una direcció negativa, el menor (`x`); per a zero, qualsevol
|
||||||
|
// (no hi ha avenç en aquest eix).
|
||||||
|
const auto BOX = getCollisionRect();
|
||||||
|
const float LEAD_X = (dir_x_ > 0.0F) ? (BOX.x + BOX.w - 1.0F) : BOX.x;
|
||||||
|
const float LEAD_Y = (dir_y_ > 0.0F) ? (BOX.y + BOX.h - 1.0F) : BOX.y;
|
||||||
|
const auto [CUR_COL, CUR_ROW] = Map::pixelToTile(LEAD_X, LEAD_Y);
|
||||||
|
|
||||||
|
// Sense canvi de tile no hi ha res a fer — això filtra també tots els
|
||||||
|
// ticks intra-tile (la majoria, ~16 frames per tile a velocitats típiques).
|
||||||
|
if (CUR_COL == last_tile_col_ && CUR_ROW == last_tile_row_) { return; }
|
||||||
|
|
||||||
|
// Primera crida després del spawn: registrem el tile sense reaccionar.
|
||||||
|
// Així no disparem mitja volta espuria si la mòmia spawneja sobre un
|
||||||
|
// decal (defensa contra mapes mal dissenyats) o si pixel_rect_ encara
|
||||||
|
// no estava posat al constructor.
|
||||||
|
const bool FIRST_TIME = (last_tile_col_ == INT_MIN || last_tile_row_ == INT_MIN);
|
||||||
|
last_tile_col_ = CUR_COL;
|
||||||
|
last_tile_row_ = CUR_ROW;
|
||||||
|
if (FIRST_TIME) { return; }
|
||||||
|
|
||||||
|
// Sense manager injectat (smoke tests), no podem consultar decals.
|
||||||
|
if (entity_manager_ == nullptr) { return; }
|
||||||
|
|
||||||
|
// Si el tile nou conté un decal trigger (Hole o futur pinchos plans,
|
||||||
|
// plaques, etc.), mitja volta immediata. `changeDirection` també
|
||||||
|
// selecciona l'animació cardinal corresponent.
|
||||||
|
const auto TS = static_cast<float>(Map::tile_size);
|
||||||
|
const SDL_FRect TILE_RECT{
|
||||||
|
.x = (static_cast<float>(CUR_COL) * TS) + static_cast<float>(Map::tilemap_origin_x),
|
||||||
|
.y = (static_cast<float>(CUR_ROW) * TS) + static_cast<float>(Map::tilemap_origin_y),
|
||||||
|
.w = TS,
|
||||||
|
.h = TS,
|
||||||
|
};
|
||||||
|
if (entity_manager_->findFloorTriggerAt(TILE_RECT) != nullptr) {
|
||||||
|
changeDirection(-dir_x_, -dir_y_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto Mummy::detectLaneCrossing(float dx, float dy) const -> std::optional<float> {
|
auto Mummy::detectLaneCrossing(float dx, float dy) const -> std::optional<float> {
|
||||||
const auto BOX = getCollisionRect();
|
const auto BOX = getCollisionRect();
|
||||||
if (dx != 0.0F) {
|
if (dx != 0.0F) {
|
||||||
@@ -234,44 +335,72 @@ auto Mummy::detectLaneCrossing(float dx, float dy) const -> std::optional<float>
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Mummy::currentDirection() const -> Direction {
|
auto Mummy::currentDirection() const -> Direction {
|
||||||
if (dir_x_ > 0.0F) { return Direction::RIGHT; }
|
if (dir_x_ > 0.0F) { return Direction::EAST; }
|
||||||
if (dir_x_ < 0.0F) { return Direction::LEFT; }
|
if (dir_x_ < 0.0F) { return Direction::WEST; }
|
||||||
if (dir_y_ > 0.0F) { return Direction::DOWN; }
|
if (dir_y_ > 0.0F) { return Direction::SOUTH; }
|
||||||
if (dir_y_ < 0.0F) { return Direction::UP; }
|
if (dir_y_ < 0.0F) { return Direction::NORTH; }
|
||||||
return Direction::NONE;
|
return Direction::NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mummy::decideAtIntersection(float step_px) {
|
void Mummy::decideAtIntersection(float step_px) {
|
||||||
// Pool base: perpendiculars lliures (collectDirectionPools ja exclou la
|
// Cascada de tres nivells (paral·lela a la branca de bloqueig de
|
||||||
// direcció actual i la mitja volta va al seu propi calaix, que ací
|
// `step`). Aquí el Nivell 1 inclou la recta com a candidata addicional
|
||||||
// ignorem — mai fem U-turn a un creuament).
|
// (a un creuament la mòmia pot continuar recta si segueix lliure i no
|
||||||
auto pools = collectDirectionPools(step_px);
|
// apunta a un decal); el Nivell 2 admet la mitja volta — abans no es
|
||||||
std::vector<Direction> pool = std::move(pools.perp);
|
// permetia mai en creuaments, però és preferible recular que caure al
|
||||||
|
// forat quan totes les altres surten a decal.
|
||||||
|
// TODO(refactor): cascada paral·lela a la branca de bloqueig de `step`.
|
||||||
|
auto pools = collectDirectionPools(step_px, /*avoid_blocked=*/true);
|
||||||
|
|
||||||
// Afegim la recta si segueix essent lliure. A un creuament "net" ho és
|
// Nivell 1: perpendiculars (filtrades) + recta si lliure i sense decal.
|
||||||
// sempre (la mòmia hi ha arribat per aquest corredor), però als creua-
|
std::vector<Direction> level1 = std::move(pools.perp);
|
||||||
// ments de la vora del playfield la recta pot estar al límit i no
|
|
||||||
// permetre un pas més — en aquest cas la descartem.
|
|
||||||
const auto STRAIGHT_BOX = candidateBox(dir_x_ * step_px, dir_y_ * step_px);
|
const auto STRAIGHT_BOX = candidateBox(dir_x_ * step_px, dir_y_ * step_px);
|
||||||
if (Map::insidePlayfield(STRAIGHT_BOX) && !Map::collides(STRAIGHT_BOX)) {
|
const bool STRAIGHT_OPEN = Map::insidePlayfield(STRAIGHT_BOX) && !Map::collides(STRAIGHT_BOX);
|
||||||
pool.push_back(currentDirection());
|
if (STRAIGHT_OPEN && !isBlockedAheadInDir(dir_x_, dir_y_)) {
|
||||||
|
level1.push_back(currentDirection());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pool.empty()) { return; }
|
// Nivell 2: mitja volta sense decal — rescat quan Nivell 1 ja no té cap
|
||||||
applyChoice(chooseDirection(pool));
|
// opció neta. Un forat al final del passadís ha de fer recular la
|
||||||
|
// mòmia, no convertir-se en l'opció triada per defecte.
|
||||||
|
const std::vector<Direction>* chosen = nullptr;
|
||||||
|
if (!level1.empty()) {
|
||||||
|
chosen = &level1;
|
||||||
|
} else if (!pools.uturn.empty()) {
|
||||||
|
chosen = &pools.uturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nivell 3: cas degenerat (rodejada de forats). Pool sense filtre amb
|
||||||
|
// perpendiculars, recta si lliure i mitja volta. Permet caure al forat
|
||||||
|
// com a últim recurs.
|
||||||
|
DirectionPools pools_unfiltered;
|
||||||
|
std::vector<Direction> level3;
|
||||||
|
if (chosen == nullptr) {
|
||||||
|
pools_unfiltered = collectDirectionPools(step_px, /*avoid_blocked=*/false);
|
||||||
|
level3 = std::move(pools_unfiltered.perp);
|
||||||
|
if (STRAIGHT_OPEN) { level3.push_back(currentDirection()); }
|
||||||
|
if (!level3.empty()) {
|
||||||
|
chosen = &level3;
|
||||||
|
} else if (!pools_unfiltered.uturn.empty()) {
|
||||||
|
chosen = &pools_unfiltered.uturn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chosen == nullptr) { return; }
|
||||||
|
applyChoice(chooseDirection(*chosen));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mummy::collectDirectionPools(float step) const -> DirectionPools {
|
auto Mummy::collectDirectionPools(float step, bool avoid_blocked) const -> DirectionPools {
|
||||||
struct Candidate {
|
struct Candidate {
|
||||||
float dx;
|
float dx;
|
||||||
float dy;
|
float dy;
|
||||||
Direction dir;
|
Direction dir;
|
||||||
};
|
};
|
||||||
const std::array<Candidate, 4> ALL{{
|
const std::array<Candidate, 4> ALL{{
|
||||||
{.dx = step, .dy = 0.0F, .dir = Direction::RIGHT},
|
{.dx = step, .dy = 0.0F, .dir = Direction::EAST},
|
||||||
{.dx = -step, .dy = 0.0F, .dir = Direction::LEFT},
|
{.dx = -step, .dy = 0.0F, .dir = Direction::WEST},
|
||||||
{.dx = 0.0F, .dy = -step, .dir = Direction::UP},
|
{.dx = 0.0F, .dy = -step, .dir = Direction::NORTH},
|
||||||
{.dx = 0.0F, .dy = step, .dir = Direction::DOWN},
|
{.dx = 0.0F, .dy = step, .dir = Direction::SOUTH},
|
||||||
}};
|
}};
|
||||||
DirectionPools pools;
|
DirectionPools pools;
|
||||||
for (const auto& c : ALL) {
|
for (const auto& c : ALL) {
|
||||||
@@ -281,6 +410,11 @@ auto Mummy::collectDirectionPools(float step) const -> DirectionPools {
|
|||||||
if (IS_CURRENT) { continue; }
|
if (IS_CURRENT) { continue; }
|
||||||
const auto TEST = candidateBox(c.dx, c.dy);
|
const auto TEST = candidateBox(c.dx, c.dy);
|
||||||
if (!Map::insidePlayfield(TEST) || Map::collides(TEST)) { continue; }
|
if (!Map::insidePlayfield(TEST) || Map::collides(TEST)) { continue; }
|
||||||
|
// Filtre de decals (Hole i futurs decals trigger): comprova el
|
||||||
|
// tile cardinal contigu; si conté un trigger, descartem la
|
||||||
|
// direcció. El caller pot demanar `avoid_blocked=false` com a
|
||||||
|
// fallback quan totes les filtrades estan bloquejades.
|
||||||
|
if (avoid_blocked && isBlockedAheadInDir(c.dx, c.dy)) { continue; }
|
||||||
const bool IS_UTURN = (c.dx * dir_x_ < 0.0F) || (c.dy * dir_y_ < 0.0F);
|
const bool IS_UTURN = (c.dx * dir_x_ < 0.0F) || (c.dy * dir_y_ < 0.0F);
|
||||||
if (IS_UTURN) {
|
if (IS_UTURN) {
|
||||||
pools.uturn.push_back(c.dir);
|
pools.uturn.push_back(c.dir);
|
||||||
@@ -291,18 +425,56 @@ auto Mummy::collectDirectionPools(float step) const -> DirectionPools {
|
|||||||
return pools;
|
return pools;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Mummy::isBlockedAheadInDir(float dx_signed, float dy_signed) const -> bool {
|
||||||
|
if (dx_signed == 0.0F && dy_signed == 0.0F) { return false; }
|
||||||
|
// El rect a consultar és el AABB del tile cardinal contigu (mida
|
||||||
|
// `Map::tile_size`), ancorat al tile actual de la mòmia. Calculem el
|
||||||
|
// tile a partir del **centre** de la BOX, no de la cantonada superior-
|
||||||
|
// esquerra: amb una BOX de mida `tile_size`, la cantonada esquerra
|
||||||
|
// entra al tile següent un sub-píxel abans que el cos de la mòmia hi
|
||||||
|
// sigui majoritàriament. Si féssim servir `BOX.x` directament, una
|
||||||
|
// mòmia a `BOX.x = 199.5` (cobrint [199.5, 215.5], 0.5 px al tile 12
|
||||||
|
// i 15.5 px al tile 13) seria classificada al tile 12 i giraria un
|
||||||
|
// tile abans del veritablement adjacent al decal.
|
||||||
|
const auto BOX = getCollisionRect();
|
||||||
|
const auto TS = static_cast<float>(Map::tile_size);
|
||||||
|
const auto SIGN_OF = [](float v) -> float {
|
||||||
|
if (v > 0.0F) { return 1.0F; }
|
||||||
|
if (v < 0.0F) { return -1.0F; }
|
||||||
|
return 0.0F;
|
||||||
|
};
|
||||||
|
const float CENTER_X = BOX.x + (BOX.w * 0.5F);
|
||||||
|
const float CENTER_Y = BOX.y + (BOX.h * 0.5F);
|
||||||
|
const auto [CUR_COL, CUR_ROW] = Map::pixelToTile(CENTER_X, CENTER_Y);
|
||||||
|
const float TILE_X = (static_cast<float>(CUR_COL) * TS) + static_cast<float>(Map::tilemap_origin_x);
|
||||||
|
const float TILE_Y = (static_cast<float>(CUR_ROW) * TS) + static_cast<float>(Map::tilemap_origin_y);
|
||||||
|
const SDL_FRect NEXT{
|
||||||
|
.x = TILE_X + (SIGN_OF(dx_signed) * TS),
|
||||||
|
.y = TILE_Y + (SIGN_OF(dy_signed) * TS),
|
||||||
|
.w = TS,
|
||||||
|
.h = TS,
|
||||||
|
};
|
||||||
|
// Ordre per cost creixent: comparació de coordenades, després iteració
|
||||||
|
// sobre sarcs i porta, i finalment cerca a l'index d'entitats. Sense
|
||||||
|
// manager (smoke tests) saltem només l'últim filtre.
|
||||||
|
if (!Map::insidePlayfield(NEXT)) { return true; }
|
||||||
|
if (Map::collides(NEXT)) { return true; }
|
||||||
|
if (entity_manager_ != nullptr && entity_manager_->findFloorTriggerAt(NEXT) != nullptr) { return true; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Mummy::applyChoice(Direction dir) {
|
void Mummy::applyChoice(Direction dir) {
|
||||||
switch (dir) {
|
switch (dir) {
|
||||||
case Direction::UP:
|
case Direction::NORTH:
|
||||||
changeDirection(0.0F, -1.0F);
|
changeDirection(0.0F, -1.0F);
|
||||||
break;
|
break;
|
||||||
case Direction::DOWN:
|
case Direction::SOUTH:
|
||||||
changeDirection(0.0F, 1.0F);
|
changeDirection(0.0F, 1.0F);
|
||||||
break;
|
break;
|
||||||
case Direction::LEFT:
|
case Direction::WEST:
|
||||||
changeDirection(-1.0F, 0.0F);
|
changeDirection(-1.0F, 0.0F);
|
||||||
break;
|
break;
|
||||||
case Direction::RIGHT:
|
case Direction::EAST:
|
||||||
changeDirection(1.0F, 0.0F);
|
changeDirection(1.0F, 0.0F);
|
||||||
break;
|
break;
|
||||||
case Direction::NONE:
|
case Direction::NONE:
|
||||||
@@ -312,11 +484,25 @@ void Mummy::applyChoice(Direction dir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Mummy::onPlayerContact(Player* player) {
|
void Mummy::onPlayerContact(Player* player) {
|
||||||
if (bus_ != nullptr) {
|
if (bus_ == nullptr) { return; }
|
||||||
|
const int PID = (player != nullptr) ? player->getId() : -1;
|
||||||
|
bus_->publish(Event{
|
||||||
|
.name = "player_hit",
|
||||||
|
.payload = getDamage(),
|
||||||
|
.player_id = PID,
|
||||||
|
});
|
||||||
|
// Path letal: si la mòmia està marcada com a `kills_outright`
|
||||||
|
// (default a Oh Mummy!), notifiquem al GameScene que el jugador
|
||||||
|
// ha de morir. La transició concreta (`Player::enterDying`) la fa
|
||||||
|
// el subscriptor — així mummy.cpp no toca cap símbol del Player més
|
||||||
|
// enllà de la forward-decl, i els tests d'entitats no han d'arrossegar
|
||||||
|
// l'stack pesat del jugador per linkar-se. El subscriptor (GameScene)
|
||||||
|
// és qui resol el `player_id` a la instància concreta.
|
||||||
|
if (kills_outright_) {
|
||||||
bus_->publish(Event{
|
bus_->publish(Event{
|
||||||
.name = "player_hit",
|
.name = "player_killed",
|
||||||
.payload = getDamage(),
|
.payload = getDamage(),
|
||||||
.player_id = (player != nullptr) ? player->getId() : -1,
|
.player_id = PID,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,16 +533,16 @@ auto Mummy::chooseHurtDirection(Direction preferred) const -> Direction {
|
|||||||
float dx = 0.0F;
|
float dx = 0.0F;
|
||||||
float dy = 0.0F;
|
float dy = 0.0F;
|
||||||
switch (d) {
|
switch (d) {
|
||||||
case Direction::UP:
|
case Direction::NORTH:
|
||||||
dy = -STEP;
|
dy = -STEP;
|
||||||
break;
|
break;
|
||||||
case Direction::DOWN:
|
case Direction::SOUTH:
|
||||||
dy = STEP;
|
dy = STEP;
|
||||||
break;
|
break;
|
||||||
case Direction::LEFT:
|
case Direction::WEST:
|
||||||
dx = -STEP;
|
dx = -STEP;
|
||||||
break;
|
break;
|
||||||
case Direction::RIGHT:
|
case Direction::EAST:
|
||||||
dx = STEP;
|
dx = STEP;
|
||||||
break;
|
break;
|
||||||
case Direction::NONE:
|
case Direction::NONE:
|
||||||
@@ -373,21 +559,21 @@ auto Mummy::chooseHurtDirection(Direction preferred) const -> Direction {
|
|||||||
Direction left = Direction::NONE;
|
Direction left = Direction::NONE;
|
||||||
Direction right = Direction::NONE;
|
Direction right = Direction::NONE;
|
||||||
switch (preferred) {
|
switch (preferred) {
|
||||||
case Direction::UP:
|
case Direction::NORTH:
|
||||||
left = Direction::LEFT;
|
left = Direction::WEST;
|
||||||
right = Direction::RIGHT;
|
right = Direction::EAST;
|
||||||
break;
|
break;
|
||||||
case Direction::DOWN:
|
case Direction::SOUTH:
|
||||||
left = Direction::RIGHT;
|
left = Direction::EAST;
|
||||||
right = Direction::LEFT;
|
right = Direction::WEST;
|
||||||
break;
|
break;
|
||||||
case Direction::LEFT:
|
case Direction::WEST:
|
||||||
left = Direction::DOWN;
|
left = Direction::SOUTH;
|
||||||
right = Direction::UP;
|
right = Direction::NORTH;
|
||||||
break;
|
break;
|
||||||
case Direction::RIGHT:
|
case Direction::EAST:
|
||||||
left = Direction::UP;
|
left = Direction::NORTH;
|
||||||
right = Direction::DOWN;
|
right = Direction::SOUTH;
|
||||||
break;
|
break;
|
||||||
case Direction::NONE:
|
case Direction::NONE:
|
||||||
return Direction::NONE;
|
return Direction::NONE;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -16,10 +17,10 @@
|
|||||||
class Mummy : public DynamicHazard {
|
class Mummy : public DynamicHazard {
|
||||||
public:
|
public:
|
||||||
enum class Direction : std::uint8_t { NONE,
|
enum class Direction : std::uint8_t { NONE,
|
||||||
UP,
|
NORTH,
|
||||||
DOWN,
|
SOUTH,
|
||||||
LEFT,
|
WEST,
|
||||||
RIGHT };
|
EAST };
|
||||||
|
|
||||||
// Estat d'alt nivell. ROAMING = IA normal (corridor_wanderer); HURT =
|
// Estat d'alt nivell. ROAMING = IA normal (corridor_wanderer); HURT =
|
||||||
// post-fuetada, fugint en línia recta durant `hurt_config_.duration_s`.
|
// post-fuetada, fugint en línia recta durant `hurt_config_.duration_s`.
|
||||||
@@ -69,6 +70,7 @@ class Mummy : public DynamicHazard {
|
|||||||
[[nodiscard]] auto getState() const -> State { return state_; }
|
[[nodiscard]] auto getState() const -> State { return state_; }
|
||||||
[[nodiscard]] auto isWhippable() const -> bool { return whippable_; }
|
[[nodiscard]] auto isWhippable() const -> bool { return whippable_; }
|
||||||
[[nodiscard]] auto getHurtRemainingS() const -> float { return hurt_remaining_s_; }
|
[[nodiscard]] auto getHurtRemainingS() const -> float { return hurt_remaining_s_; }
|
||||||
|
[[nodiscard]] auto killsOutright() const -> bool { return kills_outright_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Caixa de col·lisió candidata si l'entitat es desplaçara (dx, dy).
|
// Caixa de col·lisió candidata si l'entitat es desplaçara (dx, dy).
|
||||||
@@ -84,17 +86,37 @@ class Mummy : public DynamicHazard {
|
|||||||
// Recull les direccions alternatives viables des de la posició actual:
|
// Recull les direccions alternatives viables des de la posició actual:
|
||||||
// `perpendiculars` (girs) i `uturn` (inversa). La direcció actual s'exclou
|
// `perpendiculars` (girs) i `uturn` (inversa). La direcció actual s'exclou
|
||||||
// perquè ja sabem que està bloquejada. Cada candidata es valida amb
|
// perquè ja sabem que està bloquejada. Cada candidata es valida amb
|
||||||
// `Map::insidePlayfield` + `Map::collides` sobre la caja resultant.
|
// `Map::insidePlayfield` + `Map::collides` sobre la caja resultant. Si
|
||||||
|
// `avoid_blocked` és true i hi ha `EntityManager` injectat, també es
|
||||||
|
// descarten les direccions que apunten a un tile amb un decal trigger
|
||||||
|
// (Hole o futurs pinchos plans/charcos). El caller és responsable de
|
||||||
|
// re-cridar amb `avoid_blocked=false` com a fallback si totes les
|
||||||
|
// direccions filtrades estan bloquejades.
|
||||||
struct DirectionPools {
|
struct DirectionPools {
|
||||||
std::vector<Direction> perp;
|
std::vector<Direction> perp;
|
||||||
std::vector<Direction> uturn;
|
std::vector<Direction> uturn;
|
||||||
};
|
};
|
||||||
[[nodiscard]] auto collectDirectionPools(float step) const -> DirectionPools;
|
[[nodiscard]] auto collectDirectionPools(float step, bool avoid_blocked = true) const -> DirectionPools;
|
||||||
|
|
||||||
// Canvi de direcció + aplicació de l'animació i flip corresponents.
|
// True si el tile cardinal contigu en la direcció (signe de dx, signe
|
||||||
// Tots els camins que trien nova direcció (ctor amb `initial_direction`,
|
// de dy) està bloquejat per qualsevol motiu: fora del playfield, ocupat
|
||||||
// gir després d'un bloqueig) passen per ací per mantenir anim_ i
|
// per geometria estàtica (sarcs, door_corridor tancat) via `Map::collides`,
|
||||||
// flip_ en sincronia amb (dir_x_, dir_y_).
|
// o conté un decal trigger (`is_floor_decal_ && triggers_on_overlap_`)
|
||||||
|
// segons `EntityManager::findFloorTriggerAt`. Captura `Hole` i qualsevol
|
||||||
|
// futur decal trigger sense hardcodejar tipus, i evita que la mòmia
|
||||||
|
// triï a un creuament una direcció que el frame següent toparia (cas
|
||||||
|
// típic: a la intersecció anterior al límit nord del playfield, on el
|
||||||
|
// pas immediat encara hi cap però el tile sencer següent ja està fora).
|
||||||
|
// Si `entity_manager_` no està injectat (smoke tests sense manager),
|
||||||
|
// el filtre de decals és silenciós però els altres dos (playfield i
|
||||||
|
// collides) segueixen actius.
|
||||||
|
[[nodiscard]] auto isBlockedAheadInDir(float dx_signed, float dy_signed) const -> bool;
|
||||||
|
|
||||||
|
// Canvi de direcció + selecció de l'animació corresponent (un cardinal
|
||||||
|
// per branch). Tots els camins que trien nova direcció (ctor amb
|
||||||
|
// `initial_direction`, gir després d'un bloqueig) passen per ací; el
|
||||||
|
// flip viu al YAML (`walk_west: flip_h: true`) i es propaga des de
|
||||||
|
// l'animació al `flip_` heretat.
|
||||||
void changeDirection(float dx, float dy);
|
void changeDirection(float dx, float dy);
|
||||||
|
|
||||||
// Aplica la direcció triada (wrapper del switch en una sola crida).
|
// Aplica la direcció triada (wrapper del switch en una sola crida).
|
||||||
@@ -128,11 +150,24 @@ class Mummy : public DynamicHazard {
|
|||||||
// totes ho estan, retorna `NONE` (la mòmia queda quieta en HURT).
|
// totes ho estan, retorna `NONE` (la mòmia queda quieta en HURT).
|
||||||
[[nodiscard]] auto chooseHurtDirection(Direction preferred) const -> Direction;
|
[[nodiscard]] auto chooseHurtDirection(Direction preferred) const -> Direction;
|
||||||
|
|
||||||
// Animacions carregades del YAML, una per cada facing. Per a horitzontal
|
// Detecció reactiva al canvi de tile (substitueix el lookahead sub-tile
|
||||||
// només en cal una (`walk`) i el flip ja diferencia esquerra/dreta.
|
// entre creuaments). Es crida al principi de cada `step()`. Si el tile
|
||||||
AnimationComponent anim_walk_down_;
|
// que ocupa la mòmia (calculat des del centre de la BOX) ha canviat
|
||||||
AnimationComponent anim_walk_up_;
|
// respecte a l'última crida, comprova si el tile nou conté un decal
|
||||||
AnimationComponent anim_walk_lateral_;
|
// trigger; si el conté, dispara mitja volta immediata. La primera crida
|
||||||
|
// (last_tile_col_ == INT_MIN) només registra el tile actual sense
|
||||||
|
// reaccionar — evita un falç positiu si la mòmia spawneja sobre un
|
||||||
|
// decal o si el setup encara no és estable.
|
||||||
|
void reactToCurrentTile();
|
||||||
|
|
||||||
|
// Animacions carregades del YAML, una per cada cardinal. La variant
|
||||||
|
// `walk_west` reaprofita els frames de l'est però amb `flip_h: true`
|
||||||
|
// declarat al YAML — el motor d'animació aplica el flip al sprite
|
||||||
|
// automàticament; aquesta classe no toca mai `flip_` directament.
|
||||||
|
AnimationComponent anim_walk_south_;
|
||||||
|
AnimationComponent anim_walk_north_;
|
||||||
|
AnimationComponent anim_walk_east_;
|
||||||
|
AnimationComponent anim_walk_west_;
|
||||||
|
|
||||||
// Estat HURT i la seua configuració. `hurt_speed_` és l'efectiva
|
// Estat HURT i la seua configuració. `hurt_speed_` és l'efectiva
|
||||||
// (resultat d'aplicar el `speed` del bloc `hurt:` o, si val 0, la
|
// (resultat d'aplicar el `speed` del bloc `hurt:` o, si val 0, la
|
||||||
@@ -142,4 +177,18 @@ class Mummy : public DynamicHazard {
|
|||||||
bool whippable_{true};
|
bool whippable_{true};
|
||||||
float hurt_remaining_s_{0.0F};
|
float hurt_remaining_s_{0.0F};
|
||||||
float hurt_speed_{0.0F};
|
float hurt_speed_{0.0F};
|
||||||
|
|
||||||
|
// Si true, el contacte amb el jugador dispara `Player::enterDying`
|
||||||
|
// (mort d'un sol toc, bypass del flux `hurt`/knockback). Llegit del
|
||||||
|
// bloc `stats.kills_outright` del YAML; default true per coherència
|
||||||
|
// amb el comportament d'Oh Mummy! (les mòmies maten).
|
||||||
|
bool kills_outright_{true};
|
||||||
|
|
||||||
|
// Tile que ocupava la mòmia a l'última crida a `reactToCurrentTile`.
|
||||||
|
// Inicialitzats a `INT_MIN` perquè la primera crida (lazy init: no
|
||||||
|
// podem fer-ho al constructor perquè `pixel_rect_` encara no té
|
||||||
|
// posició; setPixelRect del manager arriba després) registra el tile
|
||||||
|
// de spawn sense reaccionar.
|
||||||
|
int last_tile_col_{INT_MIN};
|
||||||
|
int last_tile_row_{INT_MIN};
|
||||||
};
|
};
|
||||||
@@ -10,6 +10,9 @@ WallTrap::WallTrap(const fkyaml::node& yaml) {
|
|||||||
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("wall_trap");
|
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("wall_trap");
|
||||||
collides_ = false;
|
collides_ = false;
|
||||||
interactable_ = false;
|
interactable_ = false;
|
||||||
|
// Trigger d'àrea: igual que FloorTrap. Tanca el gap latent del filtre
|
||||||
|
// de `checkPlayerCollisions` que ho deixava mut.
|
||||||
|
triggers_on_overlap_ = true;
|
||||||
|
|
||||||
if (yaml.contains("cycle")) {
|
if (yaml.contains("cycle")) {
|
||||||
const auto& c = yaml["cycle"];
|
const auto& c = yaml["cycle"];
|
||||||
|
|||||||
@@ -255,13 +255,13 @@ namespace {
|
|||||||
auto whipDirToMummyDir(WhipAttack::Direction d) -> Mummy::Direction {
|
auto whipDirToMummyDir(WhipAttack::Direction d) -> Mummy::Direction {
|
||||||
switch (d) {
|
switch (d) {
|
||||||
case WhipAttack::Direction::NORTH:
|
case WhipAttack::Direction::NORTH:
|
||||||
return Mummy::Direction::UP;
|
return Mummy::Direction::NORTH;
|
||||||
case WhipAttack::Direction::SOUTH:
|
case WhipAttack::Direction::SOUTH:
|
||||||
return Mummy::Direction::DOWN;
|
return Mummy::Direction::SOUTH;
|
||||||
case WhipAttack::Direction::EAST:
|
case WhipAttack::Direction::EAST:
|
||||||
return Mummy::Direction::RIGHT;
|
return Mummy::Direction::EAST;
|
||||||
case WhipAttack::Direction::WEST:
|
case WhipAttack::Direction::WEST:
|
||||||
return Mummy::Direction::LEFT;
|
return Mummy::Direction::WEST;
|
||||||
}
|
}
|
||||||
return Mummy::Direction::NONE;
|
return Mummy::Direction::NONE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class WhipAttack : public Attack {
|
|||||||
// del nostre `getFeetY()` per delegar el bucket d'y-sort al jugador.
|
// del nostre `getFeetY()` per delegar el bucket d'y-sort al jugador.
|
||||||
float owner_feet_y_{0.0F};
|
float owner_feet_y_{0.0F};
|
||||||
|
|
||||||
EntityManager* entity_manager_{nullptr};
|
// `entity_manager_` viu a Entity (protected): l'utilitzem d'allà.
|
||||||
const std::vector<Player*>* players_{nullptr};
|
const std::vector<Player*>* players_{nullptr};
|
||||||
|
|
||||||
DirSprite sprite_north_;
|
DirSprite sprite_north_;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "game/entities/entity.hpp"
|
#include "game/entities/entity.hpp"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
Entity::Entity(std::string name, TilePos tile, SDL_FRect pixel_rect)
|
Entity::Entity(std::string name, TilePos tile, SDL_FRect pixel_rect)
|
||||||
@@ -38,6 +39,12 @@ auto Entity::getCollisionRect() const -> SDL_FRect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Entity::getFeetY() const -> float {
|
auto Entity::getFeetY() const -> float {
|
||||||
|
// Decal de sòl: sentinel mínim per quedar al principi del Y-sort
|
||||||
|
// ascendent de `GameScene` (stable_sort) i així pintar-se ABANS de
|
||||||
|
// qualsevol actor. Sense passades de render extra.
|
||||||
|
if (is_floor_decal_) {
|
||||||
|
return std::numeric_limits<float>::lowest();
|
||||||
|
}
|
||||||
const auto RECT = getCollisionRect();
|
const auto RECT = getCollisionRect();
|
||||||
return RECT.y + RECT.h;
|
return RECT.y + RECT.h;
|
||||||
}
|
}
|
||||||
@@ -12,9 +12,13 @@
|
|||||||
// `source/game/entities/player.hpp` i només es referencia per referència
|
// `source/game/entities/player.hpp` i només es referencia per referència
|
||||||
// dins `onPlayerContact`; `EventBus` és opcional a cada entitat i viu al
|
// dins `onPlayerContact`; `EventBus` és opcional a cada entitat i viu al
|
||||||
// seu header propi. `Surface` és el backend de render paletitzat del motor.
|
// seu header propi. `Surface` és el backend de render paletitzat del motor.
|
||||||
|
// `EntityManager` només es referencia per punter (injectat opcionalment
|
||||||
|
// per a entitats que necessiten consultar el món d'entitats — p.ex. la
|
||||||
|
// mòmia per saber si el pròxim tile té un decal trigger tipus Hole).
|
||||||
class Player;
|
class Player;
|
||||||
class EventBus;
|
class EventBus;
|
||||||
class Surface;
|
class Surface;
|
||||||
|
class EntityManager;
|
||||||
|
|
||||||
// Entitat genèrica del món. Actua com a dada en comú per a tots els actors
|
// Entitat genèrica del món. Actua com a dada en comú per a tots els actors
|
||||||
// del joc (decoratius, interactables, perills mòbils i estàtics): manté
|
// del joc (decoratius, interactables, perills mòbils i estàtics): manté
|
||||||
@@ -88,6 +92,27 @@ class Entity {
|
|||||||
[[nodiscard]] auto collides() const -> bool { return collides_; }
|
[[nodiscard]] auto collides() const -> bool { return collides_; }
|
||||||
[[nodiscard]] auto interactable() const -> bool { return interactable_; }
|
[[nodiscard]] auto interactable() const -> bool { return interactable_; }
|
||||||
|
|
||||||
|
// Trigger d'àrea: l'entitat NO bloqueja el moviment (`collides_=false`)
|
||||||
|
// però sí vol rebre `onPlayerContact` quan el jugador la solapa. Pensat
|
||||||
|
// per a hazards "no físics" (forats al sòl, picos retràctils, plaques
|
||||||
|
// de pressió). El bucle de contacte (`checkPlayerCollisions`) admet
|
||||||
|
// l'entitat si compleix `collides_ || triggers_on_overlap_`. El filtre
|
||||||
|
// d'altura per salt (`height_`) i la caixa de col·lisió funcionen igual.
|
||||||
|
[[nodiscard]] auto triggersOnOverlap() const -> bool { return triggers_on_overlap_; }
|
||||||
|
void setTriggersOnOverlap(bool v) { triggers_on_overlap_ = v; }
|
||||||
|
|
||||||
|
// Decal de sòl: hazards estàtics sense presència vertical (forats,
|
||||||
|
// plaques de pressió, charcos). Es pinten ABANS de qualsevol actor
|
||||||
|
// dins del Y-sort de `GameScene` perquè conceptualment són "pintura
|
||||||
|
// sobre el tile". Implementació: `getFeetY()` retorna un sentinel
|
||||||
|
// mínim (`std::numeric_limits<float>::lowest()`) per quedar al
|
||||||
|
// principi del `stable_sort` ascendent — així no calen passades de
|
||||||
|
// render extra. Les subclasses (p.ex. `Hole`) poden sobreescriure
|
||||||
|
// `getFeetY` per saltar-se el sentinel en moments concrets (p.ex.
|
||||||
|
// un forat que està tragant un jugador i ha de pintar-se al damunt).
|
||||||
|
[[nodiscard]] auto isFloorDecal() const -> bool { return is_floor_decal_; }
|
||||||
|
void setFloorDecal(bool v) { is_floor_decal_ = v; }
|
||||||
|
|
||||||
// Marca per a entitats transitòries: efectes (polvet, espurnes,
|
// Marca per a entitats transitòries: efectes (polvet, espurnes,
|
||||||
// explosions...) que viuen una sola animació i s'autoeliminen quan
|
// explosions...) que viuen una sola animació i s'autoeliminen quan
|
||||||
// acaba. L'`EntityManager` purga totes les entitats amb `dead_=true`
|
// acaba. L'`EntityManager` purga totes les entitats amb `dead_=true`
|
||||||
@@ -101,6 +126,14 @@ class Entity {
|
|||||||
// estàtiques de moment) el poden ignorar.
|
// estàtiques de moment) el poden ignorar.
|
||||||
void setEventBus(EventBus* bus) { bus_ = bus; }
|
void setEventBus(EventBus* bus) { bus_ = bus; }
|
||||||
|
|
||||||
|
// Injecció opcional del manager d'entitats (mateix patró que
|
||||||
|
// `setEventBus`: el manager s'auto-injecta a `add`/`setDoor`/
|
||||||
|
// `addSarcophagus`). El consumeixen entitats que volen consultar el
|
||||||
|
// món d'entitats — avui la mòmia, per saber si el tile següent
|
||||||
|
// conté un decal trigger (Hole) i evitar caure-hi. Entitats que no
|
||||||
|
// l'usen el poden ignorar.
|
||||||
|
void setEntityManager(EntityManager* manager) { entity_manager_ = manager; }
|
||||||
|
|
||||||
// Assigna la caixa de render en píxels. La invoca `EntityManager` durant
|
// Assigna la caixa de render en píxels. La invoca `EntityManager` durant
|
||||||
// `loadFromLevel` un cop sap la posició definitiva de l'entitat al mapa.
|
// `loadFromLevel` un cop sap la posició definitiva de l'entitat al mapa.
|
||||||
void setPixelRect(SDL_FRect rect) { pixel_rect_ = rect; }
|
void setPixelRect(SDL_FRect rect) { pixel_rect_ = rect; }
|
||||||
@@ -155,10 +188,11 @@ class Entity {
|
|||||||
// el sprite) amb el tile. Nullopt si l'entitat no en té.
|
// el sprite) amb el tile. Nullopt si l'entitat no en té.
|
||||||
[[nodiscard]] auto getCollisionBox() const -> const std::optional<CollisionBox>& { return collision_box_; }
|
[[nodiscard]] auto getCollisionBox() const -> const std::optional<CollisionBox>& { return collision_box_; }
|
||||||
|
|
||||||
// Flip del sprite. Les entitats estàtiques mai no el toquen (queda a
|
// Flip del sprite, propagat des del motor d'animació
|
||||||
// SDL_FLIP_NONE); les dinàmiques que miren cap als costats (p. ex.
|
// (`AnimationComponent::flipMode()`): cap entitat el decideix per
|
||||||
// Mummy en direcció horitzontal) el canvien quan gira. El manager
|
// direcció — el YAML declara `flip_h: true` a l'animació simètrica
|
||||||
// el passa a Surface::render.
|
// (p. ex. `walk_west`) i el setter d'animació l'aplica al `flip_`.
|
||||||
|
// El manager el passa a `Surface::render`.
|
||||||
[[nodiscard]] auto getFlip() const -> SDL_FlipMode { return flip_; }
|
[[nodiscard]] auto getFlip() const -> SDL_FlipMode { return flip_; }
|
||||||
void setFlip(SDL_FlipMode flip) { flip_ = flip; }
|
void setFlip(SDL_FlipMode flip) { flip_ = flip; }
|
||||||
|
|
||||||
@@ -171,8 +205,11 @@ class Entity {
|
|||||||
std::shared_ptr<Surface> surface_{nullptr};
|
std::shared_ptr<Surface> surface_{nullptr};
|
||||||
bool collides_{false};
|
bool collides_{false};
|
||||||
bool interactable_{false};
|
bool interactable_{false};
|
||||||
|
bool triggers_on_overlap_{false};
|
||||||
|
bool is_floor_decal_{false};
|
||||||
int height_{0};
|
int height_{0};
|
||||||
bool dead_{false};
|
bool dead_{false};
|
||||||
SDL_FlipMode flip_{SDL_FLIP_NONE};
|
SDL_FlipMode flip_{SDL_FLIP_NONE};
|
||||||
EventBus* bus_{nullptr};
|
EventBus* bus_{nullptr};
|
||||||
|
EntityManager* entity_manager_{nullptr};
|
||||||
};
|
};
|
||||||
@@ -72,6 +72,7 @@ void EntityManager::forceOpenDoor() {
|
|||||||
auto EntityManager::add(std::unique_ptr<Entity> entity) -> Entity* {
|
auto EntityManager::add(std::unique_ptr<Entity> entity) -> Entity* {
|
||||||
if (entity == nullptr) { return nullptr; }
|
if (entity == nullptr) { return nullptr; }
|
||||||
entity->setEventBus(&bus_);
|
entity->setEventBus(&bus_);
|
||||||
|
entity->setEntityManager(this);
|
||||||
Entity* raw = entity.get();
|
Entity* raw = entity.get();
|
||||||
entities_.push_back(std::move(entity));
|
entities_.push_back(std::move(entity));
|
||||||
// cppcheck-suppress returnDanglingLifetime
|
// cppcheck-suppress returnDanglingLifetime
|
||||||
@@ -80,12 +81,31 @@ auto EntityManager::add(std::unique_ptr<Entity> entity) -> Entity* {
|
|||||||
|
|
||||||
void EntityManager::setDoor(Door* door) {
|
void EntityManager::setDoor(Door* door) {
|
||||||
door_ = door;
|
door_ = door;
|
||||||
if (door_ != nullptr) { door_->setEventBus(&bus_); }
|
if (door_ != nullptr) {
|
||||||
|
door_->setEventBus(&bus_);
|
||||||
|
door_->setEntityManager(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto EntityManager::removeEntity(Entity* observer) -> bool {
|
||||||
|
if (observer == nullptr) { return false; }
|
||||||
|
const bool WAS_DOOR = (door_ == observer);
|
||||||
|
const bool WAS_SARC = (observer->getName() == "sarcophagus");
|
||||||
|
auto it = std::ranges::find_if(entities_, [observer](const auto& up) { return up.get() == observer; });
|
||||||
|
if (it == entities_.end()) { return false; }
|
||||||
|
entities_.erase(it);
|
||||||
|
if (WAS_DOOR) { door_ = nullptr; }
|
||||||
|
if (WAS_SARC && total_sarcs_ > 0) { --total_sarcs_; }
|
||||||
|
// No tocar `opened_sarcs_` aquí: l'editor congela el joc i no pot
|
||||||
|
// disparar-se cap esdeveniment derivat. Quan el GameScene faça el
|
||||||
|
// reload posterior, els comptadors es re-deriven d'inici.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto EntityManager::addSarcophagus(std::unique_ptr<Sarcophagus> sarc) -> Sarcophagus* {
|
auto EntityManager::addSarcophagus(std::unique_ptr<Sarcophagus> sarc) -> Sarcophagus* {
|
||||||
if (sarc == nullptr) { return nullptr; }
|
if (sarc == nullptr) { return nullptr; }
|
||||||
sarc->setEventBus(&bus_);
|
sarc->setEventBus(&bus_);
|
||||||
|
sarc->setEntityManager(this);
|
||||||
Sarcophagus* raw = sarc.get();
|
Sarcophagus* raw = sarc.get();
|
||||||
entities_.push_back(std::move(sarc));
|
entities_.push_back(std::move(sarc));
|
||||||
++total_sarcs_;
|
++total_sarcs_;
|
||||||
@@ -110,6 +130,24 @@ void EntityManager::notifyPlayerTilePainted(int col, int row) {
|
|||||||
for (auto& e : entities_) { e->notifyTilePainted(col, row); }
|
for (auto& e : entities_) { e->notifyTilePainted(col, row); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto EntityManager::findFloorTriggerAt(const SDL_FRect& rect) const -> const Entity* {
|
||||||
|
// Cerca lineal sobre `entities_`: cost menyspreable amb poques
|
||||||
|
// desenes d'entitats per sala i una crida per frame des dels
|
||||||
|
// consumidors actius (Player a l'aterratge, Mòmia a la decisió de
|
||||||
|
// direcció). Si esdevé calent, es podria indexar per cel·la del
|
||||||
|
// tilemap. La doble condició filtra exclusivament decals de sòl que
|
||||||
|
// volen rebre triggers — avui Hole; en el futur pinchos plans,
|
||||||
|
// plaques de pressió, charcos.
|
||||||
|
for (const auto& entity : entities_) {
|
||||||
|
if (!entity->triggersOnOverlap()) { continue; }
|
||||||
|
if (!entity->isFloorDecal()) { continue; }
|
||||||
|
const auto BOX = entity->getCollisionRect();
|
||||||
|
const bool OVERLAP = rect.x + rect.w > BOX.x && BOX.x + BOX.w > rect.x && rect.y + rect.h > BOX.y && BOX.y + BOX.h > rect.y;
|
||||||
|
if (OVERLAP) { return entity.get(); }
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
auto EntityManager::getEntities() const -> std::vector<Entity*> {
|
auto EntityManager::getEntities() const -> std::vector<Entity*> {
|
||||||
std::vector<Entity*> out;
|
std::vector<Entity*> out;
|
||||||
out.reserve(entities_.size());
|
out.reserve(entities_.size());
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "external/fkyaml_node.hpp"
|
||||||
#include "game/entities/event_bus.hpp"
|
#include "game/entities/event_bus.hpp"
|
||||||
|
|
||||||
class Entity;
|
class Entity;
|
||||||
@@ -41,6 +44,13 @@ class EntityManager {
|
|||||||
// una. Si ja n'hi ha una assignada, la substitueix (útil a tests).
|
// una. Si ja n'hi ha una assignada, la substitueix (útil a tests).
|
||||||
void setDoor(Door* door);
|
void setDoor(Door* door);
|
||||||
|
|
||||||
|
// Treu i destrueix l'entitat observada. Si era la porta o un sarc,
|
||||||
|
// ajusta `door_` / `total_sarcs_` en conseqüència. Pensat per al
|
||||||
|
// editor: no toca `opened_sarcs_` ni publica events — el joc està
|
||||||
|
// congelat i, en sortir del editor, GameScene torna a fer reload(),
|
||||||
|
// així que l'estat es deriva net.
|
||||||
|
auto removeEntity(Entity* observer) -> bool;
|
||||||
|
|
||||||
// Marca una entitat com a sarcòfag — si no es passa per `add` amb
|
// Marca una entitat com a sarcòfag — si no es passa per `add` amb
|
||||||
// type=sarcophagus no es compta al total. Útil en combinació amb
|
// type=sarcophagus no es compta al total. Útil en combinació amb
|
||||||
// `add` quan es fa addSarcophagus(unique_ptr<Sarcophagus>).
|
// `add` quan es fa addSarcophagus(unique_ptr<Sarcophagus>).
|
||||||
@@ -74,6 +84,16 @@ class EntityManager {
|
|||||||
// reflecteixi l'estat del frame actual.
|
// reflecteixi l'estat del frame actual.
|
||||||
void checkPlayerCollisions(const std::vector<Player*>& players);
|
void checkPlayerCollisions(const std::vector<Player*>& players);
|
||||||
|
|
||||||
|
// Cerca la primera entitat que (a) sigui decal de sòl
|
||||||
|
// (`is_floor_decal_=true`), (b) admeti triggers d'àrea
|
||||||
|
// (`triggers_on_overlap_=true`), i (c) la seva caixa de col·lisió
|
||||||
|
// solape amb el rect donat. Pensat per a `Player::integrateJump`,
|
||||||
|
// que la consulta a l'aterratge per detectar caigudes sobre Hole
|
||||||
|
// (i en el futur pinchos plans, plaques de pressió, charcos...) i
|
||||||
|
// saltar-se el LANDING_STUN normal. Retorna `nullptr` si no hi ha
|
||||||
|
// cap match — el caller usa el camí d'aterratge habitual.
|
||||||
|
[[nodiscard]] auto findFloorTriggerAt(const SDL_FRect& rect) const -> const Entity*;
|
||||||
|
|
||||||
// Fan-out del hook de pintat: per a cada sarcòfag, cridar
|
// Fan-out del hook de pintat: per a cada sarcòfag, cridar
|
||||||
// notifyTilePainted. Les entitats no-sarcòfag ho ignoren per defecte
|
// notifyTilePainted. Les entitats no-sarcòfag ho ignoren per defecte
|
||||||
// (veure Entity::notifyTilePainted). Invocat com a reacció a
|
// (veure Entity::notifyTilePainted). Invocat com a reacció a
|
||||||
@@ -90,6 +110,15 @@ class EntityManager {
|
|||||||
// `Map::tile_size`, `tilemap_origin_*` i `getSarcRect`).
|
// `Map::tile_size`, `tilemap_origin_*` i `getSarcRect`).
|
||||||
void loadFromLevel(const std::string& level_yaml_path);
|
void loadFromLevel(const std::string& level_yaml_path);
|
||||||
|
|
||||||
|
// Crea i afegeix una entitat a partir d'un node spawn (mateix format
|
||||||
|
// que els items del bloc `entities:` del YAML del nivell). Retorna el
|
||||||
|
// punter observacional a l'entitat creada, o `nullptr` si la plantilla
|
||||||
|
// no es pot carregar, el tipus no està registrat o el spawn no té els
|
||||||
|
// camps requerits (`position`, `sarc_position`, etc. segons el tipus).
|
||||||
|
// Pensat per al editor de Debug: construeix un node sintètic des
|
||||||
|
// d'una posició del cursor i el passa.
|
||||||
|
auto addSpawn(const fkyaml::node& spawn) -> Entity*;
|
||||||
|
|
||||||
// Reset complet del nivell: destrueix totes les entitats, reseteja
|
// Reset complet del nivell: destrueix totes les entitats, reseteja
|
||||||
// els comptadors interns (sarcòfags oberts, total, exited, flag de
|
// els comptadors interns (sarcòfags oberts, total, exited, flag de
|
||||||
// level_complete) i torna a cridar `loadFromLevel` per regenerar les
|
// level_complete) i torna a cridar `loadFromLevel` per regenerar les
|
||||||
|
|||||||
@@ -61,11 +61,32 @@ void EntityManager::renderAll() {
|
|||||||
void EntityManager::checkPlayerCollisions(const std::vector<Player*>& players) {
|
void EntityManager::checkPlayerCollisions(const std::vector<Player*>& players) {
|
||||||
if (players.empty()) { return; }
|
if (players.empty()) { return; }
|
||||||
for (const auto& entity : entities_) {
|
for (const auto& entity : entities_) {
|
||||||
if (!entity->collides()) { continue; }
|
// Acceptem entitats sòlides (xoc físic) i triggers d'àrea (no
|
||||||
|
// bloquegen però volen rebre `onPlayerContact`). Tanquem el gap
|
||||||
|
// latent de FloorTrap/WallTrap (que tenien `collides_=false` i
|
||||||
|
// mai no es disparaven) i obrim la porta a hazards no-físics
|
||||||
|
// com `Hole`.
|
||||||
|
if (!entity->collides() && !entity->triggersOnOverlap()) { continue; }
|
||||||
const auto BOX = entity->getCollisionRect();
|
const auto BOX = entity->getCollisionRect();
|
||||||
const int E_HEIGHT = entity->getHeight();
|
const int E_HEIGHT = entity->getHeight();
|
||||||
for (auto* p : players) {
|
for (auto* p : players) {
|
||||||
if (p == nullptr || p->isOutOfPlay()) { continue; }
|
// Filtrem també DYING: el jugador en cicle de mort no rep
|
||||||
|
// contacte amb hazards (un segon toc de mòmia mentre la roda
|
||||||
|
// ja és no-op; cap altra entitat l'ha de fer transicionar).
|
||||||
|
if (p == nullptr || p->isOutOfPlay() || p->isDying()) { continue; }
|
||||||
|
// Mentre el jugador cau per un forat, ignorem qualsevol altre
|
||||||
|
// hazard: la transició a `enterDying()` ja la dispara
|
||||||
|
// `integrateJump` quan supera `HOLE_FALL_DEPTH`.
|
||||||
|
//
|
||||||
|
// NOTA: aquest skip és la defensa que evita que un jugador
|
||||||
|
// caient per un forat reba contacte d'altres hazards
|
||||||
|
// "subterranis" durant la caiguda. La cadena de mort per
|
||||||
|
// forat (`enterDying(HOLE)` → respawn) depén de no ser
|
||||||
|
// interrompuda per un event `player_hit`/`player_killed`
|
||||||
|
// mentre `falling_through_hole_=true`. Si el dia que es
|
||||||
|
// toque aquest skip s'elimina, cal revisar la cadena
|
||||||
|
// mort/respawn per forat.
|
||||||
|
if (p->isFallingThroughHole()) { continue; }
|
||||||
// Filtre per altura: si el jugador està en l'aire i està per
|
// Filtre per altura: si el jugador està en l'aire i està per
|
||||||
// damunt de l'alçada del cos de l'entitat, no hi ha contacte.
|
// damunt de l'alçada del cos de l'entitat, no hi ha contacte.
|
||||||
// És el mecanisme del salt per esquivar momies, roques, etc.
|
// És el mecanisme del salt per esquivar momies, roques, etc.
|
||||||
@@ -374,31 +395,58 @@ void EntityManager::reload(const std::string& level_yaml_path) {
|
|||||||
loadFromLevel(level_yaml_path);
|
loadFromLevel(level_yaml_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto EntityManager::addSpawn(const fkyaml::node& spawn) -> Entity* {
|
||||||
|
if (!spawn.contains("type")) { return nullptr; }
|
||||||
|
const auto TYPE = spawn["type"].get_value<std::string>();
|
||||||
|
|
||||||
|
SpriteInfo sprite;
|
||||||
|
std::string anchor;
|
||||||
|
auto entity = buildEntity(TYPE, sprite, anchor);
|
||||||
|
if (entity == nullptr) { return nullptr; }
|
||||||
|
Entity* observer = entity.get();
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
if (TYPE == "sarcophagus") {
|
||||||
|
ok = placeSarcophagus(*this, std::move(entity), sprite, spawn);
|
||||||
|
} else if (TYPE == "door") {
|
||||||
|
ok = placeDoor(*this, std::move(entity), sprite);
|
||||||
|
} else {
|
||||||
|
ok = placeGeneric(*this, std::move(entity), TYPE, sprite, anchor, spawn);
|
||||||
|
}
|
||||||
|
return ok ? observer : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void EntityManager::loadFromLevel(const std::string& level_yaml_path) {
|
void EntityManager::loadFromLevel(const std::string& level_yaml_path) {
|
||||||
const auto SPAWNS = EntityLoader::readSpawns(level_yaml_path);
|
const auto SPAWNS = EntityLoader::readSpawns(level_yaml_path);
|
||||||
int loaded = 0;
|
const int LOADED = static_cast<int>(std::ranges::count_if(SPAWNS,
|
||||||
|
[this](const fkyaml::node& spawn) { return addSpawn(spawn) != nullptr; }));
|
||||||
|
|
||||||
for (const auto& spawn : SPAWNS) {
|
// Broadcast inicial: marca com a "pintats" tots els tiles ocupats per
|
||||||
if (!spawn.contains("type")) { continue; }
|
// decals de sòl (Hole, i futurs pinchos plans, plaques, charcos...).
|
||||||
const auto TYPE = spawn["type"].get_value<std::string>();
|
// Imprescindible perquè el jugador no pot trepitjar aquests tiles
|
||||||
|
// (cauria al forat) i els sarcòfags vecins no podrien completar el
|
||||||
SpriteInfo sprite;
|
// perímetre — la sala seria incompletable. Usa el canal semàntic
|
||||||
std::string anchor;
|
// `tile_painted` (lògica de joc), no `on_tile_entered` (presència
|
||||||
auto entity = buildEntity(TYPE, sprite, anchor);
|
// física del jugador). Ho fem aquí, després del bucle de spawns,
|
||||||
if (entity == nullptr) { continue; }
|
// perquè tots els sarcòfags ja estan registrats al fan-out (la
|
||||||
|
// subscripció del manager és al ctor, anterior a qualsevol load).
|
||||||
bool ok = false;
|
int decal_broadcast = 0;
|
||||||
if (TYPE == "sarcophagus") {
|
for (const auto& entity : entities_) {
|
||||||
ok = placeSarcophagus(*this, std::move(entity), sprite, spawn);
|
if (!entity->isFloorDecal()) { continue; }
|
||||||
} else if (TYPE == "door") {
|
const auto BOX = entity->getCollisionRect();
|
||||||
ok = placeDoor(*this, std::move(entity), sprite);
|
const auto [COL, ROW] = Map::pixelToTile(BOX.x, BOX.y);
|
||||||
} else {
|
bus_.publish(Event{
|
||||||
ok = placeGeneric(*this, std::move(entity), TYPE, sprite, anchor, spawn);
|
.name = "tile_painted",
|
||||||
}
|
.payload = TilePos{.col = COL, .row = ROW},
|
||||||
const bool OK = ok;
|
.player_id = -1,
|
||||||
if (OK) { ++loaded; }
|
});
|
||||||
|
++decal_broadcast;
|
||||||
|
}
|
||||||
|
if (decal_broadcast > 0) {
|
||||||
|
std::cout << "EntityManager: " << decal_broadcast
|
||||||
|
<< " tile_painted broadcast inicial (decals de sòl)\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "EntityManager: " << loaded << " entitats carregades des de "
|
std::cout << "EntityManager: " << LOADED << " entitats carregades des de "
|
||||||
<< level_yaml_path << '\n';
|
<< level_yaml_path << '\n';
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#include "game/entities/entity_writer.hpp"
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
|
namespace EntityWriter {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Cerca la línia que comença per `<key>:` a columna 0. Mateix
|
||||||
|
// patró que TilemapWriter::findDataLine però parametritzat. Si
|
||||||
|
// un dia paga la pena, podem extreure'l a un helper compartit.
|
||||||
|
auto findKeyLine(const std::string& content, const std::string& key) -> std::size_t {
|
||||||
|
const std::string PREFIX = key + ":";
|
||||||
|
std::size_t pos = 0;
|
||||||
|
while (pos < content.size()) {
|
||||||
|
const bool AT_LINE_START = (pos == 0 || content[pos - 1] == '\n');
|
||||||
|
if (AT_LINE_START && content.compare(pos, PREFIX.size(), PREFIX) == 0) {
|
||||||
|
const std::size_t AFTER = pos + PREFIX.size();
|
||||||
|
if (AFTER >= content.size() || content[AFTER] == '\n' || content[AFTER] == '\r' || content[AFTER] == ' ' || content[AFTER] == '\t' || content[AFTER] == '#') {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto NL = content.find('\n', pos);
|
||||||
|
if (NL == std::string::npos) { break; }
|
||||||
|
pos = NL + 1;
|
||||||
|
}
|
||||||
|
return std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avança mentre trobe línies indentades (espai/tab) o línies buides.
|
||||||
|
// A diferència del TilemapWriter, ací incloem línies buides dins
|
||||||
|
// del bloc: l'editor de YAML deixa sovint blanc entre grups
|
||||||
|
// d'entitats, i volem absorbir-lo per no deixar restes orfes
|
||||||
|
// entre el bloc nou i el següent top-level.
|
||||||
|
auto advancePastBlock(const std::string& content, std::size_t pos) -> std::size_t {
|
||||||
|
while (pos < content.size()) {
|
||||||
|
const char C = content[pos];
|
||||||
|
const bool IS_INDENT = (C == ' ' || C == '\t');
|
||||||
|
const bool IS_BLANK = (C == '\n' || C == '\r');
|
||||||
|
if (!IS_INDENT && !IS_BLANK) { break; }
|
||||||
|
const auto NL = content.find('\n', pos);
|
||||||
|
pos = (NL == std::string::npos) ? content.size() : NL + 1;
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buildBlock(const std::vector<Spawn>& spawns) -> std::string {
|
||||||
|
std::string out;
|
||||||
|
for (const auto& s : spawns) {
|
||||||
|
out += " - type: " + s.type + "\n";
|
||||||
|
if (s.sarc_col.has_value() && s.sarc_row.has_value()) {
|
||||||
|
out += " sarc_position: [" + std::to_string(*s.sarc_col) + ", " + std::to_string(*s.sarc_row) + "]\n";
|
||||||
|
} else if (s.col.has_value() && s.row.has_value()) {
|
||||||
|
out += " position: [" + std::to_string(*s.col) + ", " + std::to_string(*s.row) + "]\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
auto saveEntitiesBlock(const std::string& path, const std::vector<Spawn>& spawns) -> bool {
|
||||||
|
std::ifstream in(path, std::ios::binary);
|
||||||
|
if (!in) { return false; }
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << in.rdbuf();
|
||||||
|
const std::string CONTENT = ss.str();
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
const auto KEY_POS = findKeyLine(CONTENT, "entities");
|
||||||
|
if (KEY_POS == std::string::npos) { return false; }
|
||||||
|
|
||||||
|
const auto LINE_END = CONTENT.find('\n', KEY_POS);
|
||||||
|
const auto AFTER_KEY = (LINE_END == std::string::npos) ? CONTENT.size() : LINE_END + 1;
|
||||||
|
const auto BLOCK_END = advancePastBlock(CONTENT, AFTER_KEY);
|
||||||
|
|
||||||
|
// Mantenim una línia en blanc darrere del bloc si abans n'hi
|
||||||
|
// havia: ajuda a separar visualment el `entities:` de la pròxima
|
||||||
|
// secció. Si no n'hi havia, no afegim res.
|
||||||
|
const std::string TAIL = CONTENT.substr(BLOCK_END);
|
||||||
|
const std::string NEW_CONTENT =
|
||||||
|
CONTENT.substr(0, AFTER_KEY) + buildBlock(spawns) + TAIL;
|
||||||
|
|
||||||
|
const auto TMP_PATH = path + ".tmp";
|
||||||
|
{
|
||||||
|
std::ofstream out(TMP_PATH, std::ios::binary | std::ios::trunc);
|
||||||
|
if (!out) { return false; }
|
||||||
|
out.write(NEW_CONTENT.data(), static_cast<std::streamsize>(NEW_CONTENT.size()));
|
||||||
|
out.close();
|
||||||
|
if (!out) {
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::remove(TMP_PATH, ec);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::rename(TMP_PATH, path, ec);
|
||||||
|
if (ec) {
|
||||||
|
std::filesystem::remove(TMP_PATH, ec);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace EntityWriter
|
||||||
|
|
||||||
|
#endif // _DEBUG
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Reescriu NOMÉS el bloc `entities:` d'un YAML de nivell, preservant byte
|
||||||
|
// a byte la resta del fitxer: comentaris, ordre de claus, format del
|
||||||
|
// `geometry:` i `data:`, encoding i line endings entre línies no tocades.
|
||||||
|
// Pensat exclusivament per a l'editor (Debug).
|
||||||
|
//
|
||||||
|
// Format de sortida (estable, llegible a l'editor de text):
|
||||||
|
// entities:
|
||||||
|
// - type: torch
|
||||||
|
// position: [4, 2]
|
||||||
|
// - type: sarcophagus
|
||||||
|
// sarc_position: [0, 0]
|
||||||
|
// - type: door
|
||||||
|
//
|
||||||
|
// Semàntica del bloc origen:
|
||||||
|
// - Línia de capçalera: primera aparició de "entities:" a columna 0.
|
||||||
|
// Si manca, el writer FALLA i no toca el fitxer.
|
||||||
|
// - Contingut: línies indentades (espai o tab). Una línia buida o una
|
||||||
|
// línia a columna 0 marquen la fi del bloc.
|
||||||
|
//
|
||||||
|
// Si el fitxer original no té el bloc `entities:`, el writer no inventa
|
||||||
|
// ubicació: retorna false per que el caller decidisca (mostrar error,
|
||||||
|
// avortar, etc.) — no és funció del writer escollir l'estil del YAML.
|
||||||
|
//
|
||||||
|
// Escriptura atòmica: via `<path>.tmp` + `std::filesystem::rename`. Si
|
||||||
|
// l'escriptura falla a meitat, el YAML original queda intacte.
|
||||||
|
namespace EntityWriter {
|
||||||
|
|
||||||
|
// Cada spawn duplica l'estructura mínima del que el lector espera:
|
||||||
|
// type sempre, i un dels (position, sarc_position) o cap (cas door).
|
||||||
|
struct Spawn {
|
||||||
|
std::string type;
|
||||||
|
std::optional<int> col;
|
||||||
|
std::optional<int> row;
|
||||||
|
std::optional<int> sarc_col;
|
||||||
|
std::optional<int> sarc_row;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto saveEntitiesBlock(const std::string& path, const std::vector<Spawn>& spawns) -> bool;
|
||||||
|
|
||||||
|
} // namespace EntityWriter
|
||||||
|
|
||||||
|
#endif // _DEBUG
|
||||||
@@ -9,13 +9,18 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "core/audio/audio.hpp"
|
#include "core/audio/audio.hpp"
|
||||||
|
#include "core/input/gamepad_bindings.hpp"
|
||||||
#include "core/input/gamepad_manager.hpp"
|
#include "core/input/gamepad_manager.hpp"
|
||||||
#include "core/input/input_state.hpp"
|
#include "core/input/input_state.hpp"
|
||||||
|
#include "core/rendering/canvas.hpp"
|
||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/resources/resource_cache.hpp"
|
#include "core/resources/resource_cache.hpp"
|
||||||
#include "core/resources/resource_helper.hpp"
|
#include "core/resources/resource_helper.hpp"
|
||||||
#include "external/fkyaml_node.hpp"
|
#include "external/fkyaml_node.hpp"
|
||||||
|
#include "game/entities/entity.hpp"
|
||||||
|
#include "game/entities/entity_manager.hpp"
|
||||||
#include "game/entities/event_bus.hpp"
|
#include "game/entities/event_bus.hpp"
|
||||||
|
#include "game/options.hpp"
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -69,6 +74,7 @@ Player::InertiaConfig Player::inertia_config;
|
|||||||
Player::JumpConfig Player::jump_config;
|
Player::JumpConfig Player::jump_config;
|
||||||
Player::HurtConfig Player::hurt_config;
|
Player::HurtConfig Player::hurt_config;
|
||||||
Player::WhipConfig Player::whip_config;
|
Player::WhipConfig Player::whip_config;
|
||||||
|
Player::DeathConfig Player::death_config;
|
||||||
|
|
||||||
void Player::loadFrom(const std::string& yaml_path) {
|
void Player::loadFrom(const std::string& yaml_path) {
|
||||||
auto bytes = Resource::Helper::loadFile(yaml_path);
|
auto bytes = Resource::Helper::loadFile(yaml_path);
|
||||||
@@ -120,6 +126,11 @@ void Player::loadFrom(const std::string& yaml_path) {
|
|||||||
// jugador no atacarà efectivament (ATTACKING dura 0 ms i el flanc
|
// jugador no atacarà efectivament (ATTACKING dura 0 ms i el flanc
|
||||||
// d'input es perd).
|
// d'input es perd).
|
||||||
readWhipBlock(yaml);
|
readWhipBlock(yaml);
|
||||||
|
|
||||||
|
// Bloc opcional `death:` — pausa de respawn i comptador inicial de
|
||||||
|
// vides. Si manca, el respawn és immediat i les vides queden a 3
|
||||||
|
// per defecte (no és un valor crític: avui no hi ha game over).
|
||||||
|
readDeathBlock(yaml);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) {
|
void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) {
|
||||||
@@ -179,6 +190,19 @@ void Player::readHurtBlock(const fkyaml::node& yaml) {
|
|||||||
const auto& h = yaml["hurt"];
|
const auto& h = yaml["hurt"];
|
||||||
if (h.contains("speed")) { hurt_config.initial_speed_px_per_s = h["speed"].get_value<float>(); }
|
if (h.contains("speed")) { hurt_config.initial_speed_px_per_s = h["speed"].get_value<float>(); }
|
||||||
if (h.contains("brake_time_ms")) { hurt_config.brake_time_ms = h["brake_time_ms"].get_value<int>(); }
|
if (h.contains("brake_time_ms")) { hurt_config.brake_time_ms = h["brake_time_ms"].get_value<int>(); }
|
||||||
|
// So d'entrada a HURT — mateix patró que `jump.sound` i `whip.sound`.
|
||||||
|
if (h.contains("sound")) {
|
||||||
|
hurt_config.sound_key = h["sound"].get_value<std::string>();
|
||||||
|
Resource::Cache::get().loadSound(hurt_config.sound_key, "data/sounds/" + hurt_config.sound_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::readDeathBlock(const fkyaml::node& yaml) {
|
||||||
|
death_config = DeathConfig{};
|
||||||
|
if (!yaml.contains("death")) { return; }
|
||||||
|
const auto& d = yaml["death"];
|
||||||
|
if (d.contains("respawn_delay_ms")) { death_config.respawn_delay_ms = d["respawn_delay_ms"].get_value<int>(); }
|
||||||
|
if (d.contains("lives")) { death_config.initial_lives = d["lives"].get_value<int>(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::readWhipBlock(const fkyaml::node& yaml) {
|
void Player::readWhipBlock(const fkyaml::node& yaml) {
|
||||||
@@ -189,25 +213,30 @@ void Player::readWhipBlock(const fkyaml::node& yaml) {
|
|||||||
if (w.contains("swing_ms")) { whip_config.swing_ms = w["swing_ms"].get_value<int>(); }
|
if (w.contains("swing_ms")) { whip_config.swing_ms = w["swing_ms"].get_value<int>(); }
|
||||||
if (w.contains("cooldown_ms")) { whip_config.cooldown_ms = w["cooldown_ms"].get_value<int>(); }
|
if (w.contains("cooldown_ms")) { whip_config.cooldown_ms = w["cooldown_ms"].get_value<int>(); }
|
||||||
if (w.contains("damage")) { whip_config.damage = w["damage"].get_value<int>(); }
|
if (w.contains("damage")) { whip_config.damage = w["damage"].get_value<int>(); }
|
||||||
|
// So del swing: mateix patró que `jump.sound` — basename val de
|
||||||
|
// clau al cache i de path. Si manca, queda buit i no es dispara.
|
||||||
|
if (w.contains("sound")) {
|
||||||
|
whip_config.sound_key = w["sound"].get_value<std::string>();
|
||||||
|
Resource::Cache::get().loadSound(whip_config.sound_key, "data/sounds/" + whip_config.sound_key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::applyCardinal(Cardinal c) {
|
void Player::applyCardinal(Cardinal c) {
|
||||||
|
// El flip del sprite el governa l'animació al motor: només cal posar
|
||||||
|
// el `facing_` que tocaria; `animationName()` ja resol el sufix
|
||||||
|
// (`_west` per Cardinal::WEST) i el YAML hi declara `flip_h: true`.
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case Cardinal::NORTH:
|
case Cardinal::NORTH:
|
||||||
facing_ = Facing::UP;
|
facing_ = Facing::NORTH;
|
||||||
setFlip(SDL_FLIP_NONE);
|
|
||||||
break;
|
break;
|
||||||
case Cardinal::SOUTH:
|
case Cardinal::SOUTH:
|
||||||
facing_ = Facing::DOWN;
|
facing_ = Facing::SOUTH;
|
||||||
setFlip(SDL_FLIP_NONE);
|
|
||||||
break;
|
break;
|
||||||
case Cardinal::EAST:
|
case Cardinal::EAST:
|
||||||
facing_ = Facing::SIDE;
|
facing_ = Facing::EAST;
|
||||||
setFlip(SDL_FLIP_NONE);
|
|
||||||
break;
|
break;
|
||||||
case Cardinal::WEST:
|
case Cardinal::WEST:
|
||||||
facing_ = Facing::SIDE;
|
facing_ = Facing::WEST;
|
||||||
setFlip(SDL_FLIP_HORIZONTAL);
|
|
||||||
break;
|
break;
|
||||||
case Cardinal::NONE:
|
case Cardinal::NONE:
|
||||||
break;
|
break;
|
||||||
@@ -225,10 +254,10 @@ auto Player::cardinalFromInput(float vx, float vy) -> Cardinal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Player::cardinalFromString(const std::string& s) -> Cardinal {
|
auto Player::cardinalFromString(const std::string& s) -> Cardinal {
|
||||||
if (s == "north" || s == "up") { return Cardinal::NORTH; }
|
if (s == "north") { return Cardinal::NORTH; }
|
||||||
if (s == "south" || s == "down") { return Cardinal::SOUTH; }
|
if (s == "south") { return Cardinal::SOUTH; }
|
||||||
if (s == "east" || s == "right") { return Cardinal::EAST; }
|
if (s == "east") { return Cardinal::EAST; }
|
||||||
if (s == "west" || s == "left") { return Cardinal::WEST; }
|
if (s == "west") { return Cardinal::WEST; }
|
||||||
throw std::runtime_error("Player: cardinal desconegut '" + s + "' al bloc turn.transitions");
|
throw std::runtime_error("Player: cardinal desconegut '" + s + "' al bloc turn.transitions");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,18 +330,18 @@ void Player::updateFacingAndTurn(float delta_time, float vx, float vy) {
|
|||||||
intermediate_cardinal_ = intermediateFor(INPUT_CARD);
|
intermediate_cardinal_ = intermediateFor(INPUT_CARD);
|
||||||
turning_remaining_ms_ = static_cast<float>(turn_config.duration_ms);
|
turning_remaining_ms_ = static_cast<float>(turn_config.duration_ms);
|
||||||
applyCardinal(intermediate_cardinal_);
|
applyCardinal(intermediate_cardinal_);
|
||||||
} else if (vx != 0.0F) {
|
|
||||||
// Tria de facing original: prevalença lateral, flip segons el
|
|
||||||
// signe de vx; verticals purs van a UP/DOWN amb flip NONE; en
|
|
||||||
// aturar-se conservem `facing_` per a l'animació stand.
|
|
||||||
facing_ = Facing::SIDE;
|
|
||||||
setFlip(vx < 0.0F ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE);
|
|
||||||
} else if (vy < 0.0F) {
|
} else if (vy < 0.0F) {
|
||||||
facing_ = Facing::UP;
|
// Tria de facing amb prevalença vertical: si hi ha component
|
||||||
setFlip(SDL_FLIP_NONE);
|
// vertical, el sprite mira NORTH/SOUTH encara que també hi
|
||||||
|
// haja horitzontal. Així una diagonal (down+right, up+left,
|
||||||
|
// etc.) mostra el sprite mirant amunt o avall, no de costat.
|
||||||
|
// Només quan el moviment és purament horitzontal anem a
|
||||||
|
// EAST/WEST. En aturar-se conservem `facing_` per a stand.
|
||||||
|
facing_ = Facing::NORTH;
|
||||||
} else if (vy > 0.0F) {
|
} else if (vy > 0.0F) {
|
||||||
facing_ = Facing::DOWN;
|
facing_ = Facing::SOUTH;
|
||||||
setFlip(SDL_FLIP_NONE);
|
} else if (vx != 0.0F) {
|
||||||
|
facing_ = (vx < 0.0F) ? Facing::WEST : Facing::EAST;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,11 +360,18 @@ Player::Player(const std::string& anim_key, int id, float x, float y, const Cont
|
|||||||
paint_tile_index_(paint_tile_index) {
|
paint_tile_index_(paint_tile_index) {
|
||||||
setPos(x, y);
|
setPos(x, y);
|
||||||
// Postura inicial mirant cap al jugador (cara avall), no de costat:
|
// Postura inicial mirant cap al jugador (cara avall), no de costat:
|
||||||
// forcem facing_ a DOWN i l'animació `stand_down` abans del primer
|
// forcem facing_ a SOUTH i l'animació `stand_south` abans del primer
|
||||||
// frame d'input — el primer `update` la preservarà si l'usuari no
|
// frame d'input — el primer `update` la preservarà si l'usuari no
|
||||||
// prem cap tecla.
|
// prem cap tecla.
|
||||||
facing_ = Facing::DOWN;
|
facing_ = Facing::SOUTH;
|
||||||
setCurrentAnimation("stand_down");
|
setCurrentAnimation("stand_south");
|
||||||
|
// Comptador d'intents inicialitzat amb el valor del YAML (default 3
|
||||||
|
// si el bloc `death:` manca). Fora del cicle DYING no toca a ningú.
|
||||||
|
lives_remaining_ = death_config.initial_lives;
|
||||||
|
// Spawn point inicial: la posició del ctor val per defecte; el
|
||||||
|
// GameScene el reescriu via `setSpawnPoint` quan canvia de sala.
|
||||||
|
spawn_x_ = x;
|
||||||
|
spawn_y_ = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::update(float delta_time) {
|
void Player::update(float delta_time) {
|
||||||
@@ -371,9 +407,13 @@ auto Player::collidesWithWorld(const SDL_FRect& box) const -> bool {
|
|||||||
const bool MY_AIR = (state_ == State::AIRBORNE);
|
const bool MY_AIR = (state_ == State::AIRBORNE);
|
||||||
const bool MY_HURT = (state_ == State::HURT);
|
const bool MY_HURT = (state_ == State::HURT);
|
||||||
const float MY_Z = jump_z_;
|
const float MY_Z = jump_z_;
|
||||||
return std::ranges::any_of(*all_players_, [this, &PROPOSED_HIT, MY_AIR, MY_HURT, MY_Z](const Player* p) {
|
const bool MY_DYING = (state_ == State::DYING);
|
||||||
|
return std::ranges::any_of(*all_players_, [this, &PROPOSED_HIT, MY_AIR, MY_HURT, MY_DYING, MY_Z](const Player* p) {
|
||||||
if (p == nullptr || p == this) { return false; }
|
if (p == nullptr || p == this) { return false; }
|
||||||
if (p->isOutOfPlay()) { return false; }
|
if (p->isOutOfPlay()) { return false; }
|
||||||
|
// DYING és intangible per a PvP: el jugador mort no actua de
|
||||||
|
// mur, i un altre jugador en DYING no pot empentar tampoc.
|
||||||
|
if (MY_DYING || p->isDying()) { return false; }
|
||||||
if (MY_HURT || p->isHurt()) { return false; }
|
if (MY_HURT || p->isHurt()) { return false; }
|
||||||
if (MY_AIR != p->isAirborne()) { return false; }
|
if (MY_AIR != p->isAirborne()) { return false; }
|
||||||
// Aquí els dos estats coincideixen. Si tots dos volen, el filtre
|
// Aquí els dos estats coincideixen. Si tots dos volen, el filtre
|
||||||
@@ -429,6 +469,25 @@ void Player::tick(float delta_time) {
|
|||||||
// que GameScene el reactiva (respawn / spawnFromAir).
|
// que GameScene el reactiva (respawn / spawnFromAir).
|
||||||
if (isOutOfPlay()) { return; }
|
if (isOutOfPlay()) { return; }
|
||||||
|
|
||||||
|
// DYING: animació `death_spinning` + pausa de respawn. Ignorem tot
|
||||||
|
// l'input (ni moviment, ni salt, ni atac). El moviment queda
|
||||||
|
// congelat al lloc de la mort; `applyMovement` avança l'animació i
|
||||||
|
// detecta la fi per disparar el respawn. Mantenim els flancs de
|
||||||
|
// botó actualitzats perquè una tecla sostinguda no es comporte com
|
||||||
|
// a flanc nou en sortir del cicle.
|
||||||
|
if (state_ == State::DYING) {
|
||||||
|
(void)consumeJumpEdge();
|
||||||
|
(void)consumeAttackEdge();
|
||||||
|
input_vx_ = 0.0F;
|
||||||
|
input_vy_ = 0.0F;
|
||||||
|
intended_vx_ = 0.0F;
|
||||||
|
intended_vy_ = 0.0F;
|
||||||
|
motion_time_ms_ = 0.0F;
|
||||||
|
setVelX(0.0F);
|
||||||
|
setVelY(0.0F);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset per frame: el resolver del GameScene el tornarà a posar a
|
// Reset per frame: el resolver del GameScene el tornarà a posar a
|
||||||
// true si aquest frame hi ha push actiu. Així quan el push acaba
|
// true si aquest frame hi ha push actiu. Així quan el push acaba
|
||||||
// (un deixa d'empentar, l'altre comença a caminar), l'animació torna
|
// (un deixa d'empentar, l'altre comença a caminar), l'animació torna
|
||||||
@@ -527,6 +586,12 @@ void Player::tick(float delta_time) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresca les vinculacions des d'Options en calent: així un remap fet
|
||||||
|
// al menú de servei té efecte immediatament, sense esperar al pròxim
|
||||||
|
// GameScene::onEnter. Replica el comportament d'AEE
|
||||||
|
// (`KeyRemap::update` que llig `Options::keys_game` cada frame).
|
||||||
|
refreshControlsFromOptions();
|
||||||
|
|
||||||
// Via `InputState` en lloc de `SDL_GetKeyboardState` directe: quan el
|
// Via `InputState` en lloc de `SDL_GetKeyboardState` directe: quan el
|
||||||
// service menu (o qualsevol UI modal) està obert, el wrapper retorna
|
// service menu (o qualsevol UI modal) està obert, el wrapper retorna
|
||||||
// un buffer tot-a-zero perquè el jugador no es mogui mentre l'usuari
|
// un buffer tot-a-zero perquè el jugador no es mogui mentre l'usuari
|
||||||
@@ -602,6 +667,18 @@ void Player::tick(float delta_time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Player::refreshControlsFromOptions() {
|
||||||
|
if (id_ != 0 && id_ != 1) { return; }
|
||||||
|
const auto& kb = (id_ == 0) ? Options::controls().keyboard_p1
|
||||||
|
: Options::controls().keyboard_p2;
|
||||||
|
controls_.up = kb.up;
|
||||||
|
controls_.down = kb.down;
|
||||||
|
controls_.left = kb.left;
|
||||||
|
controls_.right = kb.right;
|
||||||
|
controls_.jump = kb.jump;
|
||||||
|
controls_.attack = kb.attack;
|
||||||
|
}
|
||||||
|
|
||||||
auto Player::consumeJumpEdge() -> bool {
|
auto Player::consumeJumpEdge() -> bool {
|
||||||
// Edge-detection independent per teclat i mando: si qualsevol dels
|
// Edge-detection independent per teclat i mando: si qualsevol dels
|
||||||
// dos passa de no-premut a premut aquest frame, comptem com a salt.
|
// dos passa de no-premut a premut aquest frame, comptem com a salt.
|
||||||
@@ -613,7 +690,11 @@ auto Player::consumeJumpEdge() -> bool {
|
|||||||
jump_held_keyboard_ = KEY_NOW;
|
jump_held_keyboard_ = KEY_NOW;
|
||||||
|
|
||||||
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(id_);
|
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(id_);
|
||||||
const bool PAD_NOW = (pad != nullptr) && InputState::getGamepadButton(pad, SDL_GAMEPAD_BUTTON_SOUTH);
|
const auto& slot = (id_ == 0) ? Options::controls().gamepad_p1
|
||||||
|
: Options::controls().gamepad_p2;
|
||||||
|
const int JUMP_BTN = GamepadBindings::get().getButton(slot.name, slot.path, GamepadBindings::Action::JUMP);
|
||||||
|
const bool PAD_NOW = (pad != nullptr) && !InputState::isBlocked() &&
|
||||||
|
GamepadBindings::isPressed(pad, JUMP_BTN);
|
||||||
if (PAD_NOW && !jump_held_gamepad_) { edge = true; }
|
if (PAD_NOW && !jump_held_gamepad_) { edge = true; }
|
||||||
jump_held_gamepad_ = PAD_NOW;
|
jump_held_gamepad_ = PAD_NOW;
|
||||||
|
|
||||||
@@ -621,16 +702,20 @@ auto Player::consumeJumpEdge() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Player::consumeAttackEdge() -> bool {
|
auto Player::consumeAttackEdge() -> bool {
|
||||||
// Mateix patró que `consumeJumpEdge` però per al botó d'atac. Mando:
|
// Mateix patró que `consumeJumpEdge` però per al botó d'atac. El botó
|
||||||
// SDL_GAMEPAD_BUTTON_WEST (X a Xbox / Quadrat a PlayStation), oposat
|
// físic ve d'`GamepadBindings`, persistit per dispositiu — l'usuari
|
||||||
// a SOUTH per evitar topades semàntiques.
|
// l'ha pogut remapejar des del menú de servei.
|
||||||
const bool* keys = InputState::getKeyboardState();
|
const bool* keys = InputState::getKeyboardState();
|
||||||
const bool KEY_NOW = keys[controls_.attack];
|
const bool KEY_NOW = keys[controls_.attack];
|
||||||
bool edge = (KEY_NOW && !attack_held_keyboard_);
|
bool edge = (KEY_NOW && !attack_held_keyboard_);
|
||||||
attack_held_keyboard_ = KEY_NOW;
|
attack_held_keyboard_ = KEY_NOW;
|
||||||
|
|
||||||
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(id_);
|
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(id_);
|
||||||
const bool PAD_NOW = (pad != nullptr) && InputState::getGamepadButton(pad, SDL_GAMEPAD_BUTTON_WEST);
|
const auto& slot = (id_ == 0) ? Options::controls().gamepad_p1
|
||||||
|
: Options::controls().gamepad_p2;
|
||||||
|
const int ATK_BTN = GamepadBindings::get().getButton(slot.name, slot.path, GamepadBindings::Action::ATTACK);
|
||||||
|
const bool PAD_NOW = (pad != nullptr) && !InputState::isBlocked() &&
|
||||||
|
GamepadBindings::isPressed(pad, ATK_BTN);
|
||||||
if (PAD_NOW && !attack_held_gamepad_) { edge = true; }
|
if (PAD_NOW && !attack_held_gamepad_) { edge = true; }
|
||||||
attack_held_gamepad_ = PAD_NOW;
|
attack_held_gamepad_ = PAD_NOW;
|
||||||
|
|
||||||
@@ -639,16 +724,16 @@ auto Player::consumeAttackEdge() -> bool {
|
|||||||
|
|
||||||
auto Player::currentFacingCardinal() const -> Cardinal {
|
auto Player::currentFacingCardinal() const -> Cardinal {
|
||||||
switch (facing_) {
|
switch (facing_) {
|
||||||
case Facing::UP:
|
case Facing::NORTH:
|
||||||
return Cardinal::NORTH;
|
return Cardinal::NORTH;
|
||||||
case Facing::DOWN:
|
case Facing::SOUTH:
|
||||||
return Cardinal::SOUTH;
|
return Cardinal::SOUTH;
|
||||||
case Facing::SIDE:
|
case Facing::EAST:
|
||||||
break;
|
return Cardinal::EAST;
|
||||||
|
case Facing::WEST:
|
||||||
|
return Cardinal::WEST;
|
||||||
}
|
}
|
||||||
// Lateral: el flip discrimina E/W. Sense flip = mira a la dreta
|
return Cardinal::EAST;
|
||||||
// (convenció estable a tots els assets actuals).
|
|
||||||
return (getFlip() == SDL_FLIP_HORIZONTAL) ? Cardinal::WEST : Cardinal::EAST;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::enterAttacking(Cardinal facing) {
|
void Player::enterAttacking(Cardinal facing) {
|
||||||
@@ -662,6 +747,13 @@ void Player::enterAttacking(Cardinal facing) {
|
|||||||
|
|
||||||
if (facing == Cardinal::NONE) { return; }
|
if (facing == Cardinal::NONE) { return; }
|
||||||
|
|
||||||
|
// So del swing — disparat ací (no des del WhipAttack) perquè és el
|
||||||
|
// jugador qui executa l'acció; si demà el spawn falla per qualsevol
|
||||||
|
// motiu, el so del gest segueix sonant.
|
||||||
|
if (!whip_config.sound_key.empty() && Audio::get() != nullptr) {
|
||||||
|
Audio::get()->playSound(whip_config.sound_key, Audio::Group::GAME);
|
||||||
|
}
|
||||||
|
|
||||||
// Mapa de Cardinal → int al payload (vegeu `WhipSpawnPayload`).
|
// Mapa de Cardinal → int al payload (vegeu `WhipSpawnPayload`).
|
||||||
int dir_int = 0;
|
int dir_int = 0;
|
||||||
switch (facing) {
|
switch (facing) {
|
||||||
@@ -707,7 +799,7 @@ void Player::startJump() {
|
|||||||
// Un gir de 180° en curs es compromet immediatament: si l'usuari
|
// Un gir de 180° en curs es compromet immediatament: si l'usuari
|
||||||
// ha disparat un giro (oest→est, p.ex.) i salta abans que s'esgote
|
// ha disparat un giro (oest→est, p.ex.) i salta abans que s'esgote
|
||||||
// el timer, l'animació del salt ha de mostrar la direcció final
|
// el timer, l'animació del salt ha de mostrar la direcció final
|
||||||
// (jump lateral), no la intermèdia (jump_down). Aplem el pending
|
// (jump lateral), no la intermèdia (jump_south). Aplem el pending
|
||||||
// ara mateix i resetem l'estat del gir. Si no hi havia gir en
|
// ara mateix i resetem l'estat del gir. Si no hi havia gir en
|
||||||
// curs, és no-op.
|
// curs, és no-op.
|
||||||
if (turning_remaining_ms_ > 0.0F) {
|
if (turning_remaining_ms_ > 0.0F) {
|
||||||
@@ -802,6 +894,9 @@ void Player::enterHurt(Cardinal preferred) {
|
|||||||
|
|
||||||
// Activem l'estat i fixem la velocitat segons la direcció triada.
|
// Activem l'estat i fixem la velocitat segons la direcció triada.
|
||||||
state_ = State::HURT;
|
state_ = State::HURT;
|
||||||
|
if (!hurt_config.sound_key.empty() && Audio::get() != nullptr) {
|
||||||
|
Audio::get()->playSound(hurt_config.sound_key, Audio::Group::GAME);
|
||||||
|
}
|
||||||
applyCardinal(CHOSEN);
|
applyCardinal(CHOSEN);
|
||||||
last_input_cardinal_ = CHOSEN;
|
last_input_cardinal_ = CHOSEN;
|
||||||
turning_remaining_ms_ = 0.0F;
|
turning_remaining_ms_ = 0.0F;
|
||||||
@@ -821,7 +916,40 @@ auto Player::integrateJump(float delta_time) -> bool {
|
|||||||
if (state_ != State::AIRBORNE) { return false; }
|
if (state_ != State::AIRBORNE) { return false; }
|
||||||
jump_vz_ -= jump_config.gravity_px_per_s2 * delta_time;
|
jump_vz_ -= jump_config.gravity_px_per_s2 * delta_time;
|
||||||
jump_z_ += jump_vz_ * delta_time;
|
jump_z_ += jump_vz_ * delta_time;
|
||||||
|
// Caiguda per agujero: el sòl ja no és present sota el jugador, així
|
||||||
|
// que NO clampem `jump_z_` a 0. Deixem baixar a negatius (visualment
|
||||||
|
// el sprite "s'enfonsa" sota el sòl) i, en superar `HOLE_FALL_DEPTH`,
|
||||||
|
// disparem la mort. La cinemàtica de respawn (`enterDying →
|
||||||
|
// spawnFromAir`) reseteja el flag i la posició al spawn point.
|
||||||
|
if (falling_through_hole_) {
|
||||||
|
if (jump_z_ <= -HOLE_FALL_DEPTH) {
|
||||||
|
enterDying(DeathCause::HOLE);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (jump_z_ <= 0.0F) {
|
if (jump_z_ <= 0.0F) {
|
||||||
|
// Aterratge sobre decal-trigger (Hole, futurs pinchos plans...):
|
||||||
|
// saltem el LANDING_STUN i transicionem directament a la caiguda
|
||||||
|
// pel forat. Sense aquesta detecció, l'aterratge entraria a
|
||||||
|
// LANDING_STUN (~200 ms flotant sobre el forat) i només quan
|
||||||
|
// tornés a GROUNDED `Hole::onPlayerContact` ho dispararia. La
|
||||||
|
// hit-box (8×8 centrada al sprite) és la mateixa que `Hole`
|
||||||
|
// usa al seu re-check; coherent. Si el manager no s'ha
|
||||||
|
// injectat (tests stand-alone), el camí d'aterratge segueix sent
|
||||||
|
// el habitual.
|
||||||
|
if (entity_manager_ != nullptr) {
|
||||||
|
const Entity* trigger = entity_manager_->findFloorTriggerAt(getHitBox());
|
||||||
|
if (trigger != nullptr) {
|
||||||
|
const auto BOX = trigger->getCollisionRect();
|
||||||
|
const float CENTER_X = BOX.x + (BOX.w * 0.5F);
|
||||||
|
const float CENTER_Y = BOX.y + (BOX.h * 0.5F);
|
||||||
|
const float FLOOR_Y = BOX.y + BOX.h;
|
||||||
|
enterHoleFall(CENTER_X, CENTER_Y, FLOOR_Y);
|
||||||
|
// No retornem `true`: no és un aterratge "normal" i
|
||||||
|
// `applyMovement` no ha de cridar `resolveLandingOverlap`.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
jump_z_ = 0.0F;
|
jump_z_ = 0.0F;
|
||||||
jump_vz_ = 0.0F;
|
jump_vz_ = 0.0F;
|
||||||
state_ = State::LANDING_STUN;
|
state_ = State::LANDING_STUN;
|
||||||
@@ -860,7 +988,9 @@ void Player::resolveLandingOverlap() {
|
|||||||
if (other == nullptr || other == this) { continue; }
|
if (other == nullptr || other == this) { continue; }
|
||||||
// Salta jugadors fora del joc: un caigut sobre un slot DISABLED
|
// Salta jugadors fora del joc: un caigut sobre un slot DISABLED
|
||||||
// (o sobre un EXITED encara al vector) no els ha de projectar.
|
// (o sobre un EXITED encara al vector) no els ha de projectar.
|
||||||
if (other->isOutOfPlay()) { continue; }
|
// També DYING — un jugador en cicle de mort no rep cap més
|
||||||
|
// efecte fins que respawneja.
|
||||||
|
if (other->isOutOfPlay() || other->isDying()) { continue; }
|
||||||
// Test amb la hit-box (8×8 de contacte precís), no la base-box.
|
// Test amb la hit-box (8×8 de contacte precís), no la base-box.
|
||||||
// Coherent amb la resta de col·lisions actor-actor del joc:
|
// Coherent amb la resta de col·lisions actor-actor del joc:
|
||||||
// momies, fuetades futures, resolver PvP… tots usen hit-box. Un
|
// momies, fuetades futures, resolver PvP… tots usen hit-box. Un
|
||||||
@@ -908,6 +1038,11 @@ void Player::applyMovement(float delta_time) {
|
|||||||
resolveLandingOverlap();
|
resolveLandingOverlap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state_ == State::DYING) {
|
||||||
|
applyDyingMovement(delta_time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (state_ == State::HURT) {
|
if (state_ == State::HURT) {
|
||||||
applyHurtMovement(delta_time);
|
applyHurtMovement(delta_time);
|
||||||
return;
|
return;
|
||||||
@@ -965,6 +1100,31 @@ void Player::applyMovement(float delta_time) {
|
|||||||
|
|
||||||
clampToPlayfield();
|
clampToPlayfield();
|
||||||
|
|
||||||
|
// Refinem el `facing_` amb el desplaçament REAL del frame (post
|
||||||
|
// col·lisió/snap), no amb la velocitat d'entrada: si l'usuari prem
|
||||||
|
// diagonal en un pasillo, `tryMove` ja haurà anul·lat l'eix tapat
|
||||||
|
// per la paret, així que el sprite mira en la direcció realment
|
||||||
|
// efectuada (la d'input cru no ho sap). En AIRBORNE el facing es
|
||||||
|
// preserva des del despegue (vegeu el comentari de `integrateJump`),
|
||||||
|
// i durant un gir actiu el cardinal intermedi mana, així que els
|
||||||
|
// saltem. Si DX==DY==0 (parat o totalment bloquejat) preservem el
|
||||||
|
// facing previ per a l'animació stand. La prioritat vertical en
|
||||||
|
// diagonals lliures és intranscendent en pasillos (un dels dos eixos
|
||||||
|
// serà sempre 0); manté coherència amb la tria d'`updateFacingAndTurn`.
|
||||||
|
if (!AIRBORNE && turning_remaining_ms_ <= 0.0F) {
|
||||||
|
const float DX = getPosX() - FRAME_START_X;
|
||||||
|
const float DY = getPosY() - FRAME_START_Y;
|
||||||
|
if (DY < 0.0F) {
|
||||||
|
facing_ = Facing::NORTH;
|
||||||
|
} else if (DY > 0.0F) {
|
||||||
|
facing_ = Facing::SOUTH;
|
||||||
|
} else if (DX < 0.0F) {
|
||||||
|
facing_ = Facing::WEST;
|
||||||
|
} else if (DX > 0.0F) {
|
||||||
|
facing_ = Facing::EAST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const bool MOVED = (getPosX() != FRAME_START_X) || (getPosY() != FRAME_START_Y);
|
const bool MOVED = (getPosX() != FRAME_START_X) || (getPosY() != FRAME_START_Y);
|
||||||
// `being_pushed_` força `stand_*`: el jugador es desplaça sense input,
|
// `being_pushed_` força `stand_*`: el jugador es desplaça sense input,
|
||||||
// animar-lo com a caminant faria creure que està caminant quan de
|
// animar-lo com a caminant faria creure que està caminant quan de
|
||||||
@@ -979,6 +1139,88 @@ void Player::applyMovement(float delta_time) {
|
|||||||
if (state_ == State::GROUNDED) { publishTileEnteredIfChanged(); }
|
if (state_ == State::GROUNDED) { publishTileEnteredIfChanged(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Player::enterDying(DeathCause cause) {
|
||||||
|
// Idempotent. Un segon contacte de momia mentre el jugador ja està
|
||||||
|
// en DYING no reinicia l'animació ni el comptador. Tampoc disparem
|
||||||
|
// el cicle si el jugador està fora de joc (EXITED al passar la
|
||||||
|
// porta, DISABLED durant les transicions de sala).
|
||||||
|
if (state_ == State::DYING || isOutOfPlay()) { return; }
|
||||||
|
|
||||||
|
state_ = State::DYING;
|
||||||
|
intended_vx_ = 0.0F;
|
||||||
|
intended_vy_ = 0.0F;
|
||||||
|
input_vx_ = 0.0F;
|
||||||
|
input_vy_ = 0.0F;
|
||||||
|
motion_time_ms_ = 0.0F;
|
||||||
|
being_pushed_ = false;
|
||||||
|
jump_z_ = 0.0F;
|
||||||
|
jump_vz_ = 0.0F;
|
||||||
|
falling_through_hole_ = false;
|
||||||
|
hole_clip_bottom_y_ = 0.0F;
|
||||||
|
landing_stun_remaining_ms_ = 0.0F;
|
||||||
|
attacking_remaining_ms_ = 0.0F;
|
||||||
|
turning_remaining_ms_ = 0.0F;
|
||||||
|
pending_cardinal_ = Cardinal::NONE;
|
||||||
|
intermediate_cardinal_ = Cardinal::NONE;
|
||||||
|
last_input_cardinal_ = Cardinal::NONE;
|
||||||
|
setVelX(0.0F);
|
||||||
|
setVelY(0.0F);
|
||||||
|
|
||||||
|
death_animation_complete_ = false;
|
||||||
|
respawn_remaining_ms_ = 0.0F;
|
||||||
|
// Presentació segons la causa. Cada `DeathCause` té la seua animació
|
||||||
|
// dedicada al YAML del player; `applyDyingMovement` arma el respawn
|
||||||
|
// timer quan `animationIsCompleted()` flanqueja, igual per a totes.
|
||||||
|
switch (cause) {
|
||||||
|
case DeathCause::HOLE:
|
||||||
|
setCurrentAnimation("death_hole");
|
||||||
|
break;
|
||||||
|
case DeathCause::GENERIC:
|
||||||
|
default:
|
||||||
|
setCurrentAnimation("death_spinning");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// So del cop letal: reaprofitem `hurt.wav` (ja precarregat per
|
||||||
|
// `readHurtBlock`). El bloc `death:` no té camp de so propi —
|
||||||
|
// semànticament l'impacte mortal és la versió forta de `hurt`.
|
||||||
|
if (!hurt_config.sound_key.empty() && Audio::get() != nullptr) {
|
||||||
|
Audio::get()->playSound(hurt_config.sound_key, Audio::Group::GAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::applyDyingMovement(float delta_time) {
|
||||||
|
// Avancem l'animació; el sprite no es mou (la posició queda
|
||||||
|
// congelada al punt de la mort). `animationIsCompleted()` flanqueja
|
||||||
|
// un cop la roda arriba al darrer frame (loop:false). Llavors
|
||||||
|
// descomptem `respawn_delay_ms` mantenint el sprite al darrer frame
|
||||||
|
// i, en arribar a 0, disparem el respawn.
|
||||||
|
animate(delta_time);
|
||||||
|
if (!death_animation_complete_ && animationIsCompleted()) {
|
||||||
|
death_animation_complete_ = true;
|
||||||
|
respawn_remaining_ms_ = static_cast<float>(death_config.respawn_delay_ms);
|
||||||
|
}
|
||||||
|
if (death_animation_complete_) {
|
||||||
|
respawn_remaining_ms_ -= delta_time * 1000.0F;
|
||||||
|
if (respawn_remaining_ms_ <= 0.0F) {
|
||||||
|
respawnFromDying();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::respawnFromDying() {
|
||||||
|
// Vides: decreix sempre. Pot anar a negatius — no hi ha game over.
|
||||||
|
--lives_remaining_;
|
||||||
|
death_animation_complete_ = false;
|
||||||
|
respawn_remaining_ms_ = 0.0F;
|
||||||
|
// Cinemàtica idèntica a la d'INTRO de sala: el jugador cau des de
|
||||||
|
// dalt fins al passadís d'spawn (`spawnFromAir` reseteja primer i
|
||||||
|
// després posa AIRBORNE amb l'altura derivada de la geometria).
|
||||||
|
// Així el respawn post-mort comparteix tota la sensació visual i
|
||||||
|
// física del primer spawn — incloent el LANDING_STUN final.
|
||||||
|
spawnFromAir(spawn_x_, spawn_y_);
|
||||||
|
}
|
||||||
|
|
||||||
void Player::applyHurtMovement(float delta_time) {
|
void Player::applyHurtMovement(float delta_time) {
|
||||||
// En HURT el jugador es desplaça segons `intended_v*` (escrit a
|
// En HURT el jugador es desplaça segons `intended_v*` (escrit a
|
||||||
// `enterHurt` i decremental a `tick`). Si `tryMove` no avança
|
// `enterHurt` i decremental a `tick`). Si `tryMove` no avança
|
||||||
@@ -1031,20 +1273,54 @@ void Player::render() {
|
|||||||
// vector.
|
// vector.
|
||||||
if (isOutOfPlay()) { return; }
|
if (isOutOfPlay()) { return; }
|
||||||
|
|
||||||
|
// Limbo post-mort: durant `respawn_delay_ms` entre la fi de
|
||||||
|
// `death_spinning` i el `spawnFromAir`, el jugador queda invisible.
|
||||||
|
// Així el cicle complet és: animació rodant → desapareix → ressuscita
|
||||||
|
// caient des de dalt. Sense aquest gat, el sprite quedaria pintat al
|
||||||
|
// darrer frame de la roda mentre passa el delay.
|
||||||
|
if (state_ == State::DYING && death_animation_complete_) { return; }
|
||||||
|
|
||||||
// Mentre el jugador està enlaire, el sprite es desplaça visualment
|
// Mentre el jugador està enlaire, el sprite es desplaça visualment
|
||||||
// cap amunt `jump_z_` píxels. Modifiquem `pos_.y` (la posició entera
|
// `jump_z_` píxels: cap amunt si és positiu (salt), cap avall si és
|
||||||
|
// negatiu (caiguda per `Hole`, que deixa baixar `jump_z_` a negatius
|
||||||
|
// fins a `-HOLE_FALL_DEPTH`). Modifiquem `pos_.y` (la posició entera
|
||||||
// de render) abans de cridar el render base, i la restaurem després
|
// de render) abans de cridar el render base, i la restaurem després
|
||||||
// perquè l'estat seguent del frame ho recalcule des de `y_` (la font
|
// perquè l'estat seguent del frame ho recalcule des de `y_` (la font
|
||||||
// de veritat float). El sòl sota el jugador no es pinta des d'ací:
|
// de veritat float). El sòl sota el jugador no es pinta des d'ací:
|
||||||
// això ho fa el GameScene en una passada de sombres a banda.
|
// això ho fa el GameScene en una passada de sombres a banda. El cas
|
||||||
if (jump_z_ <= 0.0F) {
|
// `jump_z_ == 0` (a terra) surt per l'early-return per evitar tocar
|
||||||
AnimatedSprite::render();
|
// `pos_.y` innecessàriament.
|
||||||
return;
|
// Caiguda per agujero: el sprite es "menja" pel sòl. Fixem un clip
|
||||||
|
// global a la franja [0, hole_clip_bottom_y_] del canvas (el blitter
|
||||||
|
// de `Surface` respecta `Rendering::activeClip()`); a mesura que
|
||||||
|
// `pos_.y - jump_z_` baixa, els píxels del sprite que creuen la
|
||||||
|
// línia deixen de pintar-se. Fem el push/pop al voltant de tot el
|
||||||
|
// render del sprite per cobrir tant el cas `jump_z_==0` (caiguda
|
||||||
|
// just iniciada) com els frames posteriors. `pushClip` ja clamp
|
||||||
|
// a la mida del canvas, així que un `h` negatiu es traduiria en
|
||||||
|
// una franja buida i el sprite quedaria invisible — comportament
|
||||||
|
// correcte si el clip ja ha "tapat" tota la pantalla.
|
||||||
|
const bool CLIP_HOLE = falling_through_hole_;
|
||||||
|
if (CLIP_HOLE) {
|
||||||
|
const auto CANVAS = Rendering::activeCanvas();
|
||||||
|
Rendering::pushClip({
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.w = CANVAS.width,
|
||||||
|
.h = static_cast<int>(hole_clip_bottom_y_),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const float SAVED_Y = pos_.y;
|
|
||||||
pos_.y = SAVED_Y - jump_z_;
|
if (jump_z_ == 0.0F) {
|
||||||
AnimatedSprite::render();
|
AnimatedSprite::render();
|
||||||
pos_.y = SAVED_Y;
|
} else {
|
||||||
|
const float SAVED_Y = pos_.y;
|
||||||
|
pos_.y = SAVED_Y - jump_z_;
|
||||||
|
AnimatedSprite::render();
|
||||||
|
pos_.y = SAVED_Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CLIP_HOLE) { Rendering::popClip(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::renderShadow() const {
|
void Player::renderShadow() const {
|
||||||
@@ -1052,6 +1328,10 @@ void Player::renderShadow() const {
|
|||||||
// que poden cridar `renderShadow` per a tots els jugadors sense
|
// que poden cridar `renderShadow` per a tots els jugadors sense
|
||||||
// discriminar.
|
// discriminar.
|
||||||
if (!isAirborne()) { return; }
|
if (!isAirborne()) { return; }
|
||||||
|
// Caient per un agujero: el cos s'enfonsa sota el sòl, dibuixar una
|
||||||
|
// sombra al ras del sòl trenca la il·lusió. Suprimim la sombra fins
|
||||||
|
// que `enterDying` netegi el flag.
|
||||||
|
if (falling_through_hole_) { return; }
|
||||||
|
|
||||||
// Sombra centrada al sprite, recolzada a la línia dels peus reals
|
// Sombra centrada al sprite, recolzada a la línia dels peus reals
|
||||||
// (ignorant `jump_z_`). L'amplada es redueix proporcional a l'altura
|
// (ignorant `jump_z_`). L'amplada es redueix proporcional a l'altura
|
||||||
@@ -1085,91 +1365,55 @@ void Player::renderShadow() const {
|
|||||||
Screen::get().blendRect(SX, SY, SHADOW_W, SHADOW_H, SHADOW_ARGB, SHADOW_ALPHA);
|
Screen::get().blendRect(SX, SY, SHADOW_W, SHADOW_H, SHADOW_ARGB, SHADOW_ALPHA);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Player::animationName(bool moving) const -> const char* {
|
// Sufix d'animació per cardinal — empat 1:1 amb l'enum `Facing` i el
|
||||||
// En l'aire forcem la variant `jump_*` (per direcció), independent de
|
// sufix declarat als YAMLs de jugador (sam.yaml, morcus.yaml).
|
||||||
// si hi ha velocitat horitzontal: per al jugador, l'estat visual del
|
auto Player::facingSuffix(Facing facing) -> const char* {
|
||||||
// salt és per direcció, no per velocitat.
|
switch (facing) {
|
||||||
if (state_ == State::AIRBORNE) {
|
case Facing::NORTH:
|
||||||
switch (facing_) {
|
return "_north";
|
||||||
case Facing::UP:
|
case Facing::SOUTH:
|
||||||
return "jump_up";
|
return "_south";
|
||||||
case Facing::DOWN:
|
case Facing::EAST:
|
||||||
return "jump_down";
|
return "_east";
|
||||||
case Facing::SIDE:
|
case Facing::WEST:
|
||||||
break;
|
return "_west";
|
||||||
}
|
|
||||||
return "jump";
|
|
||||||
}
|
}
|
||||||
// En LANDING_STUN, `stun_*` (per direcció): mostrem el frame
|
return "_east";
|
||||||
// d'aterratge fins que s'esgota el timer. La direcció ve fixada del
|
}
|
||||||
// moment del despegue (no es canvia mentre estem en l'aire).
|
|
||||||
if (state_ == State::LANDING_STUN) {
|
// Prefix d'animació per estat. ATTACKING té dues sub-fases: durant
|
||||||
switch (facing_) {
|
// `swing_ms` és `attack_*` (animació del braç llançant el látigo); durant
|
||||||
case Facing::UP:
|
// `cooldown_ms` posterior, `recover_*`. A AIRBORNE/LANDING_STUN/HURT, la
|
||||||
return "stun_up";
|
// variant és per direcció, no per velocitat: l'estat bloqueja el `moving`
|
||||||
case Facing::DOWN:
|
// argument i no fa servir walk/stand.
|
||||||
return "stun_down";
|
auto Player::statePrefix(State state, bool moving, bool in_attack_swing) -> const char* {
|
||||||
case Facing::SIDE:
|
switch (state) {
|
||||||
break;
|
case State::AIRBORNE:
|
||||||
}
|
return "jump";
|
||||||
return "stun";
|
case State::LANDING_STUN:
|
||||||
}
|
return "stun";
|
||||||
// En HURT, `hurt_*` (per direcció): el jugador llisca sense
|
case State::HURT:
|
||||||
// control en la direcció triada per `enterHurt`. Mateixa estructura
|
return "hurt";
|
||||||
// que walk per facilitat (ara mateix els frames són clons).
|
case State::ATTACKING:
|
||||||
if (state_ == State::HURT) {
|
return in_attack_swing ? "attack" : "recover";
|
||||||
switch (facing_) {
|
case State::DYING:
|
||||||
case Facing::UP:
|
// L'animació concreta (`death_spinning` o `death_hole`) la
|
||||||
return "hurt_up";
|
// selecciona `enterDying` segons `DeathCause`; aquest retorn
|
||||||
case Facing::DOWN:
|
// només cobreix exhaustivitat del switch i no s'usa al runtime.
|
||||||
return "hurt_down";
|
return "death";
|
||||||
case Facing::SIDE:
|
case State::GROUNDED:
|
||||||
break;
|
case State::EXITED:
|
||||||
}
|
case State::DISABLED:
|
||||||
return "hurt";
|
|
||||||
}
|
|
||||||
// ATTACKING té dues sub-fases: durant `swing_ms` el sprite és
|
|
||||||
// `attack_*` (animació del braç llançant el látigo); durant
|
|
||||||
// `cooldown_ms` posterior canviem a `recover_*` per a frames
|
|
||||||
// pròpis de recuperació. Mentre l'art no estiga dibuixat, els
|
|
||||||
// YAMLs apunten `recover_*` als mateixos frames que `stand_*` —
|
|
||||||
// visualment és idèntic, però la separació deixa l'arquitectura
|
|
||||||
// preparada perquè en afegir frames reals només calga tocar el
|
|
||||||
// YAML, no el codi.
|
|
||||||
if (state_ == State::ATTACKING) {
|
|
||||||
const bool IN_SWING = (attacking_remaining_ms_ > static_cast<float>(whip_config.cooldown_ms));
|
|
||||||
if (IN_SWING) {
|
|
||||||
switch (facing_) {
|
|
||||||
case Facing::UP:
|
|
||||||
return "attack_up";
|
|
||||||
case Facing::DOWN:
|
|
||||||
return "attack_down";
|
|
||||||
case Facing::SIDE:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return "attack";
|
|
||||||
}
|
|
||||||
switch (facing_) {
|
|
||||||
case Facing::UP:
|
|
||||||
return "recover_up";
|
|
||||||
case Facing::DOWN:
|
|
||||||
return "recover_down";
|
|
||||||
case Facing::SIDE:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return "recover";
|
|
||||||
}
|
|
||||||
switch (facing_) {
|
|
||||||
case Facing::UP:
|
|
||||||
return moving ? "walk_up" : "stand_up";
|
|
||||||
case Facing::DOWN:
|
|
||||||
return moving ? "walk_down" : "stand_down";
|
|
||||||
case Facing::SIDE:
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return moving ? "walk" : "stand";
|
return moving ? "walk" : "stand";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Player::animationName(bool moving) const -> std::string {
|
||||||
|
const bool IN_ATTACK_SWING = (attacking_remaining_ms_ > static_cast<float>(whip_config.cooldown_ms));
|
||||||
|
return std::string(statePrefix(state_, moving, IN_ATTACK_SWING)) + facingSuffix(facing_);
|
||||||
|
}
|
||||||
|
|
||||||
auto Player::getCurrentTile() const -> std::pair<int, int> {
|
auto Player::getCurrentTile() const -> std::pair<int, int> {
|
||||||
// El grid del tilemap és de `tile_size` píxels i està 16-alineat
|
// El grid del tilemap és de `tile_size` píxels i està 16-alineat
|
||||||
// respecte a l'origen del tilemap (pot ser negatiu per overscan). El
|
// respecte a l'origen del tilemap (pot ser negatiu per overscan). El
|
||||||
@@ -1222,17 +1466,24 @@ void Player::respawn(float x, float y) {
|
|||||||
being_pushed_ = false;
|
being_pushed_ = false;
|
||||||
jump_z_ = 0.0F;
|
jump_z_ = 0.0F;
|
||||||
jump_vz_ = 0.0F;
|
jump_vz_ = 0.0F;
|
||||||
|
falling_through_hole_ = false;
|
||||||
|
hole_clip_bottom_y_ = 0.0F;
|
||||||
landing_stun_remaining_ms_ = 0.0F;
|
landing_stun_remaining_ms_ = 0.0F;
|
||||||
|
attacking_remaining_ms_ = 0.0F;
|
||||||
turning_remaining_ms_ = 0.0F;
|
turning_remaining_ms_ = 0.0F;
|
||||||
pending_cardinal_ = Cardinal::NONE;
|
pending_cardinal_ = Cardinal::NONE;
|
||||||
intermediate_cardinal_ = Cardinal::NONE;
|
intermediate_cardinal_ = Cardinal::NONE;
|
||||||
last_input_cardinal_ = Cardinal::NONE;
|
last_input_cardinal_ = Cardinal::NONE;
|
||||||
|
// Flags del cicle DYING (que `respawnFromDying` també neteja, però
|
||||||
|
// si algú crida `respawn` directament des d'un altre estat, han de
|
||||||
|
// quedar coherents per al pròxim cicle de mort).
|
||||||
|
death_animation_complete_ = false;
|
||||||
|
respawn_remaining_ms_ = 0.0F;
|
||||||
// Reseteig del facing al d'spawn original (mirant cap a la càmera):
|
// Reseteig del facing al d'spawn original (mirant cap a la càmera):
|
||||||
// sense això, un EXITED que entrava per la porta amb facing UP
|
// sense això, un EXITED que entrava per la porta amb facing NORTH
|
||||||
// re-aparix d'esquena al pròxim cicle. Mateixa lògica que el ctor.
|
// re-aparix d'esquena al pròxim cicle. Mateixa lògica que el ctor.
|
||||||
facing_ = Facing::DOWN;
|
facing_ = Facing::SOUTH;
|
||||||
setFlip(SDL_FLIP_NONE);
|
setCurrentAnimation("stand_south");
|
||||||
setCurrentAnimation("stand_down");
|
|
||||||
setPosX(x);
|
setPosX(x);
|
||||||
setPosY(y);
|
setPosY(y);
|
||||||
}
|
}
|
||||||
@@ -1254,6 +1505,57 @@ void Player::disable() {
|
|||||||
last_input_cardinal_ = Cardinal::NONE;
|
last_input_cardinal_ = Cardinal::NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Player::enterHoleFall(float center_x, float center_y, float floor_y) {
|
||||||
|
// Cap guard de state ací: la responsabilitat la té el caller perquè
|
||||||
|
// hi ha dos callers vàlids amb states distints:
|
||||||
|
// - `Hole::onPlayerContact` (state GROUNDED, el jugador camina i
|
||||||
|
// trepitja el tile del forat).
|
||||||
|
// - `Player::integrateJump` (state AIRBORNE, en aterrar damunt
|
||||||
|
// d'un decal-trigger en lloc d'entrar a LANDING_STUN).
|
||||||
|
// Posar `if (state_ != GROUNDED) return` ací bloquejaria el segon
|
||||||
|
// cas i obligaria a hacks tipus "forçar GROUNDED abans de cridar".
|
||||||
|
|
||||||
|
// Snap del centre de la hit-box a la coordenada central del forat.
|
||||||
|
// Resolem `pos = center - (hit_offset + hit_size/2)` per a cada eix:
|
||||||
|
// així el centre exacte de la caja de contacte queda alineat amb
|
||||||
|
// (center_x, center_y), independentment de la geometria interna del
|
||||||
|
// sprite. `setPosX/Y` és l'API canònica per teleportar el jugador
|
||||||
|
// (la usen `respawn` i `spawnFromAir`).
|
||||||
|
const float HIT_HALF = static_cast<float>(hit_size) * 0.5F;
|
||||||
|
setPosX(center_x - static_cast<float>(hit_x_offset) - HIT_HALF);
|
||||||
|
setPosY(center_y - static_cast<float>(hit_y_offset) - HIT_HALF);
|
||||||
|
|
||||||
|
// Frenem qualsevol inèrcia prèvia: la caiguda ha de ser estàtica al
|
||||||
|
// centre del forat.
|
||||||
|
intended_vx_ = 0.0F;
|
||||||
|
intended_vy_ = 0.0F;
|
||||||
|
input_vx_ = 0.0F;
|
||||||
|
input_vy_ = 0.0F;
|
||||||
|
motion_time_ms_ = 0.0F;
|
||||||
|
being_pushed_ = false;
|
||||||
|
setVelX(0.0F);
|
||||||
|
setVelY(0.0F);
|
||||||
|
|
||||||
|
jump_z_ = 0.0F;
|
||||||
|
jump_vz_ = 0.0F;
|
||||||
|
state_ = State::AIRBORNE;
|
||||||
|
falling_through_hole_ = true;
|
||||||
|
// Línia visual on el clip rect ha de retallar el sprite. Restem
|
||||||
|
// `SPRITE_FLOOR_OFFSET` perquè la base del sprite té píxels
|
||||||
|
// transparents — el retall ha de coincidir amb la silueta visible,
|
||||||
|
// no amb la vora del rect del sprite.
|
||||||
|
hole_clip_bottom_y_ = floor_y - SPRITE_FLOOR_OFFSET;
|
||||||
|
|
||||||
|
// Animació de caiguda: respectem el `facing_` que ja portava el jugador
|
||||||
|
// (no té sentit reorientar-lo cap al sud només perquè caigui en un
|
||||||
|
// forat). Com `state_` ja és AIRBORNE, `animationName(false)` resol el
|
||||||
|
// prefix `jump_` i hi afegeix el sufix cardinal correcte —
|
||||||
|
// `jump_north`, `jump_south`, `jump_east` o `jump_west` segons el
|
||||||
|
// facing actual. Si demà cal una animació de "cara de pànic", només
|
||||||
|
// cal canviar aquí pel `setCurrentAnimation` corresponent.
|
||||||
|
setCurrentAnimation(animationName(false));
|
||||||
|
}
|
||||||
|
|
||||||
void Player::spawnFromAir(float x, float y) {
|
void Player::spawnFromAir(float x, float y) {
|
||||||
// Net total: equival a un `respawn` seguit d'una entrada en l'aire.
|
// Net total: equival a un `respawn` seguit d'una entrada en l'aire.
|
||||||
// Així el flag de gir, l'animació, els flancs del salt i les
|
// Així el flag de gir, l'animació, els flancs del salt i les
|
||||||
@@ -1279,10 +1581,10 @@ void Player::spawnFromAir(float x, float y) {
|
|||||||
intended_vy_ = 0.0F;
|
intended_vy_ = 0.0F;
|
||||||
|
|
||||||
// Mirant cap avall durant la caiguda — `animationName(AIRBORNE)` amb
|
// Mirant cap avall durant la caiguda — `animationName(AIRBORNE)` amb
|
||||||
// facing DOWN ja resol a `jump_down`. `respawn` ja ha posat facing a
|
// facing SOUTH ja resol a `jump_south`. `respawn` ja ha posat facing a
|
||||||
// DOWN; ho deixem explícit ací per claredat de la cinemàtica.
|
// SOUTH; ho deixem explícit ací per claredat de la cinemàtica.
|
||||||
facing_ = Facing::DOWN;
|
facing_ = Facing::SOUTH;
|
||||||
setCurrentAnimation("jump_down");
|
setCurrentAnimation("jump_south");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Player::getBaseBox() const -> SDL_FRect {
|
auto Player::getBaseBox() const -> SDL_FRect {
|
||||||
@@ -1315,24 +1617,13 @@ auto Player::hitBoxFromBase(const SDL_FRect& base) -> SDL_FRect {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::snapToLane(
|
void Player::snapTowardsLane(
|
||||||
float delta_time,
|
float delta_time,
|
||||||
float base,
|
float target_lane,
|
||||||
float pitch,
|
|
||||||
int max_idx,
|
|
||||||
float (Player::*get_pos)() const,
|
float (Player::*get_pos)() const,
|
||||||
void (Player::*set_pos)(float)) {
|
void (Player::*set_pos)(float)) {
|
||||||
// Lanes en pos_axis: base + i·pitch per a i ∈ [0, max_idx]. Correspon a
|
|
||||||
// la caixa de col·lisió ocupant exactament un passadís perpendicular.
|
|
||||||
const auto THRESHOLD = static_cast<float>(collision_size) / 2.0F;
|
const auto THRESHOLD = static_cast<float>(collision_size) / 2.0F;
|
||||||
|
const float DELTA = target_lane - (this->*get_pos)();
|
||||||
const auto IDX = std::clamp(
|
|
||||||
static_cast<int>(std::round(((this->*get_pos)() - base) / pitch)),
|
|
||||||
0,
|
|
||||||
max_idx);
|
|
||||||
const float LANE = base + (static_cast<float>(IDX) * pitch);
|
|
||||||
const float DELTA = LANE - (this->*get_pos)();
|
|
||||||
|
|
||||||
if (std::abs(DELTA) <= 0.0F || std::abs(DELTA) > THRESHOLD) { return; }
|
if (std::abs(DELTA) <= 0.0F || std::abs(DELTA) > THRESHOLD) { return; }
|
||||||
|
|
||||||
const float FULL_STEP = std::min(std::abs(DELTA), SPEED_PX_PER_S * delta_time);
|
const float FULL_STEP = std::min(std::abs(DELTA), SPEED_PX_PER_S * delta_time);
|
||||||
@@ -1417,20 +1708,72 @@ auto Player::tryPushSnapBlockers(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Player::wouldHitOuterWall(float dx, float dy) const -> bool {
|
||||||
|
SDL_FRect probe = getBaseBox();
|
||||||
|
probe.x += dx;
|
||||||
|
probe.y += dy;
|
||||||
|
if (Map::insidePlayfield(probe)) { return false; }
|
||||||
|
// Excepció: corridor de la porta OBERTA quan l'usuari prem cap
|
||||||
|
// amunt (dy < 0). No demanem que la probe ja estigui dins del
|
||||||
|
// corridor — encara no ho està, perquè el snap és justament qui
|
||||||
|
// ha d'aliniar-la. En lloc d'això comprovem si la base box està
|
||||||
|
// prou prop del centre del corridor com per a que el snap
|
||||||
|
// (mateix THRESHOLD = collision_size/2) puga completar-lo.
|
||||||
|
if (Map::door_open && dy < 0.0F && map_.door_corridor_w > 0) {
|
||||||
|
const float CORRIDOR_TARGET = static_cast<float>(map_.door_corridor_x) + (static_cast<float>(map_.door_corridor_w - collision_size) / 2.0F);
|
||||||
|
const auto THRESHOLD = static_cast<float>(collision_size) / 2.0F;
|
||||||
|
if (std::abs(CORRIDOR_TARGET - probe.x) <= THRESHOLD) { return false; }
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Player::snapToHorizontalLane(float delta_time) {
|
void Player::snapToHorizontalLane(float delta_time) {
|
||||||
snapToLane(delta_time,
|
// Si el moviment principal del frame (eix X) està blocat per un mur
|
||||||
static_cast<float>(map_.playfield_y - collision_y_offset),
|
// extern, no fem snap perpendicular: l'usuari està picant contra el
|
||||||
static_cast<float>(map_.pitch_y),
|
// mur i no té sentit arrossegar-lo a un carril. Step probe d'1 px.
|
||||||
map_.grid_rows,
|
if (intended_vx_ != 0.0F && wouldHitOuterWall(std::copysign(1.0F, intended_vx_), 0.0F)) {
|
||||||
&Player::getPosY,
|
return;
|
||||||
&Player::setPosY);
|
}
|
||||||
|
|
||||||
|
const auto BASE_Y = static_cast<float>(map_.playfield_y - collision_y_offset);
|
||||||
|
const auto PITCH_Y = static_cast<float>(map_.pitch_y);
|
||||||
|
const int IDX = std::clamp(
|
||||||
|
static_cast<int>(std::round((getPosY() - BASE_Y) / PITCH_Y)),
|
||||||
|
0,
|
||||||
|
map_.grid_rows);
|
||||||
|
const float LANE_REG = BASE_Y + (static_cast<float>(IDX) * PITCH_Y);
|
||||||
|
snapTowardsLane(delta_time, LANE_REG, &Player::getPosY, &Player::setPosY);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::snapToVerticalLane(float delta_time) {
|
void Player::snapToVerticalLane(float delta_time) {
|
||||||
snapToLane(delta_time,
|
// Mateixa idea que la versió horitzontal: si el moviment principal
|
||||||
static_cast<float>(map_.playfield_x - collision_x_offset),
|
// (eix Y) està blocat per un mur extern, no aliniem en X. La
|
||||||
static_cast<float>(map_.pitch_x),
|
// excepció del corridor de la porta hi aplica naturalment dins
|
||||||
map_.grid_cols,
|
// `wouldHitOuterWall` (només quan és OBERT).
|
||||||
&Player::getPosX,
|
if (intended_vy_ != 0.0F && wouldHitOuterWall(0.0F, std::copysign(1.0F, intended_vy_))) {
|
||||||
&Player::setPosX);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Candidat 1: lane regular del laberint.
|
||||||
|
const auto BASE_X = static_cast<float>(map_.playfield_x - collision_x_offset);
|
||||||
|
const auto PITCH_X = static_cast<float>(map_.pitch_x);
|
||||||
|
const int IDX = std::clamp(
|
||||||
|
static_cast<int>(std::round((getPosX() - BASE_X) / PITCH_X)),
|
||||||
|
0,
|
||||||
|
map_.grid_cols);
|
||||||
|
const float LANE_REG = BASE_X + (static_cast<float>(IDX) * PITCH_X);
|
||||||
|
|
||||||
|
// Candidat 2: corridor de la porta. NOMÉS si la porta està oberta —
|
||||||
|
// si està tancada no té sentit arrossegar el jugador cap a un
|
||||||
|
// forat per on no pot passar.
|
||||||
|
float best_lane = LANE_REG;
|
||||||
|
if (Map::door_open && map_.door_corridor_w > 0) {
|
||||||
|
const float CORRIDOR_CENTER = static_cast<float>(map_.door_corridor_x) + (static_cast<float>(map_.door_corridor_w - collision_size) / 2.0F);
|
||||||
|
const float LANE_DOOR = CORRIDOR_CENTER - static_cast<float>(collision_x_offset);
|
||||||
|
const float DELTA_REG = LANE_REG - getPosX();
|
||||||
|
const float DELTA_DOOR = LANE_DOOR - getPosX();
|
||||||
|
if (std::abs(DELTA_DOOR) < std::abs(DELTA_REG)) { best_lane = LANE_DOOR; }
|
||||||
|
}
|
||||||
|
|
||||||
|
snapTowardsLane(delta_time, best_lane, &Player::getPosX, &Player::setPosX);
|
||||||
}
|
}
|
||||||