#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). S'inclouen els tiles adjacents per N/S/E/O i també les // quatre cantonades diagonals (NO/NE/SO/SE), perquè el jugador les // trepitja en doblar les cantonades del sarcòfag i, sense comptar-les, // el cerco s'obriria abans d'haver-se tancat visualment. Els tiles 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, int player_id) 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_; } // Recompensa que es donarà al jugador que pinta el tile que tanca el // perímetre (el `last_closer_player_id_`). Llegits del YAML del sarc; // 0/0 si el bloc `reward:` no existeix. El GameScene escolta // `sarcophagus_opened` i aplica aquests valors al Player corresponent. [[nodiscard]] auto getPointsReward() const -> int { return points_reward_; } [[nodiscard]] auto getTreasuresReward() const -> int { return treasures_reward_; } // Jugador atribuit (qui pinta el tile que dispara CLOSED→OPENING). -1 // si el sarcòfag s'ha tancat per pintades sense atribució (p.ex. el // broadcast inicial de càrrega) — cas que el GameScene ignora. [[nodiscard]] auto getLastCloserPlayerId() const -> int { return last_closer_player_id_; } 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}; // Recompenses llegides del YAML (bloc `reward:`). 0/0 si el bloc no // hi és — el GameScene salta el pas de repartiment quan són 0. int points_reward_{0}; int treasures_reward_{0}; // Jugador que ha pintat el tile que tanca el perímetre. Es fixa una // sola vegada, en la transició CLOSED→OPENING dins de notifyTilePainted. // -1 mentre està a CLOSED, o si la pintada que el tanca no porta // atribució (player_id == -1 al broadcast inicial). int last_closer_player_id_{-1}; bool opened_event_emitted_{false}; };