#include "core/rendering/sprite/animated_sprite.hpp" #include // Para std::fmod #include // Para size_t #include // Para basic_ostream, basic_istream, operator<<, basic... #include // Para cout, cerr #include // Para std::accumulate #include // Para basic_stringstream #include // Para runtime_error #include #include "core/rendering/surface.hpp" // Para Surface #include "core/resources/resource_cache.hpp" // Para Resource #include "core/resources/resource_helper.hpp" // Para ResourceHelper #include "external/fkyaml_node.hpp" // Para fkyaml::node #include "utils/utils.hpp" // Para printWithDots // Helper: Convierte un nodo YAML de frames (array) a vector de SDL_FRect auto convertYAMLFramesToRects(const fkyaml::node& frames_node, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> std::vector { std::vector frames; SDL_FRect rect = {.x = 0.0F, .y = 0.0F, .w = frame_width, .h = frame_height}; for (const auto& frame_index_node : frames_node) { const int NUM_TILE = frame_index_node.get_value(); if (NUM_TILE <= max_tiles) { rect.x = (NUM_TILE % frames_per_row) * frame_width; rect.y = (NUM_TILE / frames_per_row) * frame_height; frames.emplace_back(rect); } } return frames; } // Helper: parsea el array de animaciones de un nodo YAML static auto parseAnimations(const fkyaml::node& yaml, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> std::vector { std::vector animations; if (!yaml.contains("animations") || !yaml["animations"].is_sequence()) { return animations; } for (const auto& anim_node : yaml["animations"]) { AnimatedSprite::AnimationData animation; if (anim_node.contains("name")) { animation.name = anim_node["name"].get_value(); } if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) { animation.frames = convertYAMLFramesToRects( anim_node["frames"], frame_width, frame_height, frames_per_row, max_tiles); } if (anim_node.contains("speed")) { const auto& speed_node = anim_node["speed"]; if (speed_node.is_sequence()) { for (const auto& s : speed_node) { animation.speeds.push_back(s.get_value()); } } else { auto spd = speed_node.get_value(); if (spd > 0.0F) { animation.speeds.assign(animation.frames.size(), spd); } } } if (anim_node.contains("loopFrom")) { animation.loop_from = anim_node["loopFrom"].get_value(); } animations.push_back(animation); } return animations; } // Helper: parsea la configuración global (tileSetFile, frameWidth, frameHeight) y calcula parámetros del spritesheet struct SheetParams { float frame_width{0.0F}; float frame_height{0.0F}; int frames_per_row{1}; int max_tiles{1}; }; static auto parseGlobalConfig(const fkyaml::node& yaml, std::shared_ptr& surface) -> SheetParams { SheetParams params; if (yaml.contains("tileSetFile")) { surface = Resource::Cache::get()->getSurface(yaml["tileSetFile"].get_value()); } if (yaml.contains("frameWidth")) { params.frame_width = static_cast(yaml["frameWidth"].get_value()); } if (yaml.contains("frameHeight")) { params.frame_height = static_cast(yaml["frameHeight"].get_value()); } if (surface && params.frame_width > 0.0F && params.frame_height > 0.0F) { params.frames_per_row = surface->getWidth() / static_cast(params.frame_width); const int H = surface->getHeight() / static_cast(params.frame_height); params.max_tiles = params.frames_per_row * H; } return params; } // Constructor con bytes YAML del cache AnimatedSprite::AnimatedSprite(const AnimationResource& cached_data) { std::string yaml_content(cached_data.yaml_data.begin(), cached_data.yaml_data.end()); try { auto yaml = fkyaml::node::deserialize(yaml_content); auto params = parseGlobalConfig(yaml, surface_); animations_ = parseAnimations(yaml, params.frame_width, params.frame_height, params.frames_per_row, params.max_tiles); buildNameIndex(); setWidth(params.frame_width); setHeight(params.frame_height); if (!animations_.empty() && !animations_[0].frames.empty()) { setClip(animations_[0].frames[0]); } } catch (const fkyaml::exception& e) { std::cerr << "YAML parsing error in animation " << cached_data.name << ": " << e.what() << '\n'; throw; } catch (const std::exception& e) { std::cerr << "Error loading animation " << cached_data.name << ": " << e.what() << '\n'; throw; } } // Constructor per a subclasses amb surface directa (sense YAML) AnimatedSprite::AnimatedSprite(std::shared_ptr surface, SDL_FRect pos) : MovingSprite(std::move(surface), pos) { // animations_ queda buit (protegit per el guard de animate()) if (surface_) { clip_ = {.x = 0, .y = 0, .w = static_cast(surface_->getWidth()), .h = static_cast(surface_->getHeight())}; } } // Construye el mapa nombre→índice para búsqueda O(1) void AnimatedSprite::buildNameIndex() { animation_index_.clear(); for (int i = 0; i < static_cast(animations_.size()); ++i) { animation_index_[animations_[i].name] = i; } } // Calcula el frame correspondiente a la animación (time-based) void AnimatedSprite::animate(float delta_time) { if (animations_.empty()) { return; } auto& anim = animations_[current_animation_]; if (anim.speeds.empty()) { return; } // Animación estática anim.accumulated_time += delta_time; // Calcular duración total de la animación const float TOTAL = std::accumulate(anim.speeds.begin(), anim.speeds.end(), 0.0F); // Si hemos superado la duración total, manejar loop o congelar if (anim.accumulated_time >= TOTAL) { if (anim.loop_from < 0) { // Sin loop: congelar en el último frame anim.current_frame = static_cast(anim.frames.size()) - 1; anim.completed = true; setClip(anim.frames[anim.current_frame]); return; } // Con loop: envolver el tiempo en el rango del loop const float LOOP_START = std::accumulate(anim.speeds.begin(), anim.speeds.begin() + anim.loop_from, 0.0F); const float LOOP_LEN = TOTAL - LOOP_START; anim.accumulated_time = LOOP_START + std::fmod(anim.accumulated_time - LOOP_START, LOOP_LEN); } // Buscar el frame correspondiente al tiempo acumulado float cursor = 0.0F; for (int i = 0; i < static_cast(anim.frames.size()); ++i) { cursor += anim.speeds[i]; if (anim.accumulated_time < cursor) { anim.current_frame = i; setClip(anim.frames[i]); return; } } // Seguridad: último frame anim.current_frame = static_cast(anim.frames.size()) - 1; setClip(anim.frames[anim.current_frame]); } // Comprueba si ha terminado la animación auto AnimatedSprite::animationIsCompleted() -> bool { return animations_[current_animation_].completed; } // Establece la animacion actual por nombre void AnimatedSprite::setCurrentAnimation(const std::string& name) { auto it = animation_index_.find(name); if (it == animation_index_.end()) { std::cout << "** Warning: could not find \"" << name << "\" animation" << '\n'; return; } setCurrentAnimation(it->second); } // Establece la animacion actual void AnimatedSprite::setCurrentAnimation(int index) { const auto NEW_ANIMATION = index; if (current_animation_ != NEW_ANIMATION) { current_animation_ = NEW_ANIMATION; animations_[current_animation_].current_frame = 0; animations_[current_animation_].accumulated_time = 0.0F; animations_[current_animation_].completed = false; setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); } } // Actualiza las variables del objeto (time-based) void AnimatedSprite::update(float delta_time) { animate(delta_time); MovingSprite::update(delta_time); } // Reinicia la animación void AnimatedSprite::resetAnimation() { animations_[current_animation_].current_frame = 0; animations_[current_animation_].accumulated_time = 0.0F; animations_[current_animation_].completed = false; } // Establece el frame actual de la animación void AnimatedSprite::setCurrentAnimationFrame(int num) { // Descarta valores fuera de rango if (num < 0 || num >= static_cast(animations_[current_animation_].frames.size())) { num = 0; } // Cambia el valor de la variable animations_[current_animation_].current_frame = num; // Escoge el frame correspondiente de la animación setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); }