165 lines
6.5 KiB
C++
165 lines
6.5 KiB
C++
#pragma once
|
||
|
||
#include <SDL3/SDL.h>
|
||
|
||
#include <cstddef>
|
||
#include <cstdint>
|
||
#include <functional>
|
||
#include <string>
|
||
#include <unordered_map>
|
||
#include <utility>
|
||
#include <variant>
|
||
#include <vector>
|
||
|
||
// 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,
|
||
// player_stats_changed) 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.
|
||
//
|
||
// `player_stats_changed`: l'únic canal numèric que toca el HUD. El `Player`
|
||
// és la font de veritat de vides/score/tresors i el publica cada vegada
|
||
// que muta qualsevol d'aquests camps. El HUD el consumeix com a "snapshot
|
||
// total" (no com a delta) — sobreescriu la seua caché amb el payload. Així
|
||
// qualsevol via que modifique stats al Player (mort per forat, recompensa
|
||
// de sarc, etc.) queda automàticament reflectida sense que cada caller
|
||
// haja de recordar publicar res addicional. `player_hit` queda només per
|
||
// al feedback visual (flash/shake), separat del comptador.
|
||
|
||
// Tile (col, row) — payload de `tile_painted` (i altres events de tile sense
|
||
// metadada extra). No és un `pair<int,int>` 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};
|
||
};
|
||
|
||
// Snapshot complet dels stats d'un jugador — payload de `player_stats_changed`.
|
||
// El Player publica un d'aquests cada vegada que muta vides/score/tresors.
|
||
// El HUD el consumeix com a estat absolut (no delta): sobreescriu la seua
|
||
// caché amb aquests valors. El `player_id` de l'`Event` indica a quin
|
||
// bloc del HUD va dirigit.
|
||
struct PlayerStatsPayload {
|
||
int lives{0};
|
||
int score{0};
|
||
int treasures{0};
|
||
};
|
||
|
||
struct Event {
|
||
std::string name;
|
||
using Payload = std::variant<std::monostate, int, std::string, Entity*, TilePos, PaintedTile, WorldPos, WhipSpawnPayload, PlayerStatsPayload>;
|
||
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<void(const Event&)>;
|
||
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});
|
||
}
|
||
void publish(const std::string& name, const PlayerStatsPayload& stats) {
|
||
publish(Event{.name = name, .payload = stats});
|
||
}
|
||
|
||
private:
|
||
struct Subscription {
|
||
SubId id;
|
||
Handler handler;
|
||
};
|
||
|
||
std::unordered_map<std::string, std::vector<Subscription>> subs_;
|
||
std::uint64_t next_id_{1};
|
||
};
|