feat(stats): Player com a font de veritat de vides/score/tresors i sarcs amb recompensa

This commit is contained in:
JailDesigner committed 2026-05-12 19:51:42 +02:00
1 parent d186f66c2c
commit f5d251c33b
14 files changed
+226 -34

No files matched your search

+28 -9
View File
@@ -135,10 +135,14 @@ Hud::Hud(EventBus& bus, std::shared_ptr<Text> font, int initial_lives)
players_[1].active = false;
players_[1].lives = initial_lives_;
// Subscripcions al bus de l'EntityManager. De moment només `player_hit`
// afecta el HUD visualment: decrementa vides, dispara flash i shake al
// bloc del jugador. Quan els events evolucionin per portar `player_id`
// i puntuacions, ampliarem aquí sense tocar callers.
// Subscripcions al bus de l'EntityManager.
//
// `player_hit` només fa feedback visual (flash + shake) al bloc del
// jugador colpejat. **No** toca el comptador de vides — la veritat
// viu al Player i arriba per `player_stats_changed`. Així si un hit
// no és mortal (HURT recoverable) el flash es manté coherent sense
// mentir el comptador, i si és mortal el comptador baixa pel canal
// numèric quan el cicle DYING acaba a `respawnFromDying`.
subs_.push_back(bus_.subscribe("player_hit", [this](const Event& ev) {
const auto* dmg = std::get_if<int>(&ev.payload);
// Dispatch pel `player_id` de l'event. 0 = P1 (bloc LEFT), 1 = P2
@@ -147,14 +151,28 @@ Hud::Hud(EventBus& bus, std::shared_ptr<Text> font, int initial_lives)
// publishers de `player_hit` actuals sí que porten id.
const std::size_t IDX = (ev.player_id == 1) ? 1U : 0U;
if (!players_[IDX].active) { return; }
if (players_[IDX].lives > 0) { --players_[IDX].lives; }
const std::size_t BLK_IDX = (IDX == 0) ? static_cast<std::size_t>(BlockId::LEFT)
: static_cast<std::size_t>(BlockId::RIGHT);
auto& blk = blocks_[BLK_IDX];
blk.flash_ms_left = UiConfig::get().hudFlashMs();
blk.shake_ms_left = UiConfig::get().hudShakeMs();
blk.shake_intensity = 1.5F;
Log::info("HUD", "player_hit p{} dmg={} lives={}", IDX + 1, dmg != nullptr ? *dmg : 0, players_[IDX].lives);
Log::info("HUD", "player_hit p{} dmg={}", IDX + 1, dmg != nullptr ? *dmg : 0);
}));
// `player_stats_changed`: snapshot total (vides/score/tresors) emès
// pel Player cada vegada que muta qualsevol dels tres. El HUD el
// consumeix com a estat absolut: sobreescriu la caché local sense
// raonament addicional. Això unifica totes les fonts de canvi (mort
// per qualsevol causa, recompensa de sarc, futurs pickups…) en un
// únic canal.
subs_.push_back(bus_.subscribe("player_stats_changed", [this](const Event& ev) {
const auto* st = std::get_if<PlayerStatsPayload>(&ev.payload);
if (st == nullptr) { return; }
const std::size_t IDX = (ev.player_id == 1) ? 1U : 0U;
players_[IDX].lives = st->lives;
players_[IDX].score = st->score;
players_[IDX].treasures = st->treasures;
}));
subs_.push_back(bus_.subscribe("level_complete", [](const Event& /*ev*/) {
@@ -207,10 +225,11 @@ void Hud::activatePlayer(int player_one_based) {
if (player_one_based < 1 || player_one_based > 2) { return; }
auto& p = players_[static_cast<std::size_t>(player_one_based - 1)];
p.active = true;
p.lives = initial_lives_;
p.score = 0;
p.treasures = 0;
p.join_prompt = JoinPromptMode::NONE;
// Els camps numèrics (lives/score/treasures) ja els ha sobreescrit el
// `player_stats_changed` que el Player publica dins de `resetForJoin`,
// així que aquí no els toquem — evitem trepitjar la veritat del bus
// amb defaults locals.
}
void Hud::setJoinPrompt(int player_one_based, JoinPromptMode mode) {