feat(whip): separa draw_rect i damage_rect; sprites 80×32 / 16×96; feet_y delegat a l'owner; recover_* per al cooldown

This commit is contained in:
JailDesigner committed 2026-04-28 18:28:19 +02:00
1 parent 121558b1fd
commit 8f66961c7b
15 files changed
+353 -127

No files matched your search

+65 -2
View File
@@ -89,18 +89,81 @@ WhipAttack::WhipAttack(const fkyaml::node& yaml) {
anim_ = anim_swing_;
}
void WhipAttack::configure(Direction dir, float spawn_x, float spawn_y, float range_px) {
void WhipAttack::configure(Direction dir, const SDL_FRect& owner_box, float range_px) {
direction_ = dir;
range_px_ = range_px;
// Draw rect (= pixel_rect_): cobreix tot el sprite del látigo segons
// la taula d'offsets del disseny:
// E: (player.x - 16, player.y ), 80×32
// W: (player.x - 48, player.y ), 80×32
// N: (player.x, player.y - 48 ), 16×96
// S: (player.x, player.y - 16 ), 16×96
const auto& chosen = spriteFor(dir);
setPixelRect({.x = spawn_x, .y = spawn_y, .w = chosen.frame_w, .h = chosen.frame_h});
SDL_FRect draw{0.0F, 0.0F, chosen.frame_w, chosen.frame_h};
switch (dir) {
case Direction::EAST:
draw.x = owner_box.x - 16.0F;
draw.y = owner_box.y;
break;
case Direction::WEST:
draw.x = owner_box.x - 48.0F;
draw.y = owner_box.y;
break;
case Direction::NORTH:
draw.x = owner_box.x;
draw.y = owner_box.y - 48.0F;
break;
case Direction::SOUTH:
draw.x = owner_box.x;
draw.y = owner_box.y - 16.0F;
break;
}
setPixelRect(draw);
// Damage rect: 48×16 (E/W) o 16×48 (N/S) al nivell dels peus del
// jugador (els últims 16 px del seu pixel_rect — convenció estàndard
// del player AEE), pegat al borde en la direcció d'atac.
// computeDamageRect rep la cantonada del rect MÀXIM (sense retallar)
// i recorre internament la línia per topar amb el primer bloquejador.
const float COLLISION_TOP_Y = owner_box.y + owner_box.h - 16.0F;
const float OWNER_RIGHT = owner_box.x + owner_box.w;
const float OWNER_BOTTOM = owner_box.y + owner_box.h;
float spawn_x = 0.0F;
float spawn_y = 0.0F;
switch (dir) {
case Direction::EAST:
spawn_x = OWNER_RIGHT;
spawn_y = COLLISION_TOP_Y;
break;
case Direction::WEST:
spawn_x = owner_box.x - range_px;
spawn_y = COLLISION_TOP_Y;
break;
case Direction::NORTH:
spawn_x = owner_box.x;
spawn_y = COLLISION_TOP_Y - range_px;
break;
case Direction::SOUTH:
spawn_x = owner_box.x;
spawn_y = OWNER_BOTTOM;
break;
}
computeDamageRect(dir, spawn_x, spawn_y, range_px);
// Snapshot del feet_y del jugador. La convenció estàndard del Player
// AEE té el seu feet_y a la base inferior del pixel_rect (collision
// 16×16 als últims 16 px del sprite 16×32), així que owner_box.y +
// owner_box.h reprodueix `Player::getFeetY()` exactament.
owner_feet_y_ = owner_box.y + owner_box.h;
if (anim_.has_value()) { anim_->reset(); }
}
auto WhipAttack::getFeetY() const -> float {
return owner_feet_y_;
}
void WhipAttack::setWorldContext(EntityManager* manager, const std::vector<Player*>* players) {
entity_manager_ = manager;
players_ = players;
+43 -14
View File
@@ -18,16 +18,27 @@ class Player;
// Atac de látigo: spawnejat pel jugador en entrar a `ATTACKING`. Viu la
// durada de la seua animació (`swing`) i s'autoelimina al final.
//
// Sprites: 4 fitxers GIF distints, un per cardinal. La mida del frame
// canvia entre direccions (E/W: 48×16, N/S: 16×48), cosa que el sistema
// d'animació actual no permet expressar amb una sola surface (una entitat
// = una graella). Per això el YAML llista les 4 entrades sota `sprites:`
// Sprites: 4 fitxers GIF distints, un per cardinal (sense flips). La
// mida del frame canvia entre direccions (E/W: 80×32, N/S: 16×96),
// cosa que el sistema d'animació actual no permet expressar amb una
// sola surface. Per això el YAML llista les 4 entrades sota `sprites:`
// i `configure()` tria la que toca segons direcció.
//
// Hitbox de dany: el rectangle de dany és el `pixel_rect_` retallat al
// primer bloquejador en la línia d'atac (mur extern del playfield o
// sarcòfag). Aquesta lògica viu a la fase de damage application; en
// aquest skeleton només viu la part visual i de cicle de vida.
// Dos rectangles distints, intencionadament diferenciats:
// - `pixel_rect_` (heretat d'`Entity`) = `draw_rect`. Cobreix tot el
// sprite del látigo: 16 px de wind-up darrere del jugador, 16 px
// de solapament amb el jugador, i 48 px d'atac frontal (per E/W;
// proporcions equivalents per N/S amb 32 px de solapament). NO es
// retalla per bloquejadors — només render visual.
// - `damage_rect_` = caixa de dany al nivell dels peus del jugador
// (centrada sobre la base 16×16). 48×16 (E/W) o 16×48 (N/S),
// pegada al borde del jugador en la direcció d'atac. SÍ es retalla
// pel primer bloquejador (mur extern del playfield o sarcòfag).
//
// Z-order: `getFeetY()` delega al `owner_` per garantir que el látigo
// quede al mateix bucket d'y-sort que el jugador que l'ha llançat. Si
// no, l'altura física del sprite (esp. en N/S, que té 96 px) crearia
// solapaments raros amb mòmies a la trajectòria.
class WhipAttack : public Attack {
public:
// Direcció cardinal del swing. Coincideix valors amb
@@ -53,12 +64,16 @@ class WhipAttack : public Attack {
explicit WhipAttack(const fkyaml::node& yaml);
// Configura l'estat runtime de la instància. Cal cridar-lo abans del
// primer `update`. Selecciona la surface i la mida del pixel_rect_
// segons direcció, i posa la cantonada superior-esquerra de la
// hitbox a (spawn_x, spawn_y). També calcula la `damage_rect_`
// retallada contra murs i sarcs (mai sortiria del playfield ni
// travessaria un sarc).
void configure(Direction dir, float spawn_x, float spawn_y, float range_px);
// primer `update`. A partir del `owner_box` (pixel_rect del jugador,
// típicament 16×32) i la direcció d'atac, deriva:
// - `pixel_rect_` (= draw_rect): cantonada i mida del sprite del
// látigo segons la taula d'offsets (E: -16, 0, 80×32; etc.).
// - `damage_rect_`: caixa de dany 48×16 (o 16×48) pegada al
// borde del jugador, retallada contra murs i sarcs.
// Convenció implícita: el jugador té collision rect 16×16 als
// últims 16 px del seu pixel_rect (els "peus"); aquesta funció
// n'usa la geometria per al damage_rect i el feet_y delegat.
void configure(Direction dir, const SDL_FRect& owner_box, float range_px);
// Injecta el context que necessita la fase de dany: punter a
// l'`EntityManager` (per iterar Hazards) i a la llista de jugadors
@@ -90,6 +105,14 @@ class WhipAttack : public Attack {
// consumeix la pròpia `applyDamage`.
[[nodiscard]] auto getDamageRect() const -> SDL_FRect { return damage_rect_; }
// Y dels peus delegada al `owner_box` capturat a `configure()`: el
// látigo es z-ordena al mateix bucket que el jugador que l'ha
// llançat (= base inferior del seu pixel_rect, que coincideix amb
// el feet_y del jugador a la convenció estàndard del player AEE).
// Snapshot, no consulta dinàmica: el jugador queda quiet durant
// ATTACKING, així que snapshot ≡ live per a la vida del whip.
[[nodiscard]] auto getFeetY() const -> float override;
// Clau al `Resource::Cache` per a la surface de la direcció actual
// (la que ha triat `configure()`). El caller (típicament `GameScene`)
// resol la surface i la passa via `setSurface()`. Aquesta separació
@@ -123,6 +146,12 @@ class WhipAttack : public Attack {
float range_px_{0.0F};
SDL_FRect damage_rect_{0.0F, 0.0F, 0.0F, 0.0F};
// Snapshot del feet_y de l'owner pres a `configure()` (= base
// inferior del seu pixel_rect, que coincideix amb el `getFeetY()`
// del Player en la convenció estàndard d'AEE). El retornem des
// del nostre `getFeetY()` per delegar el bucket d'y-sort al jugador.
float owner_feet_y_{0.0F};
EntityManager* entity_manager_{nullptr};
const std::vector<Player*>* players_{nullptr};