Complete AOLayer reimplementation, ...
* Complete AOLayer reimplementation * Reimplemented sliding as well.
This commit is contained in:
		
							parent
							
								
									137a2d3a04
								
							
						
					
					
						commit
						695d51dbfe
					
				@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										662
									
								
								src/animationlayer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										662
									
								
								src/animationlayer.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,662 @@
 | 
			
		||||
#include "animationlayer.h"
 | 
			
		||||
 | 
			
		||||
#include "aoapplication.h"
 | 
			
		||||
#include "options.h"
 | 
			
		||||
 | 
			
		||||
#include <QRectF>
 | 
			
		||||
 | 
			
		||||
static QThreadPool *thread_pool;
 | 
			
		||||
 | 
			
		||||
namespace kal
 | 
			
		||||
{
 | 
			
		||||
AnimationLayer::AnimationLayer(QWidget *parent)
 | 
			
		||||
    : QLabel(parent)
 | 
			
		||||
{
 | 
			
		||||
  setAlignment(Qt::AlignCenter);
 | 
			
		||||
 | 
			
		||||
  m_ticker = new QTimer(this);
 | 
			
		||||
  m_ticker->setSingleShot(true);
 | 
			
		||||
  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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (m_ticker->isActive())
 | 
			
		||||
  {
 | 
			
		||||
    m_ticker->stop();
 | 
			
		||||
  }
 | 
			
		||||
  m_frame_number = number;
 | 
			
		||||
  frameTicker();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 = mode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimationLayer::setMinimumDurationPerFrame(int duration)
 | 
			
		||||
{
 | 
			
		||||
  return;
 | 
			
		||||
  m_minimum_duration = duration;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimationLayer::setMaximumDurationPerFrame(int duration)
 | 
			
		||||
{
 | 
			
		||||
  return;
 | 
			
		||||
  m_maximum_duration = duration;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimationLayer::setMaskingRect(QRect rect)
 | 
			
		||||
{
 | 
			
		||||
  return; // TODO re-enable
 | 
			
		||||
 | 
			
		||||
  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());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  displayCurrentFrame();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimationLayer::finishPlayback()
 | 
			
		||||
{
 | 
			
		||||
  stopPlayback();
 | 
			
		||||
  Q_EMIT finishedPlayback();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimationLayer::prepareNextTick()
 | 
			
		||||
{
 | 
			
		||||
  int duration = m_current_frame.duration;
 | 
			
		||||
 | 
			
		||||
  duration = (m_minimum_duration > 0) ? qMax(m_minimum_duration, duration) : 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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
  m_current_frame = m_loader->frame(m_frame_number);
 | 
			
		||||
  displayCurrentFrame();
 | 
			
		||||
  Q_EMIT frameNumberChanged(m_frame_number);
 | 
			
		||||
  ++m_frame_number;
 | 
			
		||||
 | 
			
		||||
  if (!m_pause)
 | 
			
		||||
  {
 | 
			
		||||
    m_ticker->start(m_current_frame.duration);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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_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;
 | 
			
		||||
  for (const QString &prefix : qAsConst(prefixes))
 | 
			
		||||
  {
 | 
			
		||||
    path_list << ao_app->get_character_path(character, prefix + m_emote);
 | 
			
		||||
  }
 | 
			
		||||
  path_list << ao_app->get_character_path(character, m_emote);
 | 
			
		||||
 | 
			
		||||
  if (placeholder_fallback)
 | 
			
		||||
  {
 | 
			
		||||
    path_list << ao_app->get_character_path(character, QStringLiteral("placeholder"));
 | 
			
		||||
    path_list << ao_app->get_theme_path("placeholder", ao_app->default_theme);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setFileName(ao_app->get_image_path(path_list));
 | 
			
		||||
  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_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
 | 
			
		||||
 | 
			
		||||
  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"));
 | 
			
		||||
  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)
 | 
			
		||||
  {
 | 
			
		||||
    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
 | 
			
		||||
							
								
								
									
										261
									
								
								src/animationlayer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								src/animationlayer.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,261 @@
 | 
			
		||||
#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);
 | 
			
		||||
 | 
			
		||||
  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 = 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_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;
 | 
			
		||||
  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
 | 
			
		||||
							
								
								
									
										103
									
								
								src/animationloader.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/animationloader.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
			
		||||
#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)
 | 
			
		||||
  {
 | 
			
		||||
    qDebug().noquote() << "Waiting for frame" << frameNumber << QString("(file: %1, frame count: %2)").arg(m_file_name).arg(m_frame_count);
 | 
			
		||||
    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
									
								
							
							
						
						
									
										55
									
								
								src/animationloader.h
									
									
									
									
									
										Normal 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
 | 
			
		||||
@ -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())
 | 
			
		||||
 | 
			
		||||
@ -126,10 +126,12 @@ public:
 | 
			
		||||
  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());
 | 
			
		||||
  QPair<QString, int> 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										749
									
								
								src/aolayer.cpp
									
									
									
									
									
								
							
							
						
						
									
										749
									
								
								src/aolayer.cpp
									
									
									
									
									
								
							@ -1,749 +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())
 | 
			
		||||
  {
 | 
			
		||||
    scaling_factor = float(f_h) / float(f_pixmap.height());
 | 
			
		||||
    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)
 | 
			
		||||
{
 | 
			
		||||
  if (g_center == -1)
 | 
			
		||||
  {
 | 
			
		||||
    QLabel::move(x + (f_w - f_pixmap.width()) / 2,
 | 
			
		||||
                 y + (f_h - f_pixmap.height())); // Always center horizontally, always
 | 
			
		||||
                                                 // put at the bottom vertically
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    QLabel::move(get_pos_from_center(g_center), y + (f_h - f_pixmap.height()));
 | 
			
		||||
  }
 | 
			
		||||
  if (masked)
 | 
			
		||||
  {
 | 
			
		||||
    if (g_center == -1)
 | 
			
		||||
    {
 | 
			
		||||
      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
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      int center_scaled = int(float(g_center) * scaling_factor);
 | 
			
		||||
      this->setMask(QRegion((f_pixmap.width() - center_scaled) / 2, (f_pixmap.height() - f_h) / 2, f_w, f_h));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int AOLayer::get_pos_from_center(int f_center)
 | 
			
		||||
{
 | 
			
		||||
  int center_scaled = int(float(f_center) * scaling_factor);
 | 
			
		||||
  int f_pos = x + (center_scaled - (f_w / 2)) * -1;
 | 
			
		||||
#ifdef DEBUG_MOVIE
 | 
			
		||||
  qDebug() << "centering image at center" << f_center << "final position" << f_pos;
 | 
			
		||||
#endif
 | 
			
		||||
  return f_pos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float AOLayer::get_scaling_factor()
 | 
			
		||||
{
 | 
			
		||||
  return scaling_factor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BackgroundLayer::load_image(QString p_filename, int p_center)
 | 
			
		||||
{
 | 
			
		||||
  g_center = p_center;
 | 
			
		||||
  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 && g_center == last_center)
 | 
			
		||||
  {
 | 
			
		||||
    // 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) && (g_center == last_center)) || 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;
 | 
			
		||||
  last_center = g_center;
 | 
			
		||||
  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();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										297
									
								
								src/aolayer.h
									
									
									
									
									
								
							
							
						
						
									
										297
									
								
								src/aolayer.h
									
									
									
									
									
								
							@ -1,297 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <QBitmap>
 | 
			
		||||
#include <QDebug>
 | 
			
		||||
#include <QElapsedTimer>
 | 
			
		||||
#include <QImageReader>
 | 
			
		||||
#include <QLabel>
 | 
			
		||||
#include <QMutex>
 | 
			
		||||
#include <QPropertyAnimation>
 | 
			
		||||
#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);
 | 
			
		||||
 | 
			
		||||
  // Returns the factor by which the image is scaled
 | 
			
		||||
  float get_scaling_factor();
 | 
			
		||||
 | 
			
		||||
  // 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);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Returns the x offset to use to ensure proper centering in the
 | 
			
		||||
   * viewport. This is used by courtroom transition code to know exactly where
 | 
			
		||||
   * to put the characters at the start and end of the animation.
 | 
			
		||||
   * @return The offset to center the pixmap in the viewport
 | 
			
		||||
   */
 | 
			
		||||
  int get_centered_offset();
 | 
			
		||||
 | 
			
		||||
  // 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);
 | 
			
		||||
 | 
			
		||||
  QPropertyAnimation *slide(int newcenter, int duration);
 | 
			
		||||
 | 
			
		||||
  // Start playback of the movie (if animated).
 | 
			
		||||
  void play();
 | 
			
		||||
 | 
			
		||||
  // Freeze the movie at the current frame.
 | 
			
		||||
  void freeze();
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
  float scaling_factor = 0.0;
 | 
			
		||||
 | 
			
		||||
  int frame = 0;
 | 
			
		||||
  int max_frames = 0;
 | 
			
		||||
  int last_max_frames = 0;
 | 
			
		||||
 | 
			
		||||
  int speed = 100;
 | 
			
		||||
 | 
			
		||||
  bool m_flipped = false;
 | 
			
		||||
 | 
			
		||||
  int duration = 0;
 | 
			
		||||
 | 
			
		||||
  // 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);
 | 
			
		||||
 | 
			
		||||
  // If set to anything other than -1, overrides center_pixmap to use it as a
 | 
			
		||||
  // pixel position to center at. Currently only used by background layers
 | 
			
		||||
  int g_center = -1;
 | 
			
		||||
  int last_center = -1; // g_center from the last image.
 | 
			
		||||
  int centered_offset = 0;
 | 
			
		||||
 | 
			
		||||
  // Center the QLabel in the viewport based on the dimensions of f_pixmap
 | 
			
		||||
  void center_pixmap(QPixmap f_pixmap);
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
    @brief Get the position to move us to, given the pixel X position of the
 | 
			
		||||
    point in the original image that we'd like to be centered.
 | 
			
		||||
    @return The position to move to.
 | 
			
		||||
   */
 | 
			
		||||
  int get_pos_from_center(int f_center);
 | 
			
		||||
 | 
			
		||||
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, int center = -1);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
};
 | 
			
		||||
@ -1,6 +1,11 @@
 | 
			
		||||
#include "courtroom.h"
 | 
			
		||||
 | 
			
		||||
#include "options.h"
 | 
			
		||||
 | 
			
		||||
#include <QtConcurrent/QtConcurrent>
 | 
			
		||||
 | 
			
		||||
// #define DEBUG_TRANSITION
 | 
			
		||||
 | 
			
		||||
Courtroom::Courtroom(AOApplication *p_ao_app)
 | 
			
		||||
    : QMainWindow()
 | 
			
		||||
{
 | 
			
		||||
@ -45,30 +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_background->masked = false;
 | 
			
		||||
  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_dummy_char = new CharLayer(ao_app, ui_viewport);
 | 
			
		||||
  ui_vp_dummy_char->masked = false;
 | 
			
		||||
  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 CharLayer(ao_app, ui_viewport);
 | 
			
		||||
  ui_vp_sidedummy_char->masked = false;
 | 
			
		||||
  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_desk = new BackgroundLayer(ao_app, ui_viewport);
 | 
			
		||||
  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_desk->masked = false;
 | 
			
		||||
 | 
			
		||||
  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");
 | 
			
		||||
 | 
			
		||||
@ -78,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);
 | 
			
		||||
@ -99,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");
 | 
			
		||||
 | 
			
		||||
@ -159,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");
 | 
			
		||||
 | 
			
		||||
@ -419,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);
 | 
			
		||||
 | 
			
		||||
@ -521,7 +514,7 @@ 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::on_transition_finish);
 | 
			
		||||
  connect(transition_animation_group, &QParallelAnimationGroup::finished, this, &Courtroom::post_transition_cleanup);
 | 
			
		||||
 | 
			
		||||
  set_widgets();
 | 
			
		||||
 | 
			
		||||
@ -763,29 +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_and_center(0, 0);
 | 
			
		||||
  ui_vp_dummy_char->combo_resize(ui_viewport->width(), ui_viewport->height());
 | 
			
		||||
  ui_vp_sidedummy_char->move_and_center(0, 0);
 | 
			
		||||
  ui_vp_sidedummy_char->combo_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());
 | 
			
		||||
@ -794,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();
 | 
			
		||||
 | 
			
		||||
@ -888,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++)
 | 
			
		||||
  {
 | 
			
		||||
@ -902,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");
 | 
			
		||||
@ -1390,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
 | 
			
		||||
@ -1440,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();
 | 
			
		||||
@ -1465,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;
 | 
			
		||||
@ -1730,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());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1894,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<QtMsgType, QString> 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"));
 | 
			
		||||
@ -2312,8 +2307,9 @@ void Courtroom::on_chat_return_pressed()
 | 
			
		||||
  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
 | 
			
		||||
  }
 | 
			
		||||
  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));
 | 
			
		||||
}
 | 
			
		||||
@ -2473,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
 | 
			
		||||
@ -2502,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.
 | 
			
		||||
@ -2699,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)
 | 
			
		||||
    {
 | 
			
		||||
@ -2731,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] != "")
 | 
			
		||||
@ -2747,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
 | 
			
		||||
@ -2757,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
 | 
			
		||||
@ -2770,15 +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);
 | 
			
		||||
  ui_vp_player_char->setFlipped(m_chatmessage[FLIP].toInt() == 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Courtroom::display_pair_character(QString other_charid, QString other_offset)
 | 
			
		||||
@ -2833,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();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -2973,33 +2972,35 @@ void Courtroom::do_screenshake()
 | 
			
		||||
  screenshake_animation_group->start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Courtroom::do_transition(QString p_desk_mod, QString old_pos, QString new_pos)
 | 
			
		||||
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 = old_pos;
 | 
			
		||||
  QString t_new_pos = new_pos;
 | 
			
		||||
  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(old_pos))
 | 
			
		||||
    if (legacy_pos.contains(oldPosId))
 | 
			
		||||
    {
 | 
			
		||||
      t_old_pos = "court:" + old_pos;
 | 
			
		||||
      t_old_pos = "court:" + oldPosId;
 | 
			
		||||
    }
 | 
			
		||||
    if (legacy_pos.contains(new_pos))
 | 
			
		||||
    if (legacy_pos.contains(newPosId))
 | 
			
		||||
    {
 | 
			
		||||
      t_new_pos = "court:" + new_pos;
 | 
			
		||||
      t_new_pos = "court:" + newPosId;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  QPair<QString, int> old_pos_pair = ao_app->get_pos_path(t_old_pos);
 | 
			
		||||
  QPair<QString, int> new_pos_pair = ao_app->get_pos_path(t_new_pos);
 | 
			
		||||
  QPair<QString, QRect> old_pos_pair = ao_app->get_pos_path(t_old_pos);
 | 
			
		||||
  QPair<QString, QRect> 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 (old_pos == new_pos || old_pos_pair.first != new_pos_pair.first || new_pos_pair.second == -1 || !Options::getInstance().slidesEnabled() || m_chatmessage[SLIDE] != "1" || duration == -1 || m_chatmessage[EMOTE_MOD].toInt() == ZOOM || m_chatmessage[EMOTE_MOD].toInt() == PREANIM_ZOOM)
 | 
			
		||||
  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";
 | 
			
		||||
@ -3016,130 +3017,105 @@ void Courtroom::do_transition(QString p_desk_mod, QString old_pos, QString new_p
 | 
			
		||||
  qDebug() << "STARTING TRANSITION, CURRENT TIME:" << transition_animation_group->currentTime();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  set_scene(p_desk_mod.toInt(), old_pos);
 | 
			
		||||
  set_scene(p_desk_mod.toInt(), oldPosId);
 | 
			
		||||
 | 
			
		||||
  QList<AOLayer *> affected_list = {ui_vp_background, ui_vp_desk, ui_vp_player_char};
 | 
			
		||||
  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);
 | 
			
		||||
 | 
			
		||||
  bool paired = false;
 | 
			
		||||
  if (!ui_vp_sideplayer_char->isHidden())
 | 
			
		||||
  {
 | 
			
		||||
    affected_list.append(ui_vp_sideplayer_char);
 | 
			
		||||
    paired = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Set up the background, desk, and player objects' animations
 | 
			
		||||
 | 
			
		||||
  float scaling_factor = ui_vp_background->get_scaling_factor();
 | 
			
		||||
  int offset = (float(old_pos_pair.second) * scaling_factor) - (float(new_pos_pair.second) * scaling_factor);
 | 
			
		||||
 | 
			
		||||
  for (AOLayer *ui_element : affected_list)
 | 
			
		||||
  QList<kal::AnimationLayer *> 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->setStartValue(ui_element->pos());
 | 
			
		||||
    transition_animation->setDuration(duration);
 | 
			
		||||
    transition_animation->setEndValue(QPoint(ui_element->pos().x() + offset, ui_element->pos().y()));
 | 
			
		||||
    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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Setting up the dummy characters to work for us as our stand-in for the next characters
 | 
			
		||||
  // This should be easy. But it isn't
 | 
			
		||||
 | 
			
		||||
  QString slide_emote;
 | 
			
		||||
  if (m_chatmessage[OBJECTION_MOD].contains("4") || m_chatmessage[OBJECTION_MOD].toInt() == 2)
 | 
			
		||||
  {
 | 
			
		||||
    slide_emote = "(a)" + ao_app->read_char_ini(m_chatmessage[CHAR_NAME], "objection_pose", "Options");
 | 
			
		||||
    if (slide_emote == "(a)")
 | 
			
		||||
  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)
 | 
			
		||||
    {
 | 
			
		||||
      slide_emote = "(a)" + m_chatmessage[EMOTE];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
    slide_emote = "(a)" + m_chatmessage[EMOTE];
 | 
			
		||||
 | 
			
		||||
  QString other_slide_emote = "(a)" + m_chatmessage[OTHER_EMOTE];
 | 
			
		||||
 | 
			
		||||
  // Load the image we're going to use to get scaling information, and move it into the final position for animation data
 | 
			
		||||
  ui_vp_dummy_char->set_flipped(m_chatmessage[FLIP].toInt());
 | 
			
		||||
  ui_vp_dummy_char->load_image(slide_emote, m_chatmessage[CHAR_NAME], 0, false);
 | 
			
		||||
  set_self_offset(m_chatmessage[SELF_OFFSET], ui_vp_dummy_char);
 | 
			
		||||
 | 
			
		||||
  QPoint starting_position = QPoint(ui_vp_player_char->pos().x() - offset, ui_vp_player_char->pos().y());
 | 
			
		||||
  QPropertyAnimation *ui_vp_dummy_animation = new QPropertyAnimation(ui_vp_dummy_char, "pos", this);
 | 
			
		||||
 | 
			
		||||
  ui_vp_dummy_animation->setDuration(duration);
 | 
			
		||||
  ui_vp_dummy_animation->setStartValue(starting_position);
 | 
			
		||||
  ui_vp_dummy_animation->setEndValue(ui_vp_dummy_char->pos());
 | 
			
		||||
  ui_vp_dummy_animation->setEasingCurve(QEasingCurve::InOutCubic);
 | 
			
		||||
  transition_animation_group->addAnimation(ui_vp_dummy_animation);
 | 
			
		||||
 | 
			
		||||
  ui_vp_dummy_char->move(starting_position.x(), starting_position.y());
 | 
			
		||||
 | 
			
		||||
  // If the new message is paired, do it all again for the pair character. Yippee!
 | 
			
		||||
  if (m_chatmessage[OTHER_CHARID].toInt() != -1 && !m_chatmessage[OTHER_NAME].isEmpty())
 | 
			
		||||
  {
 | 
			
		||||
    ui_vp_sidedummy_char->set_flipped(m_chatmessage[OTHER_FLIP].toInt());
 | 
			
		||||
    ui_vp_sidedummy_char->load_image(other_slide_emote, m_chatmessage[OTHER_NAME], 0, false);
 | 
			
		||||
    set_self_offset(m_chatmessage[OTHER_OFFSET], ui_vp_sidedummy_char);
 | 
			
		||||
    QStringList args = m_chatmessage[OTHER_CHARID].split("^");
 | 
			
		||||
    if (args.size() > 1)
 | 
			
		||||
    {
 | 
			
		||||
      // Change the order of appearance based on the pair order variable
 | 
			
		||||
      int order = args.at(1).toInt();
 | 
			
		||||
      switch (order)
 | 
			
		||||
      {
 | 
			
		||||
      case 0: // Our character is in front
 | 
			
		||||
        ui_vp_sidedummy_char->stackUnder(ui_vp_dummy_char);
 | 
			
		||||
        break;
 | 
			
		||||
      case 1: // Our character is behind
 | 
			
		||||
        ui_vp_dummy_char->stackUnder(ui_vp_sidedummy_char);
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      offset.setY(viewport_height * offset_data.at(1).toInt() * 0.01);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    QPoint other_starting_position = QPoint(ui_vp_sideplayer_char->pos().x() - offset, ui_vp_sideplayer_char->pos().y());
 | 
			
		||||
    QPropertyAnimation *ui_vp_sidedummy_animation = new QPropertyAnimation(ui_vp_sidedummy_char, "pos", this);
 | 
			
		||||
    layer->setParent(ui_vp_background);
 | 
			
		||||
    layer->setPlayOnce(false);
 | 
			
		||||
    layer->pausePlayback(true);
 | 
			
		||||
    layer->startPlayback();
 | 
			
		||||
    layer->move(newPos);
 | 
			
		||||
    layer->show();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
    ui_vp_sidedummy_animation->setDuration(duration);
 | 
			
		||||
    ui_vp_sidedummy_animation->setStartValue(starting_position);
 | 
			
		||||
    ui_vp_sidedummy_animation->setEndValue(ui_vp_sidedummy_char->pos());
 | 
			
		||||
    ui_vp_sidedummy_animation->setEasingCurve(QEasingCurve::InOutCubic);
 | 
			
		||||
    transition_animation_group->addAnimation(ui_vp_sidedummy_animation);
 | 
			
		||||
  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]);
 | 
			
		||||
 | 
			
		||||
    ui_vp_sidedummy_char->move(starting_position.x(), starting_position.y());
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  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_sidedummy_char->stop();
 | 
			
		||||
    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_player_char->freeze();
 | 
			
		||||
  ui_vp_player_char->show();
 | 
			
		||||
  if (paired)
 | 
			
		||||
  {
 | 
			
		||||
    ui_vp_sideplayer_char->freeze();
 | 
			
		||||
    ui_vp_sideplayer_char->show();
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    ui_vp_sideplayer_char->stop();
 | 
			
		||||
  }
 | 
			
		||||
  ui_vp_dummy_char->freeze();
 | 
			
		||||
  ui_vp_sidedummy_char->freeze();
 | 
			
		||||
  QTimer::singleShot(TRANSITION_BOOKEND_DELAY, transition_animation_group, SLOT(start()));
 | 
			
		||||
}
 | 
			
		||||
  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]);
 | 
			
		||||
 | 
			
		||||
void Courtroom::on_transition_finish()
 | 
			
		||||
{
 | 
			
		||||
  transition_animation_group->clear();
 | 
			
		||||
  transition_animation_group->setCurrentTime(0);
 | 
			
		||||
  QTimer::singleShot(TRANSITION_BOOKEND_DELAY, this, SLOT(post_transition_cleanup()));
 | 
			
		||||
  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->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
 | 
			
		||||
@ -3148,10 +3124,6 @@ void Courtroom::post_transition_cleanup()
 | 
			
		||||
  int emote_mod = m_chatmessage[EMOTE_MOD].toInt();
 | 
			
		||||
  bool immediate = m_chatmessage[IMMEDIATE].toInt() == 1;
 | 
			
		||||
 | 
			
		||||
  // Reset the pair character
 | 
			
		||||
  ui_vp_sideplayer_char->stop();
 | 
			
		||||
  ui_vp_sideplayer_char->move_and_center(0, 0);
 | 
			
		||||
 | 
			
		||||
  // If the emote_mod is not zooming
 | 
			
		||||
  if (emote_mod != ZOOM && emote_rows != PREANIM_ZOOM)
 | 
			
		||||
  {
 | 
			
		||||
@ -3159,12 +3131,6 @@ void Courtroom::post_transition_cleanup()
 | 
			
		||||
    display_pair_character(m_chatmessage[OTHER_CHARID], m_chatmessage[OTHER_OFFSET]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Reset tweedle dee and tweedle dummy
 | 
			
		||||
  ui_vp_dummy_char->stop();
 | 
			
		||||
  ui_vp_dummy_char->move_and_center(0, 0);
 | 
			
		||||
  ui_vp_sidedummy_char->stop();
 | 
			
		||||
  ui_vp_sidedummy_char->move_and_center(0, 0);
 | 
			
		||||
 | 
			
		||||
  // Parse the emote_mod part of the chat message
 | 
			
		||||
  handle_emote_mod(emote_mod, immediate);
 | 
			
		||||
}
 | 
			
		||||
@ -3203,11 +3169,11 @@ 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);
 | 
			
		||||
  ui_vp_effect->setPlayOnce(false); // The effects themselves dictate whether or not they're looping.
 | 
			
		||||
                                    // Static effects will linger.
 | 
			
		||||
 | 
			
		||||
  bool looping = ao_app->get_effect_property(fx_path, p_char, p_folder, "loop").startsWith("true");
 | 
			
		||||
 | 
			
		||||
@ -3269,10 +3235,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)
 | 
			
		||||
@ -3384,7 +3349,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;
 | 
			
		||||
@ -3464,8 +3429,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.)
 | 
			
		||||
@ -3474,23 +3439,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
 | 
			
		||||
@ -4025,15 +3989,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:
 | 
			
		||||
@ -4100,7 +4065,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:
 | 
			
		||||
@ -4154,12 +4119,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.
 | 
			
		||||
@ -4176,7 +4141,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")
 | 
			
		||||
@ -4226,8 +4191,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())
 | 
			
		||||
  {
 | 
			
		||||
@ -4239,20 +4202,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
 | 
			
		||||
@ -4266,8 +4230,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)
 | 
			
		||||
    {
 | 
			
		||||
@ -4503,19 +4467,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
 | 
			
		||||
@ -4544,12 +4506,21 @@ void Courtroom::play_sfx()
 | 
			
		||||
 | 
			
		||||
void Courtroom::set_scene(bool show_desk, const QString f_side)
 | 
			
		||||
{
 | 
			
		||||
  QPair<QString, int> bg_pair = ao_app->get_pos_path(f_side);
 | 
			
		||||
  QPair<QString, int> desk_pair = ao_app->get_pos_path(f_side, true);
 | 
			
		||||
  ui_vp_background->load_image(bg_pair.first, bg_pair.second);
 | 
			
		||||
  ui_vp_desk->load_image(desk_pair.first, desk_pair.second);
 | 
			
		||||
  last_side = f_side;
 | 
			
		||||
  QPair<QString, QRect> bg_pair = ao_app->get_pos_path(f_side);
 | 
			
		||||
  QPair<QString, QRect> 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();
 | 
			
		||||
@ -4560,7 +4531,7 @@ void Courtroom::set_scene(bool show_desk, const QString f_side)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Courtroom::set_self_offset(const QString &p_list, AOLayer *p_layer)
 | 
			
		||||
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();
 | 
			
		||||
@ -4573,7 +4544,7 @@ void Courtroom::set_self_offset(const QString &p_list, AOLayer *p_layer)
 | 
			
		||||
  {
 | 
			
		||||
    self_offset_v = self_offsets[1].toInt();
 | 
			
		||||
  }
 | 
			
		||||
  p_layer->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)
 | 
			
		||||
@ -4729,15 +4700,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);
 | 
			
		||||
@ -4746,7 +4716,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")
 | 
			
		||||
@ -4757,12 +4727,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")
 | 
			
		||||
    {
 | 
			
		||||
@ -4774,13 +4743,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
 | 
			
		||||
@ -4791,8 +4760,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)
 | 
			
		||||
 | 
			
		||||
@ -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"
 | 
			
		||||
@ -152,7 +152,7 @@ public:
 | 
			
		||||
 | 
			
		||||
  // 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, AOLayer *p_layer);
 | 
			
		||||
  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
 | 
			
		||||
@ -217,7 +217,7 @@ public:
 | 
			
		||||
  void handle_ic_message();
 | 
			
		||||
 | 
			
		||||
  // Start the logic for doing a courtroom pan slide
 | 
			
		||||
  void do_transition(QString desk_mod, QString old_pos, QString new_pos);
 | 
			
		||||
  void do_transition(QString desk_mod, QString oldPosId, QString new_pos);
 | 
			
		||||
 | 
			
		||||
  // Display the character.
 | 
			
		||||
  void display_character();
 | 
			
		||||
@ -444,6 +444,7 @@ private:
 | 
			
		||||
  static const int MS_MINIMUM = 15;
 | 
			
		||||
  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
 | 
			
		||||
@ -451,7 +452,6 @@ private:
 | 
			
		||||
   */
 | 
			
		||||
  static const int TRANSITION_BOOKEND_DELAY = 300;
 | 
			
		||||
 | 
			
		||||
  QString previous_ic_message;
 | 
			
		||||
  QString additive_previous;
 | 
			
		||||
 | 
			
		||||
  // char id, muted or not
 | 
			
		||||
@ -608,22 +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;
 | 
			
		||||
  CharLayer *ui_vp_dummy_char;
 | 
			
		||||
  CharLayer *ui_vp_sidedummy_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;
 | 
			
		||||
 | 
			
		||||
@ -635,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];
 | 
			
		||||
@ -805,7 +806,6 @@ public Q_SLOTS:
 | 
			
		||||
  void objection_done();
 | 
			
		||||
  void preanim_done();
 | 
			
		||||
  void do_screenshake();
 | 
			
		||||
  void on_transition_finish();
 | 
			
		||||
  void do_flash();
 | 
			
		||||
  void do_effect(QString fx_path, QString fx_sound, QString p_char, QString p_folder);
 | 
			
		||||
  void play_char_sfx(QString sfx_name);
 | 
			
		||||
@ -978,7 +978,7 @@ 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
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
 | 
			
		||||
@ -78,7 +78,7 @@ VPath AOApplication::get_default_background_path(QString p_file)
 | 
			
		||||
  return VPath("background/default/" + p_file);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QPair<QString, int> 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;
 | 
			
		||||
@ -96,14 +96,18 @@ QPair<QString, int> AOApplication::get_pos_path(const QString &pos, const bool d
 | 
			
		||||
    f_pos = "court:wit";
 | 
			
		||||
  }
 | 
			
		||||
  QStringList f_pos_split = f_pos.split(":");
 | 
			
		||||
  int f_center = -1;
 | 
			
		||||
 | 
			
		||||
  QRect f_rect;
 | 
			
		||||
  if (f_pos_split.size() > 1)
 | 
			
		||||
  { // Subposition, get center info
 | 
			
		||||
    bool bOk;
 | 
			
		||||
    int subpos_center = read_design_ini(f_pos + "/pos_center", get_background_path("design.ini")).toInt(&bOk);
 | 
			
		||||
    if (bOk)
 | 
			
		||||
    QStringList arglist = read_design_ini(f_pos + "/pos_center", get_background_path("design.ini")).split(",");
 | 
			
		||||
    if (arglist.size() == 4)
 | 
			
		||||
    {
 | 
			
		||||
      f_center = subpos_center;
 | 
			
		||||
      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;
 | 
			
		||||
@ -166,11 +170,12 @@ QPair<QString, int> AOApplication::get_pos_path(const QString &pos, const bool d
 | 
			
		||||
  {
 | 
			
		||||
    f_desk_image = desk_override;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (desk)
 | 
			
		||||
  {
 | 
			
		||||
    return {f_desk_image, f_center};
 | 
			
		||||
    return {f_desk_image, f_rect};
 | 
			
		||||
  }
 | 
			
		||||
  return {f_background, f_center};
 | 
			
		||||
  return {f_background, f_rect};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VPath AOApplication::get_evidence_path(QString p_file)
 | 
			
		||||
 | 
			
		||||
@ -929,5 +929,7 @@ int AOApplication::get_pos_transition_duration(const QString &old_pos, const QSt
 | 
			
		||||
    return duration;
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    return -1; // invalid
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user