#include "game/entities/concrete/sarcophagus.hpp" #include #include #include "game/entities/entity_factory.hpp" #include "game/entities/event_bus.hpp" namespace { 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}; } // Llig `tile_size: [w, h]` del YAML. Fallback [1,1] si no existeix. void readTileSize(const fkyaml::node& yaml, int& w, int& h) { w = 1; h = 1; if (!yaml.contains("tile_size")) { return; } const auto& ts = yaml["tile_size"]; if (ts.is_sequence() && ts.size() == 2) { w = ts[0].get_value(); h = ts[1].get_value(); } } } // namespace Sarcophagus::Sarcophagus(const fkyaml::node& yaml, int anchor_col, int anchor_row, const BlockedPredicate& is_blocked) : Sarcophagus(yaml) { place(anchor_col, anchor_row, is_blocked); } Sarcophagus::Sarcophagus(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("sarcophagus"); collides_ = true; interactable_ = true; readTileSize(yaml, tile_size_w_, tile_size_h_); anim_ = anim_closed_; } void Sarcophagus::place(int anchor_col, int anchor_row, const BlockedPredicate& is_blocked) { anchor_col_ = anchor_col; anchor_row_ = anchor_row; tile_.col = anchor_col; tile_.row = anchor_row; computePerimeter(is_blocked); visited_.clear(); } void Sarcophagus::computePerimeter(const BlockedPredicate& is_blocked) { perimeter_tiles_.clear(); // El perímetre ortogonal d'un rectangle (w,h) de tiles són les files // de tiles immediatament N, S, E, O. No s'hi inclouen les cantonades // diagonals (tile NO, NE, SO, SE), perquè el jugador fa passes pels // quatre costats i no pot "tallar" per una diagonal: això garanteix // que el recorregut completa només si passa per cada costat. // // - Fila Nord: (col, row-1) per col ∈ [col0, col0+w-1] // - Fila Sud: (col, row+h) per col ∈ [col0, col0+w-1] // - Columna O: (col0-1, row) per row ∈ [row0, row0+h-1] // - Columna E: (col0+w, row) per row ∈ [row0, row0+h-1] auto maybe_insert = [&](int col, int row) { if (is_blocked && is_blocked(col, row)) { return; } perimeter_tiles_.insert({col, row}); }; // Nord i Sud for (int dc = 0; dc < tile_size_w_; ++dc) { maybe_insert(anchor_col_ + dc, anchor_row_ - 1); maybe_insert(anchor_col_ + dc, anchor_row_ + tile_size_h_); } // Oest i Est for (int dr = 0; dr < tile_size_h_; ++dr) { maybe_insert(anchor_col_ - 1, anchor_row_ + dr); maybe_insert(anchor_col_ + tile_size_w_, anchor_row_ + dr); } } void Sarcophagus::update(float delta_time) { Entity::update(delta_time); if (state_ == State::OPENING && anim_.has_value() && anim_->completed()) { state_ = State::OPEN; anim_ = anim_open_; if (anim_.has_value()) { anim_->reset(); } if (!opened_event_emitted_ && bus_ != nullptr) { bus_->publish("sarcophagus_opened", static_cast(this)); opened_event_emitted_ = true; } } } void Sarcophagus::notifyTileEntered(int col, int row) { if (state_ != State::CLOSED) { return; } const TileKey KEY{.col = col, .row = row}; if (perimeter_tiles_.find(KEY) == perimeter_tiles_.end()) { return; } visited_.insert(KEY); if (visited_.size() >= perimeter_tiles_.size()) { state_ = State::OPENING; anim_ = anim_opening_; if (anim_.has_value()) { anim_->reset(); } } } REGISTER_ENTITY("sarcophagus", Sarcophagus)