#pragma once #include #include #include #include #include #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* 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& { 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 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 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* players_{nullptr}; DirSprite sprite_north_; DirSprite sprite_south_; DirSprite sprite_east_; DirSprite sprite_west_; };