Files
aee-arcade/source/game/entities/event_bus.hpp
T

165 lines
6.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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};
};