#include "core/rendering/animatedsprite.h" #include // for cout #include // for basic_stringstream #include "core/rendering/texture.h" // for Texture #include "core/resources/resource_helper.h" // for loadFile (pack + filesystem fallback) namespace { // Normalitza CRLF: fitxers .ani amb terminadors de Windows fan que // line == "[animation]" no faci match i el parser entri en bucle // infinit / no carregui cap animació. void stripCr(std::string &s) { if (!s.empty() && s.back() == '\r') { s.pop_back(); } } void parseFramesList(const std::string &value, Animation &buffer, int frame_width, int frame_height, int frames_per_row, int max_tiles) { std::stringstream ss(value); std::string tmp; SDL_Rect rect = {0, 0, frame_width, frame_height}; while (getline(ss, tmp, ',')) { const int NUM_TILE = std::stoi(tmp) > max_tiles ? 0 : std::stoi(tmp); rect.x = (NUM_TILE % frames_per_row) * frame_width; rect.y = (NUM_TILE / frames_per_row) * frame_height; buffer.frames.push_back(rect); } } void parseAnimationField(const std::string &line, int pos, Animation &buffer, int frame_width, int frame_height, int frames_per_row, int max_tiles, const std::string &filename) { const std::string KEY = line.substr(0, pos); const std::string VALUE = line.substr(pos + 1, line.length()); if (KEY == "name") { buffer.name = VALUE; } else if (KEY == "speed") { buffer.speed = std::stoi(VALUE); // Time-based: el valor del .ani s'expressa en "ticks per frame // d'animació" (assumint 60 Hz). El camp `speed` (int) es manté per al // fallback frame-based; el nou `step_duration_s` (float) és el que // gasta animate(dt). buffer.step_duration_s = static_cast(buffer.speed) / 60.0F; } else if (KEY == "loop") { buffer.loop = std::stoi(VALUE); } else if (KEY == "frames") { parseFramesList(VALUE, buffer, frame_width, frame_height, frames_per_row, max_tiles); } else { std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << KEY.c_str() << "\"" << '\n'; } } auto parseAnimationBlock(std::istream &file, int frame_width, int frame_height, int frames_per_row, int max_tiles, const std::string &filename) -> Animation { Animation buffer; buffer.speed = 0; buffer.step_duration_s = 0.0F; buffer.loop = -1; buffer.counter = 0; buffer.current_frame = 0; buffer.completed = false; buffer.time_accumulator_s = 0.0F; std::string line; do { if (!std::getline(file, line)) { break; } stripCr(line); int pos = line.find('='); if (pos != (int)std::string::npos) { parseAnimationField(line, pos, buffer, frame_width, frame_height, frames_per_row, max_tiles, filename); } } while (line != "[/animation]"); return buffer; } void parseGlobalField(const std::string &line, int pos, int &frames_per_row, int &frame_width, int &frame_height, int &max_tiles, const Texture *texture, const std::string &filename) { const std::string KEY = line.substr(0, pos); const std::string VALUE = line.substr(pos + 1, line.length()); if (KEY == "framesPerRow") { frames_per_row = std::stoi(VALUE); } else if (KEY == "frameWidth") { frame_width = std::stoi(VALUE); } else if (KEY == "frameHeight") { frame_height = std::stoi(VALUE); } else { std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << KEY.c_str() << "\"" << '\n'; } if (frames_per_row == 0 && frame_width > 0) { frames_per_row = texture->getWidth() / frame_width; } if (max_tiles == 0 && frame_width > 0 && frame_height > 0) { const int W = texture->getWidth() / frame_width; const int H = texture->getHeight() / frame_height; max_tiles = W * H; } } } // namespace // Parser compartido: lee un istream con el formato .ani static auto parseAnimationStream(std::istream &file, Texture *texture, const std::string &filename, bool verbose) -> AnimatedSpriteData { AnimatedSpriteData as; as.texture = texture; int frames_per_row = 0; int frame_width = 0; int frame_height = 0; int max_tiles = 0; std::string line; if (verbose) { std::cout << "Animation loaded: " << filename << '\n'; } while (std::getline(file, line)) { stripCr(line); if (line == "[animation]") { as.animations.push_back(parseAnimationBlock(file, frame_width, frame_height, frames_per_row, max_tiles, filename)); } else { int pos = line.find('='); if (pos != (int)std::string::npos) { parseGlobalField(line, pos, frames_per_row, frame_width, frame_height, max_tiles, texture, filename); } } } return as; } // Carga la animación desde un fichero (vía ResourceHelper: pack si està inicialitzat, filesystem si no) auto loadAnimationFromFile(Texture *texture, const std::string &file_path, bool verbose) -> AnimatedSpriteData { const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1); auto bytes = ResourceHelper::loadFile(file_path); if (bytes.empty()) { if (verbose) { std::cout << "Warning: Unable to open " << FILE_NAME.c_str() << " file" << '\n'; } AnimatedSpriteData as; as.texture = texture; return as; } return loadAnimationFromMemory(texture, bytes, FILE_NAME, verbose); } // Carga la animación desde bytes en memoria auto loadAnimationFromMemory(Texture *texture, const std::vector &bytes, const std::string &name_for_logs, bool verbose) -> AnimatedSpriteData { if (bytes.empty()) { AnimatedSpriteData as; as.texture = texture; return as; } std::string content(reinterpret_cast(bytes.data()), bytes.size()); std::stringstream ss(content); return parseAnimationStream(ss, texture, name_for_logs, verbose); } // Constructor AnimatedSprite::AnimatedSprite(Texture *texture, SDL_Renderer *renderer, const std::string &file, const std::vector *buffer) : current_animation_(0) { // Copia los punteros setTexture(texture); setRenderer(renderer); // Carga las animaciones if (!file.empty()) { AnimatedSpriteData as = loadAnimationFromFile(texture, file); // Copia los datos de las animaciones animation_.insert(animation_.end(), as.animations.begin(), as.animations.end()); } else if (buffer != nullptr) { loadFromVector(buffer); } } // Constructor AnimatedSprite::AnimatedSprite(SDL_Renderer *renderer, AnimatedSpriteData *data) : current_animation_(0) { // Copia los punteros setTexture(data->texture); setRenderer(renderer); // Copia los datos de las animaciones this->animation_.insert(this->animation_.end(), data->animations.begin(), data->animations.end()); } // Destructor AnimatedSprite::~AnimatedSprite() { for (auto &a : animation_) { a.frames.clear(); } animation_.clear(); } // Obtiene el indice de la animación a partir del nombre auto AnimatedSprite::getIndex(const std::string &name) -> int { int index = -1; for (const auto &a : animation_) { index++; if (a.name == name) { return index; } } std::cout << "** Warning: could not find \"" << name.c_str() << "\" animation" << '\n'; return -1; } // Avança l'acumulador i calcula el frame actual a partir de `step_duration_s`. void AnimatedSprite::animate(float dt_s) { Animation &anim = animation_[current_animation_]; if (!enabled_ || anim.step_duration_s <= 0.0F) { return; } anim.time_accumulator_s += dt_s; anim.current_frame = static_cast(anim.time_accumulator_s / anim.step_duration_s); if (anim.current_frame >= (int)anim.frames.size()) { if (anim.loop == -1) { anim.current_frame = anim.frames.size(); anim.completed = true; } else { anim.time_accumulator_s = 0.0F; anim.current_frame = anim.loop; } } else { setSpriteClip(anim.frames[anim.current_frame]); } } // Obtiene el numero de frames de la animación actual auto AnimatedSprite::getNumFrames() -> int { return (int)animation_[current_animation_].frames.size(); } // Establece el frame actual de la animación void AnimatedSprite::setCurrentFrame(int num) { // Descarta valores fuera de rango if (num >= (int)animation_[current_animation_].frames.size()) { num = 0; } // Cambia el valor de la variable animation_[current_animation_].current_frame = num; animation_[current_animation_].counter = 0; // Escoge el frame correspondiente de la animación setSpriteClip(animation_[current_animation_].frames[animation_[current_animation_].current_frame]); } // Establece el valor del contador void AnimatedSprite::setAnimationCounter(const std::string &name, int num) { animation_[getIndex(name)].counter = num; } // Establece la velocidad de una animación void AnimatedSprite::setAnimationSpeed(const std::string &name, int speed) { animation_[getIndex(name)].counter = speed; } // Establece la velocidad de una animación void AnimatedSprite::setAnimationSpeed(int index, int speed) { animation_[index].counter = speed; } // Establece si la animación se reproduce en bucle void AnimatedSprite::setAnimationLoop(const std::string &name, int loop) { animation_[getIndex(name)].loop = loop; } // Establece si la animación se reproduce en bucle void AnimatedSprite::setAnimationLoop(int index, int loop) { animation_[index].loop = loop; } // Establece el valor de la variable void AnimatedSprite::setAnimationCompleted(const std::string &name, bool value) { animation_[getIndex(name)].completed = value; } // OLD - Establece el valor de la variable void AnimatedSprite::setAnimationCompleted(int index, bool value) { animation_[index].completed = value; } // Comprueba si ha terminado la animación auto AnimatedSprite::animationIsCompleted() -> bool { return animation_[current_animation_].completed; } // Devuelve el rectangulo de una animación y frame concreto auto AnimatedSprite::getAnimationClip(const std::string &name, Uint8 index) -> SDL_Rect { return animation_[getIndex(name)].frames[index]; } // Devuelve el rectangulo de una animación y frame concreto auto AnimatedSprite::getAnimationClip(int index_a, Uint8 index_f) -> SDL_Rect { return animation_[index_a].frames[index_f]; } // Carga la animación desde un vector (reutiliza parseAnimationStream via stringstream) auto AnimatedSprite::loadFromVector(const std::vector *source) -> bool { std::stringstream ss; for (const auto &line : *source) { ss << line << '\n'; } AnimatedSpriteData as = parseAnimationStream(ss, texture_, "", false); animation_.insert(animation_.end(), as.animations.begin(), as.animations.end()); // El primer frame lleva frame_width/frame_height en .w/.h — los usamos como rect por defecto if (!as.animations.empty() && !as.animations.front().frames.empty()) { const auto &first = as.animations.front().frames.front(); setRect({0, 0, first.w, first.h}); } return true; } // Establece la animacion actual void AnimatedSprite::setCurrentAnimation(const std::string &name) { const int NEW_ANIMATION = getIndex(name); if (current_animation_ != NEW_ANIMATION) { current_animation_ = NEW_ANIMATION; animation_[current_animation_].current_frame = 0; animation_[current_animation_].counter = 0; animation_[current_animation_].completed = false; } } // Establece la animacion actual void AnimatedSprite::setCurrentAnimation(int index) { const int NEW_ANIMATION = index; if (current_animation_ != NEW_ANIMATION) { current_animation_ = NEW_ANIMATION; animation_[current_animation_].current_frame = 0; animation_[current_animation_].counter = 0; animation_[current_animation_].completed = false; } } // animate(dt) + MovingSprite::update(dt) (move + rotació) void AnimatedSprite::update(float dt_s) { animate(dt_s); MovingSprite::update(dt_s); } // Establece el rectangulo para un frame de una animación void AnimatedSprite::setAnimationFrames(Uint8 index_animation, Uint8 /*index_frame*/, int x, int y, int w, int h) { animation_[index_animation].frames.push_back({x, y, w, h}); } // OLD - Establece el contador para todas las animaciones void AnimatedSprite::setAnimationCounter(int value) { for (auto &a : animation_) { a.counter = value; } } // Reinicia la animación void AnimatedSprite::resetAnimation() { animation_[current_animation_].current_frame = 0; animation_[current_animation_].counter = 0; animation_[current_animation_].completed = false; }