Files
aee-arcade/source/game/map/map.hpp
T

219 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <SDL3/SDL.h>
#include <array>
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <utility>
#include "core/rendering/lighting.hpp"
// Mapa d'una piràmide: sala rectangular amb marcador superior, muros pels
// quatre costats i graella de sarcòfags envoltada per un passadís a cada
// cantó. La geometria (graella, pitch, amplada de murs) es carrega des del
// bloc `geometry:` del YAML de la piràmide — així cada sala pot tenir la
// seua forma sense recompilar.
//
// `Map` és un namespace (no una classe) perquè no té estat d'instància:
// els membres viuen com a globals del namespace i els callers hi accedeixen
// amb `Map::grid_cols`, `Map::collides(...)`, etc. El canvi respecte a
// l'anterior `class Map { static ... };` és purament sintàctic.
//
// `MapInfo` (POD trivialment copiable) empaqueta la geometria + dos
// punters a funció de col·lisió per injectar-la a entitats que han
// d'evitar conèixer el namespace `Map::` directament. Construïda via
// `Map::makeInfo()` després de `loadFrom`. Avui només l'usa el Player
// (COUP-02 a docs/audit-game.md); momies, roques i `loadFromLevel`
// continuen llegint `Map::*` directament — la nota original justifica
// que mentre hi hagi un Map global actiu, l'acoblament és acceptable.
namespace Map {
// Geometria de la graella (carregada de pyramid.yaml > geometry).
inline int grid_cols = 0;
inline int grid_rows = 0;
inline int sarc_w = 0;
inline int sarc_h = 0;
inline int pitch_x = 0;
inline int pitch_y = 0;
// Metadades del tilemap (carregades de l'arrel del YAML: `tile_size:` i
// `origin: [x, y]`). Són necessàries per a consumidors externs com el
// sistema d'entitats, que tradueix coordenades píxel ↔ tile amb
// tile_col = floor((px - tilemap_origin_x) / tile_size)
// El grid és 16-alineat respecte a l'origen del tilemap (no del canvas);
// origen habitual a pyramid.yaml és [-8, 0] (overscan horitzontal CPC).
inline int tile_size = 0;
inline int tilemap_origin_x = 0;
inline int tilemap_origin_y = 0;
// Cromat de la sala: marcador (HUD), mur posterior (dalt) i murs
// laterals. El mur frontal es deriva del passadís exterior inferior.
inline int hud_h = 0;
inline int back_wall_h = 0;
inline int side_wall_w = 0;
// Nivell d'il·luminació base de la sala. 1.0 = totalment il·luminada
// (com hui, sense efecte visual); 0.0 = completament negra fora del
// radi d'un emissor. La clau `light_level:` al YAML de la sala el
// sobreescriu — si manca, queda al default 1.0 i la pasada
// d'il·luminació del frame es salta com a no-op. La forma nova és
// declarar-ho dins del bloc `lighting:` (vegeu més avall); `light_level:`
// al root es manté per compatibilitat enrere.
inline float light_level = 1.0F;
// Mode de composició de la pasada d'il·luminació (subtractive per
// defecte; additive si la sala declara `lighting.mode: additive`).
// Veure el comentari de `core/rendering/lighting.hpp` per a la
// semàntica de cada mode. En subtractive, els colors dels emissors
// s'ignoren — el camí és el de sempre.
inline Lighting::Mode lighting_mode = Lighting::Mode::SUBTRACTIVE;
// Override d'emissor declarat al bloc opcional `lighting.emitters.<tipus>`
// de la sala. Tres formes acceptades al YAML:
// <tipus>: false → desactiva la llum per a tot el tipus
// <tipus>: true → activa amb els defaults del tipus
// <tipus>: { radius: N, flicker: B } → activa i sobreescriu camps
// Els camps absents (`std::nullopt`) cauen al default del tipus
// d'entitat (radi/flicker del seu YAML). Els tipus no presents al
// mapping conserven el seu comportament per defecte (com avui).
//
// Conversió de clau YAML → tipus d'entitat: el YAML usa plural
// ("players", "mummies", "torches") perquè es llegeix natural per a
// l'autor de la sala; `loadFrom` normalitza a la clau singular
// ("player", "mummy", "torch") que coincideix amb el camp `type:` de
// cada plantilla d'entitat. Tipus no reconeguts s'accepten "tal qual"
// (singular pass-through) per si en el futur s'hi afegeixen emissors
// nous sense tocar el mapatge.
struct RoomEmitterOverride {
bool enabled{true};
std::optional<int> radius;
std::optional<bool> flicker;
// Color emès en mode additive. `nullopt` → conservar el color
// del tipus (default blanc 255,255,255 si el tipus no en declara).
// La forma YAML és `color: [r, g, b]` (bytes 0..255).
std::optional<std::array<std::uint8_t, 3>> color;
// Offset del centre del cercle de llum respecte el centre del
// sprite (píxels enters; Y creix avall). `nullopt` → conservar
// l'offset del tipus (default [0, 0]). Forma YAML: `offset: [x, y]`.
std::optional<std::array<int, 2>> offset;
};
// Consulta l'override de sala per a un tipus d'entitat (clau singular,
// p.ex. "player" / "mummy" / "torch"). Retorna `nullptr` si la sala no
// declara res per aquest tipus — el caller usa els defaults del tipus
// sense modificació. El punter retornat és vàlid fins a la propera
// crida a `Map::loadFrom` (cada càrrega de nivell substitueix el
// mapping intern).
[[nodiscard]] auto getRoomEmitterOverride(const std::string& type) -> const RoomEmitterOverride*;
// Derivats de la geometria base: el pas entre sarcòfags ha de cabre
// exactament el jugador, i el mateix passadís s'usa a l'exterior.
inline int corridor_w = 0;
inline int corridor_h = 0;
inline int outer_corridor = 0;
inline int board_w = 0;
inline int board_h = 0;
inline int origin_x = 0;
inline int origin_y = 0;
inline int playfield_x = 0;
inline int playfield_y = 0;
inline int playfield_w = 0;
inline int playfield_h = 0;
// Porta de sortida del nivell. La porta sempre viu al mur trasser
// (`wall_north`); només la columna varia per nivell. `door_col` és la
// columna central transitable: el sprite 48×32 es dibuixa centrat
// sobre aquesta columna i els 16 px centrals (just `door_col`) són
// l'únic forat del mur trasser per on el jugador surt. Derivat de
// `geometry.door_col` al YAML.
inline int door_col = 0;
inline int door_corridor_x = 0;
inline int door_corridor_y = 0;
inline int door_corridor_w = 0;
inline int door_corridor_h = 0;
// Estat del passadís: si la porta està oberta, el `door_corridor` no
// bloqueja el moviment del jugador (vegeu `Map::collides`). Qui muta
// aquest flag és l'entitat `Door` quan transita CLOSED → OPEN; el
// reset el porta `Map::loadFrom` (cada càrrega de nivell el torna a
// posar a false).
inline bool door_open = false;
// Carrega el bloc `geometry:` del YAML i recalcula tots els derivats.
// Ha de cridar-se abans d'instanciar `Player` o consultar `collides()`:
// GameScene ho fa al ctor.
void loadFrom(const std::string& yaml_path);
// Rectangle ocupat pel sarcòfag a la posició (col, row) de la graella.
[[nodiscard]] auto getSarcRect(int col, int row) -> SDL_FRect;
// Mapa de colisions (separat del mapa de dibuix): retorna true si el
// rectangle `base_box` (la base del jugador, per exemple) s'encavalca
// amb cap sarcòfag, el passadís de la porta tancada, o cap entitat
// sòlida genèrica registrada via `setEntityCollider`.
[[nodiscard]] auto collides(const SDL_FRect& base_box) -> bool;
// Registre d'un predicat extern que `collides` consulta com a sobre-
// capa: l'usa `EntityManager` per a entitats amb `blocksMovement()`
// (debris, columnes...) que no estan a la graella fixa de sarcòfags.
// Passa `nullptr` per netejar. Reset implícit a `loadFrom` (cada
// càrrega de nivell sol portar un manager nou).
void setEntityCollider(std::function<bool(const SDL_FRect&)> fn);
// true si la caixa `box` queda completament dins del playfield (zona
// interior, sense tocar els murs exteriors). El Player ja té clamp
// propi que aplica el mateix constrenyiment; això serveix a les
// entitats dinàmiques per validar posicions candidates del seu step.
[[nodiscard]] auto insidePlayfield(const SDL_FRect& box) -> bool;
// true si el tile (col, row) — coordenades del tilemap, amb origen
// `tilemap_origin_*` — és "sòl transitable" i per tant pintable: cau
// completament dins del playfield i no s'encavalca cap sarcòfag. Ho
// usa el `PaintLayer` per filtrar quines cel·les accepten pintat.
[[nodiscard]] auto isPaintable(int col, int row) -> bool;
// Converteix una coordenada píxel del canvas en (col, row) del
// tilemap. Assumeix `tile_size > 0`; si la coordenada cau fora del
// playfield, retorna el tile encara que sigui negatiu o estigui fora
// de rang — el caller valida si li cal. El càlcul és el típic
// `floor((p - origin) / tile_size)` que apareixia en diversos llocs;
// factoritzat aquí per coherència. Consumidors que tinguen un
// snapshot de `Map::Info` (p.ex. el Player, decoupling COUP-02) no
// han d'usar aquesta funció — han de seguir treballant amb el
// snapshot per no re-acoblar-se al namespace global.
[[nodiscard]] auto pixelToTile(float px, float py) -> std::pair<int, int>;
// Snapshot plà de la geometria del mapa + punters a `collides` /
// `insidePlayfield`. Pensat per ser passat per valor (POD petit,
// trivialment copiable). Un cop construïda, llegeix els valors
// congelats — si `Map::loadFrom` es torna a cridar amb un mapa
// diferent, cal demanar un MapInfo nou.
struct Info {
int playfield_x{0};
int playfield_y{0};
int playfield_w{0};
int playfield_h{0};
int pitch_x{0};
int pitch_y{0};
int grid_cols{0};
int grid_rows{0};
int tile_size{0};
int tilemap_origin_x{0};
int tilemap_origin_y{0};
// Passadís de sortida (forat del mur trasser a la columna de la
// porta). El Player l'usa des del clamp per permetre pujar al
// strip del mur quan la base box queda dins del rang X.
int door_corridor_x{0};
int door_corridor_y{0};
int door_corridor_w{0};
int door_corridor_h{0};
bool (*collides)(const SDL_FRect&){nullptr};
bool (*inside_playfield)(const SDL_FRect&){nullptr};
};
[[nodiscard]] auto makeInfo() -> Info;
} // namespace Map