feat(player): mort i respawn (DYING + death_spinning + lives counter)

This commit is contained in:
JailDesigner committed 2026-04-29 11:40:05 +02:00
1 parent 6397f9d4eb
commit 9f0a566ffb
11 files changed
+377 -7

No files matched your search

+74
View File
@@ -68,6 +68,13 @@ class Player : public AnimatedSprite {
// tots els slots durant les transicions de sala abans del spawn
// aeri, i (c) en el futur, l'estat post-mort abans del respawn.
DISABLED,
// Mort en curs: animació `death_spinning` (3 voltes horàries) +
// pausa `respawn_delay_ms` abans del respawn. Bloqueja input i
// moviment; no rep contacte amb hazards (cf. `isDying`). Quan el
// timer s'esgota, `respawn(spawn_x_, spawn_y_)` torna a GROUNDED
// i decrementa el comptador de vides (pot anar a negatius — no
// hi ha game over).
DYING,
};
// Geometria carregada des del YAML del player (p.ex. `data/player/sam.yaml`).
@@ -203,6 +210,7 @@ class Player : public AnimatedSprite {
[[nodiscard]] auto isHurt() const -> bool { return state_ == State::HURT; }
[[nodiscard]] auto isExited() const -> bool { return state_ == State::EXITED; }
[[nodiscard]] auto isDisabled() const -> bool { return state_ == State::DISABLED; }
[[nodiscard]] auto isDying() const -> bool { return state_ == State::DYING; }
// Resum dels estats "fora del món": EXITED i DISABLED comparteixen el
// mateix perfil físic (no es pinten, no llegeixen input, no
// col·lisionen, no participen al resolver PvP). Tots dos gates de
@@ -240,6 +248,28 @@ class Player : public AnimatedSprite {
being_pushed_ = false;
}
// Mata el jugador d'un sol toc: transició a DYING, animació
// `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).
void enterDying();
// Coordenades del spawn de respawn (cantó superior-esquerra del
// sprite, mateixa convenció que `respawn(x, y)`). El GameScene les
// assigna després de `computeSpawn` al ctor i a cada `loadRoom`. Sense
// això, el respawn enviaria el jugador a (0, 0).
void setSpawnPoint(float x, float y) {
spawn_x_ = x;
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.
[[nodiscard]] auto getLivesRemaining() const -> int { return lives_remaining_; }
// Reset complet a l'estat inicial: posició a (x, y), velocitats a 0,
// estat GROUNDED, qualsevol flag de control reinicialitzat. Cridat
// per GameScene a la transició ROOM_OUTRO → ROOM_INTRO. Idempotent
@@ -554,6 +584,21 @@ class Player : public AnimatedSprite {
// d'input es perd silenciosament).
static void readWhipBlock(const fkyaml::node& yaml);
// Configuració de mort/respawn llegida del bloc `death:` del YAML.
// `respawn_delay_ms` és la pausa entre la fi de `death_spinning` i la
// reaparició al spawn; `initial_lives` és el comptador d'intents amb
// què naix cada Player. Si el bloc manca, defaults segurs (0/3).
struct DeathConfig {
int respawn_delay_ms{0};
int initial_lives{3};
};
static DeathConfig death_config;
// Llig el bloc `death:` del YAML i n'omple `death_config`. Si manca,
// `respawn_delay_ms` queda a 0 (el respawn és immediat al final de
// l'animació) i `initial_lives` es manté al default (3).
static void readDeathBlock(const fkyaml::node& yaml);
// Activa l'estat ATTACKING en la direcció cardinal indicada (la
// direcció cap on mira el jugador) i publica `whip_spawn` al bus
// perquè el GameScene cree el `WhipAttack` corresponent. Posa el
@@ -624,6 +669,35 @@ class Player : public AnimatedSprite {
// bloquejat i no rep input direccional ni de salt ni d'atac.
float attacking_remaining_ms_{0.0F};
// Estat de DYING. `death_animation_complete_` es marca quan
// `animationIsCompleted()` retorna true per primera vegada al cicle
// actual; només llavors comença a descomptar `respawn_remaining_ms_`.
// Quan el delay arriba a 0, `respawnFromDying()` aplica `respawn(...)`
// i decreix el comptador de vides.
bool death_animation_complete_{false};
float respawn_remaining_ms_{0.0F};
// 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.
int lives_remaining_{0};
// Coordenades del spawn de respawn (cantó superior-esquerra del
// sprite). Les assigna el GameScene via `setSpawnPoint`. 0/0 fins que
// el GameScene n'invoque el setter al ctor / `loadRoom`.
float spawn_x_{0.0F};
float spawn_y_{0.0F};
// Aplica el respawn al final del cicle DYING: decreix vides (sense
// clamp), neteja els flags interns del cicle i delega a `respawn`
// amb les coordenades del spawn.
void respawnFromDying();
// Branca de `applyMovement` per a DYING. Avança l'animació, detecta
// la fi i descompta el delay; al final, dispara el respawn.
void applyDyingMovement(float delta_time);
// Llig el botó de salt aquest frame (teclat + mando assignat) i,
// amb edge-detection, retorna true si s'ha de despegar el salt.
[[nodiscard]] auto consumeJumpEdge() -> bool;