#include "aocharmovie.h" #include "aoapplication.h" #include "file_functions.h" #include "misc_functions.h" AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) { ao_app = p_ao_app; preanim_timer = new QTimer(this); preanim_timer->setSingleShot(true); ticker = new QTimer(this); ticker->setTimerType(Qt::PreciseTimer); ticker->setSingleShot(false); connect(ticker, SIGNAL(timeout()), this, SLOT(movie_ticker())); // connect(m_movie, SIGNAL(frameChanged(int)), this, // SLOT(frame_change(int))); connect(preanim_timer, SIGNAL(timeout()), this, SLOT(preanim_done())); } void AOCharMovie::load_image(QString p_char, QString p_emote, QString emote_prefix) { #ifdef DEBUG_CHARMOVIE actual_time.restart(); #endif QString emote_path; QList pathlist; pathlist = { ao_app->get_image_suffix(ao_app->get_character_path( p_char, emote_prefix + p_emote)), // Default path ao_app->get_image_suffix(ao_app->get_character_path( p_char, emote_prefix + "/" + p_emote)), // Path check if it's categorized into a folder ao_app->get_image_suffix(ao_app->get_character_path( p_char, p_emote)), // Just use the non-prefixed image, animated or not ao_app->get_image_suffix( ao_app->get_theme_path("placeholder")), // Theme placeholder path ao_app->get_image_suffix(ao_app->get_default_theme_path( "placeholder")), // Default theme placeholder path }; for (QString path : pathlist) { if (file_exists(path)) { emote_path = path; break; } } this->clear(); ticker->stop(); preanim_timer->stop(); movie_frames.clear(); movie_delays.clear(); movie_effects.clear(); if (!file_exists(emote_path)) return; m_reader->setFileName(emote_path); // set format to apng if png supports animation if (emote_path.endsWith("png")) { m_reader->setFormat("apng"); if (!m_reader->supportsAnimation()) { m_reader->setFormat("png"); } } QPixmap f_pixmap = this->get_pixmap(m_reader->read()); int f_delay = m_reader->nextImageDelay(); frame = 0; max_frames = m_reader->imageCount(); this->set_frame(f_pixmap); this->show(); if (max_frames > 1) { movie_frames.append(f_pixmap); movie_delays.append(f_delay); } m_char = p_char; m_emote = emote_prefix + p_emote; if (network_strings.size() > 0) // our FX overwritten by networked ones this->load_network_effects(); else // Use default ini FX this->load_effects(); #ifdef DEBUG_CHARMOVIE qDebug() << max_frames << "Setting image to " << emote_path << "Time taken to process image:" << actual_time.elapsed(); actual_time.restart(); #endif } void AOCharMovie::load_effects() { movie_effects.clear(); movie_effects.resize(max_frames); for (int e_frame = 0; e_frame < max_frames; ++e_frame) { QString effect = ao_app->get_screenshake_frame(m_char, m_emote, e_frame); if (effect != "") { movie_effects[e_frame].append("shake"); } effect = ao_app->get_flash_frame(m_char, m_emote, e_frame); if (effect != "") { movie_effects[e_frame].append("flash"); } effect = ao_app->get_sfx_frame(m_char, m_emote, e_frame); if (effect != "") { movie_effects[e_frame].append("sfx^" + effect); } } } void AOCharMovie::load_network_effects() { movie_effects.clear(); movie_effects.resize(max_frames); // Order is important!!! QStringList effects_list = {"shake", "flash", "sfx^"}; // Determines which list is smaller - effects_list or network_strings - and // uses it as basis for the loop. This way, incomplete network_strings would // still be parsed, and excess/unaccounted for networked information is // omitted. int effects_size = qMin(effects_list.size(), network_strings.size()); for (int i = 0; i < effects_size; ++i) { QString netstring = network_strings.at(i); QStringList emote_splits = netstring.split("^"); foreach (QString emote, emote_splits) { QStringList parsed = emote.split("|"); if (parsed.size() <= 0 || parsed.at(0) != m_emote) continue; foreach (QString frame_data, parsed) { QStringList frame_split = frame_data.split("="); if (frame_split.size() <= 1) // We might still be hanging at the emote itself (entry 0). continue; int f_frame = frame_split.at(0).toInt(); if (f_frame >= max_frames) { qDebug() << "Warning: out of bounds" << effects_list[i] << "frame" << f_frame << "out of" << max_frames << "for" << m_char << m_emote; continue; } QString f_data = frame_split.at(1); if (f_data != "") { QString effect = effects_list[i]; if (effect == "sfx^") // Currently the only frame result that feeds us // data, let's yank it in. effect += f_data; qDebug() << effect << f_data << "frame" << f_frame << "for" << m_char << m_emote; movie_effects[f_frame].append(effect); } } } } } void AOCharMovie::play() { play_frame_effect(frame); if (max_frames <= 1) { if (play_once) ticker->start(60); } else ticker->start(this->get_frame_delay(movie_delays[frame])); } void AOCharMovie::play_pre(QString p_char, QString p_emote, int duration) { load_image(p_char, p_emote, ""); // As much as I'd like to screw around with [Time] durations modifying the // animation speed, I don't think I can reliably do that, not without looping // through all frames in the image at least - which causes lag. So for now it // simply ends the preanimation early instead. play_once = true; if (duration > 0) // It's -1 if there's no definition in [Time] for it. In which case, it // will let the animation run out in full. Duration 0 does the same. preanim_timer->start(duration * time_mod); // This timer will not fire if the animation // finishes earlier than that play(); } void AOCharMovie::play_talking(QString p_char, QString p_emote) { play_once = false; load_image(p_char, p_emote, "(b)"); play(); } void AOCharMovie::play_idle(QString p_char, QString p_emote) { play_once = false; load_image(p_char, p_emote, "(a)"); play(); } void AOCharMovie::play_frame_effect(int frame) { if (frame < max_frames) { foreach (QString effect, movie_effects[frame]) { if (effect == "shake") { shake(); #ifdef DEBUG_CHARMOVIE qDebug() << "Attempting to play shake on frame" << frame; #endif } if (effect == "flash") { flash(); #ifdef DEBUG_CHARMOVIE qDebug() << "Attempting to play flash on frame" << frame; #endif } if (effect.startsWith("sfx^")) { QString sfx = effect.section("^", 1); play_sfx(sfx); #ifdef DEBUG_CHARMOVIE qDebug() << "Attempting to play sfx" << sfx << "on frame" << frame; #endif } } } } void AOCharMovie::stop() { // for all intents and purposes, stopping is the same as hiding. at no point // do we want a frozen gif to display ticker->stop(); preanim_timer->stop(); this->hide(); } QPixmap AOCharMovie::get_pixmap(QImage image) { QPixmap f_pixmap; if (m_flipped) f_pixmap = QPixmap::fromImage(image.mirrored(true, false)); else f_pixmap = QPixmap::fromImage(image); // auto aspect_ratio = Qt::KeepAspectRatio; auto transform_mode = Qt::FastTransformation; if (f_pixmap.height() > f_h) // We are downscaling, use anti-aliasing. transform_mode = Qt::SmoothTransformation; f_pixmap = f_pixmap.scaledToHeight(f_h, transform_mode); this->resize(f_pixmap.size()); return f_pixmap; } void AOCharMovie::set_frame(QPixmap f_pixmap) { this->setPixmap(f_pixmap); QLabel::move( x + (f_w - f_pixmap.width()) / 2, y + (f_h - f_pixmap.height())); // Always center horizontally, always put // at the bottom vertically } void AOCharMovie::combo_resize(int w, int h) { QSize f_size(w, h); f_w = w; f_h = h; this->resize(f_size); } int AOCharMovie::get_frame_delay(int delay) { return static_cast(double(delay) * double(speed / 100)); } void AOCharMovie::move(int ax, int ay) { x = ax; y = ay; QLabel::move(x, y); } void AOCharMovie::movie_ticker() { ++frame; if (frame >= max_frames) { if (play_once) { preanim_done(); return; } else frame = 0; } // qint64 difference = elapsed - movie_delays[frame]; if (frame >= movie_frames.size()) { m_reader->jumpToImage(frame); movie_frames.resize(frame + 1); movie_frames[frame] = this->get_pixmap(m_reader->read()); movie_delays.resize(frame + 1); movie_delays[frame] = m_reader->nextImageDelay(); } #ifdef DEBUG_CHARMOVIE qDebug() << frame << movie_delays[frame] << "actual time taken from last frame:" << actual_time.restart(); #endif this->set_frame(movie_frames[frame]); play_frame_effect(frame); ticker->setInterval(this->get_frame_delay(movie_delays[frame])); } void AOCharMovie::preanim_done() { ticker->stop(); preanim_timer->stop(); done(); }