feat(lighting): flicker proporcional al radi amb overrides per emissor

This commit is contained in:
JailDesigner committed 2026-05-13 19:38:34 +02:00
1 parent 3a42c7958a
commit 2272d3a37a
13 files changed
+298 -150

No files matched your search

+11
View File
@@ -7,6 +7,7 @@
#include <optional>
#include <string>
#include "core/rendering/lighting.hpp"
#include "game/entities/animation_component.hpp"
// Forward-declaracions: mantenim aquest header lleuger. `Player` viu a
@@ -296,6 +297,15 @@ class Entity {
light_offset_y_ = y;
}
// Paràmetres del flicker resolts per a aquest emissor (combinació
// del global de `data/config/lighting.yaml` + overrides del tipus,
// sala i spawn). Només té efecte si `light_flicker_` és true. El
// construct path li passa una còpia ja resolta — el raster (lighting.cpp)
// només la llegeix. Es publica per camps perquè el bloc YAML
// `flicker_params:` permet sobreescriure qualsevol subconjunt.
[[nodiscard]] auto getFlickerParams() const -> const Lighting::FlickerConfig& { return flicker_params_; }
void setFlickerParams(const Lighting::FlickerConfig& fp) { flicker_params_ = fp; }
// Identificador estable per al flicker. Cada Entity rep un valor
// únic a la construcció (contador estàtic) i el conserva fins a la
// mort — així el patró del flicker no "deriva" quan l'entitat es
@@ -339,6 +349,7 @@ class Entity {
std::uint8_t light_color_b_{255};
int light_offset_x_{0};
int light_offset_y_{0};
Lighting::FlickerConfig flicker_params_{};
bool dead_{false};
SDL_FlipMode flip_{SDL_FLIP_NONE};
EventBus* bus_{nullptr};
@@ -174,6 +174,9 @@ namespace {
std::uint8_t color_b{255};
int offset_x{0};
int offset_y{0};
// Inicialitzat al global a `readLight`; overrides parcials del
// tipus/sala/spawn modifiquen camps concrets sense tocar la resta.
Lighting::FlickerConfig flicker_params{};
};
// Helper compartit entre les tres capes (tipus, sala via Map, spawn)
@@ -196,14 +199,28 @@ namespace {
return true;
}
// Aplica els camps presents al bloc `flicker_params: { ... }` sobre
// una `FlickerConfig` ja inicialitzada (típicament amb el global o
// amb el resultat d'una capa anterior). Camps absents es respecten
// — per a poder sobreescriure'n només un.
void applyFlickerOverrides(const fkyaml::node& fp, Lighting::FlickerConfig& out) {
if (fp.contains("wave_amplitude_ratio")) { out.wave_amplitude_ratio = fp["wave_amplitude_ratio"].get_value<float>(); }
if (fp.contains("wave_frequency_hz")) { out.wave_frequency_hz = fp["wave_frequency_hz"].get_value<float>(); }
if (fp.contains("noise_amplitude_ratio")) { out.noise_amplitude_ratio = fp["noise_amplitude_ratio"].get_value<float>(); }
if (fp.contains("noise_frequency_hz")) { out.noise_frequency_hz = fp["noise_frequency_hz"].get_value<float>(); }
}
auto readLight(const fkyaml::node& tmpl) -> LightTemplate {
if (!tmpl.contains("light")) { return {}; }
const auto& l = tmpl["light"];
LightTemplate out;
// Punt de partida del flicker: el global de lighting.yaml.
out.flicker_params = Lighting::flickerConfig();
if (!tmpl.contains("light")) { return out; }
const auto& l = tmpl["light"];
if (l.contains("radius")) { out.radius = l["radius"].get_value<int>(); }
if (l.contains("flicker")) { out.flicker = l["flicker"].get_value<bool>(); }
if (l.contains("color")) { readColorTriplet(l["color"], out.color_r, out.color_g, out.color_b); }
if (l.contains("offset")) { readOffsetPair(l["offset"], out.offset_x, out.offset_y); }
if (l.contains("flicker_params")) { applyFlickerOverrides(l["flicker_params"], out.flicker_params); }
return out;
}
@@ -215,10 +232,14 @@ namespace {
// Forma 3: la sala declara `<tipus>: true` o `<tipus>: { ... }` → els
// camps presents a l'override sobreescriuen el default; els absents
// (`std::nullopt`) cauen al default del tipus.
auto resolveLightWithRoom(const std::string& type, LightTemplate type_default) -> LightTemplate {
auto resolveLightWithRoom(const std::string& type, const LightTemplate& type_default) -> LightTemplate {
const auto* room = Map::getRoomEmitterOverride(type);
if (room == nullptr) { return type_default; }
if (!room->enabled) { return LightTemplate{}; }
if (!room->enabled) {
LightTemplate empty;
empty.flicker_params = Lighting::flickerConfig();
return empty;
}
LightTemplate out = type_default;
if (room->radius.has_value()) { out.radius = *room->radius; }
if (room->flicker.has_value()) { out.flicker = *room->flicker; }
@@ -231,6 +252,10 @@ namespace {
out.offset_x = (*room->offset)[0];
out.offset_y = (*room->offset)[1];
}
if (room->flicker_wave_amplitude_ratio.has_value()) { out.flicker_params.wave_amplitude_ratio = *room->flicker_wave_amplitude_ratio; }
if (room->flicker_wave_frequency_hz.has_value()) { out.flicker_params.wave_frequency_hz = *room->flicker_wave_frequency_hz; }
if (room->flicker_noise_amplitude_ratio.has_value()) { out.flicker_params.noise_amplitude_ratio = *room->flicker_noise_amplitude_ratio; }
if (room->flicker_noise_frequency_hz.has_value()) { out.flicker_params.noise_frequency_hz = *room->flicker_noise_frequency_hz; }
return out;
}
@@ -266,6 +291,11 @@ namespace {
int y{};
if (readOffsetPair(l["offset"], x, y)) { entity.setLightOffset(x, y); }
}
if (l.contains("flicker_params")) {
Lighting::FlickerConfig fp = entity.getFlickerParams();
applyFlickerOverrides(l["flicker_params"], fp);
entity.setFlickerParams(fp);
}
}
// Bloc `collision: { size: [w, h], base_size: [bw, bh]?, offset: [x, y]? }`
@@ -399,6 +429,7 @@ namespace {
entity->setLightFlicker(LIGHT.flicker);
entity->setLightColor(LIGHT.color_r, LIGHT.color_g, LIGHT.color_b);
entity->setLightOffset(LIGHT.offset_x, LIGHT.offset_y);
entity->setFlickerParams(LIGHT.flicker_params);
anchor_out = readAnchor(tmpl);
return entity;
}
+12
View File
@@ -155,6 +155,9 @@ void Player::readLightBlock(const fkyaml::node& yaml) {
light_color_b = 255;
light_offset_x = 0;
light_offset_y = 0;
// Punt de partida del flicker: el global de lighting.yaml. El bloc
// opcional `flicker_params:` del personatge sobreescriu camps concrets.
flicker_params = Lighting::flickerConfig();
if (!yaml.contains("light")) { return; }
const auto& l = yaml["light"];
if (l.contains("radius")) { light_radius = l["radius"].get_value<int>(); }
@@ -178,6 +181,15 @@ void Player::readLightBlock(const fkyaml::node& yaml) {
light_offset_y = o[1].get_value<int>();
}
}
// `flicker_params: { ... }` opcional. Qualsevol dels 4 camps absent
// conserva el valor heretat del global (vegeu inicialització a sobre).
if (l.contains("flicker_params")) {
const auto& fp = l["flicker_params"];
if (fp.contains("wave_amplitude_ratio")) { flicker_params.wave_amplitude_ratio = fp["wave_amplitude_ratio"].get_value<float>(); }
if (fp.contains("wave_frequency_hz")) { flicker_params.wave_frequency_hz = fp["wave_frequency_hz"].get_value<float>(); }
if (fp.contains("noise_amplitude_ratio")) { flicker_params.noise_amplitude_ratio = fp["noise_amplitude_ratio"].get_value<float>(); }
if (fp.contains("noise_frequency_hz")) { flicker_params.noise_frequency_hz = fp["noise_frequency_hz"].get_value<float>(); }
}
}
void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) {
+6
View File
@@ -7,6 +7,7 @@
#include <utility>
#include <vector>
#include "core/rendering/lighting.hpp"
#include "core/rendering/sprite/animated_sprite.hpp"
#include "core/rendering/surface.hpp"
#include "external/fkyaml_node.hpp"
@@ -140,6 +141,10 @@ class Player : public AnimatedSprite {
inline static std::uint8_t light_color_b = 255;
inline static int light_offset_x = 0;
inline static int light_offset_y = 0;
// Paràmetres del flicker resolts per al jugador (combinació del
// global + override del YAML del personatge + override de sala).
// Només té efecte si `light_flicker` és true.
inline static Lighting::FlickerConfig flicker_params{};
// Ha de cridar-se abans d'instanciar Player; GameScene ho fa al ctor.
static void loadFrom(const std::string& yaml_path);
@@ -160,6 +165,7 @@ class Player : public AnimatedSprite {
[[nodiscard]] static auto getLightColorB() -> std::uint8_t { return light_color_b; }
[[nodiscard]] static auto getLightOffsetX() -> int { return light_offset_x; }
[[nodiscard]] static auto getLightOffsetY() -> int { return light_offset_y; }
[[nodiscard]] static auto getFlickerParams() -> const Lighting::FlickerConfig& { return flicker_params; }
// Seed estable per al flicker: reusem `id_` (0 o 1 en multijugador
// local) amb un offset gran per evitar col·lisions amb el contador
// d'Entity (que arrenca des de 1). Així P1 i P2 oscil·len en fases