#pragma once #include #include #include #include #include "external/fkyaml_node.hpp" #include "game/entities/animation_component.hpp" #include "game/entities/interactable.hpp" // Sarcòfag: rectangle de tiles que el jugador ha de rodar completament pel // perímetre per "obrir". Tres estats (com la porta): // CLOSED → inicial; `visited_` s'acumula amb les notificacions. // OPENING → `visited_ == perimeter_tiles_`; reprodueix animació d'obertura. // OPEN → emitit l'event `sarcophagus_opened`. // // El càlcul de `perimeter_tiles_` es fa al ctor a partir de la posició del // sarcòfag a la graella del Map (`anchor_col`, `anchor_row`) i de les // dimensions en tiles (`tile_size_w_`, `tile_size_h_`, llegits del YAML // plantilla). Es recorren els tiles adjacents per N/S/E/O; els que // cauen dins d'un altre sarcòfag es descarten (predicat opcional). Si no // s'aporta el predicat, s'inclouen tots. class Sarcophagus : public Interactable { public: enum class State : std::uint8_t { CLOSED, OPENING, OPEN }; // `is_blocked` rep un (col,row) i retorna true si el tile està ocupat // per un altre sarcòfag o és fora del playfield (és a dir, NO és un // tile de corredor vàlid). L'usa `EntityManager` passant-li // `Map::collides` traduït a tiles. Si és nullptr, cap tile es descarta // i el perímetre es computa purament geomètric (útil als tests). using BlockedPredicate = std::function; Sarcophagus(const fkyaml::node& yaml, int anchor_col, int anchor_row, const BlockedPredicate& is_blocked); explicit Sarcophagus(const fkyaml::node& yaml); // variant sense ancoratge (usat pel factory) // Configura ancoratge i (re)calcula perímetre. Útil quan el factory // crea l'instància abans de conèixer la posició; el `EntityLoader` // invoca aquesta funció amb les coordenades del spawn. void place(int anchor_col, int anchor_row, const BlockedPredicate& is_blocked); void update(float delta_time) override; void notifyTilePainted(int col, int row) override; void onRemoved() override; [[nodiscard]] auto getState() const -> State { return state_; } [[nodiscard]] auto getPerimeterSize() const -> std::size_t { return perimeter_tiles_.size(); } [[nodiscard]] auto getVisitedCount() const -> std::size_t { return visited_.size(); } // Dimensions del rectangle en tiles (p.ex. 3×1 per al sarcòfag // estàndard). Exposat per al smoke test i per al render futur. [[nodiscard]] auto getTileWidth() const -> int { return tile_size_w_; } [[nodiscard]] auto getTileHeight() const -> int { return tile_size_h_; } private: struct TileKey { int col; int row; auto operator==(const TileKey& o) const -> bool { return col == o.col && row == o.row; } }; struct TileKeyHash { auto operator()(const TileKey& k) const -> std::size_t { // Combinació simple; els valors són petits (<100) i el set no és gran. return (static_cast(k.col) * 131U) ^ static_cast(k.row); } }; void computePerimeter(const BlockedPredicate& is_blocked); int tile_size_w_{1}; int tile_size_h_{1}; int anchor_col_{0}; int anchor_row_{0}; std::unordered_set perimeter_tiles_; std::unordered_set visited_; State state_{State::CLOSED}; AnimationComponent anim_closed_; AnimationComponent anim_opening_; AnimationComponent anim_open_; // Clau (basename del fitxer) al Resource::Cache del so que es dispara // a la transició CLOSED → OPENING. Buit ⇒ no sona res. std::string opening_sound_key_; // Variació uniforme del ratio de reproducció al voltant de 1.0. Cada // obertura sorteja [1 - spread, 1 + spread]. 0 ⇒ ratio fix a 1.0. float opening_pitch_spread_{0.0F}; bool opened_event_emitted_{false}; };