diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index d056f05..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "bin/base/themes"]
- path = bin/base/themes
- url = https://github.com/AttorneyOnline/AO2-Themes.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index cbd32fa..b1f2529 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,8 +38,10 @@ add_executable(Attorney_Online
src/aoevidencedisplay.h
src/aoimage.cpp
src/aoimage.h
- src/aolayer.cpp
- src/aolayer.h
+ src/animationlayer.cpp
+ src/animationlayer.h
+ src/animationloader.h
+ src/animationloader.cpp
src/aomusicplayer.cpp
src/aomusicplayer.h
src/aopacket.cpp
diff --git a/bin/base/misc/default/chatbox.png b/bin/base/misc/default/chatbox.png
deleted file mode 100644
index 0c3bae1..0000000
Binary files a/bin/base/misc/default/chatbox.png and /dev/null differ
diff --git a/bin/base/misc/default/holdit.opus b/bin/base/misc/default/holdit.opus
deleted file mode 100644
index a454be8..0000000
Binary files a/bin/base/misc/default/holdit.opus and /dev/null differ
diff --git a/bin/base/misc/default/holdit_bubble.gif b/bin/base/misc/default/holdit_bubble.gif
deleted file mode 100644
index 536f8cd..0000000
Binary files a/bin/base/misc/default/holdit_bubble.gif and /dev/null differ
diff --git a/bin/base/misc/default/objection.opus b/bin/base/misc/default/objection.opus
deleted file mode 100644
index 7e15d05..0000000
Binary files a/bin/base/misc/default/objection.opus and /dev/null differ
diff --git a/bin/base/misc/default/objection_bubble.gif b/bin/base/misc/default/objection_bubble.gif
deleted file mode 100644
index 22c2169..0000000
Binary files a/bin/base/misc/default/objection_bubble.gif and /dev/null differ
diff --git a/bin/base/misc/default/takethat.opus b/bin/base/misc/default/takethat.opus
deleted file mode 100644
index 2f143e1..0000000
Binary files a/bin/base/misc/default/takethat.opus and /dev/null differ
diff --git a/bin/base/misc/default/takethat_bubble.gif b/bin/base/misc/default/takethat_bubble.gif
deleted file mode 100644
index 795552a..0000000
Binary files a/bin/base/misc/default/takethat_bubble.gif and /dev/null differ
diff --git a/bin/base/themes b/bin/base/themes
deleted file mode 160000
index 32a130d..0000000
--- a/bin/base/themes
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 32a130d1a35220b27deecf22f59c83e92cac1fee
diff --git a/data/ui/options_dialog.ui b/data/ui/options_dialog.ui
index db6db72..19100cd 100644
--- a/data/ui/options_dialog.ui
+++ b/data/ui/options_dialog.ui
@@ -39,9 +39,9 @@
0
- 0
+ -511
394
- 858
+ 850
@@ -556,6 +556,23 @@
+ -
+
+
+ If ticked, slide animations will play when requested.
+
+
+ Slide Animations:
+
+
+
+ -
+
+
+
+
+
+
diff --git a/src/animationlayer.cpp b/src/animationlayer.cpp
new file mode 100644
index 0000000..708aef2
--- /dev/null
+++ b/src/animationlayer.cpp
@@ -0,0 +1,702 @@
+#include "animationlayer.h"
+
+#include "aoapplication.h"
+#include "options.h"
+
+#include
+#include
+
+static QThreadPool *thread_pool;
+
+namespace kal
+{
+AnimationLayer::AnimationLayer(QWidget *parent)
+ : QLabel(parent)
+{
+ setAlignment(Qt::AlignCenter);
+
+ m_ticker = new QTimer(this);
+ m_ticker->setSingleShot(true);
+ m_ticker->setTimerType(Qt::PreciseTimer);
+
+ connect(m_ticker, &QTimer::timeout, this, &AnimationLayer::frameTicker);
+
+ if (!thread_pool)
+ {
+ thread_pool = new QThreadPool(qApp);
+ thread_pool->setMaxThreadCount(8);
+ }
+
+ createLoader();
+}
+
+AnimationLayer::~AnimationLayer()
+{
+ deleteLoader();
+}
+
+QString AnimationLayer::fileName()
+{
+ return m_file_name;
+}
+
+void AnimationLayer::setFileName(QString fileName)
+{
+ stopPlayback();
+ m_file_name = fileName;
+ if (m_file_name.trimmed().isEmpty())
+ {
+#ifdef DEBUG_MOVIE
+ qWarning() << "AnimationLayer::setFileName called with empty string";
+#endif
+ m_file_name = QObject::tr("Invalid File");
+ }
+ resetData();
+}
+
+void AnimationLayer::startPlayback()
+{
+ if (m_processing)
+ {
+#ifdef DEBUG_MOVIE
+ qWarning() << "AnimationLayer::startPlayback called while already processing";
+#endif
+ return;
+ }
+ resetData();
+ m_processing = true;
+ Q_EMIT startedPlayback();
+ frameTicker();
+}
+
+void AnimationLayer::stopPlayback()
+{
+ if (m_ticker->isActive())
+ {
+ m_ticker->stop();
+ }
+ m_processing = false;
+ if (m_reset_cache_when_stopped)
+ {
+ createLoader();
+ }
+ Q_EMIT stoppedPlayback();
+}
+
+void AnimationLayer::restartPlayback()
+{
+ stopPlayback();
+ startPlayback();
+}
+
+void AnimationLayer::pausePlayback(bool enabled)
+{
+ if (m_pause == enabled)
+ {
+#ifdef DEBUG_MOVIE
+ qWarning() << "AnimationLayer::pausePlayback called with identical state";
+#endif
+ return;
+ }
+ m_pause = enabled;
+}
+
+QSize AnimationLayer::frameSize()
+{
+ return m_frame_size;
+}
+
+int AnimationLayer::frameCount()
+{
+ return m_frame_count;
+}
+
+int AnimationLayer::currentFrameNumber()
+{
+ return m_frame_number;
+}
+
+/**
+ * @brief AnimationLayer::jumpToFrame
+ * @param number The frame number to jump to. Must be in valid range. If the number is out of range, the method does nothing.
+ * @details If frame number is valid and playback is processing, the frame will immediately be displayed.
+ */
+void AnimationLayer::jumpToFrame(int number)
+{
+ if (number < 0 || number >= m_frame_count)
+ {
+#ifdef DEBUG_MOVIE
+ qWarning() << "AnimationLayer::jumpToFrame failed to jump to frame" << number << "(file:" << m_file_name << ", frame count:" << m_frame_count << ")";
+#endif
+ return;
+ }
+
+ bool is_processing = m_processing;
+ if (m_ticker->isActive())
+ {
+ m_ticker->stop();
+ }
+ m_target_frame_number = number;
+ if (is_processing)
+ {
+ frameTicker();
+ }
+}
+
+bool AnimationLayer::isPlayOnce()
+{
+ return m_play_once;
+}
+
+void AnimationLayer::setPlayOnce(bool enabled)
+{
+ m_play_once = enabled;
+}
+
+void AnimationLayer::setStretchToFit(bool enabled)
+{
+ m_stretch_to_fit = enabled;
+}
+
+void AnimationLayer::setResetCacheWhenStopped(bool enabled)
+{
+ m_reset_cache_when_stopped = enabled;
+}
+
+void AnimationLayer::setFlipped(bool enabled)
+{
+ m_flipped = enabled;
+}
+
+void AnimationLayer::setTransformationMode(Qt::TransformationMode mode)
+{
+ m_transformation_mode_hint = mode;
+}
+
+void AnimationLayer::setMinimumDurationPerFrame(int duration)
+{
+ m_minimum_duration = duration;
+}
+
+void AnimationLayer::setMaximumDurationPerFrame(int duration)
+{
+ m_maximum_duration = duration;
+}
+
+void AnimationLayer::setMaskingRect(QRect rect)
+{
+ m_mask_rect_hint = rect;
+ calculateFrameGeometry();
+}
+
+void AnimationLayer::resizeEvent(QResizeEvent *event)
+{
+ QLabel::resizeEvent(event);
+ calculateFrameGeometry();
+}
+
+void AnimationLayer::createLoader()
+{
+ deleteLoader();
+ m_loader = new AnimationLoader(thread_pool);
+}
+
+void AnimationLayer::deleteLoader()
+{
+ if (m_loader)
+ {
+ delete m_loader;
+ m_loader = nullptr;
+ }
+}
+
+void AnimationLayer::resetData()
+{
+ m_first_frame = true;
+ m_frame_number = 0;
+ if (m_file_name != m_loader->loadedFileName())
+ {
+ m_loader->load(m_file_name);
+ }
+ m_frame_count = m_loader->frameCount();
+ m_frame_size = m_loader->size();
+ m_frame_rect = QRect(QPoint(0, 0), m_frame_size);
+ m_ticker->stop();
+ calculateFrameGeometry();
+}
+
+void AnimationLayer::calculateFrameGeometry()
+{
+ m_mask_rect = QRect();
+ m_display_rect = QRect();
+ m_scaled_frame_size = QSize();
+
+ QSize widget_size = size();
+ if (!widget_size.isValid() || !m_frame_size.isValid())
+ {
+ return;
+ }
+
+ if (m_stretch_to_fit)
+ {
+ m_scaled_frame_size = widget_size;
+ }
+ else
+ {
+ QSize target_frame_size = m_frame_size;
+ if (m_frame_rect.contains(m_mask_rect_hint))
+ {
+ m_mask_rect = m_mask_rect_hint;
+ target_frame_size = m_mask_rect_hint.size();
+ }
+
+ double scale = double(widget_size.height()) / double(target_frame_size.height());
+ m_scaled_frame_size = target_frame_size * scale;
+
+ // display the frame in its center
+ int x = (m_scaled_frame_size.width() - widget_size.width()) / 2;
+ m_display_rect = QRect(x, 0, widget_size.width(), m_scaled_frame_size.height());
+
+ if (m_transformation_mode_hint == Qt::FastTransformation)
+ {
+ m_transformation_mode = scale < 1.0 ? Qt::SmoothTransformation : Qt::FastTransformation;
+ }
+ }
+
+ displayCurrentFrame();
+}
+
+void AnimationLayer::finishPlayback()
+{
+ stopPlayback();
+ Q_EMIT finishedPlayback();
+}
+
+void AnimationLayer::prepareNextTick()
+{
+ int duration = qMax(m_minimum_duration, m_current_frame.duration);
+ duration = (m_maximum_duration > 0) ? qMin(m_maximum_duration, duration) : duration;
+ m_ticker->start(duration);
+}
+
+void AnimationLayer::displayCurrentFrame()
+{
+ QPixmap image = m_current_frame.texture;
+
+ if (m_frame_size.isValid())
+ {
+ if (m_mask_rect.isValid())
+ {
+ image = image.copy(m_mask_rect);
+ }
+
+ if (!image.isNull())
+ {
+ image = image.scaled(m_scaled_frame_size, Qt::IgnoreAspectRatio, m_transformation_mode);
+
+ if (m_display_rect.isValid())
+ {
+ image = image.copy(m_display_rect);
+ }
+
+ if (m_flipped)
+ {
+ image = image.transformed(QTransform().scale(-1.0, 1.0));
+ }
+ }
+ }
+ else
+ {
+ image = QPixmap(1, 1);
+ image.fill(Qt::transparent);
+ }
+
+ setPixmap(image);
+}
+
+void AnimationLayer::frameTicker()
+{
+ if (!m_processing)
+ {
+ return;
+ }
+
+ if (m_frame_count < 1)
+ {
+ if (m_play_once)
+ {
+ finishPlayback();
+ return;
+ }
+
+ stopPlayback();
+ return;
+ }
+
+ if (m_pause && !m_first_frame)
+ {
+ return;
+ }
+
+ if (m_frame_number == m_frame_count)
+ {
+ if (m_play_once)
+ {
+ finishPlayback();
+ return;
+ }
+
+ if (m_frame_count > 1)
+ {
+ m_frame_number = 0;
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ m_first_frame = false;
+ if (m_target_frame_number != -1)
+ {
+ m_frame_number = m_target_frame_number;
+ m_target_frame_number = -1;
+ }
+ m_current_frame = m_loader->frame(m_frame_number);
+ displayCurrentFrame();
+ Q_EMIT frameNumberChanged(m_frame_number);
+ ++m_frame_number;
+
+ if (!m_pause)
+ {
+ prepareNextTick();
+ }
+}
+
+CharacterAnimationLayer::CharacterAnimationLayer(AOApplication *ao_app, QWidget *parent)
+ : AnimationLayer(parent)
+ , ao_app(ao_app)
+{
+ m_duration_timer = new QTimer(this);
+ m_duration_timer->setSingleShot(true);
+ connect(m_duration_timer, &QTimer::timeout, this, &CharacterAnimationLayer::onDurationLimitReached);
+
+ connect(this, &CharacterAnimationLayer::stoppedPlayback, this, &CharacterAnimationLayer::onPlaybackStopped);
+ connect(this, &CharacterAnimationLayer::frameNumberChanged, this, &CharacterAnimationLayer::notifyFrameEffect);
+ connect(this, &CharacterAnimationLayer::finishedPlayback, this, &CharacterAnimationLayer::notifyEmotePlaybackFinished);
+}
+
+void CharacterAnimationLayer::loadCharacterEmote(QString character, QString fileName, EmoteType emoteType, int durationLimit)
+{
+ auto is_dialog_emote = [](EmoteType emoteType) {
+ return emoteType == IdleEmote || emoteType == TalkEmote;
+ };
+
+ bool synchronize_frame = false;
+ const int previous_frame_count = frameCount();
+ const int previous_frame_number = currentFrameNumber();
+ if (m_character == character && m_emote == fileName && is_dialog_emote(m_emote_type) && is_dialog_emote(emoteType))
+ {
+ synchronize_frame = true;
+ }
+
+ m_character = character;
+ m_emote = fileName;
+ m_resolved_emote = fileName;
+ m_emote_type = emoteType;
+
+ QStringList prefixes;
+ bool placeholder_fallback = false;
+ bool play_once = false;
+ switch (emoteType)
+ {
+ default:
+ break;
+
+ case PreEmote:
+ play_once = true;
+ break;
+
+ case IdleEmote:
+ prefixes << QStringLiteral("(a)") << QStringLiteral("(a)/");
+ placeholder_fallback = true;
+ break;
+
+ case TalkEmote:
+ prefixes << QStringLiteral("(b)") << QStringLiteral("(b)/");
+ placeholder_fallback = true;
+ break;
+
+ case PostEmote:
+ prefixes << QStringLiteral("(c)") << QStringLiteral("(c)/");
+ break;
+ }
+
+ QVector path_list;
+ QVector prefixed_emote_list;
+ for (const QString &prefix : qAsConst(prefixes))
+ {
+ path_list << ao_app->get_character_path(character, prefix + m_emote);
+ prefixed_emote_list << prefix + m_emote;
+ }
+ path_list << ao_app->get_character_path(character, m_emote);
+ prefixed_emote_list << m_emote;
+
+ if (placeholder_fallback)
+ {
+ path_list << ao_app->get_character_path(character, QStringLiteral("placeholder"));
+ prefixed_emote_list << QStringLiteral("placeholder");
+ path_list << ao_app->get_theme_path("placeholder", ao_app->default_theme);
+ prefixed_emote_list << QStringLiteral("placeholder");
+ }
+
+ int index = -1;
+ QString file_path = ao_app->get_image_path(path_list, index);
+ if (index != -1)
+ {
+ m_resolved_emote = prefixed_emote_list[index];
+ }
+
+ setFileName(file_path);
+ setPlayOnce(play_once);
+ setTransformationMode(ao_app->get_scaling(ao_app->get_emote_property(character, fileName, "scaling")));
+ setStretchToFit(ao_app->get_emote_property(character, fileName, "stretch").startsWith("true"));
+ if (synchronize_frame && previous_frame_count == frameCount())
+ {
+ jumpToFrame(previous_frame_number);
+ }
+ m_duration = durationLimit;
+}
+
+void CharacterAnimationLayer::setFrameEffects(QStringList data)
+{
+ m_effects.clear();
+
+ static const QList EFFECT_TYPE_LIST{ShakeEffect, FlashEffect, SfxEffect};
+ for (int i = 0; i < data.length(); ++i)
+ {
+ const EffectType effect_type = EFFECT_TYPE_LIST.at(i);
+
+ QStringList emotes = data.at(i).split("^");
+ for (const QString &emote : qAsConst(emotes))
+ {
+ QStringList emote_effects = emote.split("|");
+
+ const QString emote_name = emote_effects.takeFirst();
+
+ for (const QString &raw_effect : qAsConst(emote_effects))
+ {
+ QStringList frame_data = raw_effect.split("=");
+
+ const int frame_number = frame_data.at(0).toInt();
+
+ FrameEffect effect;
+ effect.emote_name = emote_name;
+ effect.type = effect_type;
+ if (effect_type == EffectType::SfxEffect)
+ {
+ effect.file_name = frame_data.at(1);
+ }
+
+ m_effects[frame_number].append(effect);
+ }
+ }
+ }
+}
+
+void CharacterAnimationLayer::startTimeLimit()
+{
+ if (m_duration > 0)
+ {
+ m_duration_timer->start(m_duration);
+ }
+}
+
+void CharacterAnimationLayer::onPlaybackStopped()
+{
+ if (m_duration_timer->isActive())
+ {
+ m_duration_timer->stop();
+ }
+}
+
+void CharacterAnimationLayer::notifyEmotePlaybackFinished()
+{
+ if (m_emote_type == PreEmote || m_emote_type == PostEmote)
+ {
+ Q_EMIT finishedPreOrPostEmotePlayback();
+ }
+}
+
+void CharacterAnimationLayer::onPlaybackFinished()
+{
+ if (m_emote_type == PreEmote || m_emote_type == PostEmote)
+ {
+ if (m_duration_timer->isActive())
+ {
+ m_duration_timer->stop();
+ }
+
+ notifyEmotePlaybackFinished();
+ }
+}
+
+void CharacterAnimationLayer::onDurationLimitReached()
+{
+ stopPlayback();
+ notifyEmotePlaybackFinished();
+}
+
+void CharacterAnimationLayer::notifyFrameEffect(int frameNumber)
+{
+ auto it = m_effects.constFind(frameNumber);
+ if (it != m_effects.constEnd())
+ {
+ for (const FrameEffect &effect : qAsConst(*it))
+ {
+ if (effect.emote_name == m_resolved_emote)
+ {
+ switch (effect.type)
+ {
+ default:
+ break;
+
+ case EffectType::SfxEffect:
+ Q_EMIT soundEffect(effect.file_name);
+ break;
+
+ case EffectType::ShakeEffect:
+ Q_EMIT shakeEffect();
+ break;
+
+ case EffectType::FlashEffect:
+ Q_EMIT flashEffect();
+ break;
+ }
+ }
+ }
+ }
+}
+
+BackgroundAnimationLayer::BackgroundAnimationLayer(AOApplication *ao_app, QWidget *parent)
+ : AnimationLayer(parent)
+ , ao_app(ao_app)
+{}
+
+void BackgroundAnimationLayer::loadAndPlayAnimation(QString fileName)
+{
+ QString file_path = ao_app->get_image_suffix(ao_app->get_background_path(fileName));
+#ifdef DEBUG_MOVIE
+ if (file_path.isEmpty())
+ {
+ qWarning() << "[BackgroundLayer] Failed to load background:" << fileName;
+ }
+ else if (file_path == this->fileName())
+ {
+ return;
+ }
+ else
+ {
+ qInfo() << "[BackgroundLayer] Loading background:" << file_path;
+ }
+#endif
+
+ bool is_different_file = file_path != this->fileName();
+ if (is_different_file)
+ {
+ setFileName(file_path);
+ }
+
+ VPath design_path = ao_app->get_background_path("design.ini");
+ setTransformationMode(ao_app->get_scaling(ao_app->read_design_ini("scaling", design_path)));
+ setStretchToFit(ao_app->read_design_ini("stretch", design_path).startsWith("true"));
+
+ if (is_different_file)
+ {
+ startPlayback();
+ }
+}
+
+SplashAnimationLayer::SplashAnimationLayer(AOApplication *ao_app, QWidget *parent)
+ : AnimationLayer(parent)
+ , ao_app(ao_app)
+{
+ connect(this, &SplashAnimationLayer::startedPlayback, this, &SplashAnimationLayer::show);
+ connect(this, &SplashAnimationLayer::stoppedPlayback, this, &SplashAnimationLayer::hide);
+}
+
+void SplashAnimationLayer::loadAndPlayAnimation(QString p_filename, QString p_charname, QString p_miscname)
+{
+ QString file_path = ao_app->get_image(p_filename, Options::getInstance().theme(), Options::getInstance().subTheme(), ao_app->default_theme, p_miscname, p_charname, "placeholder");
+ setFileName(file_path);
+ setTransformationMode(ao_app->get_misc_scaling(p_miscname));
+ startPlayback();
+}
+
+EffectAnimationLayer::EffectAnimationLayer(AOApplication *ao_app, QWidget *parent)
+ : AnimationLayer(parent)
+ , ao_app(ao_app)
+{
+ connect(this, &EffectAnimationLayer::startedPlayback, this, &EffectAnimationLayer::show);
+ connect(this, &EffectAnimationLayer::stoppedPlayback, this, &EffectAnimationLayer::maybeHide);
+}
+
+void EffectAnimationLayer::loadAndPlayAnimation(QString p_filename, bool repeat)
+{
+ setFileName(p_filename);
+ setPlayOnce(!repeat);
+ startPlayback();
+}
+
+void EffectAnimationLayer::setHideWhenStopped(bool enabled)
+{
+ m_hide_when_stopped = enabled;
+}
+
+void EffectAnimationLayer::maybeHide()
+{
+ if (m_hide_when_stopped && isPlayOnce())
+ {
+ hide();
+ }
+}
+
+InterfaceAnimationLayer::InterfaceAnimationLayer(AOApplication *ao_app, QWidget *parent)
+ : AnimationLayer(parent)
+ , ao_app(ao_app)
+{
+ setStretchToFit(true);
+
+ connect(this, &InterfaceAnimationLayer::startedPlayback, this, &InterfaceAnimationLayer::show);
+ connect(this, &InterfaceAnimationLayer::stoppedPlayback, this, &InterfaceAnimationLayer::hide);
+}
+
+void InterfaceAnimationLayer::loadAndPlayAnimation(QString fileName, QString miscName)
+{
+ QString file_path = ao_app->get_image(fileName, Options::getInstance().theme(), Options::getInstance().subTheme(), ao_app->default_theme, miscName);
+ setFileName(file_path);
+ startPlayback();
+}
+
+StickerAnimationLayer::StickerAnimationLayer(AOApplication *ao_app, QWidget *parent)
+ : AnimationLayer(parent)
+ , ao_app(ao_app)
+{
+ connect(this, &StickerAnimationLayer::startedPlayback, this, &StickerAnimationLayer::show);
+ connect(this, &StickerAnimationLayer::stoppedPlayback, this, &StickerAnimationLayer::hide);
+}
+
+void StickerAnimationLayer::loadAndPlayAnimation(QString fileName)
+{
+ QString misc_file; // FIXME this is a bad name
+ if (Options::getInstance().customChatboxEnabled())
+ {
+ misc_file = ao_app->get_chat(fileName);
+ }
+
+ QString file_path = ao_app->get_image("sticker/" + fileName, Options::getInstance().theme(), Options::getInstance().subTheme(), ao_app->default_theme, misc_file);
+ setFileName(file_path);
+ setTransformationMode(ao_app->get_misc_scaling(misc_file));
+ startPlayback();
+}
+} // namespace kal
diff --git a/src/animationlayer.h b/src/animationlayer.h
new file mode 100644
index 0000000..820703d
--- /dev/null
+++ b/src/animationlayer.h
@@ -0,0 +1,266 @@
+#pragma once
+
+#include "animationloader.h"
+
+#include
+#include
+#include
+#include
+#include
+
+// #define DEBUG_MOVIE
+
+#ifdef DEBUG_MOVIE
+#include
+#endif
+
+class AOApplication;
+class VPath;
+
+// "Brief" explanation of what the hell this is:
+//
+// AOLayer handles all animations both inside and outside
+// the viewport. It was originally devised as a layering
+// system, but turned into a full refactor of the existing
+// animation code.
+//
+// AOLayer has six subclasses, all of which differ mainly in
+// how they handle path resolution.
+//
+// - BackgroundLayer: self-explanatory, handles files found in base/background
+// - CharLayer: handles all the "wonderful" quirks of character path resolution
+// - SplashLayer: handles elements that can either be provided by a misc/ directory
+// or by the theme - speedlines, shouts, WT/CE, et cetera
+// - EffectLayer: this is basically a dummy layer since effects do their own wonky
+// path resolution in a different file
+// - InterfaceLayer: handles UI elements like the chat arrow and the music display
+// - StickerLayer: Crystalwarrior really wanted this. Handles "stickers," whatever those are.
+//
+// For questions comments or concerns, bother someone else
+
+namespace kal
+{
+class AnimationLayer : public QLabel
+{
+ Q_OBJECT
+
+public:
+ explicit AnimationLayer(QWidget *parent = nullptr);
+ virtual ~AnimationLayer();
+
+ QString fileName();
+ void setFileName(QString fileName);
+
+ void startPlayback();
+ void stopPlayback();
+ void restartPlayback();
+ void pausePlayback(bool enabled);
+
+ QSize frameSize();
+
+ int frameCount();
+ int currentFrameNumber();
+ void jumpToFrame(int number);
+
+ bool isPlayOnce();
+
+ void setPlayOnce(bool enabled);
+ void setStretchToFit(bool enabled);
+ void setResetCacheWhenStopped(bool enabled);
+ void setFlipped(bool enabled);
+ void setTransformationMode(Qt::TransformationMode mode);
+ void setMinimumDurationPerFrame(int duration);
+ void setMaximumDurationPerFrame(int duration);
+
+public Q_SLOTS:
+ void setMaskingRect(QRect rect);
+
+Q_SIGNALS:
+ void startedPlayback();
+ void stoppedPlayback(); /* Is emitted whenever playback is stopped, whether by user or by reaching the end */
+ void finishedPlayback(); /* Is emitted only when playback reaches the end */
+ void frameNumberChanged(int frameNumber);
+
+protected:
+ void resizeEvent(QResizeEvent *event) override;
+
+private:
+ QString m_file_name;
+ bool m_play_once = false;
+ bool m_stretch_to_fit = false;
+ bool m_reset_cache_when_stopped = false;
+ bool m_flipped = false;
+ int m_minimum_duration = 0;
+ int m_maximum_duration = 0;
+ Qt::TransformationMode m_transformation_mode_hint = Qt::FastTransformation;
+ Qt::TransformationMode m_transformation_mode = Qt::FastTransformation;
+ AnimationLoader *m_loader = nullptr;
+ QSize m_frame_size;
+ QRect m_frame_rect;
+ QRect m_mask_rect_hint;
+ QRect m_mask_rect;
+ QRect m_display_rect;
+ QSize m_scaled_frame_size;
+ bool m_processing = false;
+ bool m_pause = false;
+ QTimer *m_ticker = nullptr;
+ bool m_first_frame = false;
+ int m_frame_number = 0;
+ int m_target_frame_number = -1;
+ int m_frame_count = 0;
+ AnimationFrame m_current_frame;
+
+ void createLoader();
+ void deleteLoader();
+
+ void resetData();
+
+ void calculateFrameGeometry();
+
+ void finishPlayback();
+
+ void prepareNextTick();
+
+ void displayCurrentFrame();
+
+private Q_SLOTS:
+ void frameTicker();
+};
+
+class CharacterAnimationLayer : public AnimationLayer
+{
+ Q_OBJECT
+
+public:
+ enum EmoteType
+ {
+ NoEmoteType,
+ PreEmote,
+ IdleEmote,
+ TalkEmote,
+ PostEmote,
+ };
+
+ enum EffectType
+ {
+ SfxEffect,
+ ShakeEffect,
+ FlashEffect,
+ };
+
+ class FrameEffect
+ {
+ public:
+ QString emote_name;
+ EffectType type = SfxEffect;
+ QString file_name;
+ };
+
+ CharacterAnimationLayer(AOApplication *ao_app, QWidget *parent = nullptr);
+
+ void loadCharacterEmote(QString character, QString fileName, EmoteType emoteType, int durationLimit = 0);
+
+ void setFrameEffects(QStringList data);
+
+Q_SIGNALS:
+ void finishedPreOrPostEmotePlayback();
+
+ void soundEffect(QString sfx);
+ void shakeEffect();
+ void flashEffect();
+
+private:
+ AOApplication *ao_app;
+
+ QString m_character;
+ QString m_emote;
+ QString m_resolved_emote;
+ EmoteType m_emote_type = NoEmoteType;
+ QTimer *m_duration_timer = nullptr;
+ int m_duration = 0;
+
+ QMap> m_effects;
+
+ void startTimeLimit();
+
+private Q_SLOTS:
+ void onPlaybackStopped();
+ void onPlaybackFinished();
+ void onDurationLimitReached();
+
+ void notifyFrameEffect(int frame);
+ void notifyEmotePlaybackFinished();
+};
+
+class BackgroundAnimationLayer : public AnimationLayer
+{
+ Q_OBJECT
+
+public:
+ BackgroundAnimationLayer(AOApplication *ao_app, QWidget *parent = nullptr);
+
+ void loadAndPlayAnimation(QString fileName);
+
+private:
+ AOApplication *ao_app;
+};
+
+class SplashAnimationLayer : public AnimationLayer
+{
+ Q_OBJECT
+
+public:
+ SplashAnimationLayer(AOApplication *ao_app, QWidget *parent = nullptr);
+
+ void loadAndPlayAnimation(QString fileName, QString character, QString miscellaneous);
+
+private:
+ AOApplication *ao_app;
+};
+
+class EffectAnimationLayer : public AnimationLayer
+{
+ Q_OBJECT
+
+public:
+ EffectAnimationLayer(AOApplication *ao_app, QWidget *parent = nullptr);
+
+ void loadAndPlayAnimation(QString fileName, bool repeat = false);
+
+ void setHideWhenStopped(bool enabled);
+
+private:
+ AOApplication *ao_app;
+
+ bool m_hide_when_stopped = false;
+
+private Q_SLOTS:
+ void maybeHide();
+};
+
+class InterfaceAnimationLayer : public AnimationLayer
+{
+ Q_OBJECT
+
+public:
+ InterfaceAnimationLayer(AOApplication *ao_app, QWidget *parent = nullptr);
+
+ void loadAndPlayAnimation(QString fileName, QString miscName);
+
+private:
+ AOApplication *ao_app;
+};
+
+class StickerAnimationLayer : public AnimationLayer
+{
+ Q_OBJECT
+
+public:
+ StickerAnimationLayer(AOApplication *ao_app, QWidget *parent = nullptr);
+
+ void loadAndPlayAnimation(QString fileName);
+
+private:
+ AOApplication *ao_app;
+};
+} // namespace kal
diff --git a/src/animationloader.cpp b/src/animationloader.cpp
new file mode 100644
index 0000000..5f57cbe
--- /dev/null
+++ b/src/animationloader.cpp
@@ -0,0 +1,105 @@
+#include "animationloader.h"
+
+#include
+#include
+
+namespace kal
+{
+AnimationLoader::AnimationLoader(QThreadPool *threadPool)
+ : m_thread_pool(threadPool)
+{}
+
+AnimationLoader::~AnimationLoader()
+{
+ stopLoading();
+}
+
+QString AnimationLoader::loadedFileName() const
+{
+ return m_file_name;
+}
+
+void AnimationLoader::load(const QString &fileName)
+{
+ if (m_file_name == fileName)
+ {
+ return;
+ }
+ stopLoading();
+ m_file_name = fileName;
+ QImageReader *reader = new QImageReader;
+ reader->setFileName(fileName);
+ m_size = reader->size();
+ m_frames.clear();
+ m_frame_count = reader->imageCount();
+ m_loop_count = reader->loopCount();
+ m_exit_task = false;
+ m_task = QtConcurrent::run(m_thread_pool, [this, reader]() { populateVector(reader); });
+}
+
+void AnimationLoader::stopLoading()
+{
+ m_exit_task = true;
+ if (m_task.isRunning())
+ {
+ m_task.waitForFinished();
+ }
+}
+
+QSize AnimationLoader::size()
+{
+ return m_size;
+}
+
+int AnimationLoader::frameCount()
+{
+ return m_frame_count;
+}
+
+AnimationFrame AnimationLoader::frame(int frameNumber)
+{
+ if (m_frame_count <= 0)
+ {
+ return AnimationFrame();
+ }
+
+ m_task_lock.lock();
+ while (m_frames.size() < frameNumber + 1)
+ {
+#ifdef DEBUG_MOVIE
+ qDebug().noquote() << "Waiting for frame" << frameNumber << QString("(file: %1, frame count: %2)").arg(m_file_name).arg(m_frame_count);
+#endif
+ m_task_signal.wait(&m_task_lock);
+ }
+
+ AnimationFrame frame = qAsConst(m_frames)[frameNumber];
+ m_task_lock.unlock();
+
+ return frame;
+}
+
+int AnimationLoader::loopCount()
+{
+ return m_loop_count;
+}
+
+void AnimationLoader::populateVector(QImageReader *reader)
+{
+ int loaded_frame_count = 0;
+ int frame_count = reader->imageCount();
+ while (!m_exit_task && loaded_frame_count < frame_count)
+ {
+ {
+ QMutexLocker locker(&m_task_lock);
+ AnimationFrame frame;
+ frame.texture = QPixmap::fromImage(reader->read());
+ frame.duration = reader->nextImageDelay();
+ m_frames.append(frame);
+ ++loaded_frame_count;
+ }
+ m_task_signal.wakeAll();
+ }
+
+ delete reader;
+}
+} // namespace kal
diff --git a/src/animationloader.h b/src/animationloader.h
new file mode 100644
index 0000000..a3a1036
--- /dev/null
+++ b/src/animationloader.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace kal
+{
+class AnimationFrame
+{
+public:
+ QPixmap texture;
+ int duration = 0;
+};
+
+class AnimationLoader
+{
+ Q_DISABLE_COPY_MOVE(AnimationLoader)
+
+public:
+ explicit AnimationLoader(QThreadPool *threadPool);
+ virtual ~AnimationLoader();
+
+ QString loadedFileName() const;
+ void load(const QString &fileName);
+ void stopLoading();
+
+ QSize size();
+
+ int frameCount();
+ AnimationFrame frame(int frameNumber);
+
+ int loopCount();
+
+private:
+ QThreadPool *m_thread_pool;
+ QString m_file_name;
+ QSize m_size;
+ int m_frame_count = 0;
+ int m_loop_count = -1;
+ QList m_frames;
+ QFuture m_task;
+ std::atomic_bool m_exit_task = false;
+ QMutex m_task_lock;
+ QWaitCondition m_task_signal;
+
+ void populateVector(QImageReader *reader);
+};
+} // namespace kal
diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp
index a3ed6b9..f3260ed 100644
--- a/src/aoapplication.cpp
+++ b/src/aoapplication.cpp
@@ -132,6 +132,20 @@ QString AOApplication::get_version_string()
return QString::number(RELEASE) + "." + QString::number(MAJOR_VERSION) + "." + QString::number(MINOR_VERSION);
}
+QString AOApplication::find_image(QStringList p_list)
+{
+ QString image_path;
+ for (const QString &path : p_list)
+ {
+ if (file_exists(path))
+ {
+ image_path = path;
+ break;
+ }
+ }
+ return image_path;
+}
+
void AOApplication::server_disconnected()
{
if (is_courtroom_constructed())
diff --git a/src/aoapplication.h b/src/aoapplication.h
index c2a0191..1f2b1ed 100644
--- a/src/aoapplication.h
+++ b/src/aoapplication.h
@@ -120,16 +120,19 @@ public:
VPath get_evidence_path(QString p_file);
QVector get_asset_paths(QString p_element, QString p_theme = QString(), QString p_subtheme = QString(), QString p_default_theme = QString(), QString p_misc = QString(), QString p_character = QString(), QString p_placeholder = QString());
QString get_asset_path(QVector pathlist);
+ QString get_image_path(QVector pathlist, int &index, bool static_image = false);
QString get_image_path(QVector pathlist, bool static_image = false);
QString get_sfx_path(QVector pathlist);
QString get_config_value(QString p_identifier, QString p_config, QString p_theme = QString(), QString p_subtheme = QString(), QString p_default_theme = QString(), QString p_misc = QString());
QString get_asset(QString p_element, QString p_theme = QString(), QString p_subtheme = QString(), QString p_default_theme = QString(), QString p_misc = QString(), QString p_character = QString(), QString p_placeholder = QString());
QString get_image(QString p_element, QString p_theme = QString(), QString p_subtheme = QString(), QString p_default_theme = QString(), QString p_misc = QString(), QString p_character = QString(), QString p_placeholder = QString(), bool static_image = false);
QString get_sfx(QString p_sfx, QString p_misc = QString(), QString p_character = QString());
- QString get_pos_path(const QString &pos, bool desk = false);
+ QPair get_pos_path(const QString &pos, bool desk = false);
QString get_case_sensitive_path(QString p_file);
QString get_real_path(const VPath &vpath, const QStringList &suffixes = {""});
+ QString find_image(QStringList p_list);
+
////// Functions for reading and writing files //////
// Implementations file_functions.cpp
@@ -250,6 +253,12 @@ public:
// Returns whether the given pos is a judge position
bool get_pos_is_judge(const QString &p_pos);
+ /**
+ * @brief Returns the duration of the transition animation between the two
+ * given positions, if it exists
+ */
+ int get_pos_transition_duration(const QString &old_pos, const QString &new_pos);
+
// Returns the total amount of emotes of p_char
int get_emote_number(QString p_char);
diff --git a/src/aoemotepreview.cpp b/src/aoemotepreview.cpp
index e427cb8..a5a6a27 100644
--- a/src/aoemotepreview.cpp
+++ b/src/aoemotepreview.cpp
@@ -8,9 +8,8 @@ AOEmotePreview::AOEmotePreview(AOApplication *ao_app, QWidget *parent)
setWindowFlag(Qt::WindowMinMaxButtonsHint, false);
ui_viewport = new QWidget(this);
- ui_vp_player_char = new CharLayer(ao_app, ui_viewport);
+ ui_vp_player_char = new kal::CharacterAnimationLayer(ao_app, ui_viewport);
ui_vp_player_char->setObjectName("ui_vp_player_char");
- ui_vp_player_char->masked = false;
ui_size_label = new QLabel(this);
ui_size_label->setObjectName("ui_size_label");
}
@@ -19,21 +18,22 @@ void AOEmotePreview::updateViewportGeometry()
{
ui_viewport->resize(size());
- ui_vp_player_char->move_and_center(0, 0);
- ui_vp_player_char->combo_resize(ui_viewport->width(), ui_viewport->height());
+ ui_vp_player_char->move(0, 0);
+ ui_vp_player_char->resize(ui_viewport->width(), ui_viewport->height());
- ui_size_label->setText(QString::number(width()) + "x" + QString::number(height()));
+ ui_size_label->setText(QString::number(ui_viewport->width()) + "x" + QString::number(ui_viewport->height()));
}
-void AOEmotePreview::display(QString character, QString emote, bool flipped, int xOffset, int yOffset)
+void AOEmotePreview::display(QString character, QString emote, kal::CharacterAnimationLayer::EmoteType emoteType, bool flipped, int xOffset, int yOffset)
{
m_character = character;
m_emote = emote;
- ui_vp_player_char->stop();
- ui_vp_player_char->set_flipped(flipped);
- ui_vp_player_char->move_and_center(ui_viewport->width() * xOffset / 100, ui_viewport->height() * yOffset / 100);
- ui_vp_player_char->load_image(emote, character, 0, false);
- ui_vp_player_char->set_play_once(false);
+ ui_vp_player_char->stopPlayback();
+ ui_vp_player_char->move(ui_viewport->width() * xOffset / 100, ui_viewport->height() * yOffset / 100);
+ ui_vp_player_char->loadCharacterEmote(character, emote, emoteType);
+ ui_vp_player_char->setPlayOnce(false);
+ ui_vp_player_char->setFlipped(flipped);
+ ui_vp_player_char->startPlayback();
setWindowTitle(character + ": " + emote);
}
@@ -41,5 +41,4 @@ void AOEmotePreview::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
updateViewportGeometry();
- ui_vp_player_char->load_image(m_emote, m_character, 0, false);
}
diff --git a/src/aoemotepreview.h b/src/aoemotepreview.h
index 0cf88c9..a83a0d9 100644
--- a/src/aoemotepreview.h
+++ b/src/aoemotepreview.h
@@ -1,6 +1,6 @@
#pragma once
-#include "aolayer.h"
+#include "animationlayer.h"
#include
class AOEmotePreview : public QWidget
@@ -10,7 +10,7 @@ class AOEmotePreview : public QWidget
public:
AOEmotePreview(AOApplication *ao_app, QWidget *parent = nullptr);
- void display(QString character, QString emote, bool flipped = false, int xOffset = 0, int yOffset = 0);
+ void display(QString character, QString emote, kal::CharacterAnimationLayer::EmoteType emoteType, bool flipped = false, int xOffset = 0, int yOffset = 0);
void updateViewportGeometry();
@@ -24,9 +24,9 @@ private:
QString m_emote;
QWidget *ui_viewport;
- BackgroundLayer *ui_vp_background;
- SplashLayer *ui_vp_speedlines;
- CharLayer *ui_vp_player_char;
- BackgroundLayer *ui_vp_desk;
+ kal::BackgroundAnimationLayer *ui_vp_background;
+ kal::SplashAnimationLayer *ui_vp_speedlines;
+ kal::CharacterAnimationLayer *ui_vp_player_char;
+ kal::BackgroundAnimationLayer *ui_vp_desk;
QLabel *ui_size_label;
};
diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp
index 489699e..27afdb6 100644
--- a/src/aoevidencedisplay.cpp
+++ b/src/aoevidencedisplay.cpp
@@ -12,9 +12,9 @@ AOEvidenceDisplay::AOEvidenceDisplay(AOApplication *p_ao_app, QWidget *p_parent)
m_sfx_player = new AOSfxPlayer(ao_app);
- m_evidence_movie = new InterfaceLayer(ao_app, this);
+ m_evidence_movie = new kal::InterfaceAnimationLayer(ao_app, this);
- connect(m_evidence_movie, &InterfaceLayer::done, this, &AOEvidenceDisplay::show_done);
+ connect(m_evidence_movie, &kal::InterfaceAnimationLayer::finishedPlayback, this, &AOEvidenceDisplay::show_done);
connect(ui_prompt_details, &QPushButton::clicked, this, &AOEvidenceDisplay::icon_clicked);
}
@@ -52,17 +52,17 @@ void AOEvidenceDisplay::show_evidence(int p_index, QString p_evidence_image, boo
ui_prompt_details->setIconSize(f_pixmap.rect().size());
ui_prompt_details->resize(f_pixmap.rect().size());
ui_prompt_details->move(icon_dimensions.x, icon_dimensions.y);
- m_evidence_movie->static_duration = 320;
- m_evidence_movie->max_duration = 1000;
- m_evidence_movie->set_play_once(true);
- m_evidence_movie->load_image(gif_name, "");
+ m_evidence_movie->setMinimumDurationPerFrame(320);
+ m_evidence_movie->setMaximumDurationPerFrame(1000);
+ m_evidence_movie->setPlayOnce(true);
+ m_evidence_movie->loadAndPlayAnimation(gif_name, "");
m_sfx_player->findAndPlaySfx(ao_app->get_court_sfx("evidence_present"));
}
void AOEvidenceDisplay::reset()
{
m_sfx_player->stop();
- m_evidence_movie->kill();
+ m_evidence_movie->stopPlayback();
ui_prompt_details->hide();
this->clear();
}
@@ -84,5 +84,5 @@ void AOEvidenceDisplay::combo_resize(int w, int h)
{
QSize f_size(w, h);
this->resize(f_size);
- m_evidence_movie->combo_resize(w, h);
+ m_evidence_movie->resize(w, h);
}
diff --git a/src/aoevidencedisplay.h b/src/aoevidencedisplay.h
index b23bc0f..a259073 100644
--- a/src/aoevidencedisplay.h
+++ b/src/aoevidencedisplay.h
@@ -1,7 +1,7 @@
#pragma once
+#include "animationlayer.h"
#include "aoapplication.h"
-#include "aolayer.h"
#include "aosfxplayer.h"
#include
@@ -28,7 +28,7 @@ private:
int m_last_evidence_index = -1;
AOSfxPlayer *m_sfx_player;
- InterfaceLayer *m_evidence_movie;
+ kal::InterfaceAnimationLayer *m_evidence_movie;
QPushButton *ui_prompt_details;
private Q_SLOTS:
diff --git a/src/aolayer.cpp b/src/aolayer.cpp
deleted file mode 100644
index 8284230..0000000
--- a/src/aolayer.cpp
+++ /dev/null
@@ -1,716 +0,0 @@
-#include "aolayer.h"
-
-#include "aoapplication.h"
-#include "file_functions.h"
-#include "options.h"
-
-static QThreadPool *thread_pool;
-
-AOLayer::AOLayer(AOApplication *p_ao_app, QWidget *p_parent)
- : 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, &QTimer::timeout, this, &AOLayer::shfx_timer_done);
-
- ticker = new QTimer(this);
- ticker->setTimerType(Qt::PreciseTimer);
- ticker->setSingleShot(false);
- connect(ticker, &QTimer::timeout, this, &AOLayer::movie_ticker);
-
- preanim_timer = new QTimer(this);
- preanim_timer->setSingleShot(true);
- connect(preanim_timer, &QTimer::timeout, this, &AOLayer::preanim_done);
-
- if (!thread_pool)
- {
- thread_pool = new QThreadPool(p_ao_app);
- thread_pool->setMaxThreadCount(8);
- }
-}
-
-BackgroundLayer::BackgroundLayer(AOApplication *p_ao_app, QWidget *p_parent)
- : AOLayer(p_ao_app, p_parent)
-{}
-
-CharLayer::CharLayer(AOApplication *p_ao_app, QWidget *p_parent)
- : AOLayer(p_ao_app, p_parent)
-{}
-
-EffectLayer::EffectLayer(AOApplication *p_ao_app, QWidget *p_parent)
- : AOLayer(p_ao_app, p_parent)
-{}
-
-SplashLayer::SplashLayer(AOApplication *p_ao_app, QWidget *p_parent)
- : AOLayer(p_ao_app, p_parent)
-{}
-
-InterfaceLayer::InterfaceLayer(AOApplication *p_ao_app, QWidget *p_parent)
- : AOLayer(p_ao_app, p_parent)
-{}
-
-StickerLayer::StickerLayer(AOApplication *p_ao_app, QWidget *p_parent)
- : AOLayer(p_ao_app, p_parent)
-{}
-
-QString AOLayer::find_image(QStringList p_list)
-{
- QString image_path;
- for (const 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.isNull())
- {
- 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);
- this->center_pixmap(f_pixmap);
-}
-
-void AOLayer::center_pixmap(QPixmap 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
- if (masked)
- {
- 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(double(delay) * double(speed / 100));
-}
-
-void AOLayer::move(int ax, int ay)
-{
- x = ax;
- y = ay;
- QLabel::move(x, y);
-}
-
-void AOLayer::move_and_center(int ax, int ay)
-{
- x = ax;
- y = ay;
- if (movie_frames.isEmpty()) // safeguard
- {
- QLabel::move(x, y);
- }
- else
- {
- center_pixmap(movie_frames[0]); // just use the first frame since dimensions are all that matter
- }
-}
-
-void BackgroundLayer::load_image(QString p_filename)
-{
- play_once = false;
- cull_image = false;
- VPath 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");
-#ifdef DEBUG_MOVIE
- qDebug() << "[BackgroundLayer] BG loaded: " << p_filename;
-#endif
- QString final_path = ao_app->get_image_suffix(ao_app->get_background_path(p_filename));
-
- if (final_path == last_path)
- {
- // Don't restart background if background is unchanged
- return;
- }
-
- start_playback(final_path);
- play();
-}
-
-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)"))
- { // if we are playing an idle or talking animation
- prefix = p_filename.left(3); // separate the prefix from the emote name
- current_emote = p_filename.mid(3, -1);
- }
- else if ((duration > 0) || (p_filename.left(3) == "(c)"))
- { // else if we are playing a preanim or postanim
- if (p_filename.left(3) == "(c)")
- { // if we are playing a postanim
- prefix = "(c)"; // separate the prefix from the emote name
- current_emote = p_filename.mid(3, -1);
- }
- // pre/postanim specific flags
- is_preanim = true;
- play_once = true;
- preanim_timer->start(duration);
- }
-#ifdef DEBUG_MOVIE
- qDebug() << "[CharLayer] anim loaded: prefix " << prefix << " filename " << current_emote << " from character: " << p_charname << " continuous: " << continuous;
-#endif
- QVector pathlist{ // cursed character path resolution vector
- ao_app->get_character_path(p_charname, prefix + current_emote), // Default path
- ao_app->get_character_path(p_charname,
- prefix + "/" + current_emote), // Path check if it's categorized
- // into a folder
- ao_app->get_character_path(p_charname,
- current_emote), // Just use the non-prefixed image, animated or not
- VPath(current_emote), // The path by itself after the above fail
- ao_app->get_theme_path("placeholder"), // Theme placeholder path
- ao_app->get_theme_path("placeholder", ao_app->default_theme)}; // Default theme placeholder path
- start_playback(ao_app->get_image_path(pathlist));
-}
-
-void SplashLayer::load_image(QString p_filename, QString p_charname, QString p_miscname)
-{
- transform_mode = ao_app->get_misc_scaling(p_miscname);
- QString final_image = ao_app->get_image(p_filename, Options::getInstance().theme(), Options::getInstance().subTheme(), ao_app->default_theme, p_miscname, p_charname, "placeholder");
- start_playback(final_image);
- play();
-}
-
-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;
- cull_image = false;
-
- start_playback(p_filename); // path resolution is handled by the caller for EffectLayer objects
- play();
-}
-
-void InterfaceLayer::load_image(QString p_filename, QString p_miscname)
-{
- last_path = "";
- stretch = true;
- QString final_image = ao_app->get_image(p_filename, Options::getInstance().theme(), Options::getInstance().subTheme(), ao_app->default_theme, p_miscname);
- start_playback(final_image);
- play();
-}
-
-void StickerLayer::load_image(QString p_charname)
-{
- QString p_miscname;
- if (Options::getInstance().customChatboxEnabled())
- {
- p_miscname = ao_app->get_chat(p_charname);
- }
- transform_mode = ao_app->get_misc_scaling(p_miscname);
- QString final_image = ao_app->get_image("sticker/" + p_charname, Options::getInstance().theme(), Options::getInstance().subTheme(), ao_app->default_theme, p_miscname);
- start_playback(final_image);
- play();
-}
-
-void CharLayer::start_playback(QString p_image)
-{
- movie_effects.clear();
- AOLayer::start_playback(p_image);
- if (m_network_strings.size() > 0) // our FX overwritten by networked ones
- {
- load_network_effects();
- }
- else // Use default ini FX
- {
- load_effects();
- }
- play();
-}
-
-void AOLayer::start_playback(QString p_image)
-{
- if (p_image == "")
- { // image wasn't found by the path resolution function
- this->kill();
- return;
- }
-
- if (frame_loader.isRunning())
- {
- exit_loop = true; // tell the loader to stop, we have a new image to load
- }
-
- QMutexLocker locker(&mutex);
- this->show();
-
- if (!Options::getInstance().continuousPlaybackEnabled())
- {
- continuous = false;
- force_continuous = true;
- }
-
- if (((last_path == p_image) && (!force_continuous)) || p_image == "")
- {
- return;
- }
-
-#ifdef DEBUG_MOVIE
- actual_time.restart();
-#endif
- this->clear();
- this->freeze();
- movie_frames.clear();
- movie_delays.clear();
- 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");
- }
-
-#ifdef DEBUG_MOVIE
- qDebug() << "[AOLayer::start_playback] Stretch:" << stretch << "Filename:" << p_image;
-#endif
- m_reader.setFileName(p_image);
- last_max_frames = max_frames;
- max_frames = m_reader.imageCount();
- if (m_reader.loopCount() == 0 && max_frames > 1)
- {
- play_once = true;
- }
- if (!continuous || ((continuous) && (max_frames != last_max_frames)) || max_frames == 0 || frame >= max_frames)
- {
- frame = 0;
- continuous = false;
- }
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
- frame_loader = QtConcurrent::run(thread_pool, this, &AOLayer::populate_vectors);
-#else
- frame_loader = QtConcurrent::run(thread_pool, &AOLayer::populate_vectors, this);
-#endif
- last_path = p_image;
- while (movie_frames.size() <= frame) // if we haven't loaded the frame we need yet
- {
- frameAdded.wait(&mutex); // wait for the frame loader to add another frame, then check again
- }
- this->set_frame(movie_frames[frame]);
-
- if (max_frames <= 1)
- {
- duration = static_duration;
-#ifdef DEBUG_MOVIE
- qDebug() << "[AOLayer::start_playback] max_frames is <= 1, using static duration";
-#endif
- }
- if (duration > 0 && cull_image == true)
- {
- shfx_timer->start(duration);
- }
-#ifdef DEBUG_MOVIE
- qDebug() << "[AOLayer::start_playback] Max frames:" << max_frames << "Setting image to " << p_image << "Time taken to process image:" << actual_time.elapsed();
-
- actual_time.restart();
-#endif
-}
-
-void CharLayer::play()
-{
- if (max_frames <= 1)
- {
- if (play_once)
- {
- preanim_timer->start(qMax(0, duration));
- }
- return;
- }
- play_frame_effect(frame);
- AOLayer::play();
-}
-
-void AOLayer::play()
-{
- if (max_frames <= 1)
- {
- if (play_once)
- {
- if (duration > 0)
- {
- ticker->start(duration);
- }
- else
- {
- preanim_done();
- }
- }
- else
- {
- this->freeze();
- }
- }
- else
- {
- while (movie_delays.size() <= frame)
- {
- frameAdded.wait(&mutex);
- }
- 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();
- if (max_frames <= 1)
- {
- return;
- }
- 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();
- if (max_frames <= 1)
- {
- return;
- }
- movie_effects.resize(max_frames);
- // Order is important!!!
- QStringList effects_list = {"shake", "flash", "sfx^"};
-
- // Determines which list is smaller - effects_list or m_network_strings - and
- // uses it as basis for the loop. This way, incomplete m_network_strings would
- // still be parsed, and excess/unaccounted for networked information is
- // omitted.
- int effects_size = qMin(effects_list.size(), m_network_strings.size());
-
- for (int i = 0; i < effects_size; ++i)
- {
- QString netstring = m_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)
- {
- qWarning() << "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;
- }
-#ifdef DEBUG_MOVIE
- qDebug() << "[CharLayer::load_network_effects]" << effect << f_data << "frame" << f_frame << "for" << m_emote;
-#endif
- movie_effects[f_frame].append(effect);
- }
- }
- }
- }
-}
-
-void CharLayer::play_frame_effect(int p_frame)
-{
- if (p_frame >= movie_effects.size())
- {
- qWarning() << "Attempted to play a frame effect bigger than the size of movie_effects";
- return;
- }
- if (p_frame < max_frames)
- {
- foreach (QString effect, movie_effects[p_frame])
- {
- if (effect == "shake")
- {
- Q_EMIT shake();
-#ifdef DEBUG_MOVIE
- qDebug() << "[CharLayer::play_frame_effect] Attempting to play shake on frame" << frame;
-#endif
- }
-
- if (effect == "flash")
- {
- Q_EMIT flash();
-#ifdef DEBUG_MOVIE
- qDebug() << "[CharLayer::play_frame_effect] Attempting to play flash on frame" << frame;
-#endif
- }
-
- if (effect.startsWith("sfx^"))
- {
- QString sfx = effect.section("^", 1);
- Q_EMIT play_sfx(sfx);
-#ifdef DEBUG_MOVIE
- qDebug() << "[CharLayer::play_frame_effect] 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();
- shfx_timer->stop();
-}
-
-void AOLayer::freeze()
-{
- // aT nO pOiNt Do We WaNt A fRoZeN gIf To DiSpLaY
- ticker->stop();
- preanim_timer->stop();
-}
-
-void AOLayer::kill()
-{
- // used for when we want to ensure a file is loaded anew
- this->stop();
- this->clear();
- movie_frames.clear();
- movie_delays.clear();
- last_max_frames = max_frames;
- max_frames = 0;
- last_path = "";
-}
-
-void CharLayer::movie_ticker()
-{
- AOLayer::movie_ticker();
- play_frame_effect(frame);
-}
-
-void AOLayer::movie_ticker()
-{
- ++frame;
- if (frame >= max_frames)
- {
- if (play_once)
- {
- if (cull_image)
- {
- this->stop();
- }
- else
- {
- this->freeze();
- }
- preanim_done();
- return;
- }
- else
- {
- frame = 0;
- }
- }
- {
- QMutexLocker locker(&mutex);
- while (frame >= movie_frames.size() && frame < max_frames) // oops! our frame isn't ready yet
- {
- frameAdded.wait(&mutex); // wait for a new frame to be added, then check again
- }
- }
-#ifdef DEBUG_MOVIE
- qDebug() << "[AOLayer::movie_ticker] Frame:" << frame << "Delay:" << 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 AOLayer::populate_vectors()
-{
-#ifdef DEBUG_MOVIE
- qDebug() << "[AOLayer::populate_vectors] Started thread";
-#endif
- while (!exit_loop && movie_frames.size() < max_frames)
- {
- load_next_frame();
-#ifdef DEBUG_MOVIE
- qDebug() << "[AOLayer::populate_vectors] Loaded frame" << movie_frames.size();
-#endif
- }
-#ifdef DEBUG_MOVIE
- if (exit_loop)
- {
- qDebug() << "[AOLayer::populate_vectors] Exit requested";
- }
-#endif
- exit_loop = false;
-}
-
-void AOLayer::load_next_frame()
-{
- {
- QMutexLocker locker(&mutex);
- movie_frames.append(this->get_pixmap(m_reader.read()));
- movie_delays.append(m_reader.nextImageDelay());
- }
- frameAdded.wakeAll();
-}
-
-void CharLayer::preanim_done()
-{
- if (is_preanim)
- {
- AOLayer::preanim_done();
- }
- else
- {
- return;
- }
-}
-
-void AOLayer::preanim_done()
-{
- ticker->stop();
- preanim_timer->stop();
- Q_EMIT 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
- Q_EMIT done();
-}
diff --git a/src/aolayer.h b/src/aolayer.h
deleted file mode 100644
index 0a44d8f..0000000
--- a/src/aolayer.h
+++ /dev/null
@@ -1,267 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-class AOApplication;
-class VPath;
-
-// "Brief" explanation of what the hell this is:
-//
-// AOLayer handles all animations both inside and outside
-// the viewport. It was originally devised as a layering
-// system, but turned into a full refactor of the existing
-// animation code.
-//
-// AOLayer has six subclasses, all of which differ mainly in
-// how they handle path resolution.
-//
-// - BackgroundLayer: self-explanatory, handles files found in base/background
-// - CharLayer: handles all the "wonderful" quirks of character path resolution
-// - SplashLayer: handles elements that can either be provided by a misc/ directory
-// or by the theme - speedlines, shouts, WT/CE, et cetera
-// - EffectLayer: this is basically a dummy layer since effects do their own wonky
-// path resolution in a different file
-// - InterfaceLayer: handles UI elements like the chat arrow and the music display
-// - StickerLayer: Crystalwarrior really wanted this. Handles "stickers," whatever those are.
-//
-// For questions comments or concerns, bother someone else
-
-class AOLayer : public QLabel
-{
- Q_OBJECT
-
-public:
- AOLayer(AOApplication *p_ao_app, QWidget *p_parent = nullptr);
-
- QString filename; // file name without extension, i.e. "witnesstestimony"
- int static_duration; // time in ms for static images to be displayed, if
- // applicable. set to 0 for infinite
- int max_duration; // maximum duration in ms, image will be culled if it is
- // exceeded. set this to 0 for infinite duration
- bool play_once = false; // Whether to loop this animation or not
- bool cull_image = true; // if we're done playing this animation, should we
- // hide it? also controls durational culling
- // Are we loading this from the same frame we left off on?
- bool continuous = false;
- // Whether or not to forcibly bypass the simple check done by start_playback
- // and use the existent value of continuous instead
- bool force_continuous = false;
- Qt::TransformationMode transform_mode = Qt::FastTransformation; // transformation mode to use for this image
- bool stretch = false; // Should we stretch/squash this image to fill the screen?
- bool masked = true; // Set a mask to the dimensions of the widget?
-
- // Set the movie's image to provided paths, preparing for playback.
- void start_playback(QString p_image);
-
- void set_play_once(bool p_play_once);
- void set_cull_image(bool p_cull_image);
- void set_static_duration(int p_static_duration);
- void set_max_duration(int p_max_duration);
-
- // Stop the movie, clearing the image
- void stop();
-
- // Stop the movie and clear all vectors
- void kill();
-
- // Set the m_flipped variable to true/false
- void set_flipped(bool p_flipped) { m_flipped = p_flipped; }
-
- // Move the label itself around
- void move(int ax, int ay);
-
- // Move the label and center it
- void move_and_center(int ax, int ay);
-
- // This is somewhat pointless now as there's no "QMovie" object to resize, aka
- // no "combo" to speak of
- void combo_resize(int w, int h);
-
- // Return the frame delay adjusted for speed
- int get_frame_delay(int delay);
-
- // iterate through a list of paths and return the first entry that exists. if
- // none exist, return NULL (safe because we check again for existence later)
- QString find_image(QStringList p_list);
-
-protected:
- AOApplication *ao_app;
- QVector movie_frames;
- QVector movie_delays;
-
- QTimer *preanim_timer;
- QTimer *shfx_timer;
- QTimer *ticker;
- QString last_path;
- QImageReader m_reader;
-
- QElapsedTimer actual_time;
-
- // These are the X and Y values before they are fixed based on the sprite's
- // width.
- int x = 0;
- int y = 0;
- // These are the width and height values before they are fixed based on the
- // sprite's width.
- int f_w = 0;
- int f_h = 0;
-
- int frame = 0;
- int max_frames = 0;
- int last_max_frames = 0;
-
- int speed = 100;
-
- bool m_flipped = false;
-
- int duration = 0;
-
- // Start playback of the movie (if animated).
- void play();
-
- // Freeze the movie at the current frame.
- void freeze();
-
- // Retreive a pixmap adjused for mirroring/aspect ratio shenanigans from a
- // provided QImage
- QPixmap get_pixmap(QImage image);
-
- // Set the movie's frame to provided pixmap
- void set_frame(QPixmap f_pixmap);
- // Center the QLabel in the viewport based on the dimensions of f_pixmap
- void center_pixmap(QPixmap f_pixmap);
-
-private:
- // Populates the frame and delay vectors.
- void populate_vectors();
-
- // used in populate_vectors
- void load_next_frame();
- std::atomic_bool exit_loop{false}; // awful solution but i'm not fucking using QThread
- QFuture frame_loader;
- QMutex mutex;
- QWaitCondition frameAdded;
-
-Q_SIGNALS:
- void done();
-
-protected Q_SLOTS:
- virtual void preanim_done();
- void shfx_timer_done();
- virtual void movie_ticker();
-};
-
-class BackgroundLayer : public AOLayer
-{
- Q_OBJECT
-public:
- BackgroundLayer(AOApplication *p_ao_app, QWidget *p_parent);
- void load_image(QString p_filename);
-};
-
-class CharLayer : public AOLayer
-{
- Q_OBJECT
-
-public:
- CharLayer(AOApplication *p_ao_app, QWidget *p_parent = nullptr);
-
- QStringList &network_strings2() { return m_network_strings; }
- void set_network_string(QStringList list) { m_network_strings = list; }
-
- void load_image(QString p_filename, QString p_charname, int p_duration, bool p_is_preanim);
-
- void play(); // overloaded so we can play effects
-
-private:
- QString current_emote; // name of the emote we're using
- bool is_preanim; // equivalent to the old play_once, if true we don't want
- // to loop this
- QString prefix; // prefix, left blank if it's a preanim
- QStringList m_network_strings;
-
- QString last_char; // name of the last character we used
- QString last_emote; // name of the last animation we used
- QString last_prefix; // prefix of the last animation we played
- bool was_preanim = false; // whether is_preanim was true last time
-
- // Effects such as sfx, screenshakes and realization flashes are stored in
- // here. QString entry format: "sfx^[sfx_name]", "shake", "flash". The program
- // uses the QVector index as reference.
- QVector> movie_effects;
-
- // used for effect loading
- QString m_char;
- QString m_emote;
-
- // overloaded for effects reasons
- void start_playback(QString p_image);
-
- // Initialize the frame-specific effects from the char.ini
- void load_effects();
-
- // Initialize the frame-specific effects from the provided network_strings,
- // this is only initialized if network_strings has size more than 0.
- void load_network_effects();
-
- // Play a frame-specific effect, if there's any defined for that specific
- // frame.
- void play_frame_effect(int p_frame);
-
-private Q_SLOTS:
- void preanim_done() override; // overridden so we don't accidentally cull characters
- void movie_ticker() override; // overridden so we can play effects
-
-Q_SIGNALS:
- void shake();
- void flash();
- void play_sfx(QString sfx);
-};
-
-class SplashLayer : public AOLayer
-{
- Q_OBJECT
-
-public:
- SplashLayer(AOApplication *p_ao_app, QWidget *p_parent = nullptr);
-
- void load_image(QString p_filename, QString p_charname, QString p_miscname);
-};
-
-class EffectLayer : public AOLayer
-{
- Q_OBJECT
-
-public:
- EffectLayer(AOApplication *p_ao_app, QWidget *p_parent = nullptr);
-
- void load_image(QString p_filename, bool p_looping);
-};
-
-class InterfaceLayer : public AOLayer
-{
- Q_OBJECT
-
-public:
- InterfaceLayer(AOApplication *p_ao_app, QWidget *p_parent = nullptr);
-
- void load_image(QString p_filename, QString p_miscname);
-};
-
-class StickerLayer : public AOLayer
-{
- Q_OBJECT
-
-public:
- StickerLayer(AOApplication *p_ao_app, QWidget *p_parent = nullptr);
-
- void load_image(QString p_charname);
-};
diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp
index 0775eb9..ac3b851 100644
--- a/src/aosfxplayer.cpp
+++ b/src/aosfxplayer.cpp
@@ -8,7 +8,7 @@ AOSfxPlayer::AOSfxPlayer(AOApplication *ao_app)
int AOSfxPlayer::volume()
{
- return m_volume * 100;
+ return m_volume;
}
void AOSfxPlayer::setVolume(int value)
diff --git a/src/courtroom.cpp b/src/courtroom.cpp
index 89c5c75..7ea7d28 100644
--- a/src/courtroom.cpp
+++ b/src/courtroom.cpp
@@ -1,6 +1,11 @@
#include "courtroom.h"
+
#include "options.h"
+#include
+
+// #define DEBUG_TRANSITION
+
Courtroom::Courtroom(AOApplication *p_ao_app)
: QMainWindow()
{
@@ -45,22 +50,29 @@ Courtroom::Courtroom(AOApplication *p_ao_app)
ui_viewport = new QWidget(this);
ui_viewport->setObjectName("ui_viewport");
- ui_vp_background = new BackgroundLayer(ao_app, ui_viewport);
+ ui_vp_background = new kal::BackgroundAnimationLayer(ao_app, ui_viewport);
ui_vp_background->setObjectName("ui_vp_background");
- ui_vp_speedlines = new SplashLayer(ao_app, ui_viewport);
+ ui_vp_speedlines = new kal::SplashAnimationLayer(ao_app, ui_viewport);
ui_vp_speedlines->setObjectName("ui_vp_speedlines");
- ui_vp_speedlines->stretch = true;
- ui_vp_player_char = new CharLayer(ao_app, ui_viewport);
+ ui_vp_speedlines->setStretchToFit(true);
+ ui_vp_player_char = new kal::CharacterAnimationLayer(ao_app, ui_viewport);
ui_vp_player_char->setObjectName("ui_vp_player_char");
- ui_vp_player_char->masked = false;
- ui_vp_sideplayer_char = new CharLayer(ao_app, ui_viewport);
+ ui_vp_sideplayer_char = new kal::CharacterAnimationLayer(ao_app, ui_viewport);
ui_vp_sideplayer_char->setObjectName("ui_vp_sideplayer_char");
- ui_vp_sideplayer_char->masked = false;
ui_vp_sideplayer_char->hide();
- ui_vp_desk = new BackgroundLayer(ao_app, ui_viewport);
+ ui_vp_dummy_char = new kal::CharacterAnimationLayer(ao_app, ui_viewport);
+ ui_vp_dummy_char->setObjectName("ui_vp_dummy_char");
+ ui_vp_dummy_char->setResetCacheWhenStopped(true);
+ ui_vp_dummy_char->hide();
+ ui_vp_sidedummy_char = new kal::CharacterAnimationLayer(ao_app, ui_viewport);
+ ui_vp_sidedummy_char->setObjectName("ui_vp_sidedummy_char");
+ ui_vp_sidedummy_char->setResetCacheWhenStopped(true);
+ ui_vp_sidedummy_char->hide();
+ ui_vp_char_list = QList{ui_vp_player_char, ui_vp_sideplayer_char, ui_vp_dummy_char, ui_vp_sidedummy_char};
+ ui_vp_desk = new kal::BackgroundAnimationLayer(ao_app, ui_viewport);
ui_vp_desk->setObjectName("ui_vp_desk");
- ui_vp_effect = new EffectLayer(ao_app, this);
+ ui_vp_effect = new kal::EffectAnimationLayer(ao_app, this);
ui_vp_effect->setAttribute(Qt::WA_TransparentForMouseEvents);
ui_vp_effect->setObjectName("ui_vp_effect");
@@ -70,18 +82,14 @@ Courtroom::Courtroom(AOApplication *p_ao_app)
ui_vp_chatbox = new AOImage(ao_app, this);
ui_vp_chatbox->setObjectName("ui_vp_chatbox");
- ui_vp_sticker = new StickerLayer(ao_app, this);
- ui_vp_sticker->set_play_once(false);
- ui_vp_sticker->set_cull_image(false);
+ ui_vp_sticker = new kal::StickerAnimationLayer(ao_app, this);
ui_vp_sticker->setAttribute(Qt::WA_TransparentForMouseEvents);
ui_vp_sticker->setObjectName("ui_vp_sticker");
ui_vp_showname = new AOChatboxLabel(ui_vp_chatbox);
ui_vp_showname->setObjectName("ui_vp_showname");
ui_vp_showname->setAlignment(Qt::AlignLeft);
- ui_vp_chat_arrow = new InterfaceLayer(ao_app, this);
- ui_vp_chat_arrow->set_play_once(false);
- ui_vp_chat_arrow->set_cull_image(false);
+ ui_vp_chat_arrow = new kal::InterfaceAnimationLayer(ao_app, this);
ui_vp_chat_arrow->setObjectName("ui_vp_chat_arrow");
ui_vp_message = new QTextEdit(this);
@@ -91,20 +99,15 @@ Courtroom::Courtroom(AOApplication *p_ao_app)
ui_vp_message->setReadOnly(true);
ui_vp_message->setObjectName("ui_vp_message");
- ui_vp_testimony = new SplashLayer(ao_app, this);
- ui_vp_testimony->set_play_once(false);
+ ui_vp_testimony = new kal::SplashAnimationLayer(ao_app, this);
ui_vp_testimony->setAttribute(Qt::WA_TransparentForMouseEvents);
ui_vp_testimony->setObjectName("ui_vp_testimony");
- ui_vp_wtce = new SplashLayer(ao_app, this);
- ui_vp_wtce->set_play_once(true);
- ui_vp_wtce->continuous = false;
- ui_vp_wtce->force_continuous = true;
+ ui_vp_wtce = new kal::SplashAnimationLayer(ao_app, this);
ui_vp_wtce->setAttribute(Qt::WA_TransparentForMouseEvents);
ui_vp_wtce->setObjectName("ui_vp_wtce");
- ui_vp_objection = new SplashLayer(ao_app, this);
- ui_vp_objection->set_play_once(true);
- ui_vp_objection->continuous = false;
- ui_vp_objection->force_continuous = true;
+ ui_vp_wtce->setPlayOnce(true);
+ ui_vp_objection = new kal::SplashAnimationLayer(ao_app, this);
+ ui_vp_objection->setPlayOnce(true);
ui_vp_objection->setAttribute(Qt::WA_TransparentForMouseEvents);
ui_vp_objection->setObjectName("ui_vp_objection");
@@ -151,10 +154,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app)
ui_music_list->setUniformRowHeights(true);
ui_music_list->setObjectName("ui_music_list");
- ui_music_display = new InterfaceLayer(ao_app, this);
- ui_music_display->set_play_once(false);
- ui_music_display->set_cull_image(false);
- ui_music_display->transform_mode = Qt::SmoothTransformation;
+ ui_music_display = new kal::InterfaceAnimationLayer(ao_app, this);
+ ui_music_display->setTransformationMode(Qt::SmoothTransformation);
ui_music_display->setAttribute(Qt::WA_TransparentForMouseEvents);
ui_music_display->setObjectName("ui_music_display");
@@ -315,6 +316,11 @@ Courtroom::Courtroom(AOApplication *p_ao_app)
ui_showname_enable->setText(tr("Shownames"));
ui_showname_enable->setObjectName("ui_showname_enable");
+ ui_slide_enable = new QCheckBox(this);
+ ui_slide_enable->setChecked(false);
+ ui_slide_enable->setText(tr("Slide"));
+ ui_slide_enable->setObjectName("ui_slide_enable");
+
ui_immediate = new QCheckBox(this);
ui_immediate->setText(tr("Immediate"));
ui_immediate->hide();
@@ -406,11 +412,11 @@ Courtroom::Courtroom(AOApplication *p_ao_app)
connect(keepalive_timer, &QTimer::timeout, this, &Courtroom::ping_server);
- connect(ui_vp_objection, &SplashLayer::done, this, &Courtroom::objection_done);
- connect(ui_vp_player_char, &CharLayer::done, this, &Courtroom::preanim_done);
- connect(ui_vp_player_char, &CharLayer::shake, this, &Courtroom::do_screenshake);
- connect(ui_vp_player_char, &CharLayer::flash, this, &Courtroom::do_flash);
- connect(ui_vp_player_char, &CharLayer::play_sfx, this, &Courtroom::play_char_sfx);
+ connect(ui_vp_objection, &kal::SplashAnimationLayer::finishedPlayback, this, &Courtroom::objection_done);
+ connect(ui_vp_player_char, &kal::CharacterAnimationLayer::finishedPreOrPostEmotePlayback, this, &Courtroom::preanim_done);
+ connect(ui_vp_player_char, &kal::CharacterAnimationLayer::shakeEffect, this, &Courtroom::do_screenshake);
+ connect(ui_vp_player_char, &kal::CharacterAnimationLayer::flashEffect, this, &Courtroom::do_flash);
+ connect(ui_vp_player_char, &kal::CharacterAnimationLayer::soundEffect, this, &Courtroom::play_char_sfx);
connect(text_delay_timer, &QTimer::timeout, this, &Courtroom::start_chat_ticking);
@@ -487,10 +493,11 @@ Courtroom::Courtroom(AOApplication *p_ao_app)
connect(ui_settings, &AOButton::clicked, this, &Courtroom::on_settings_clicked);
connect(ui_switch_area_music, &AOButton::clicked, this, &Courtroom::on_switch_area_music_clicked);
- connect(ui_pre, &AOButton::clicked, this, &Courtroom::on_pre_clicked);
- connect(ui_flip, &AOButton::clicked, this, &Courtroom::on_flip_clicked);
+ connect(ui_pre, &AOButton::clicked, this, &Courtroom::focus_ic_input);
+ connect(ui_flip, &AOButton::clicked, this, &Courtroom::focus_ic_input);
connect(ui_additive, &AOButton::clicked, this, &Courtroom::on_additive_clicked);
- connect(ui_guard, &AOButton::clicked, this, &Courtroom::on_guard_clicked);
+ connect(ui_guard, &AOButton::clicked, this, &Courtroom::focus_ic_input);
+ connect(ui_slide_enable, &AOButton::clicked, this, &Courtroom::focus_ic_input);
connect(ui_showname_enable, &AOButton::clicked, this, &Courtroom::on_showname_enable_clicked);
@@ -507,6 +514,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app)
connect(ui_vp_evidence_display, &AOEvidenceDisplay::show_evidence_details, this, &Courtroom::show_evidence);
+ connect(transition_animation_group, &QParallelAnimationGroup::finished, this, &Courtroom::post_transition_cleanup);
+
set_widgets();
set_char_select();
@@ -747,24 +756,29 @@ void Courtroom::set_widgets()
ui_settings->show();
// make the BG's reload
- ui_vp_background->kill();
- ui_vp_desk->kill();
+ ui_vp_background->restartPlayback();
+ ui_vp_desk->restartPlayback();
- ui_vp_background->move_and_center(0, 0);
- ui_vp_background->combo_resize(ui_viewport->width(), ui_viewport->height());
+ ui_vp_background->move(0, 0);
+ ui_vp_background->resize(ui_viewport->width(), ui_viewport->height());
- ui_vp_speedlines->move_and_center(0, 0);
- ui_vp_speedlines->combo_resize(ui_viewport->width(), ui_viewport->height());
+ ui_vp_speedlines->move(0, 0);
+ ui_vp_speedlines->resize(ui_viewport->width(), ui_viewport->height());
- ui_vp_player_char->move_and_center(0, 0);
- ui_vp_player_char->combo_resize(ui_viewport->width(), ui_viewport->height());
+ ui_vp_player_char->move(0, 0);
+ ui_vp_player_char->resize(ui_viewport->width(), ui_viewport->height());
- ui_vp_sideplayer_char->move_and_center(0, 0);
- ui_vp_sideplayer_char->combo_resize(ui_viewport->width(), ui_viewport->height());
+ ui_vp_sideplayer_char->move(0, 0);
+ ui_vp_sideplayer_char->resize(ui_viewport->width(), ui_viewport->height());
+
+ ui_vp_dummy_char->move(0, 0);
+ ui_vp_dummy_char->resize(ui_viewport->width(), ui_viewport->height());
+ ui_vp_sidedummy_char->move(0, 0);
+ ui_vp_sidedummy_char->resize(ui_viewport->width(), ui_viewport->height());
// the AO2 desk element
- ui_vp_desk->move_and_center(0, 0);
- ui_vp_desk->combo_resize(ui_viewport->width(), ui_viewport->height());
+ ui_vp_desk->move(0, 0);
+ ui_vp_desk->resize(ui_viewport->width(), ui_viewport->height());
ui_vp_evidence_display->move(0, 0);
ui_vp_evidence_display->combo_resize(ui_viewport->width(), ui_viewport->height());
@@ -773,17 +787,17 @@ void Courtroom::set_widgets()
// thing, which is to parent these to ui_viewport. instead, AOLayer handles
// masking so we don't overlap parts of the UI, and they become free floating
// widgets.
- ui_vp_testimony->move_and_center(ui_viewport->x(), ui_viewport->y());
- ui_vp_testimony->combo_resize(ui_viewport->width(), ui_viewport->height());
+ ui_vp_testimony->move(ui_viewport->x(), ui_viewport->y());
+ ui_vp_testimony->resize(ui_viewport->width(), ui_viewport->height());
- ui_vp_effect->move_and_center(ui_viewport->x(), ui_viewport->y());
- ui_vp_effect->combo_resize(ui_viewport->width(), ui_viewport->height());
+ ui_vp_effect->move(ui_viewport->x(), ui_viewport->y());
+ ui_vp_effect->resize(ui_viewport->width(), ui_viewport->height());
- ui_vp_wtce->move_and_center(ui_viewport->x(), ui_viewport->y());
- ui_vp_wtce->combo_resize(ui_viewport->width(), ui_viewport->height());
+ ui_vp_wtce->move(ui_viewport->x(), ui_viewport->y());
+ ui_vp_wtce->resize(ui_viewport->width(), ui_viewport->height());
- ui_vp_objection->move_and_center(ui_viewport->x(), ui_viewport->y());
- ui_vp_objection->combo_resize(ui_viewport->width(), ui_viewport->height());
+ ui_vp_objection->move(ui_viewport->x(), ui_viewport->y());
+ ui_vp_objection->resize(ui_viewport->width(), ui_viewport->height());
log_maximum_blocks = Options::getInstance().maxLogSize();
@@ -867,9 +881,9 @@ void Courtroom::set_widgets()
else
{
ui_music_display->move(design_ini_result.x, design_ini_result.y);
- ui_music_display->combo_resize(design_ini_result.width, design_ini_result.height);
+ ui_music_display->resize(design_ini_result.width, design_ini_result.height);
}
- ui_music_display->load_image("music_display", "");
+ ui_music_display->loadAndPlayAnimation("music_display", "");
for (int i = 0; i < max_clocks; i++)
{
@@ -881,7 +895,7 @@ void Courtroom::set_widgets()
initialize_chatbox();
ui_vp_sticker->move(ui_viewport->x(), ui_viewport->y());
- ui_vp_sticker->combo_resize(ui_viewport->width(), ui_viewport->height());
+ ui_vp_sticker->resize(ui_viewport->width(), ui_viewport->height());
ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height());
ui_muted->setImage("muted");
@@ -1077,6 +1091,9 @@ void Courtroom::set_widgets()
set_size_and_pos(ui_showname_enable, "showname_enable");
ui_showname_enable->setToolTip(tr("Display customized shownames for all users when checked."));
+ set_size_and_pos(ui_slide_enable, "slide_enable");
+ ui_slide_enable->setToolTip(tr("Allow your messages to trigger slide animations when checked."));
+
set_size_and_pos(ui_custom_objection, "custom_objection");
ui_custom_objection->setText(tr("Custom Shout!"));
ui_custom_objection->setImage("custom");
@@ -1148,6 +1165,7 @@ void Courtroom::set_widgets()
truncate_label_text(ui_pre, "pre");
truncate_label_text(ui_flip, "flip");
truncate_label_text(ui_showname_enable, "showname_enable");
+ truncate_label_text(ui_slide_enable, "slide_enable");
// QLabel
truncate_label_text(ui_music_label, "music_label");
@@ -1365,7 +1383,7 @@ void Courtroom::done_received()
void Courtroom::set_background(QString p_background, bool display)
{
- ui_vp_testimony->stop();
+ ui_vp_testimony->stopPlayback();
current_background = p_background;
// welcome to hardcode central may I take your order of regularly scheduled
@@ -1390,9 +1408,21 @@ void Courtroom::set_background(QString p_background, bool display)
pos_list.append(default_pos[key]);
}
}
+ if (file_exists(ao_app->get_image_suffix(ao_app->get_background_path("court"))))
+ {
+ const QStringList overrides = {"def", "wit", "pro"};
+ for (const QString &override_pos : overrides)
+ {
+ if (!ao_app->read_design_ini("court:" + override_pos + "/rect", ao_app->get_background_path("design.ini")).isEmpty())
+ {
+ pos_list.append(override_pos);
+ }
+ }
+ }
for (const QString &pos : ao_app->read_design_ini("positions", ao_app->get_background_path("design.ini")).split(","))
{
- if (file_exists(ao_app->get_image_suffix(ao_app->get_background_path(pos))))
+ QString real_pos = pos.split(":")[0];
+ if ((file_exists(ao_app->get_image_suffix(ao_app->get_background_path(real_pos)))))
{
pos_list.append(pos);
}
@@ -1403,21 +1433,20 @@ void Courtroom::set_background(QString p_background, bool display)
if (display)
{
ui_vp_speedlines->hide();
- ui_vp_player_char->stop();
-
- ui_vp_sideplayer_char->stop();
- ui_vp_effect->stop();
+ ui_vp_player_char->stopPlayback();
+ ui_vp_sideplayer_char->stopPlayback();
+ ui_vp_effect->stopPlayback();
ui_vp_message->hide();
ui_vp_chatbox->setVisible(chatbox_always_show);
// Show it if chatbox always shows
if (Options::getInstance().characterStickerEnabled() && chatbox_always_show)
{
- ui_vp_sticker->load_image(m_chatmessage[CHAR_NAME]);
+ ui_vp_sticker->loadAndPlayAnimation(m_chatmessage[CHAR_NAME]);
}
// Hide the face sticker
else
{
- ui_vp_sticker->stop();
+ ui_vp_sticker->stopPlayback();
}
// Stop the chat arrow from animating
ui_vp_chat_arrow->hide();
@@ -1428,7 +1457,7 @@ void Courtroom::set_background(QString p_background, bool display)
text_state = 2;
anim_state = 3;
- ui_vp_objection->stop();
+ ui_vp_objection->stopPlayback();
chat_tick_timer->stop();
ui_vp_evidence_display->reset();
QString f_side = current_side;
@@ -1489,7 +1518,7 @@ void Courtroom::set_pos_dropdown(QStringList pos_dropdowns)
{
QString pos = pos_dropdown_list.at(n);
ui_pos_dropdown->addItem(pos);
- QPixmap image = QPixmap(ao_app->get_image_suffix(ao_app->get_background_path(ao_app->get_pos_path(pos))));
+ QPixmap image = QPixmap(ao_app->get_image_suffix(ao_app->get_background_path(ao_app->get_pos_path(pos).first)));
if (!image.isNull())
{
image = image.scaledToHeight(ui_pos_dropdown->iconSize().height());
@@ -1637,7 +1666,7 @@ void Courtroom::update_character(int p_cid, QString char_name, bool reset_emote)
}
ui_char_select_background->hide();
ui_ic_chat_message->setEnabled(m_cid != -1);
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
update_audio_volume();
}
@@ -1693,7 +1722,7 @@ void Courtroom::enter_courtroom()
// Update the audio sliders
update_audio_volume();
- ui_vp_testimony->stop();
+ ui_vp_testimony->stopPlayback();
// ui_server_chatlog->setHtml(ui_server_chatlog->toHtml());
}
@@ -1857,6 +1886,9 @@ void Courtroom::list_areas()
void Courtroom::debug_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
+#ifdef QT_DEBUG
+ return;
+#endif
Q_UNUSED(context);
const QMap colors = {{QtDebugMsg, "debug"}, {QtInfoMsg, "info"}, {QtWarningMsg, "warn"}, {QtCriticalMsg, "critical"}, {QtFatalMsg, "fatal"}};
const QString color_id = QString("debug_log_%1_color").arg(colors.value(type, "info"));
@@ -2272,7 +2304,12 @@ void Courtroom::on_chat_return_pressed()
}
}
- packet_contents.append(ao_app->get_blipname(current_char, current_emote));
+ if (ao_app->m_serverdata.get_feature(server::BASE_FEATURE_SET::CUSTOM_BLIPS))
+ {
+ packet_contents.append(ao_app->get_blipname(current_char, current_emote));
+
+ packet_contents.append(ui_slide_enable->isChecked() ? "1" : "0"); // just let the server figure out what to do with this
+ }
ao_app->send_server_packet(AOPacket("MS", packet_contents));
}
@@ -2310,6 +2347,9 @@ void Courtroom::reset_ui()
// Turn off our Preanim checkbox
ui_pre->setChecked(false);
}
+
+ // Slides can't be sticky for nausea reasons.
+ ui_slide_enable->setChecked(false);
}
void Courtroom::chatmessage_enqueue(QStringList p_contents)
@@ -2429,6 +2469,8 @@ void Courtroom::unpack_chatmessage(QStringList p_contents)
{
for (int n_string = 0; n_string < MS_MAXIMUM; ++n_string)
{
+ m_previous_chatmessage[n_string] = m_chatmessage[n_string];
+
// Note that we have added stuff that vanilla clients and servers simply
// won't send. So now, we have to check if the thing we want even exists
// amongst the packet's content. We also have to check if the server even
@@ -2458,7 +2500,7 @@ void Courtroom::unpack_chatmessage(QStringList p_contents)
text_state = 0;
anim_state = 0;
evidence_presented = false;
- ui_vp_objection->stop();
+ ui_vp_objection->stopPlayback();
chat_tick_timer->stop();
ui_vp_evidence_display->reset();
// This chat msg is not objection so we're not waiting on the objection animation to finish to display the character.
@@ -2655,8 +2697,7 @@ bool Courtroom::handle_objection()
ui_vp_message->setVisible(chatbox_always_show);
ui_vp_chat_arrow->setVisible(chatbox_always_show);
ui_vp_showname->setVisible(chatbox_always_show);
- ui_vp_objection->set_static_duration(shout_static_time);
- ui_vp_objection->set_max_duration(shout_max_time);
+ ui_vp_objection->setMaximumDurationPerFrame(shout_max_time);
QString filename;
switch (objection_mod)
{
@@ -2687,9 +2728,9 @@ bool Courtroom::handle_objection()
break;
m_chatmessage[EMOTE_MOD] = QChar(PREANIM);
}
- ui_vp_objection->load_image(filename, m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
+ ui_vp_objection->loadAndPlayAnimation(filename, m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
sfx_player->stopAll(); // Objection played! Cut all sfx.
- ui_vp_player_char->set_play_once(true);
+ ui_vp_player_char->setPlayOnce(true);
return true;
}
if (m_chatmessage[EMOTE] != "")
@@ -2703,8 +2744,8 @@ void Courtroom::display_character()
{
// Stop all previously playing animations, effects etc.
ui_vp_speedlines->hide();
- ui_vp_player_char->stop();
- ui_vp_effect->stop();
+ ui_vp_player_char->stopPlayback();
+ ui_vp_effect->stopPlayback();
// Clear all looping sfx to prevent obnoxiousness
sfx_player->stopAllLoopingStream();
// Hide the message and chatbox and handle the emotes
@@ -2713,12 +2754,12 @@ void Courtroom::display_character()
// Show it if chatbox always shows
if (Options::getInstance().characterStickerEnabled() && chatbox_always_show)
{
- ui_vp_sticker->load_image(m_chatmessage[CHAR_NAME]);
+ ui_vp_sticker->loadAndPlayAnimation(m_chatmessage[CHAR_NAME]);
}
// Hide the face sticker
else
{
- ui_vp_sticker->stop();
+ ui_vp_sticker->stopPlayback();
}
// Arrange the netstrings of the frame SFX for the character to know about
@@ -2726,17 +2767,15 @@ void Courtroom::display_character()
{
// ORDER IS IMPORTANT!!
QStringList netstrings = {m_chatmessage[FRAME_SCREENSHAKE], m_chatmessage[FRAME_REALIZATION], m_chatmessage[FRAME_SFX]};
- ui_vp_player_char->set_network_string(netstrings);
+ ui_vp_player_char->setFrameEffects(netstrings);
}
else
{
- ui_vp_player_char->set_network_string(QStringList());
+ ui_vp_player_char->setFrameEffects(QStringList());
}
// Determine if we should flip the character or not
- ui_vp_player_char->set_flipped(m_chatmessage[FLIP].toInt() == 1);
- // Move the character on the viewport according to the offsets
- set_self_offset(m_chatmessage[SELF_OFFSET]);
+ ui_vp_player_char->setFlipped(m_chatmessage[FLIP].toInt() == 1);
}
void Courtroom::display_pair_character(QString other_charid, QString other_offset)
@@ -2791,20 +2830,22 @@ void Courtroom::display_pair_character(QString other_charid, QString other_offse
break;
}
}
+
+ // Play the other pair character's idle animation
+ ui_vp_sideplayer_char->loadCharacterEmote(m_chatmessage[OTHER_NAME], m_chatmessage[OTHER_EMOTE], kal::CharacterAnimationLayer::IdleEmote);
+ ui_vp_sideplayer_char->setPlayOnce(false);
+
// Flip the pair character
if (ao_app->m_serverdata.get_feature(server::BASE_FEATURE_SET::FLIPPING) && m_chatmessage[OTHER_FLIP].toInt() == 1)
{
- ui_vp_sideplayer_char->set_flipped(true);
+ ui_vp_sideplayer_char->setFlipped(true);
}
else
{
- ui_vp_sideplayer_char->set_flipped(false);
+ ui_vp_sideplayer_char->setFlipped(false);
}
- // Play the other pair character's idle animation
- QString filename = "(a)" + m_chatmessage[OTHER_EMOTE];
- ui_vp_sideplayer_char->set_play_once(false);
- ui_vp_sideplayer_char->load_image(filename, m_chatmessage[OTHER_NAME], 0, false);
+ ui_vp_sideplayer_char->startPlayback();
}
}
}
@@ -2870,32 +2911,7 @@ void Courtroom::handle_ic_message()
// Update the chatbox information
initialize_chatbox();
- int emote_mod = m_chatmessage[EMOTE_MOD].toInt();
- bool immediate = m_chatmessage[IMMEDIATE].toInt() == 1;
- if (m_chatmessage[EMOTE] != "")
- {
- // Display our own character
- display_character();
-
- // Reset the pair character
- ui_vp_sideplayer_char->stop();
- ui_vp_sideplayer_char->move(0, 0);
-
- // If the emote_mod is not zooming
- if (emote_mod != ZOOM && emote_mod != PREANIM_ZOOM)
- {
- // Display the pair character
- display_pair_character(m_chatmessage[OTHER_CHARID], m_chatmessage[OTHER_OFFSET]);
- }
-
- // Parse the emote_mod part of the chat message
- handle_emote_mod(emote_mod, immediate);
- }
- else
- {
- play_sfx();
- start_chat_ticking();
- }
+ do_transition(m_chatmessage[DESK_MOD], last_side, m_chatmessage[SIDE]);
// if we have instant objections disabled, and queue is not empty, check if next message after this is an objection.
if (!Options::getInstance().objectionSkipQueueEnabled() && chatmessage_queue.size() > 0)
@@ -2956,6 +2972,170 @@ void Courtroom::do_screenshake()
screenshake_animation_group->start();
}
+void Courtroom::do_transition(QString p_desk_mod, QString oldPosId, QString newPosId)
+{
+ if (m_chatmessage[EMOTE] != "")
+ {
+ display_character();
+ }
+
+ const QStringList legacy_pos = {"def", "wit", "pro"};
+ QString t_old_pos = oldPosId;
+ QString t_new_pos = newPosId;
+ if (file_exists(ao_app->get_image_suffix(ao_app->get_background_path("court"))))
+ {
+ if (legacy_pos.contains(oldPosId))
+ {
+ t_old_pos = "court:" + oldPosId;
+ }
+ if (legacy_pos.contains(newPosId))
+ {
+ t_new_pos = "court:" + newPosId;
+ }
+ }
+
+ QPair old_pos_pair = ao_app->get_pos_path(t_old_pos);
+ QPair new_pos_pair = ao_app->get_pos_path(t_new_pos);
+
+ int duration = ao_app->get_pos_transition_duration(t_old_pos, t_new_pos);
+
+ // conditions to stop slide
+ if (oldPosId == newPosId || old_pos_pair.first != new_pos_pair.first || !new_pos_pair.second.isValid() || !Options::getInstance().slidesEnabled() || m_chatmessage[SLIDE] != "1" || duration == -1 || m_chatmessage[EMOTE_MOD].toInt() == ZOOM || m_chatmessage[EMOTE_MOD].toInt() == PREANIM_ZOOM)
+ {
+#ifdef DEBUG_TRANSITION
+ qDebug() << "skipping transition - not applicable";
+#endif
+ post_transition_cleanup();
+ return;
+ }
+#ifdef DEBUG_TRANSITION
+ // for debugging animation
+ ui_vp_sideplayer_char->setStyleSheet("background-color:rgba(0, 0, 255, 128);");
+ ui_vp_dummy_char->setStyleSheet("background-color:rgba(255, 0, 0, 128);");
+ ui_vp_sidedummy_char->setStyleSheet("background-color:rgba(0, 255, 0, 128);");
+
+ qDebug() << "STARTING TRANSITION, CURRENT TIME:" << transition_animation_group->currentTime();
+#endif
+
+ set_scene(p_desk_mod.toInt(), oldPosId);
+
+ int viewport_width = ui_viewport->width();
+ int viewport_height = ui_viewport->height();
+ double scale = double(viewport_height) / double(ui_vp_background->frameSize().height());
+ QPoint scaled_old_pos = QPoint(old_pos_pair.second.x() * scale, 0);
+ QPoint scaled_new_pos = QPoint(new_pos_pair.second.x() * scale, 0);
+
+ QList affected_list = {ui_vp_background, ui_vp_desk};
+ for (kal::AnimationLayer *ui_element : affected_list)
+ {
+ QPropertyAnimation *transition_animation = new QPropertyAnimation(ui_element, "pos", this);
+ transition_animation->setDuration(duration);
+ transition_animation->setEasingCurve(QEasingCurve::InOutCubic);
+ transition_animation->setStartValue(QPoint(-scaled_old_pos.x(), 0));
+ transition_animation->setEndValue(QPoint(-scaled_new_pos.x(), 0));
+ transition_animation_group->addAnimation(transition_animation);
+ }
+
+ auto calculate_offset_and_setup_layer = [&, this](kal::CharacterAnimationLayer *layer, QPoint newPos, QString rawOffset) {
+ QPoint offset;
+ QStringList offset_data = rawOffset.split(",");
+ offset.setX(viewport_width * offset_data.at(0).toInt() * 0.01);
+ if (offset_data.size() > 1)
+ {
+ offset.setY(viewport_height * offset_data.at(1).toInt() * 0.01);
+ }
+
+ layer->setParent(ui_vp_background);
+ layer->setPlayOnce(false);
+ layer->pausePlayback(true);
+ layer->startPlayback();
+ layer->move(newPos);
+ layer->show();
+ };
+
+ ui_vp_player_char->loadCharacterEmote(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE], kal::CharacterAnimationLayer::IdleEmote);
+ ui_vp_player_char->setFlipped(m_chatmessage[FLIP].toInt() == 1);
+ calculate_offset_and_setup_layer(ui_vp_player_char, scaled_new_pos, m_chatmessage[SELF_OFFSET]);
+
+ auto is_pairing = [](QString *data) {
+ return (data[OTHER_CHARID].toInt() != -1 && !data[OTHER_NAME].isEmpty());
+ };
+ auto is_pair_under = [](QString data) -> bool {
+ QStringList pair_data = data.split("^");
+ return (pair_data.size() > 1) ? (pair_data.at(1).toInt() == 1) : false;
+ };
+ if (is_pairing(m_chatmessage))
+ {
+ ui_vp_sideplayer_char->loadCharacterEmote(m_chatmessage[OTHER_NAME], m_chatmessage[OTHER_EMOTE], kal::CharacterAnimationLayer::IdleEmote);
+ calculate_offset_and_setup_layer(ui_vp_sideplayer_char, scaled_new_pos, m_chatmessage[OTHER_OFFSET]);
+ if (is_pair_under(m_chatmessage[OTHER_CHARID]))
+ {
+ ui_vp_player_char->stackUnder(ui_vp_sideplayer_char);
+ }
+ else
+ {
+ ui_vp_sideplayer_char->stackUnder(ui_vp_player_char);
+ }
+ }
+
+ ui_vp_dummy_char->loadCharacterEmote(m_previous_chatmessage[CHAR_NAME], m_previous_chatmessage[EMOTE], kal::CharacterAnimationLayer::IdleEmote);
+ ui_vp_dummy_char->setFlipped(m_previous_chatmessage[FLIP].toInt() == 1);
+ calculate_offset_and_setup_layer(ui_vp_dummy_char, scaled_old_pos, m_previous_chatmessage[SELF_OFFSET]);
+
+ if (is_pairing(m_previous_chatmessage))
+ {
+ ui_vp_sidedummy_char->loadCharacterEmote(m_previous_chatmessage[OTHER_NAME], m_previous_chatmessage[OTHER_EMOTE], kal::CharacterAnimationLayer::IdleEmote);
+ ui_vp_sidedummy_char->setFlipped(m_previous_chatmessage[OTHER_FLIP].toInt() == 1);
+ calculate_offset_and_setup_layer(ui_vp_sidedummy_char, scaled_old_pos, m_previous_chatmessage[OTHER_OFFSET]);
+ if (is_pair_under(m_previous_chatmessage[OTHER_CHARID]))
+ {
+ ui_vp_dummy_char->stackUnder(ui_vp_sidedummy_char);
+ }
+ else
+ {
+ ui_vp_sidedummy_char->stackUnder(ui_vp_dummy_char);
+ }
+ }
+
+ transition_animation_group->start();
+}
+
+void Courtroom::post_transition_cleanup()
+{
+ transition_animation_group->clear();
+
+ for (kal::CharacterAnimationLayer *layer : qAsConst(ui_vp_char_list))
+ {
+ bool is_visible = layer->isVisible();
+ layer->stopPlayback();
+ layer->pausePlayback(false);
+ layer->setParent(ui_viewport);
+ layer->stackUnder(ui_vp_desk);
+ layer->setVisible(is_visible);
+ }
+
+ ui_vp_dummy_char->hide();
+ ui_vp_sidedummy_char->hide();
+
+ set_scene(m_chatmessage[DESK_MOD].toInt(), m_chatmessage[SIDE]);
+
+ // Move the character on the viewport according to the offsets
+ set_self_offset(m_chatmessage[SELF_OFFSET], ui_vp_player_char);
+
+ int emote_mod = m_chatmessage[EMOTE_MOD].toInt();
+ bool immediate = m_chatmessage[IMMEDIATE].toInt() == 1;
+
+ // If the emote_mod is not zooming
+ if (emote_mod != ZOOM && emote_rows != PREANIM_ZOOM)
+ {
+ // Display the pair character
+ display_pair_character(m_chatmessage[OTHER_CHARID], m_chatmessage[OTHER_OFFSET]);
+ }
+
+ // Parse the emote_mod part of the chat message
+ handle_emote_mod(emote_mod, immediate);
+}
+
void Courtroom::do_flash()
{
if (!Options::getInstance().effectsEnabled())
@@ -2975,8 +3155,10 @@ void Courtroom::do_effect(QString fx_path, QString fx_sound, QString p_char, QSt
return;
}
QString effect = ao_app->get_effect(fx_path, p_char, p_folder);
- if (effect == "")
+ if (effect.isEmpty())
{
+ ui_vp_effect->stopPlayback();
+ ui_vp_effect->hide();
return;
}
@@ -2990,11 +3172,9 @@ void Courtroom::do_effect(QString fx_path, QString fx_sound, QString p_char, QSt
{
return;
}
- ui_vp_effect->transform_mode = ao_app->get_scaling(ao_app->get_effect_property(fx_path, p_char, p_folder, "scaling"));
- ui_vp_effect->stretch = ao_app->get_effect_property(fx_path, p_char, p_folder, "stretch").startsWith("true");
- ui_vp_effect->set_flipped(ao_app->get_effect_property(fx_path, p_char, p_folder, "respect_flip").startsWith("true") && m_chatmessage[FLIP].toInt() == 1);
- ui_vp_effect->set_play_once(false); // The effects themselves dictate whether or not they're looping.
- // Static effects will linger.
+ ui_vp_effect->setTransformationMode(ao_app->get_scaling(ao_app->get_effect_property(fx_path, p_char, p_folder, "scaling")));
+ ui_vp_effect->setStretchToFit(ao_app->get_effect_property(fx_path, p_char, p_folder, "stretch").startsWith("true"));
+ ui_vp_effect->setFlipped(ao_app->get_effect_property(fx_path, p_char, p_folder, "respect_flip").startsWith("true") && m_chatmessage[FLIP].toInt() == 1);
bool looping = ao_app->get_effect_property(fx_path, p_char, p_folder, "loop").startsWith("true");
@@ -3056,10 +3236,9 @@ void Courtroom::do_effect(QString fx_path, QString fx_sound, QString p_char, QSt
}
ui_vp_effect->move(effect_x, effect_y);
- ui_vp_effect->set_static_duration(max_duration);
- ui_vp_effect->set_max_duration(max_duration);
- ui_vp_effect->load_image(effect, looping);
- ui_vp_effect->set_cull_image(cull);
+ ui_vp_effect->setMaximumDurationPerFrame(max_duration);
+ ui_vp_effect->loadAndPlayAnimation(effect, looping);
+ ui_vp_effect->setHideWhenStopped(cull);
}
void Courtroom::play_char_sfx(QString sfx_name)
@@ -3171,7 +3350,7 @@ void Courtroom::initialize_chatbox()
else
{
ui_vp_chat_arrow->move(design_ini_result.x + ui_vp_chatbox->x(), design_ini_result.y + ui_vp_chatbox->y());
- ui_vp_chat_arrow->combo_resize(design_ini_result.width, design_ini_result.height);
+ ui_vp_chat_arrow->resize(design_ini_result.width, design_ini_result.height);
}
QString font_name;
@@ -3222,7 +3401,7 @@ void Courtroom::display_evidence_image()
QString f_image = local_evidence_list.at(f_evi_id - 1).image;
// QString f_evi_name = local_evidence_list.at(f_evi_id - 1).name;
// def jud and hlp should display the evidence icon on the RIGHT side
- bool is_left_side = !(side == "def" || side == "hlp" || side == "jud" || side == "jur");
+ bool is_left_side = !(side.startsWith("def") || side == "hlp");
ui_vp_evidence_display->show_evidence(f_evi_id, f_image, is_left_side, sfx_player->volume());
}
}
@@ -3240,7 +3419,7 @@ void Courtroom::handle_ic_speaking()
// Obtain character information for our character
QString filename;
// I still hate this hardcoding. If we're on pos pro, hlp and wit, use prosecution_speedlines. Otherwise, defense_speedlines.
- if (side == "pro" || side == "hlp" || side == "wit")
+ if (side.startsWith("pro") || side == "hlp" || side.startsWith("wit"))
{
filename = "prosecution_speedlines";
}
@@ -3251,8 +3430,8 @@ void Courtroom::handle_ic_speaking()
// We're zooming, so hide the pair character and ignore pair offsets. This ain't about them.
ui_vp_sideplayer_char->hide();
- ui_vp_player_char->move_and_center(0, 0);
- ui_vp_speedlines->load_image(filename, m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
+ ui_vp_player_char->move(0, 0);
+ ui_vp_speedlines->loadAndPlayAnimation(filename, m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
}
// Check if this is a talking color (white text, etc.)
@@ -3261,23 +3440,22 @@ void Courtroom::handle_ic_speaking()
// If color is talking, and our state isn't already talking
if (color_is_talking && text_state == 1 && anim_state < 2)
{
- // Stop the previous animation and play the talking animation
- ui_vp_player_char->stop();
- ui_vp_player_char->set_play_once(false);
- filename = "(b)" + m_chatmessage[EMOTE];
- ui_vp_player_char->load_image(filename, m_chatmessage[CHAR_NAME], 0, false);
- // Set the anim state accordingly
+ // Play the talking animation
anim_state = 2;
+ filename = m_chatmessage[EMOTE];
+ ui_vp_player_char->loadCharacterEmote(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE], kal::CharacterAnimationLayer::TalkEmote);
+ ui_vp_player_char->setPlayOnce(false);
+ ui_vp_player_char->startPlayback();
+ // Set the anim state accordingly
}
else if (anim_state < 3 && anim_state != 3) // Set it to idle as we're not on that already
{
- // Stop the previous animation and play the idle animation
- ui_vp_player_char->stop();
- ui_vp_player_char->set_play_once(false);
- filename = "(a)" + m_chatmessage[EMOTE];
- ui_vp_player_char->load_image(filename, m_chatmessage[CHAR_NAME], 0, false);
- // Set the anim state accordingly
+ // Play the idle animation
anim_state = 3;
+ filename = m_chatmessage[EMOTE];
+ ui_vp_player_char->loadCharacterEmote(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE], kal::CharacterAnimationLayer::IdleEmote);
+ ui_vp_player_char->setPlayOnce(false);
+ ui_vp_player_char->startPlayback();
}
// Begin parsing through the chatbox message
@@ -3812,15 +3990,16 @@ void Courtroom::play_preanim(bool immediate)
qWarning() << "could not find preanim" << f_preanim << "for character" << f_char;
return;
}
- ui_vp_player_char->set_static_duration(preanim_duration);
- ui_vp_player_char->set_play_once(true);
- ui_vp_player_char->load_image(f_preanim, f_char, preanim_duration, true);
+
+ ui_vp_player_char->loadCharacterEmote(f_char, f_preanim, kal::CharacterAnimationLayer::PreEmote, preanim_duration);
+ ui_vp_player_char->setPlayOnce(true);
+ ui_vp_player_char->startPlayback();
switch (m_chatmessage[DESK_MOD].toInt())
{
case DESK_EMOTE_ONLY_EX:
ui_vp_sideplayer_char->hide();
- ui_vp_player_char->move_and_center(0, 0);
+ ui_vp_player_char->move(0, 0);
[[fallthrough]];
case DESK_EMOTE_ONLY:
case DESK_HIDE:
@@ -3878,7 +4057,7 @@ void Courtroom::start_chat_ticking()
switch (m_chatmessage[DESK_MOD].toInt())
{
case DESK_EMOTE_ONLY_EX:
- set_self_offset(m_chatmessage[SELF_OFFSET]);
+ set_self_offset(m_chatmessage[SELF_OFFSET], ui_vp_player_char);
[[fallthrough]];
case DESK_EMOTE_ONLY:
case DESK_SHOW:
@@ -3887,7 +4066,7 @@ void Courtroom::start_chat_ticking()
case DESK_PRE_ONLY_EX:
ui_vp_sideplayer_char->hide();
- ui_vp_player_char->move_and_center(0, 0);
+ ui_vp_player_char->move(0, 0);
[[fallthrough]];
case DESK_PRE_ONLY:
case DESK_HIDE:
@@ -3941,12 +4120,12 @@ void Courtroom::start_chat_ticking()
// Show it if chatbox always shows
if (Options::getInstance().characterStickerEnabled() && chatbox_always_show)
{
- ui_vp_sticker->load_image(m_chatmessage[CHAR_NAME]);
+ ui_vp_sticker->loadAndPlayAnimation(m_chatmessage[CHAR_NAME]);
}
// Hide the face sticker
else
{
- ui_vp_sticker->stop();
+ ui_vp_sticker->stopPlayback();
}
}
// If we're not already waiting on the next message, start the timer. We could be overriden if there's an objection planned.
@@ -3963,7 +4142,7 @@ void Courtroom::start_chat_ticking()
if (Options::getInstance().characterStickerEnabled())
{
- ui_vp_sticker->load_image(m_chatmessage[CHAR_NAME]);
+ ui_vp_sticker->loadAndPlayAnimation(m_chatmessage[CHAR_NAME]);
}
if (m_chatmessage[ADDITIVE] != "1")
@@ -4013,8 +4192,6 @@ void Courtroom::chat_tick()
// Due to our new text speed system, we always need to stop the timer now.
chat_tick_timer->stop();
- ui_vp_player_char->set_static_duration(0);
- QString filename;
if (tick_pos >= f_message.size())
{
@@ -4026,20 +4203,21 @@ void Courtroom::chat_tick()
{
QStringList c_paths = {ao_app->get_image_suffix(ao_app->get_character_path(m_chatmessage[CHAR_NAME], "(c)" + m_chatmessage[EMOTE])), ao_app->get_image_suffix(ao_app->get_character_path(m_chatmessage[CHAR_NAME], "(c)/" + m_chatmessage[EMOTE]))};
// if there is a (c) animation for this emote and we haven't played it already
- if (file_exists(ui_vp_player_char->find_image(c_paths)) && (!c_played))
+ if (file_exists(ao_app->find_image(c_paths)) && (!c_played))
{
anim_state = 5;
- ui_vp_player_char->set_play_once(true);
- filename = "(c)" + m_chatmessage[EMOTE];
c_played = true;
+ ui_vp_player_char->loadCharacterEmote(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE], kal::CharacterAnimationLayer::PostEmote);
+ ui_vp_player_char->setPlayOnce(true);
+ ui_vp_player_char->startPlayback();
}
else
{
anim_state = 3;
- ui_vp_player_char->set_play_once(false);
- filename = "(a)" + m_chatmessage[EMOTE];
+ ui_vp_player_char->loadCharacterEmote(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE], kal::CharacterAnimationLayer::IdleEmote);
+ ui_vp_player_char->setPlayOnce(false);
+ ui_vp_player_char->startPlayback();
}
- ui_vp_player_char->load_image(filename, m_chatmessage[CHAR_NAME], 0, false);
}
}
else // We're a narrator msg
@@ -4053,8 +4231,8 @@ void Courtroom::chat_tick()
f_char = m_chatmessage[CHAR_NAME];
f_custom_theme = ao_app->get_chat(f_char);
}
- ui_vp_chat_arrow->transform_mode = ao_app->get_misc_scaling(f_custom_theme);
- ui_vp_chat_arrow->load_image("chat_arrow", f_custom_theme); // Chat stopped being processed, indicate that.
+ ui_vp_chat_arrow->setTransformationMode(ao_app->get_misc_scaling(f_custom_theme));
+ ui_vp_chat_arrow->loadAndPlayAnimation("chat_arrow", f_custom_theme); // Chat stopped being processed, indicate that.
QString f_message_filtered = filter_ic_text(f_message, true, -1, m_chatmessage[TEXT_COLOR].toInt());
for (int c = 0; c < max_colors; ++c)
{
@@ -4290,19 +4468,17 @@ void Courtroom::chat_tick()
if (color_is_talking && anim_state != 2 && anim_state < 4) // Set it to talking as we're not on that already (though we have
// to avoid interrupting a non-interrupted preanim)
{
- ui_vp_player_char->stop();
- ui_vp_player_char->set_play_once(false);
- filename = "(b)" + m_chatmessage[EMOTE];
- ui_vp_player_char->load_image(filename, m_chatmessage[CHAR_NAME], 0, false);
anim_state = 2;
+ ui_vp_player_char->loadCharacterEmote(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE], kal::CharacterAnimationLayer::TalkEmote);
+ ui_vp_player_char->setPlayOnce(false);
+ ui_vp_player_char->startPlayback();
}
else if (!color_is_talking && anim_state < 3 && anim_state != 3) // Set it to idle as we're not on that already
{
- ui_vp_player_char->stop();
- ui_vp_player_char->set_play_once(false);
- filename = "(a)" + m_chatmessage[EMOTE];
- ui_vp_player_char->load_image(filename, m_chatmessage[CHAR_NAME], 0, false);
anim_state = 3;
+ ui_vp_player_char->loadCharacterEmote(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE], kal::CharacterAnimationLayer::IdleEmote);
+ ui_vp_player_char->setPlayOnce(false);
+ ui_vp_player_char->startPlayback();
}
}
// Continue ticking
@@ -4331,9 +4507,21 @@ void Courtroom::play_sfx()
void Courtroom::set_scene(bool show_desk, const QString f_side)
{
- ui_vp_background->load_image(ao_app->get_pos_path(f_side));
- ui_vp_desk->load_image(ao_app->get_pos_path(f_side, true));
+ QPair bg_pair = ao_app->get_pos_path(f_side);
+ QPair desk_pair = ao_app->get_pos_path(f_side, true);
+ ui_vp_background->loadAndPlayAnimation(bg_pair.first);
+ ui_vp_desk->loadAndPlayAnimation(desk_pair.first);
+
+ double scale = double(ui_viewport->height()) / double(ui_vp_background->frameSize().height());
+ QSize scaled_size = ui_vp_background->frameSize() * scale;
+ QPoint scaled_offset = QPoint(-(bg_pair.second.x() * scale), 0);
+ ui_vp_background->resize(scaled_size);
+ ui_vp_background->move(scaled_offset);
+ ui_vp_desk->resize(scaled_size);
+ ui_vp_desk->move(scaled_offset);
+
+ last_side = f_side;
if (show_desk)
{
ui_vp_desk->show();
@@ -4344,7 +4532,7 @@ void Courtroom::set_scene(bool show_desk, const QString f_side)
}
}
-void Courtroom::set_self_offset(const QString &p_list)
+void Courtroom::set_self_offset(const QString &p_list, kal::AnimationLayer *p_layer)
{
QStringList self_offsets = p_list.split("&");
int self_offset = self_offsets[0].toInt();
@@ -4357,7 +4545,7 @@ void Courtroom::set_self_offset(const QString &p_list)
{
self_offset_v = self_offsets[1].toInt();
}
- ui_vp_player_char->move_and_center(ui_viewport->width() * self_offset / 100, ui_viewport->height() * self_offset_v / 100);
+ p_layer->move(ui_viewport->width() * self_offset / 100, ui_viewport->height() * self_offset_v / 100);
}
void Courtroom::set_ip_list(QString p_list)
@@ -4381,7 +4569,7 @@ void Courtroom::set_mute(bool p_muted, int p_cid)
else
{
ui_muted->hide();
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height());
@@ -4513,15 +4701,14 @@ void Courtroom::handle_wtce(QString p_wtce, int variant)
QString bg_misc = ao_app->read_design_ini("misc", ao_app->get_background_path("design.ini"));
QString sfx_name;
QString filename;
- ui_vp_wtce->set_static_duration(wtce_static_time);
- ui_vp_wtce->set_max_duration(wtce_max_time);
+ ui_vp_wtce->setMaximumDurationPerFrame(wtce_max_time);
// witness testimony
if (p_wtce == "testimony1")
{
// End testimony indicator
if (variant == 1)
{
- ui_vp_testimony->kill();
+ ui_vp_testimony->stopPlayback();
return;
}
sfx_name = ao_app->get_court_sfx("witness_testimony", bg_misc);
@@ -4530,7 +4717,7 @@ void Courtroom::handle_wtce(QString p_wtce, int variant)
sfx_name = ao_app->get_court_sfx("witnesstestimony", bg_misc);
}
filename = "witnesstestimony_bubble";
- ui_vp_testimony->load_image("testimony", "", bg_misc);
+ ui_vp_testimony->loadAndPlayAnimation("testimony", "", bg_misc);
}
// cross examination
else if (p_wtce == "testimony2")
@@ -4541,12 +4728,11 @@ void Courtroom::handle_wtce(QString p_wtce, int variant)
sfx_name = ao_app->get_court_sfx("crossexamination", bg_misc);
}
filename = "crossexamination_bubble";
- ui_vp_testimony->kill();
+ ui_vp_testimony->stopPlayback();
}
else
{
- ui_vp_wtce->set_static_duration(verdict_static_time);
- ui_vp_wtce->set_max_duration(verdict_max_time);
+ ui_vp_wtce->setMaximumDurationPerFrame(verdict_max_time);
// Verdict?
if (p_wtce == "judgeruling")
{
@@ -4558,13 +4744,13 @@ void Courtroom::handle_wtce(QString p_wtce, int variant)
sfx_name = ao_app->get_court_sfx("notguilty", bg_misc);
}
filename = "notguilty_bubble";
- ui_vp_testimony->kill();
+ ui_vp_testimony->stopPlayback();
}
else if (variant == 1)
{
sfx_name = ao_app->get_court_sfx("guilty", bg_misc);
filename = "guilty_bubble";
- ui_vp_testimony->kill();
+ ui_vp_testimony->stopPlayback();
}
}
// Completely custom WTCE
@@ -4575,8 +4761,8 @@ void Courtroom::handle_wtce(QString p_wtce, int variant)
}
}
sfx_player->findAndPlaySfx(sfx_name);
- ui_vp_wtce->load_image(filename, "", bg_misc);
- ui_vp_wtce->set_play_once(true);
+ ui_vp_wtce->loadAndPlayAnimation(filename, "", bg_misc);
+ ui_vp_wtce->setPlayOnce(true);
}
void Courtroom::set_hp_bar(int p_bar, int p_state)
@@ -5005,7 +5191,7 @@ void Courtroom::on_pos_remove_clicked()
ui_pos_dropdown->blockSignals(false);
current_side = "";
ui_pos_remove->hide();
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::set_iniswap_dropdown()
@@ -5052,7 +5238,7 @@ void Courtroom::set_iniswap_dropdown()
void Courtroom::on_iniswap_dropdown_changed(int p_index)
{
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
QString iniswap = ui_iniswap_dropdown->itemText(p_index);
QStringList swaplist;
@@ -5190,7 +5376,7 @@ void Courtroom::set_sfx_dropdown()
void Courtroom::on_sfx_dropdown_changed(int p_index)
{
custom_sfx = "";
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
if (p_index == 0)
{
ui_sfx_remove->hide();
@@ -5345,7 +5531,7 @@ void Courtroom::on_character_effects_edit_requested()
void Courtroom::on_effects_dropdown_changed(int p_index)
{
effect = ui_effects_dropdown->itemText(p_index);
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
bool Courtroom::effects_dropdown_find_and_set(QString effect)
@@ -5701,7 +5887,7 @@ void Courtroom::on_hold_it_clicked()
objection_state = 1;
}
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_objection_clicked()
@@ -5721,7 +5907,7 @@ void Courtroom::on_objection_clicked()
objection_state = 2;
}
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_take_that_clicked()
@@ -5741,7 +5927,7 @@ void Courtroom::on_take_that_clicked()
objection_state = 3;
}
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_custom_objection_clicked()
@@ -5761,7 +5947,7 @@ void Courtroom::on_custom_objection_clicked()
objection_state = 4;
}
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::show_custom_objection_menu(const QPoint &pos)
@@ -5814,7 +6000,7 @@ void Courtroom::on_realization_clicked()
ui_realization->setImage("realization");
}
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_screenshake_clicked()
@@ -5830,7 +6016,7 @@ void Courtroom::on_screenshake_clicked()
ui_screenshake->setImage("screenshake");
}
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_mute_clicked()
@@ -6033,13 +6219,13 @@ void Courtroom::on_text_color_changed(int p_color)
text_color = 0;
}
}
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_music_slider_moved(int p_value)
{
music_player->setStreamVolume(p_value, 0); // Set volume on music layer
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_sfx_slider_moved(int p_value)
@@ -6051,13 +6237,13 @@ void Courtroom::on_sfx_slider_moved(int p_value)
music_player->setStreamVolume(p_value, i);
}
objection_player->setVolume(p_value);
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_blip_slider_moved(int p_value)
{
blip_player->setVolume(p_value);
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_log_limit_changed(int value)
@@ -6084,7 +6270,7 @@ void Courtroom::on_witness_testimony_clicked()
ao_app->send_server_packet(AOPacket("RT", {"testimony1"}));
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_cross_examination_clicked()
@@ -6096,7 +6282,7 @@ void Courtroom::on_cross_examination_clicked()
ao_app->send_server_packet(AOPacket("RT", {"testimony2"}));
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_not_guilty_clicked()
@@ -6108,7 +6294,7 @@ void Courtroom::on_not_guilty_clicked()
ao_app->send_server_packet(AOPacket("RT", {"judgeruling", "0"}));
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_guilty_clicked()
@@ -6120,7 +6306,7 @@ void Courtroom::on_guilty_clicked()
ao_app->send_server_packet(AOPacket("RT", {"judgeruling", "1"}));
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_change_character_clicked()
@@ -6207,7 +6393,7 @@ void Courtroom::on_call_mod_clicked()
ao_app->send_server_packet(AOPacket("ZZ"));
}
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::on_settings_clicked()
@@ -6215,16 +6401,6 @@ void Courtroom::on_settings_clicked()
ao_app->call_settings_menu();
}
-void Courtroom::on_pre_clicked()
-{
- ui_ic_chat_message->setFocus();
-}
-
-void Courtroom::on_flip_clicked()
-{
- ui_ic_chat_message->setFocus();
-}
-
void Courtroom::on_additive_clicked()
{
if (ui_additive->isChecked())
@@ -6234,10 +6410,10 @@ void Courtroom::on_additive_clicked()
ui_ic_chat_message->end(false); // move cursor to the end of the message
// without selecting anything
}
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
-void Courtroom::on_guard_clicked()
+void Courtroom::focus_ic_input()
{
ui_ic_chat_message->setFocus();
}
@@ -6245,7 +6421,7 @@ void Courtroom::on_guard_clicked()
void Courtroom::on_showname_enable_clicked()
{
regenerate_ic_chatlog();
- ui_ic_chat_message->setFocus();
+ focus_ic_input();
}
void Courtroom::regenerate_ic_chatlog()
diff --git a/src/courtroom.h b/src/courtroom.h
index 0f49839..a7ca235 100644
--- a/src/courtroom.h
+++ b/src/courtroom.h
@@ -1,5 +1,6 @@
#pragma once
+#include "animationlayer.h"
#include "aoapplication.h"
#include "aoblipplayer.h"
#include "aobutton.h"
@@ -10,7 +11,6 @@
#include "aoevidencebutton.h"
#include "aoevidencedisplay.h"
#include "aoimage.h"
-#include "aolayer.h"
#include "aomusicplayer.h"
#include "aopacket.h"
#include "aosfxplayer.h"
@@ -150,8 +150,9 @@ public:
// sets desk and bg based on pos in chatmessage
void set_scene(bool show_desk, QString f_side);
- // sets ui_vp_player_char according to SELF_OFFSET, only a function bc it's used with desk_mod 4 and 5
- void set_self_offset(const QString &p_list);
+ // sets p_layer according to SELF_OFFSET, only a function bc it's used with
+ // desk_mod 4 and 5
+ void set_self_offset(const QString &p_list, kal::AnimationLayer *p_layer);
// takes in serverD-formatted IP list as prints a converted version to server
// OOC admittedly poorly named
@@ -215,6 +216,9 @@ public:
// Handle the stuff that comes when the character appears on screen and starts animating (preanims etc.)
void handle_ic_message();
+ // Start the logic for doing a courtroom pan slide
+ void do_transition(QString desk_mod, QString oldPosId, QString new_pos);
+
// Display the character.
void display_character();
@@ -308,6 +312,8 @@ private:
QParallelAnimationGroup *screenshake_animation_group = new QParallelAnimationGroup;
+ QParallelAnimationGroup *transition_animation_group = new QParallelAnimationGroup;
+
bool next_character_is_not_special = false; // If true, write the
// next character as it is.
@@ -436,10 +442,16 @@ private:
// Minumum and maximum number of parameters in the MS packet
static const int MS_MINIMUM = 15;
- static const int MS_MAXIMUM = 31;
+ static const int MS_MAXIMUM = 32;
QString m_chatmessage[MS_MAXIMUM];
+ QString m_previous_chatmessage[MS_MAXIMUM];
+
+ /**
+ * @brief The amount of time to wait at the start and end of slide
+ * animations
+ */
+ static const int TRANSITION_BOOKEND_DELAY = 300;
- QString previous_ic_message;
QString additive_previous;
// char id, muted or not
@@ -570,6 +582,11 @@ private:
QString current_background = "default";
QString current_side;
+ // used for courtroom slide logic
+ QString last_side = "";
+ int last_offset = 0;
+ int last_v_offset = 0;
+
QString last_music_search;
QString last_area_search;
@@ -591,20 +608,23 @@ private:
AOImage *ui_background;
QWidget *ui_viewport;
- BackgroundLayer *ui_vp_background;
- SplashLayer *ui_vp_speedlines;
- CharLayer *ui_vp_player_char;
- CharLayer *ui_vp_sideplayer_char;
- BackgroundLayer *ui_vp_desk;
+ kal::BackgroundAnimationLayer *ui_vp_background;
+ kal::SplashAnimationLayer *ui_vp_speedlines;
+ kal::CharacterAnimationLayer *ui_vp_player_char;
+ kal::CharacterAnimationLayer *ui_vp_sideplayer_char;
+ kal::CharacterAnimationLayer *ui_vp_dummy_char;
+ kal::CharacterAnimationLayer *ui_vp_sidedummy_char;
+ QList ui_vp_char_list;
+ kal::BackgroundAnimationLayer *ui_vp_desk;
AOEvidenceDisplay *ui_vp_evidence_display;
AOImage *ui_vp_chatbox;
AOChatboxLabel *ui_vp_showname;
- InterfaceLayer *ui_vp_chat_arrow;
+ kal::InterfaceAnimationLayer *ui_vp_chat_arrow;
QTextEdit *ui_vp_message;
- SplashLayer *ui_vp_testimony;
- SplashLayer *ui_vp_wtce;
- EffectLayer *ui_vp_effect;
- SplashLayer *ui_vp_objection;
+ kal::SplashAnimationLayer *ui_vp_testimony;
+ kal::SplashAnimationLayer *ui_vp_wtce;
+ kal::EffectAnimationLayer *ui_vp_effect;
+ kal::SplashAnimationLayer *ui_vp_objection;
QTextEdit *ui_ic_chatlog;
@@ -616,9 +636,9 @@ private:
QTreeWidget *ui_music_list;
ScrollText *ui_music_name;
- InterfaceLayer *ui_music_display;
+ kal::InterfaceAnimationLayer *ui_music_display;
- StickerLayer *ui_vp_sticker;
+ kal::StickerAnimationLayer *ui_vp_sticker;
static const int max_clocks = 5;
AOClockLabel *ui_clock[max_clocks];
@@ -693,6 +713,8 @@ private:
QCheckBox *ui_immediate;
QCheckBox *ui_showname_enable;
+ QCheckBox *ui_slide_enable;
+
AOButton *ui_custom_objection;
QMenu *custom_obj_menu;
AOButton *ui_realization;
@@ -912,10 +934,8 @@ private Q_SLOTS:
void on_call_mod_clicked();
void on_settings_clicked();
- void on_pre_clicked();
- void on_flip_clicked();
+ void focus_ic_input();
void on_additive_clicked();
- void on_guard_clicked();
void on_showname_enable_clicked();
@@ -958,6 +978,10 @@ private Q_SLOTS:
// Proceed to parse the oldest chatmessage and remove it from the stack
void chatmessage_dequeue();
- void preview_emote(QString emote);
+ void preview_emote(QString emote, kal::CharacterAnimationLayer::EmoteType emoteType);
void update_emote_preview();
+
+ // After attempting to play a transition animation, clean up the viewport
+ // objects for everyone else and continue the IC processing callstack
+ void post_transition_cleanup();
};
diff --git a/src/datatypes.h b/src/datatypes.h
index 9463a47..ac9c646 100644
--- a/src/datatypes.h
+++ b/src/datatypes.h
@@ -78,6 +78,7 @@ enum CHAT_MESSAGE
ADDITIVE,
EFFECTS,
BLIPNAME,
+ SLIDE,
};
enum EMOTE_MOD_TYPE
diff --git a/src/emotes.cpp b/src/emotes.cpp
index 4023b52..c120d59 100644
--- a/src/emotes.cpp
+++ b/src/emotes.cpp
@@ -231,17 +231,16 @@ void Courtroom::update_emote_preview()
{
return;
}
- QString emote;
+
QString pre = ao_app->get_pre_emote(current_char, current_emote);
if (ui_pre->isChecked() && !pre.isEmpty() && pre != "-")
{
- emote = pre;
+ preview_emote(pre, kal::CharacterAnimationLayer::PreEmote);
}
else
{
- emote = "(b)" + ao_app->get_emote(current_char, current_emote);
+ preview_emote(ao_app->get_emote(current_char, current_emote), kal::CharacterAnimationLayer::IdleEmote);
}
- preview_emote(emote);
}
void Courtroom::on_emote_clicked(int p_id)
@@ -270,30 +269,30 @@ void Courtroom::show_emote_menu(const QPoint &pos)
QString f_pre = ao_app->get_pre_emote(current_char, emote_num);
if (!f_pre.isEmpty() && f_pre != "-")
{
- emote_menu->addAction("Preview pre: " + f_pre, this, [this, f_pre] { preview_emote(f_pre); });
+ emote_menu->addAction("Preview pre: " + f_pre, this, [this, f_pre] { preview_emote(f_pre, kal::CharacterAnimationLayer::PreEmote); });
}
QString f_emote = ao_app->get_emote(current_char, emote_num);
if (!f_emote.isEmpty())
{
- emote_menu->addAction("Preview idle: " + f_emote, this, [this, f_emote] { preview_emote("(a)" + f_emote); });
- emote_menu->addAction("Preview talk: " + f_emote, this, [this, f_emote] { preview_emote("(b)" + f_emote); });
+ emote_menu->addAction("Preview idle: " + f_emote, this, [this, f_emote] { preview_emote(f_emote, kal::CharacterAnimationLayer::IdleEmote); });
+ emote_menu->addAction("Preview talk: " + f_emote, this, [this, f_emote] { preview_emote(f_emote, kal::CharacterAnimationLayer::TalkEmote); });
QStringList c_paths = {ao_app->get_image_suffix(ao_app->get_character_path(current_char, "(c)" + f_emote)), ao_app->get_image_suffix(ao_app->get_character_path(current_char, "(c)/" + f_emote))};
// if there is a (c) animation
- if (file_exists(ui_vp_player_char->find_image(c_paths)))
+ if (file_exists(ao_app->find_image(c_paths)))
{
- emote_menu->addAction("Preview segway: " + f_emote, this, [this, f_emote] { preview_emote("(c)" + f_emote); });
+ emote_menu->addAction("Preview segway: " + f_emote, this, [this, f_emote] { preview_emote(f_emote, kal::CharacterAnimationLayer::PostEmote); });
}
}
emote_menu->popup(button->mapToGlobal(pos));
}
-void Courtroom::preview_emote(QString f_emote)
+void Courtroom::preview_emote(QString f_emote, kal::CharacterAnimationLayer::EmoteType emoteType)
{
emote_preview->show();
emote_preview->raise();
emote_preview->updateViewportGeometry();
- emote_preview->display(current_char, f_emote, ui_flip->isChecked(), ui_pair_offset_spinbox->value(), ui_pair_vert_offset_spinbox->value());
+ emote_preview->display(current_char, f_emote, emoteType, ui_flip->isChecked(), ui_pair_offset_spinbox->value(), ui_pair_vert_offset_spinbox->value());
}
void Courtroom::on_emote_left_clicked()
diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp
index 69ff839..b574b63 100644
--- a/src/networkmanager.cpp
+++ b/src/networkmanager.cpp
@@ -197,8 +197,9 @@ void NetworkManager::ship_server_packet(AOPacket packet)
qCritical() << "Failed to ship packet; not connected.";
return;
}
-
+#ifdef NETWORK_DEBUG
qInfo().noquote() << "Sending packet:" << packet.toString();
+#endif
m_connection->sendPacket(packet);
}
@@ -209,6 +210,8 @@ void NetworkManager::join_to_server()
void NetworkManager::handle_server_packet(AOPacket packet)
{
+#ifdef NETWORK_DEBUG
qInfo().noquote() << "Received packet:" << packet.toString();
+#endif
ao_app->server_packet_received(packet);
}
diff --git a/src/options.cpp b/src/options.cpp
index 8006076..d06c5e4 100644
--- a/src/options.cpp
+++ b/src/options.cpp
@@ -401,6 +401,16 @@ void Options::setNetworkedFrameSfxEnabled(bool value)
config.setValue("framenetwork", value);
}
+bool Options::slidesEnabled() const
+{
+ return config.value("slides", true).toBool();
+}
+
+void Options::setSlidesEnabled(bool value)
+{
+ config.setValue("slides", value);
+}
+
bool Options::colorLogEnabled() const
{
return config.value("colorlog", true).toBool();
diff --git a/src/options.h b/src/options.h
index 088e34b..1ea7621 100644
--- a/src/options.h
+++ b/src/options.h
@@ -95,6 +95,11 @@ public:
bool networkedFrameSfxEnabled() const;
void setNetworkedFrameSfxEnabled(bool value);
+ // Returns the value of whether courtroom slide animations should be played
+ // on this client.
+ bool slidesEnabled() const;
+ void setSlidesEnabled(bool value);
+
// Returns the value of whether colored ic log should be a thing.
// from the config.ini.
bool colorLogEnabled() const;
diff --git a/src/path_functions.cpp b/src/path_functions.cpp
index d68535c..4a23ac7 100644
--- a/src/path_functions.cpp
+++ b/src/path_functions.cpp
@@ -78,9 +78,38 @@ VPath AOApplication::get_default_background_path(QString p_file)
return VPath("background/default/" + p_file);
}
-QString AOApplication::get_pos_path(const QString &pos, const bool desk)
+QPair AOApplication::get_pos_path(const QString &pos, const bool desk)
{
// witness is default if pos is invalid
+ QString f_pos = pos;
+ // legacy overrides for new format if found
+ if (pos == "def" && file_exists(get_image_suffix(get_background_path("court"))))
+ {
+ f_pos = "court:def";
+ }
+ else if (pos == "pro" && file_exists(get_image_suffix(get_background_path("court"))))
+ {
+ f_pos = "court:pro";
+ }
+ else if (pos == "wit" && file_exists(get_image_suffix(get_background_path("court"))))
+ {
+ f_pos = "court:wit";
+ }
+ QStringList f_pos_split = f_pos.split(":");
+
+ QRect f_rect;
+ if (f_pos_split.size() > 1)
+ { // Subposition, get center info
+ QStringList arglist = read_design_ini(f_pos + "/rect", get_background_path("design.ini")).split(",");
+ if (arglist.size() == 4)
+ {
+ f_rect = QRect(arglist[0].toInt(), arglist[1].toInt(), arglist[2].toInt(), arglist[3].toInt());
+ if (!f_rect.isValid())
+ {
+ f_rect = QRect();
+ }
+ }
+ }
QString f_background;
QString f_desk_image;
if (file_exists(get_image_suffix(get_background_path("witnessempty"))))
@@ -130,10 +159,10 @@ QString AOApplication::get_pos_path(const QString &pos, const bool desk)
f_desk_image = "seancedesk";
}
- if (file_exists(get_image_suffix(get_background_path(pos)))) // Unique pos path
+ if (file_exists(get_image_suffix(get_background_path(f_pos_split[0])))) // Unique pos path
{
- f_background = pos;
- f_desk_image = pos + "_overlay";
+ f_background = f_pos_split[0];
+ f_desk_image = f_pos_split[0] + "_overlay";
}
QString desk_override = read_design_ini("overlays/" + f_background, get_background_path("design.ini"));
@@ -141,11 +170,12 @@ QString AOApplication::get_pos_path(const QString &pos, const bool desk)
{
f_desk_image = desk_override;
}
+
if (desk)
{
- return f_desk_image;
+ return {f_desk_image, f_rect};
}
- return f_background;
+ return {f_background, f_rect};
}
VPath AOApplication::get_evidence_path(QString p_file)
@@ -209,19 +239,27 @@ QString AOApplication::get_asset_path(QVector pathlist)
return QString();
}
-QString AOApplication::get_image_path(QVector pathlist, bool static_image)
+QString AOApplication::get_image_path(QVector pathlist, int &index, bool static_image)
{
- for (const VPath &p : pathlist)
+ for (int i = 0; i < pathlist.size(); i++)
{
- QString path = get_image_suffix(p, static_image);
+ QString path = get_image_suffix(pathlist[i], static_image);
if (!path.isEmpty())
{
+ index = i;
return path;
}
}
+
return QString();
}
+QString AOApplication::get_image_path(QVector pathlist, bool static_image)
+{
+ int dummy;
+ return get_image_path(pathlist, dummy, static_image);
+}
+
QString AOApplication::get_sfx_path(QVector pathlist)
{
for (const VPath &p : pathlist)
diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp
index e7c4511..418de47 100644
--- a/src/text_file_functions.cpp
+++ b/src/text_file_functions.cpp
@@ -912,3 +912,24 @@ bool AOApplication::get_pos_is_judge(const QString &p_pos)
}
return positions.contains(p_pos.trimmed());
}
+
+int AOApplication::get_pos_transition_duration(const QString &old_pos, const QString &new_pos)
+{
+ if (old_pos.split(":").size() < 2 || new_pos.split(":").size() < 2)
+ {
+ return -1; // no subpositions
+ }
+
+ QString new_subpos = new_pos.split(":")[1];
+
+ bool ok;
+ int duration = read_design_ini(old_pos + "/slide_ms_" + new_subpos, get_background_path("design.ini")).toInt(&ok);
+ if (ok)
+ {
+ return duration;
+ }
+ else
+ {
+ return -1; // invalid
+ }
+}
diff --git a/src/widgets/aooptionsdialog.cpp b/src/widgets/aooptionsdialog.cpp
index beb7e55..a0b7d7c 100644
--- a/src/widgets/aooptionsdialog.cpp
+++ b/src/widgets/aooptionsdialog.cpp
@@ -358,6 +358,7 @@ void AOOptionsDialog::setupUI()
FROM_UI(QCheckBox, category_stop_cb);
FROM_UI(QCheckBox, sfx_on_idle_cb);
FROM_UI(QCheckBox, evidence_double_click_cb);
+ FROM_UI(QCheckBox, slides_cb);
registerOption("theme_scaling_factor_sb", &Options::themeScalingFactor, &Options::setThemeScalingFactor);
registerOption("animated_theme_cb", &Options::animatedThemeEnabled, &Options::setAnimatedThemeEnabled);
@@ -400,6 +401,7 @@ void AOOptionsDialog::setupUI()
registerOption("category_stop_cb", &Options::stopMusicOnCategoryEnabled, &Options::setStopMusicOnCategoryEnabled);
registerOption("sfx_on_idle_cb", &Options::playSelectedSFXOnIdle, &Options::setPlaySelectedSFXOnIdle);
registerOption("evidence_double_click_cb", &Options::evidenceDoubleClickEdit, &Options::setEvidenceDoubleClickEdit);
+ registerOption("slides_cb", &Options::slidesEnabled, &Options::setSlidesEnabled);
// Callwords tab. This could just be a QLineEdit, but no, we decided to allow
// people to put a billion entries in.
diff --git a/src/widgets/aooptionsdialog.h b/src/widgets/aooptionsdialog.h
index bccec58..b16be33 100644
--- a/src/widgets/aooptionsdialog.h
+++ b/src/widgets/aooptionsdialog.h
@@ -47,6 +47,7 @@ private:
QPushButton *ui_theme_reload_button;
QPushButton *ui_theme_folder_button;
QCheckBox *ui_evidence_double_click_cb;
+ QCheckBox *ui_slides_cb;
QCheckBox *ui_animated_theme_cb;
QSpinBox *ui_stay_time_spinbox;
QCheckBox *ui_instant_objection_cb;