#include "game/entities/concrete/door.hpp" #include #include #include "game/entities/entity_factory.hpp" #include "game/entities/event_bus.hpp" namespace { // Carrega una animació d'un bloc concret (closed/opening/open) amb // fallbacks tolerants. Si la clau no existeix, retorna una animació // d'un sol frame loopant: no fa estat invàlid, simplement no hi ha res // per reproduir. auto buildAnimation(const fkyaml::node& yaml, const std::string& key) -> AnimationComponent { std::vector frames = {0}; float fps = 1.0F; bool loop = true; if (yaml.contains("animations") && yaml["animations"].contains(key)) { const auto& anim = yaml["animations"][key]; if (anim.contains("frames")) { frames.clear(); for (const auto& f : anim["frames"]) { // cppcheck-suppress useStlAlgorithm frames.push_back(f.get_value()); } if (frames.empty()) { frames.push_back(0); } } if (anim.contains("fps")) { fps = anim["fps"].get_value(); } if (anim.contains("loop")) { loop = anim["loop"].get_value(); } } return {frames, fps, loop}; } } // namespace Door::Door(const fkyaml::node& yaml) : anim_closed_(buildAnimation(yaml, "closed")), anim_opening_(buildAnimation(yaml, "opening")), anim_open_(buildAnimation(yaml, "open")) { name_ = yaml.contains("name") ? yaml["name"].get_value() : std::string("door"); collides_ = true; interactable_ = true; anim_ = anim_closed_; } void Door::setAnimation(const AnimationComponent& anim) { anim_ = anim; if (anim_.has_value()) { anim_->reset(); } } void Door::update(float delta_time) { Entity::update(delta_time); // Transició OPENING → OPEN: amb l'animació `opening` configurada com a // loop=false, `completed()` passa a true quan s'ha recorregut l'últim // frame. Per a animacions d'un sol frame, el component la considera // completada immediatament (1 frame = 0 passos), així que la transició // ocorre al pròxim tick. if (state_ == State::OPENING && anim_.has_value() && anim_->completed()) { state_ = State::OPEN; setAnimation(anim_open_); } } void Door::onPlayerContact(Player* /*player*/) { if (state_ != State::OPEN || level_complete_emitted_) { return; } if (bus_ != nullptr) { bus_->publish("level_complete"); } level_complete_emitted_ = true; } void Door::open() { if (state_ != State::CLOSED) { return; } state_ = State::OPENING; setAnimation(anim_opening_); // Edge case: si `opening` té ≤1 frame i és loop=true, mai es // completarà. Per força, forcem loop=false del frame únic aquí. Els // placeholders defineixen `loop: false` explícitament al YAML, així // que aquest fallback només actua si algú dissenya malament. if (anim_.has_value() && anim_->frameCount() <= 1 && anim_->loops()) { anim_ = AnimationComponent({0}, 6.0F, false); } } REGISTER_ENTITY("door", Door)