aboutsummaryrefslogtreecommitdiff
path: root/src/aolayer.cpp
diff options
context:
space:
mode:
authorin1tiate <32779090+in1tiate@users.noreply.github.com>2021-01-19 05:32:11 -0600
committerGitHub <noreply@github.com>2021-01-19 14:32:11 +0300
commit894b2b2a0e6bb2744e92a2d8ed363a39d7ae59b1 (patch)
treed5c0868152ca9958173f5e4f857a2fd98cfce56b /src/aolayer.cpp
parent21b4aa5072755923f5f555605ef4dc2b01857579 (diff)
Consolidate AOScene, AOMovie, and AOCharMovie into one class, add support for (c) animations, implement emote continuity, add scaling overrides to all layer types, allow for stretch-to-fill images, allow for additional effect configuration (#302)
* Rewrite AOScene and remove the need for AOMovie and AOCharMovie by consolidation * Rename AOScene to AOLayer, apply suggestions to improve functionality * Implement suggested change to allocation * Switch from pointer to field, fix ui_vp_player_char not resetting play_once * Use the variable gif_name instead of the string "gif_name" Oops. * Total rewrite of AOLayer (again) * Add support for (c) animations, do some housekeeping * allow themes to override misc chatboxes * add support for pulling InterfaceLayer elements from theme/misc * mistakes were made * move all frame fx functionality to CharLayer subclass * virtual functions are cool mkay * remove evidence of my incompetence * allow themes to override font design inis under theme/misc * Proper support for theme/misc chatbox, fixes * Fix chatbox dimensions not updating and inline color causing missingnos * rename chat markdown to chat markup * add missing misc overrides * quick hotfix for chatblank and misc overrides * Fix oversight with backgrounds causing them to be culled * Same as last commit but for FG layer * amend comment to explain impossible shenanigans * Adjust ForegroundLayer to take charname rather than miscname, allow for checking in char folder * fix an incredibly embarrassing pathing bug * add scaling overrides for all layer types, parent orphaned viewport elements to the viewport * stupid fix because themes use "custom.png" as a button * switch to .append() * Revert "Merge branch 'aoscene_rewrite' of https://github.com/AttorneyOnline/AO2-Client into aoscene_rewrite" This reverts commit bdeb1bff7639d522031aab3c133a83b0e2a291df, reversing changes made to 125ee63b97a6f6c156e69525d88fddc625e7a978. * switch to .append() (again) * move function call to fix showname length calculation error * fix nonlooping character animations being broken Again * unparent elements from the viewport and do fancy masking arithmetic instead * use override keyword * move scaling override to char.ini, allow stretching, restructure effect property loading * fix some redundancy * unparent chat_arrow from chatbox to prevent accidental masking * at no point do we want a frozen gif to display * overhaul how wtce is handled * oops * also let sounds be pulled from theme miscs * i should probably compile before i push * actually make it work * don't check a default bg * readd 1x1 stretch thing * actually the 1x1 thing was a bad idea * Add missing parenthesis * Use load_image instead of play play is a nonexistent method now * Remote shout_message and usages because it does nothing * Remove multiple redefinitions * Add in missing brackets and indent to fix build I have know idea what this does but it brings fear * fix build error * fix chat arrow and remove duped code * remove more duped code and fix misc themes * only update chat_arrow when needed * consolidate log_chatmessage and display_log_chatmessage Co-authored-by: scatterflower <marisaposs@gameboyprinter.moe> Co-authored-by: Skye Deving <76892045+skyedeving@users.noreply.github.com> Co-authored-by: oldmud0 <oldmud0@users.noreply.github.com>
Diffstat (limited to 'src/aolayer.cpp')
-rw-r--r--src/aolayer.cpp577
1 files changed, 577 insertions, 0 deletions
diff --git a/src/aolayer.cpp b/src/aolayer.cpp
new file mode 100644
index 00000000..de8a451c
--- /dev/null
+++ b/src/aolayer.cpp
@@ -0,0 +1,577 @@
+#include "aolayer.h"
+
+#include "aoapplication.h"
+#include "file_functions.h"
+#include "misc_functions.h"
+
+AOLayer::AOLayer(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent)
+{
+ ao_app = p_ao_app;
+
+ // used for culling images when their max_duration is exceeded
+ shfx_timer = new QTimer(this);
+ shfx_timer->setTimerType(Qt::PreciseTimer);
+ shfx_timer->setSingleShot(true);
+ connect(shfx_timer, SIGNAL(timeout()), this, SLOT(shfx_timer_done()));
+
+ ticker = new QTimer(this);
+ ticker->setTimerType(Qt::PreciseTimer);
+ ticker->setSingleShot(false);
+ connect(ticker, SIGNAL(timeout()), this, SLOT(movie_ticker()));
+
+ preanim_timer = new QTimer(this);
+ preanim_timer->setSingleShot(true);
+ connect(preanim_timer, SIGNAL(timeout()), this, SLOT(preanim_done()));
+}
+
+BackgroundLayer::BackgroundLayer(QWidget *p_parent, AOApplication *p_ao_app)
+ : AOLayer(p_parent, p_ao_app)
+{
+}
+ForegroundLayer::ForegroundLayer(QWidget *p_parent, AOApplication *p_ao_app)
+ : AOLayer(p_parent, p_ao_app)
+{
+}
+CharLayer::CharLayer(QWidget *p_parent, AOApplication *p_ao_app)
+ : AOLayer(p_parent, p_ao_app)
+{
+}
+EffectLayer::EffectLayer(QWidget *p_parent, AOApplication *p_ao_app)
+ : AOLayer(p_parent, p_ao_app)
+{
+}
+InterjectionLayer::InterjectionLayer(QWidget *p_parent, AOApplication *p_ao_app)
+ : AOLayer(p_parent, p_ao_app)
+{
+}
+InterfaceLayer::InterfaceLayer(QWidget *p_parent, AOApplication *p_ao_app)
+ : AOLayer(p_parent, p_ao_app)
+{
+}
+
+QString AOLayer::find_image(QList<QString> p_list)
+{
+ QString image_path;
+ for (QString path : p_list) {
+#ifdef DEBUG_MOVIE
+ qDebug() << "checking path " << path;
+#endif
+ if (file_exists(path)) {
+ image_path = path;
+#ifdef DEBUG_MOVIE
+ qDebug() << "found path " << path;
+#endif
+ break;
+ }
+ }
+ return image_path;
+}
+
+QPixmap AOLayer::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;
+ if (f_pixmap.height() > f_h) // We are downscaling, use anti-aliasing.
+ transform_mode = Qt::SmoothTransformation;
+ if (stretch)
+ f_pixmap = f_pixmap.scaled(f_w, f_h);
+ else
+ f_pixmap = f_pixmap.scaledToHeight(f_h, transform_mode);
+ this->resize(f_pixmap.size());
+
+ return f_pixmap;
+}
+
+void AOLayer::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
+ this->setMask(
+ QRegion((f_pixmap.width() - f_w) / 2, (f_pixmap.height() - f_h) / 2, f_w,
+ f_h)); // make sure we don't escape the area we've been given
+}
+
+void AOLayer::combo_resize(int w, int h)
+{
+ QSize f_size(w, h);
+ f_w = w;
+ f_h = h;
+ this->resize(f_size);
+}
+
+int AOLayer::get_frame_delay(int delay)
+{
+ return static_cast<int>(double(delay) * double(speed / 100));
+}
+
+void AOLayer::move(int ax, int ay)
+{
+ x = ax;
+ y = ay;
+ QLabel::move(x, y);
+}
+
+void BackgroundLayer::load_image(QString p_filename)
+{
+ play_once = false;
+ cull_image = false;
+ QString design_path = ao_app->get_background_path("design.ini");
+ transform_mode =
+ ao_app->get_scaling(ao_app->read_design_ini("scaling", design_path));
+ stretch = ao_app->read_design_ini("stretch", design_path).startsWith("true");
+ qDebug() << "[BackgroundLayer] BG loaded: " << p_filename;
+ start_playback(ao_app->get_image_suffix(ao_app->get_background_path(p_filename)));
+}
+
+void ForegroundLayer::load_image(QString p_filename, QString p_charname)
+{
+ play_once = false;
+ cull_image = false;
+ miscname = ao_app->get_char_shouts(p_charname);
+ qDebug() << "[ForegroundLayer] FG loaded: " << p_filename;
+ QList<QString> pathlist = {
+ ao_app->get_image_suffix(ao_app->get_character_path(
+ p_charname, p_filename)), // first check the character folder
+ ao_app->get_image_suffix(ao_app->get_theme_path(
+ "misc/" + miscname + "/" +
+ p_filename)), // then check our theme's misc directory
+ ao_app->get_image_suffix(ao_app->get_misc_path(
+ miscname, p_filename)), // then check our global misc folder
+ ao_app->get_image_suffix(
+ ao_app->get_theme_path(p_filename)), // then check the user's theme
+ ao_app->get_image_suffix(ao_app->get_default_theme_path(
+ p_filename))}; // and finally check the default theme
+ start_playback(find_image(pathlist));
+}
+
+void CharLayer::load_image(QString p_filename, QString p_charname,
+ int p_duration, bool p_is_preanim)
+{
+ duration = p_duration;
+ cull_image = false;
+ force_continuous = false;
+ transform_mode = ao_app->get_scaling(
+ ao_app->get_emote_property(p_charname, p_filename, "scaling"));
+ stretch = ao_app->get_emote_property(p_charname, p_filename, "stretch")
+ .startsWith(true);
+ if ((p_charname == last_char) &&
+ ((p_filename == last_emote) ||
+ (p_filename.mid(3, -1) == last_emote.mid(3, -1))) &&
+ (!is_preanim) && (!was_preanim)) {
+ continuous = true;
+ force_continuous = true;
+ }
+ else {
+ continuous = false;
+ force_continuous = true;
+ }
+ prefix = "";
+ current_emote = p_filename;
+ was_preanim = is_preanim;
+ m_char = p_charname;
+ m_emote = current_emote;
+ last_char = p_charname;
+ last_emote = current_emote;
+ last_prefix = prefix;
+ is_preanim = p_is_preanim;
+ if ((p_filename.left(3) == "(a)") || (p_filename.left(3) == "(b)")) {
+ prefix = p_filename.left(3);
+ current_emote = p_filename.mid(3, -1);
+ }
+ else if ((duration > 0) || (p_filename.left(3) == "(c)")) {
+ if (p_filename.left(3) == "(c)") {
+ prefix = "(c)";
+ current_emote = p_filename.mid(3, -1);
+ }
+ is_preanim = true;
+ play_once = true;
+ preanim_timer->start(duration * tick_ms);
+ }
+ qDebug() << "[CharLayer] anim loaded: prefix " << prefix << " filename "
+ << current_emote << " from character: " << p_charname
+ << " continuous: " << continuous;
+ QList<QString> pathlist = {
+ ao_app->get_image_suffix(ao_app->get_character_path(
+ p_charname, prefix + current_emote)), // Default path
+ ao_app->get_image_suffix(ao_app->get_character_path(
+ p_charname,
+ prefix + "/" + current_emote)), // Path check if it's categorized
+ // into a folder
+ ao_app->get_image_suffix(ao_app->get_character_path(
+ p_charname,
+ current_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
+ this->start_playback(find_image(pathlist));
+}
+
+void InterjectionLayer::load_image(QString p_filename, QString p_charname,
+ QString p_miscname)
+{
+ continuous = false;
+ force_continuous = true;
+ play_once = true;
+ transform_mode = ao_app->get_misc_scaling(p_miscname);
+ QList<QString> pathlist = {
+ ao_app->get_image_suffix(ao_app->get_character_path(
+ p_charname, p_filename)), // Character folder
+ ao_app->get_image_suffix(ao_app->get_theme_path(
+ "misc/" + p_miscname + "/" + p_filename)), // Theme misc path
+ ao_app->get_image_suffix(
+ ao_app->get_misc_path(p_miscname, p_filename)), // Misc path
+ ao_app->get_image_suffix(
+ ao_app->get_theme_path(p_filename)), // Theme path
+ ao_app->get_image_suffix(
+ ao_app->get_default_theme_path(p_filename)), // Default theme path
+ ao_app->get_image_suffix(
+ ao_app->get_theme_path("placeholder")), // Placeholder path
+ ao_app->get_image_suffix(ao_app->get_default_theme_path(
+ "placeholder")), // Default placeholder path
+ };
+ QString final_image = find_image(pathlist);
+ if (final_image == ao_app->get_theme_path("custom.png") ||
+ final_image == ao_app->get_default_theme_path("custom.png") ||
+ final_image == ao_app->get_theme_path("witnesstestimony.png") ||
+ final_image == ao_app->get_default_theme_path("witnesstestimony.png") ||
+ final_image == ao_app->get_theme_path("crossexamination.png") ||
+ final_image == ao_app->get_default_theme_path("crossexamination.png"))
+ // stupid exceptions because themes are stupid
+ final_image = find_image(
+ {ao_app->get_image_suffix(ao_app->get_theme_path("placeholder")),
+ ao_app->get_image_suffix(ao_app->get_default_theme_path("placeholder"))});
+ start_playback(final_image);
+}
+
+void EffectLayer::load_image(QString p_filename, bool p_looping)
+{
+ if (p_looping)
+ play_once = false;
+ else
+ play_once = true;
+ continuous = false;
+ force_continuous = true;
+ start_playback(p_filename); // handled in its own file before we see it
+}
+
+void InterfaceLayer::load_image(QString p_filename, QString p_miscname)
+{
+ transform_mode = ao_app->get_misc_scaling(p_miscname);
+ QList<QString> pathlist = {
+ ao_app->get_image_suffix(ao_app->get_theme_path(
+ "misc/" + p_miscname + "/" +
+ p_filename)), // first check our theme's misc directory
+ ao_app->get_image_suffix(ao_app->get_misc_path(
+ p_miscname, p_filename)), // then check our global misc folder
+ ao_app->get_image_suffix(ao_app->get_theme_path(
+ p_filename)), // then check the user's theme for a default image
+ ao_app->get_image_suffix(ao_app->get_default_theme_path(
+ p_filename))}; // and finally check the default theme
+ start_playback(find_image(pathlist));
+}
+
+void CharLayer::start_playback(QString p_image)
+{
+ movie_effects.clear();
+ AOLayer::start_playback(p_image);
+ if (network_strings.size() > 0) // our FX overwritten by networked ones
+ load_network_effects();
+ else // Use default ini FX
+ load_effects();
+}
+
+void AOLayer::start_playback(QString p_image)
+{
+#ifdef DEBUG_MOVIE
+ actual_time.restart();
+#endif
+ this->clear();
+ freeze();
+ movie_frames.clear();
+ movie_delays.clear();
+
+ if (!file_exists(p_image))
+ return;
+
+ QString scaling_override =
+ ao_app->read_design_ini("scaling", p_image + ".ini");
+ if (scaling_override != "")
+ transform_mode = ao_app->get_scaling(scaling_override);
+ QString stretch_override =
+ ao_app->read_design_ini("stretch", p_image + ".ini");
+ if (stretch_override != "")
+ stretch = stretch_override.startsWith("true");
+
+ qDebug() << "stretch:" << stretch << "filename:" << p_image;
+ m_reader.setFileName(p_image);
+ if (m_reader.loopCount() == 0)
+ play_once = true;
+ if ((last_path == p_image) && (!force_continuous))
+ continuous = true;
+ else if ((last_path != p_image) && !force_continuous)
+ continuous = false;
+ if (!continuous)
+ frame = 0;
+ force_continuous = false;
+ last_max_frames = max_frames;
+ max_frames = m_reader.imageCount();
+ if (((continuous) && (max_frames != last_max_frames)) || max_frames == 0) {
+ frame = 0;
+ continuous = false;
+ }
+ // CANTFIX: this causes a slight hitch
+ // The correct way of doing this would be to use QImageReader::jumpToImage()
+ // and populate missing data in the movie ticker when it's needed. This is
+ // unforunately completely impossible, because QImageReader::jumpToImage() is
+ // not implemented in any image format AO2 is equipped to use. Instead, the
+ // default behavior is used - that is, absolutely nothing.
+ if (continuous) {
+ for (int i = frame; i--;) {
+ if (i <= -1)
+ break;
+ QPixmap l_pixmap = this->get_pixmap(m_reader.read());
+ int l_delay = m_reader.nextImageDelay();
+ movie_frames.append(l_pixmap);
+ movie_delays.append(l_delay);
+ // qDebug() << "appending delay of " << l_delay;
+ }
+ }
+ // qDebug() << "CONT: " << continuous << " MAX: " << max_frames
+ // << " LAST MAX: " << last_max_frames << " FRAME: " << frame;
+ QPixmap f_pixmap = this->get_pixmap(m_reader.read());
+ int f_delay = m_reader.nextImageDelay();
+
+ this->set_frame(f_pixmap);
+ this->show();
+ if (max_frames > 1) {
+ movie_frames.append(f_pixmap);
+ movie_delays.append(f_delay);
+ }
+ else if (max_frames <= 1) {
+ duration = static_duration;
+ play_once = false;
+#ifdef DEBUG_MOVIE
+ qDebug() << "max_frames is <= 1, using static duration";
+#endif
+ }
+ if (duration > 0 && cull_image == true)
+ shfx_timer->start(duration);
+ play();
+#ifdef DEBUG_MOVIE
+ qDebug() << max_frames << "Setting image to " << image_path
+ << "Time taken to process image:" << actual_time.elapsed();
+
+ actual_time.restart();
+#endif
+}
+
+void CharLayer::play()
+{
+ play_frame_effect(frame);
+ AOLayer::play();
+}
+
+void AOLayer::play()
+{
+ if (max_frames <= 1) {
+ if (play_once)
+ ticker->start(tick_ms);
+ else
+ this->freeze();
+ }
+ else
+ ticker->start(this->get_frame_delay(movie_delays[frame]));
+}
+
+void AOLayer::set_play_once(bool p_play_once) { play_once = p_play_once; }
+void AOLayer::set_cull_image(bool p_cull_image) { cull_image = p_cull_image; }
+void AOLayer::set_static_duration(int p_static_duration)
+{
+ static_duration = p_static_duration;
+}
+void AOLayer::set_max_duration(int p_max_duration)
+{
+ max_duration = p_max_duration;
+}
+
+void CharLayer::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 CharLayer::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("^");
+ for (const 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 || f_frame < 0) {
+ qDebug() << "Warning: out of bounds" << effects_list[i] << "frame"
+ << f_frame << "out of" << max_frames << "for" << 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_emote;
+ movie_effects[f_frame].append(effect);
+ }
+ }
+ }
+ }
+}
+
+void CharLayer::play_frame_effect(int p_frame)
+{
+ if (p_frame < max_frames) {
+ foreach (QString effect, movie_effects[p_frame]) {
+ if (effect == "shake") {
+ shake();
+#ifdef DEBUG_MOVIE
+ qDebug() << "Attempting to play shake on frame" << frame;
+#endif
+ }
+
+ if (effect == "flash") {
+ flash();
+#ifdef DEBUG_MOVIE
+ qDebug() << "Attempting to play flash on frame" << frame;
+#endif
+ }
+
+ if (effect.startsWith("sfx^")) {
+ QString sfx = effect.section("^", 1);
+ play_sfx(sfx);
+#ifdef DEBUG_MOVIE
+ qDebug() << "Attempting to play sfx" << sfx << "on frame" << frame;
+#endif
+ }
+ }
+ }
+}
+
+void AOLayer::stop()
+{
+ // for all intents and purposes, stopping is the same as hiding. at no point
+ // do we want a frozen gif to display
+ this->freeze();
+ this->hide();
+}
+
+void AOLayer::freeze()
+{
+ // aT nO pOiNt Do We WaNt A fRoZeN gIf To DiSpLaY
+ ticker->stop();
+ preanim_timer->stop();
+ shfx_timer->stop();
+}
+
+void CharLayer::movie_ticker()
+{
+ AOLayer::movie_ticker();
+ play_frame_effect(frame);
+}
+
+void AOLayer::movie_ticker()
+{
+ ++frame;
+ if ((frame >= max_frames) && (max_frames > 1)) {
+ if (play_once) {
+ if (cull_image)
+ this->stop();
+ else
+ this->freeze();
+ preanim_done();
+ return;
+ }
+ else
+ frame = 0;
+ }
+ // qint64 difference = elapsed - movie_delays[frame];
+ if (frame >= movie_frames.size()) {
+ movie_frames.append(this->get_pixmap(m_reader.read()));
+ movie_delays.append(m_reader.nextImageDelay());
+ }
+
+#ifdef DEBUG_MOVIE
+ qDebug() << frame << movie_delays[frame]
+ << "actual time taken from last frame:" << actual_time.restart();
+#endif
+
+ this->set_frame(movie_frames[frame]);
+ ticker->setInterval(this->get_frame_delay(movie_delays[frame]));
+}
+
+void CharLayer::preanim_done()
+{
+ if (is_preanim)
+ AOLayer::preanim_done();
+ else
+ return;
+}
+
+void AOLayer::preanim_done()
+{
+ ticker->stop();
+ preanim_timer->stop();
+ done();
+}
+
+void AOLayer::shfx_timer_done()
+{
+ this->stop();
+#ifdef DEBUG_MOVIE
+ qDebug() << "shfx timer signaled done";
+#endif
+ // signal connected to courtroom object, let it figure out what to do
+ done();
+}