diff --git a/source/game/entities/entity.cpp b/source/game/entities/entity.cpp index f235f5a..1e4902e 100644 --- a/source/game/entities/entity.cpp +++ b/source/game/entities/entity.cpp @@ -63,6 +63,16 @@ auto Entity::getCollisionRect() const -> SDL_FRect { }; } +auto Entity::getLightCenter() const -> SDL_FPoint { + // Centre del rect de render. Per a entitats que es desplacen + // visualment sobre la seva posició lògica (p.ex. el Player saltant), + // la subclasse override aquest mètode per restar el desplaçament. + return { + .x = pixel_rect_.x + (pixel_rect_.w * 0.5F), + .y = pixel_rect_.y + (pixel_rect_.h * 0.5F), + }; +} + 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 diff --git a/source/game/entities/entity.hpp b/source/game/entities/entity.hpp index 05c655c..0aa76f5 100644 --- a/source/game/entities/entity.hpp +++ b/source/game/entities/entity.hpp @@ -253,6 +253,19 @@ class Entity { [[nodiscard]] auto getFlip() const -> SDL_FlipMode { return flip_; } void setFlip(SDL_FlipMode flip) { flip_ = flip; } + // Radi de la llum emesa per aquesta entitat (en píxels del canvas). + // 0 = no emet (cas habitual). El bloc opcional `light: { radius: N }` + // del YAML de la plantilla el pobla via `EntityManager::buildEntity`. + [[nodiscard]] auto getLightRadius() const -> int { return light_radius_; } + void setLightRadius(int r) { light_radius_ = r; } + + // Centre de la llum en píxels del canvas. Per defecte és el centre del + // `pixel_rect_` (que és la caixa de render: sprite top-left + mida). + // Virtual perquè entitats amb desplaçament visual sobre la posició + // lògica (p.ex. el Player en l'aire, que rebaixa visualment `pos_.y` + // amb `jump_z_`) puguin retornar el centre que realment es veu. + [[nodiscard]] virtual auto getLightCenter() const -> SDL_FPoint; + protected: std::string name_; TilePos tile_{}; @@ -266,6 +279,7 @@ class Entity { bool is_floor_decal_{false}; bool blocks_movement_{false}; int height_{0}; + int light_radius_{0}; bool dead_{false}; SDL_FlipMode flip_{SDL_FLIP_NONE}; EventBus* bus_{nullptr}; diff --git a/source/game/entities/entity_manager_loadlevel.cpp b/source/game/entities/entity_manager_loadlevel.cpp index 443e6a3..6a1e9ab 100644 --- a/source/game/entities/entity_manager_loadlevel.cpp +++ b/source/game/entities/entity_manager_loadlevel.cpp @@ -155,6 +155,17 @@ namespace { return c["height"].get_value(); } + // Bloc opcional `light: { radius: N }` de la plantilla. Si manca, el + // radi és 0 i l'entitat no entra a la pasada d'il·luminació. La forma + // del gradient (3 anells) viu a data/config/lighting.yaml — no cal + // declarar-la per entitat. + auto readLightRadius(const fkyaml::node& tmpl) -> int { + if (!tmpl.contains("light")) { return 0; } + const auto& l = tmpl["light"]; + if (!l.contains("radius")) { return 0; } + return l["radius"].get_value(); + } + // Bloc `collision: { size: [w, h], base_size: [bw, bh]?, offset: [x, y]? }` // de la plantilla d'entitat. // - Sense `base_size` ni `offset`: la caja queda alineada a la vora @@ -277,6 +288,7 @@ namespace { entity->setCollisionBox(*BOX); } entity->setHeight(readHeight(tmpl)); + entity->setLightRadius(readLightRadius(tmpl)); anchor_out = readAnchor(tmpl); return entity; } diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index 78f093b..3fab317 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -141,6 +141,14 @@ void Player::loadFrom(const std::string& yaml_path) { // `duration_ms` és 0 (o el bloc manca) la mecànica queda // desactivada i els hazards toquen al primer frame, com abans. readImmunityBlock(yaml); + + // Bloc opcional `light: { radius: N }` — radi en píxels del cercle + // de llum emès pel jugador. 0 = no emet. La forma del gradient és + // global (data/config/lighting.yaml), no per jugador. + light_radius = 0; + if (yaml.contains("light") && yaml["light"].contains("radius")) { + light_radius = yaml["light"]["radius"].get_value(); + } } void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) { @@ -1462,6 +1470,16 @@ void Player::render() { if (CLIP_HOLE) { Rendering::popClip(); } } +auto Player::getLightCenter() const -> SDL_FPoint { + // Sprite top-left = (pos_.x, pos_.y). El render desplaça verticalment + // `jump_z_` píxels durant el salt; aplicar el mateix offset aquí fa + // que la llum "salti" amb el jugador en lloc de quedar-se al sòl. + return { + .x = getPosX() + (static_cast(sprite_w) * 0.5F), + .y = getPosY() - jump_z_ + (static_cast(sprite_h) * 0.5F), + }; +} + void Player::renderShadow() const { // Sense salt actiu, no hi ha sombra: això simplifica els callsites, // que poden cridar `renderShadow` per a tots els jugadors sense diff --git a/source/game/entities/player.hpp b/source/game/entities/player.hpp index f85d4d3..b311c60 100644 --- a/source/game/entities/player.hpp +++ b/source/game/entities/player.hpp @@ -124,6 +124,12 @@ class Player : public AnimatedSprite { // creua mentre `jump_z_` és superior a aquest valor, no col·lisionem. inline static int body_height = 0; + // Radi de la llum emesa pel jugador (en píxels del canvas), llegit + // del bloc opcional `light: { radius: N }` del YAML. 0 = no emet. + // Es declara estàtic com la resta de configs del jugador (immunity, + // jump, ...): tots els jugadors del mateix YAML comparteixen el valor. + inline static int light_radius = 0; + // Ha de cridar-se abans d'instanciar Player; GameScene ho fa al ctor. static void loadFrom(const std::string& yaml_path); @@ -132,6 +138,13 @@ class Player : public AnimatedSprite { // marcador i `Player::resetForJoin` per a re-entrades mid-partida. [[nodiscard]] static auto getInitialLives() -> int { return death_config.initial_lives; } + // Emissió de llum (mateix paper que `Entity::getLightRadius` per a + // les entitats). El centre puja amb el salt: durant `jump_z_>0` el + // sprite es dibuixa amb `pos_.y - jump_z_`, i la llum ha de seguir-lo + // per a que el cercle "salti" amb el jugador, no es quedi al sòl. + [[nodiscard]] static auto getLightRadius() -> int { return light_radius; } + [[nodiscard]] auto getLightCenter() const -> SDL_FPoint; + Player(const std::string& anim_key, int id, float x, float y, const Controls& controls, const Map::Info& map_info, int paint_tile_index = -1); // Update en una sola crida: manté l'API anterior per als tests i