#include "game/entities/concrete/whip_attack.hpp" #include #include #include #include #include "game/entities/entity_factory.hpp" namespace { // Llig una entrada `sprites.: { path, size: [w, h] }` del YAML. // Si manquen camps, retorna un DirSprite buit (queda invalidat al // configure() per `surface_key.empty()`). auto readDirSprite(const fkyaml::node& s) -> WhipAttack::DirSprite { WhipAttack::DirSprite sprite; if (s.contains("path")) { const auto REL = s["path"].get_value(); sprite.surface_key = std::filesystem::path(REL).filename().string(); } if (s.contains("size")) { const auto& sz = s["size"]; if (sz.is_sequence() && sz.size() == 2) { sprite.frame_w = static_cast(sz[0].get_value()); sprite.frame_h = static_cast(sz[1].get_value()); } } return sprite; } // Construeix l'`AnimationComponent` del swing a partir del bloc // `animations.swing`. Convenció igual que la resta d'entitats: // - `frames` : llista d'índexs dins l'spritesheet (obligatori). // - `frame_ms` : durada uniforme per frame (ms). Alternativament `fps`. // - `loop` : per defecte true; al látigo el volem false (mor en // completar). auto buildSwingAnim(const fkyaml::node& yaml, std::vector& active_out) -> AnimationComponent { std::vector frames = {0}; float fps = 8.0F; bool loop = false; if (yaml.contains("animations") && yaml["animations"].contains("swing")) { const auto& sw = yaml["animations"]["swing"]; if (sw.contains("frames")) { frames.clear(); for (const auto& f : sw["frames"]) { // cppcheck-suppress useStlAlgorithm frames.push_back(f.get_value()); } if (frames.empty()) { frames.push_back(0); } } if (sw.contains("frame_ms")) { const auto MS = static_cast(sw["frame_ms"].get_value()); if (MS > 0.0F) { fps = 1000.0F / MS; } } else if (sw.contains("fps")) { fps = sw["fps"].get_value(); } if (sw.contains("loop")) { loop = sw["loop"].get_value(); } if (sw.contains("active_frames")) { for (const auto& f : sw["active_frames"]) { // cppcheck-suppress useStlAlgorithm active_out.push_back(f.get_value()); } } } return {frames, fps, loop}; } } // namespace WhipAttack::WhipAttack(const fkyaml::node& yaml) { name_ = yaml.contains("name") ? yaml["name"].get_value() : std::string("whip_attack"); collides_ = false; interactable_ = false; if (yaml.contains("sprites")) { const auto& s = yaml["sprites"]; if (s.contains("north")) { sprite_north_ = readDirSprite(s["north"]); } if (s.contains("south")) { sprite_south_ = readDirSprite(s["south"]); } if (s.contains("east")) { sprite_east_ = readDirSprite(s["east"]); } if (s.contains("west")) { sprite_west_ = readDirSprite(s["west"]); } } anim_swing_ = buildSwingAnim(yaml, active_frames_); anim_ = anim_swing_; } void WhipAttack::configure(Direction dir, float spawn_x, float spawn_y, float range_px) { direction_ = dir; range_px_ = range_px; const auto& chosen = spriteFor(dir); setPixelRect({.x = spawn_x, .y = spawn_y, .w = chosen.frame_w, .h = chosen.frame_h}); if (anim_.has_value()) { anim_->reset(); } } auto WhipAttack::getSurfaceKey() const -> const std::string& { return spriteFor(direction_).surface_key; } auto WhipAttack::spriteFor(Direction dir) const -> const DirSprite& { switch (dir) { case Direction::NORTH: return sprite_north_; case Direction::SOUTH: return sprite_south_; case Direction::EAST: return sprite_east_; case Direction::WEST: return sprite_west_; } return sprite_east_; } void WhipAttack::update(float delta_time) { Entity::update(delta_time); if (anim_.has_value() && anim_->completed()) { markDead(); } } auto WhipAttack::isFrameActive() const -> bool { if (!anim_.has_value()) { return false; } const int FRAME = anim_->currentFrame(); return std::ranges::any_of(active_frames_, [FRAME](int f) { return f == FRAME; }); } REGISTER_ENTITY("whip_attack", WhipAttack)