#pragma once #include #include #include #include #include #include #include #include #include // Forward: els events poden dur un punter a `Entity` (p. ex. quin sarcòfag // s'ha obert). No cal incloure el header complet aquí. class Entity; class Player; // Event pub/sub minimalista. Hi ha un únic `EventBus` per mundus (propietat // de l'`EntityManager`); les entitats que el necessiten en reben un punter // via `Entity::setEventBus`. Aquest sistema cobreix les necessitats // actuals (sarcophagus_opened, level_complete, player_hit, on_tile_entered) // sense portar-hi senyalització més pesada. // // Decisió: `std::variant` per al payload — evita any/void* i facilita // consumir events tipats amb `std::get_if`. Si apareixen més tipus de // payload, s'amplia aquí sense tocar cridants existents. // Tile (col, row) — payload de `tile_painted` (i altres events de tile sense // metadada extra). No és un `pair` per evitar alias ambigus al // variant (dos ints sense nom). struct TilePos { int col{0}; int row{0}; }; // Tile pintat pel jugador — payload de `on_tile_entered`. Porta l'índex // de tile amb què el jugador pinta (cada Player pot pintar amb un tile // diferent, llegit del YAML del mapa). El PaintLayer filtra pintabilitat, // marca i re-publica `tile_painted` amb només la posició. struct PaintedTile { int col{0}; int row{0}; int tile_index{-1}; }; // Coordenades en píxels al canvas. Payload genèric per a events que // volen indicar "una cosa ha passat aquí" — actualment `player_jumped` // i `player_landed`, que el GameScene escolta per spawnejar polvet. struct WorldPos { float x{0.0F}; float y{0.0F}; }; // Payload de `whip_spawn`: el `Player` el publica quan entra a ATTACKING // i el `GameScene` el consumeix per crear un `WhipAttack` via // `EntityManager::add`. Així el Player no depèn ni de `EntityManager` ni de // `WhipAttack`, i el contracte queda dins del bus. // - `owner` : Player que ha llançat l'atac (no rep dany propi). // - `direction` : Player::Cardinal castat a int (1=N, 2=S, 3=E, 4=W) per // evitar dependència cíclica al header. // - `owner_box` : pixel_rect del jugador (típicament 16×32 a AEE). El // WhipAttack en deriva tant la `draw_rect_` (sprite del // látigo, més gran que el jugador per cobrir wind-up i // solapament) com la `damage_rect_` (caixa de dany al // nivell dels peus, retallada per murs/sarcs). // - `range_px` : longitud nominal de la hitbox de dany abans de // retallar contra murs i sarcòfags. Llegida del YAML // del jugador. struct WhipSpawnPayload { Player* owner{nullptr}; int direction{0}; SDL_FRect owner_box{0.0F, 0.0F, 0.0F, 0.0F}; float range_px{0.0F}; }; struct Event { std::string name; using Payload = std::variant; Payload payload; // Jugador que ha originat (o provocat) l'event. -1 = sense jugador // associat (events del món, com `sarcophagus_opened` des de la pròpia // entitat). Usat per al HUD (dispatch a P1/P2) i per a regles futures // de puntuació que atribueixin accions al jugador concret. int player_id{-1}; }; class EventBus { public: using Handler = std::function; using SubId = std::size_t; EventBus() = default; EventBus(const EventBus&) = delete; EventBus(EventBus&&) = delete; auto operator=(const EventBus&) -> EventBus& = delete; auto operator=(EventBus&&) -> EventBus& = delete; ~EventBus() = default; // Retorna un identificador únic per poder-se donar de baixa amb // `unsubscribe`. Els identificadors s'assignen globalment i no es // reutilitzen fins a overflow (acceptable per a la vida d'un nivell). auto subscribe(const std::string& name, Handler handler) -> SubId; void unsubscribe(SubId id); // Publica a tots els subscriptors registrats per al nom. Si no n'hi ha // cap, no fa res. void publish(const Event& event); // Forma ergonòmica: construeix un Event amb nom + payload opcional. void publish(const std::string& name) { publish(Event{.name = name, .payload = std::monostate{}}); } void publish(const std::string& name, Entity* entity) { publish(Event{.name = name, .payload = entity}); } void publish(const std::string& name, int value) { publish(Event{.name = name, .payload = value}); } void publish(const std::string& name, TilePos tile) { publish(Event{.name = name, .payload = tile}); } void publish(const std::string& name, PaintedTile painted) { publish(Event{.name = name, .payload = painted}); } void publish(const std::string& name, WorldPos pos) { publish(Event{.name = name, .payload = pos}); } void publish(const std::string& name, const WhipSpawnPayload& whip) { publish(Event{.name = name, .payload = whip}); } private: struct Subscription { SubId id; Handler handler; }; std::unordered_map> subs_; std::uint64_t next_id_{1}; };