Files
aee-arcade/source/game/entities/concrete/whip_attack.hpp
T

134 lines
5.7 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 <cstdint>
#include <string>
#include <unordered_set>
#include <vector>
#include "external/fkyaml_node.hpp"
#include "game/entities/animation_component.hpp"
#include "game/entities/attack.hpp"
class Entity;
class EntityManager;
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:`
// 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.
class WhipAttack : public Attack {
public:
// Direcció cardinal del swing. Coincideix valors amb
// `Player::Cardinal` (1=N, 2=S, 3=E, 4=W) per facilitar la
// conversió a la frontera de l'event `whip_spawn`.
enum class Direction : std::uint8_t {
NORTH = 1,
SOUTH = 2,
EAST = 3,
WEST = 4,
};
// Definició estàtica d'una direcció: clau al `Resource::Cache` i mida
// del frame en píxels. Llegida del YAML una vegada al ctor; usada per
// configure() per omplir surface_ i pixel_rect_. Pública perquè el
// helper d'anàlisi del YAML viu en un namespace anònim al .cpp.
struct DirSprite {
std::string surface_key;
float frame_w{0.0F};
float frame_h{0.0F};
};
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);
// Injecta el context que necessita la fase de dany: punter a
// l'`EntityManager` (per iterar Hazards) i a la llista de jugadors
// (per al friendly fire). Si no es crida, els frames actius no
// apliquen dany — el whip queda merament visual. Cridar abans del
// primer `update` (típicament just després de `configure`).
void setWorldContext(EntityManager* manager, const std::vector<Player*>* players);
// Avança animació; durant frames actius, aplica dany; quan
// l'animació es completa, marca dead.
void update(float delta_time) override;
[[nodiscard]] auto getDirection() const -> Direction { return direction_; }
// True si el frame actual de l'animació és un dels `active_frames`
// declarats al YAML (els frames durant els quals la hitbox aplica
// dany). Wind-up i recovery retornen false.
[[nodiscard]] auto isFrameActive() const -> bool;
// Llistat dels frames actius (lectura del YAML, immutable per
// instància). Exposat per als smoke tests.
[[nodiscard]] auto getActiveFrames() const -> const std::vector<int>& { return active_frames_; }
// Llargada de la hitbox abans de retallar contra murs/sarcs (en píxels).
[[nodiscard]] auto getRangePx() const -> float { return range_px_; }
// Hitbox efectiva de dany (ja retallada pel primer bloquejador en la
// línia d'atac). Exposada per als smoke tests; en producció només la
// consumeix la pròpia `applyDamage`.
[[nodiscard]] auto getDamageRect() const -> SDL_FRect { return damage_rect_; }
// 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ó
// manté el codi de l'entitat sense dependència directa al
// `Resource::Cache`, igual que la resta d'entitats del joc.
[[nodiscard]] auto getSurfaceKey() const -> const std::string&;
private:
[[nodiscard]] auto spriteFor(Direction dir) const -> const DirSprite&;
// Calcula la `damage_rect_` final: retalla la longitud de l'atac
// contra el primer bloquejador (mur extern del playfield o sarc) en
// la línia d'atac. Implementació iterativa píxel a píxel — el rang
// típic és ~48 px, així que el cost és menyspreable.
void computeDamageRect(Direction dir, float spawn_x, float spawn_y, float range_px);
// Aplica dany aquest frame: itera Hazards (Mummy whippable) i
// jugadors (excepte `owner_`) que solapen amb `damage_rect_` i
// estan en estat "atacable", crida `enterHurt` amb la direcció
// adequada i els marca a `already_hit_` per garantir un sol
// impacte per swing per objectiu.
void applyDamage();
Direction direction_{Direction::EAST};
AnimationComponent anim_swing_;
std::vector<int> active_frames_;
// Set d'objectius ja colpejats aquest swing: usem `void*` perquè
// hi guardem tant `Entity*` (Mummy) com `Player*` (que NO hereta
// d'Entity) sense `reinterpret_cast` semànticament dubtós.
std::unordered_set<void*> already_hit_;
float range_px_{0.0F};
SDL_FRect damage_rect_{0.0F, 0.0F, 0.0F, 0.0F};
EntityManager* entity_manager_{nullptr};
const std::vector<Player*>* players_{nullptr};
DirSprite sprite_north_;
DirSprite sprite_south_;
DirSprite sprite_east_;
DirSprite sprite_west_;
};