Merge pull request #966 from AttorneyOnline/coolslide-rebased

[Feature] Courtroom slides + major AOLayer overhaul
This commit is contained in:
Salanto 2024-05-24 04:54:48 +02:00 committed by GitHub
commit 4c56a25463
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 1746 additions and 1284 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "bin/base/themes"]
path = bin/base/themes
url = https://github.com/AttorneyOnline/AO2-Themes.git

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

@ -1 +0,0 @@
Subproject commit 32a130d1a35220b27deecf22f59c83e92cac1fee

View File

@ -39,9 +39,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<y>-511</y>
<width>394</width>
<height>858</height>
<height>850</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
@ -556,6 +556,23 @@
</property>
</widget>
</item>
<item row="33" column="0">
<widget class="QLabel" name="slides_lbl">
<property name="toolTip">
<string>If ticked, slide animations will play when requested.</string>
</property>
<property name="text">
<string>Slide Animations:</string>
</property>
</widget>
</item>
<item row="33" column="1">
<widget class="QCheckBox" name="slides_cb">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</widget>

702
src/animationlayer.cpp Normal file
View File

@ -0,0 +1,702 @@
#include "animationlayer.h"
#include "aoapplication.h"
#include "options.h"
#include <QRectF>
#include <QThreadPool>
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<VPath> path_list;
QVector<QString> 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<EffectType> 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

266
src/animationlayer.h Normal file
View File

@ -0,0 +1,266 @@
#pragma once
#include "animationloader.h"
#include <QBitmap>
#include <QDebug>
#include <QLabel>
#include <QPropertyAnimation>
#include <QTimer>
// #define DEBUG_MOVIE
#ifdef DEBUG_MOVIE
#include <QElapsedTimer>
#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<int, QList<FrameEffect>> 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

105
src/animationloader.cpp Normal file
View File

@ -0,0 +1,105 @@
#include "animationloader.h"
#include <QMutexLocker>
#include <QtConcurrent/QtConcurrent>
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

55
src/animationloader.h Normal file
View File

@ -0,0 +1,55 @@
#pragma once
#include <QFuture>
#include <QImageReader>
#include <QMutex>
#include <QObject>
#include <QPixmap>
#include <QString>
#include <QWaitCondition>
#include <atomic>
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<AnimationFrame> m_frames;
QFuture<void> m_task;
std::atomic_bool m_exit_task = false;
QMutex m_task_lock;
QWaitCondition m_task_signal;
void populateVector(QImageReader *reader);
};
} // namespace kal

View File

@ -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())

View File

@ -120,16 +120,19 @@ public:
VPath get_evidence_path(QString p_file);
QVector<VPath> 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<VPath> pathlist);
QString get_image_path(QVector<VPath> pathlist, int &index, bool static_image = false);
QString get_image_path(QVector<VPath> pathlist, bool static_image = false);
QString get_sfx_path(QVector<VPath> 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<QString, QRect> 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);

View File

@ -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);
}

View File

@ -1,6 +1,6 @@
#pragma once
#include "aolayer.h"
#include "animationlayer.h"
#include <QWidget>
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;
};

View File

@ -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);
}

View File

@ -1,7 +1,7 @@
#pragma once
#include "animationlayer.h"
#include "aoapplication.h"
#include "aolayer.h"
#include "aosfxplayer.h"
#include <QDebug>
@ -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:

View File

@ -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<int>(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<VPath> 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();
}

View File

@ -1,267 +0,0 @@
#pragma once
#include <QBitmap>
#include <QDebug>
#include <QElapsedTimer>
#include <QImageReader>
#include <QLabel>
#include <QMutex>
#include <QTimer>
#include <QWaitCondition>
#include <QtConcurrent/QtConcurrentRun>
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<QPixmap> movie_frames;
QVector<int> 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<void> 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<QVector<QString>> 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);
};

View File

@ -8,7 +8,7 @@ AOSfxPlayer::AOSfxPlayer(AOApplication *ao_app)
int AOSfxPlayer::volume()
{
return m_volume * 100;
return m_volume;
}
void AOSfxPlayer::setVolume(int value)

File diff suppressed because it is too large Load Diff

View File

@ -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<kal::CharacterAnimationLayer *> 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();
};

View File

@ -78,6 +78,7 @@ enum CHAT_MESSAGE
ADDITIVE,
EFFECTS,
BLIPNAME,
SLIDE,
};
enum EMOTE_MOD_TYPE

View File

@ -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()

View File

@ -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);
}

View File

@ -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();

View File

@ -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;

View File

@ -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<QString, QRect> 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<VPath> pathlist)
return QString();
}
QString AOApplication::get_image_path(QVector<VPath> pathlist, bool static_image)
QString AOApplication::get_image_path(QVector<VPath> 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<VPath> pathlist, bool static_image)
{
int dummy;
return get_image_path(pathlist, dummy, static_image);
}
QString AOApplication::get_sfx_path(QVector<VPath> pathlist)
{
for (const VPath &p : pathlist)

View File

@ -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
}
}

View File

@ -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<QSpinBox, int>("theme_scaling_factor_sb", &Options::themeScalingFactor, &Options::setThemeScalingFactor);
registerOption<QCheckBox, bool>("animated_theme_cb", &Options::animatedThemeEnabled, &Options::setAnimatedThemeEnabled);
@ -400,6 +401,7 @@ void AOOptionsDialog::setupUI()
registerOption<QCheckBox, bool>("category_stop_cb", &Options::stopMusicOnCategoryEnabled, &Options::setStopMusicOnCategoryEnabled);
registerOption<QCheckBox, bool>("sfx_on_idle_cb", &Options::playSelectedSFXOnIdle, &Options::setPlaySelectedSFXOnIdle);
registerOption<QCheckBox, bool>("evidence_double_click_cb", &Options::evidenceDoubleClickEdit, &Options::setEvidenceDoubleClickEdit);
registerOption<QCheckBox, bool>("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.

View File

@ -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;