#include "core/rendering/surface_animated_sprite.hpp" #include // Para size_t #include // Para basic_ostream, basic_istream, operator<<, basic... #include // Para cout, cerr #include // Para basic_stringstream #include // Para runtime_error #include #include "core/rendering/surface.hpp" // Para Surface #include "core/resources/resource_cache.hpp" // Para Resource::Cache #include "core/resources/resource_list.hpp" // Para Resource::List::get()->getPrintWidth #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 = {0.0F, 0.0F, frame_width, 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; } // Carga las animaciones desde un fichero YAML auto SurfaceAnimatedSprite::loadAnimationsFromYAML(const std::string& file_path, std::shared_ptr& surface, float& frame_width, float& frame_height) -> std::vector { std::vector animations; // Extract filename for logging const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1); try { // Load YAML file using ResourceHelper (supports both filesystem and pack) auto file_data = Resource::Helper::loadFile(file_path); if (file_data.empty()) { std::cerr << "Error: Unable to load animation file " << FILE_NAME << '\n'; throw std::runtime_error("Animation file not found: " + file_path); } printWithDots("Animation : ", FILE_NAME, "[ LOADED ]", Resource::List::get()->getPrintWidth()); // Parse YAML from string std::string yaml_content(file_data.begin(), file_data.end()); auto yaml = fkyaml::node::deserialize(yaml_content); // --- Parse global configuration --- if (yaml.contains("tileSetFile")) { auto tile_set_file = yaml["tileSetFile"].get_value(); surface = Resource::Cache::get()->getSurface(tile_set_file); } if (yaml.contains("frameWidth")) { frame_width = static_cast(yaml["frameWidth"].get_value()); } if (yaml.contains("frameHeight")) { frame_height = static_cast(yaml["frameHeight"].get_value()); } // Calculate sprite sheet parameters int frames_per_row = 1; int max_tiles = 1; if (surface) { frames_per_row = surface->getWidth() / static_cast(frame_width); const int W = surface->getWidth() / static_cast(frame_width); const int H = surface->getHeight() / static_cast(frame_height); max_tiles = W * H; } // --- Parse animations array --- if (yaml.contains("animations") && yaml["animations"].is_sequence()) { const auto& animations_node = yaml["animations"]; for (const auto& anim_node : animations_node) { AnimationData animation; // Parse animation name if (anim_node.contains("name")) { animation.name = anim_node["name"].get_value(); } // Parse speed (seconds per frame) if (anim_node.contains("speed")) { animation.speed = anim_node["speed"].get_value(); } // Parse loop frame index if (anim_node.contains("loop")) { animation.loop = anim_node["loop"].get_value(); } // Parse frames array 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); } animations.push_back(animation); } } } catch (const fkyaml::exception& e) { std::cerr << "YAML parsing error in " << FILE_NAME << ": " << e.what() << '\n'; throw; } catch (const std::exception& e) { std::cerr << "Error loading animation " << FILE_NAME << ": " << e.what() << '\n'; throw; } return animations; } // Constructor con bytes YAML del cache (parsing lazy) SurfaceAnimatedSprite::SurfaceAnimatedSprite(const AnimationResource& cached_data) { // Parsear YAML desde los bytes cargados en cache std::string yaml_content(cached_data.yaml_data.begin(), cached_data.yaml_data.end()); try { auto yaml = fkyaml::node::deserialize(yaml_content); // Variables para almacenar configuración global float frame_width = 0.0F; float frame_height = 0.0F; // --- Parse global configuration --- if (yaml.contains("tileSetFile")) { auto tile_set_file = yaml["tileSetFile"].get_value(); // Ahora SÍ podemos acceder al cache (ya está completamente cargado) surface_ = Resource::Cache::get()->getSurface(tile_set_file); } if (yaml.contains("frameWidth")) { frame_width = static_cast(yaml["frameWidth"].get_value()); } if (yaml.contains("frameHeight")) { frame_height = static_cast(yaml["frameHeight"].get_value()); } // Calculate sprite sheet parameters int frames_per_row = 1; int max_tiles = 1; if (surface_) { frames_per_row = surface_->getWidth() / static_cast(frame_width); const int W = surface_->getWidth() / static_cast(frame_width); const int H = surface_->getHeight() / static_cast(frame_height); max_tiles = W * H; } // --- Parse animations array --- if (yaml.contains("animations") && yaml["animations"].is_sequence()) { const auto& animations_node = yaml["animations"]; for (const auto& anim_node : animations_node) { AnimationData animation; // Parse animation name if (anim_node.contains("name")) { animation.name = anim_node["name"].get_value(); } // Parse speed (seconds per frame) if (anim_node.contains("speed")) { animation.speed = anim_node["speed"].get_value(); } // Parse loop frame index if (anim_node.contains("loop")) { animation.loop = anim_node["loop"].get_value(); } // Parse frames array 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); } animations_.push_back(animation); } } // Set dimensions setWidth(frame_width); setHeight(frame_height); // Inicializar con la primera animación si existe 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; } } // Obtiene el indice de la animación a partir del nombre auto SurfaceAnimatedSprite::getIndex(const std::string& name) -> int { auto index = -1; for (const auto& a : animations_) { index++; if (a.name == name) { return index; } } std::cout << "** Warning: could not find \"" << name.c_str() << "\" animation" << '\n'; return -1; } // Calcula el frame correspondiente a la animación (time-based) void SurfaceAnimatedSprite::animate(float delta_time) { if (animations_[current_animation_].speed <= 0.0F) { return; } // Acumula el tiempo transcurrido animations_[current_animation_].accumulated_time += delta_time; // Calcula el frame actual a partir del tiempo acumulado const int TARGET_FRAME = static_cast( animations_[current_animation_].accumulated_time / animations_[current_animation_].speed); // Si alcanza el final de la animación, maneja el loop if (TARGET_FRAME >= static_cast(animations_[current_animation_].frames.size())) { if (animations_[current_animation_].loop == -1) { // Si no hay loop, congela en el último frame animations_[current_animation_].current_frame = static_cast(animations_[current_animation_].frames.size()) - 1; animations_[current_animation_].completed = true; // Establece el clip del último frame if (animations_[current_animation_].current_frame >= 0) { setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); } } else { // Si hay loop, vuelve al frame indicado animations_[current_animation_].accumulated_time = static_cast(animations_[current_animation_].loop) * animations_[current_animation_].speed; animations_[current_animation_].current_frame = animations_[current_animation_].loop; // Establece el clip del frame de loop setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); } } else { // Actualiza el frame actual animations_[current_animation_].current_frame = TARGET_FRAME; // Establece el clip del frame actual if (animations_[current_animation_].current_frame >= 0 && animations_[current_animation_].current_frame < static_cast(animations_[current_animation_].frames.size())) { setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); } } } // Comprueba si ha terminado la animación auto SurfaceAnimatedSprite::animationIsCompleted() -> bool { return animations_[current_animation_].completed; } // Establece la animacion actual void SurfaceAnimatedSprite::setCurrentAnimation(const std::string& name) { const auto NEW_ANIMATION = getIndex(name); 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]); } } // Establece la animacion actual void SurfaceAnimatedSprite::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 SurfaceAnimatedSprite::update(float delta_time) { animate(delta_time); SurfaceMovingSprite::update(delta_time); } // Reinicia la animación void SurfaceAnimatedSprite::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 SurfaceAnimatedSprite::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]); }