feat(player): join in progress + DISABLED en agotar vidas (Fase 1)

Habilita la regla 1 de la nova FSM de joc: durant PLAYING, qualsevol
slot DISABLED pot pulsar START per entrar a partida amb spawn aeri al
seu costat de la porta i vides reseteades. Quan un jugador esgota les
vides, `respawnFromDying` el deixa a DISABLED i publica `player_disabled`
al bus; GameScene i HUD reaccionen, deixant el slot disponible per a un
nou join. Afegeix prompts al HUD ("PULSA START / PER JUGAR" o
"ESPERA / PER JUGAR") segons l'estat de la RoomFsm.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
JailDesignerandClaude Opus 4.7 committed 2026-05-08 20:04:33 +02:00
1 parent 8921b9fd0c
commit c13b11c501
12 files changed
+325 -56

No files matched your search

+20 -1
View File
@@ -1270,11 +1270,30 @@ void Player::applyDyingMovement(float delta_time) {
}
}
void Player::resetForJoin() {
lives_remaining_ = death_config.initial_lives;
}
void Player::respawnFromDying() {
// Vides: decreix sempre. Pot anar a negatius — no hi ha game over.
// Vides: decreix una unitat. Si el comptador arriba a 0 el jugador
// surt de partida (DISABLED) i la GameScene/HUD reaccionen via bus
// — això habilita el flux de "join in progress" (l'altre jugador
// pot pulsar START per entrar) i obrirà el camí al CONTINUE? +
// GAME OVER en una fase posterior.
--lives_remaining_;
death_animation_complete_ = false;
respawn_remaining_ms_ = 0.0F;
if (lives_remaining_ <= 0) {
disable();
if (bus_ != nullptr) {
bus_->publish(Event{
.name = "player_disabled",
.payload = std::monostate{},
.player_id = id_,
});
}
return;
}
// Cinemàtica idèntica a la d'INTRO de sala: el jugador cau des de
// dalt fins al passadís d'spawn (`spawnFromAir` reseteja primer i
// després posa AIRBORNE amb l'altura derivada de la geometria).
+22 -7
View File
@@ -296,8 +296,11 @@ class Player : public AnimatedSprite {
// `death_spinning`, posició congelada, input ignorat. Idempotent —
// si ja està DYING, EXITED o DISABLED, és no-op (un segon toc de
// momia mentre la roda no la reinicia). Quan l'animació acaba i
// `respawn_delay_ms` expira, `respawn(spawn_x_, spawn_y_)` torna a
// GROUNDED i `lives_remaining_` decreix en 1 (pot anar a negatius).
// `respawn_delay_ms` expira, `respawnFromDying()` decrementa
// `lives_remaining_`: si encara queden vides, fa `spawnFromAir`
// al spawn point; si arriba a 0, fa `disable()` i publica al bus
// `player_disabled` perquè GameScene/HUD reaccionin (slot fora de
// partida, esperant un nou START per re-entrar via "join in progress").
void enterDying(DeathCause cause = DeathCause::GENERIC);
// Coordenades del spawn de respawn (cantó superior-esquerra del
@@ -309,9 +312,11 @@ class Player : public AnimatedSprite {
spawn_y_ = y;
}
// Comptador d'intents (estil Mario, NO HP). Decreix a cada respawn;
// no es clampa a 0 — permet anar a negatius perquè avui no hi ha
// game over. Públic per a HUD i tests.
// Comptador d'intents (estil Mario, NO HP). Decreix a cada respawn
// post-mort. En arribar a 0 el jugador passa a DISABLED i publica
// `player_disabled` al bus; pot tornar a partida via "join in
// progress" (GameScene::joinPlayer crida `resetForJoin`). Públic
// per a HUD i tests.
[[nodiscard]] auto getLivesRemaining() const -> int { return lives_remaining_; }
// Activa la immunitat per `duration_ms` ms. Mentre `isImmune()` és
@@ -342,6 +347,14 @@ class Player : public AnimatedSprite {
// sala usa `disable()` + `spawnFromAir()`.
void respawn(float x, float y);
// Restaura el comptador de vides al valor inicial configurat
// (`death_config.initial_lives`). Cridat per la GameScene quan un
// slot inactiu pulsa START per entrar mid-partida (regla 6 de la
// FSM de joc): el jugador entra amb les vides plenes. La resta de
// l'estat (posició, animació, flags) la reseteja `spawnFromAir`
// que GameScene crida tot seguit.
void resetForJoin();
// Posa el jugador en el slot inactiu (DISABLED). Idempotent: la
// segona crida és no-op. Mateix patró que `exit()` per simetria.
// No publica res al bus — el GameScene decideix quan disparar el
@@ -829,8 +842,10 @@ class Player : public AnimatedSprite {
// Comptador d'intents (estil Mario). Inicialitzat a `death_config.
// initial_lives` al ctor; decreix una vegada per cada respawn
// post-mort (no per respawns de canvi de sala). Pot anar a negatius:
// avui no hi ha game over.
// post-mort (no per respawns de canvi de sala). En arribar a 0,
// `respawnFromDying` posa el jugador a DISABLED i publica
// `player_disabled` al bus. `resetForJoin()` el restaura al valor
// inicial quan el jugador re-entra mid-partida.
int lives_remaining_{0};
// Coordenades del spawn de respawn (cantó superior-esquerra del