diff --git a/src/aoapplication.h b/src/aoapplication.h index c2a0191..ccc509d 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -126,7 +126,7 @@ 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()); - QString get_pos_path(const QString &pos, bool desk = false); + QPair get_pos_path(const QString &pos, bool desk = false); QString get_case_sensitive_path(QString p_file); QString get_real_path(const VPath &vpath, const QStringList &suffixes = {""}); @@ -250,6 +250,12 @@ public: // Returns whether the given pos is a judge position bool get_pos_is_judge(const QString &p_pos); + /** + * @brief Returns the duration of the transition animation between the two + * given positions, if it exists + */ + int get_pos_transition_duration(const QString &old_pos, const QString &new_pos); + // Returns the total amount of emotes of p_char int get_emote_number(QString p_char); diff --git a/src/aolayer.cpp b/src/aolayer.cpp index 8284230..4ac7d48 100644 --- a/src/aolayer.cpp +++ b/src/aolayer.cpp @@ -116,16 +116,45 @@ void AOLayer::set_frame(QPixmap f_pixmap) void AOLayer::center_pixmap(QPixmap f_pixmap) { - QLabel::move(x + (f_w - f_pixmap.width()) / 2, - y + (f_h - f_pixmap.height())); // Always center horizontally, always put - // at the bottom vertically + if (g_center == -1) + { + centered_offset = (f_w - f_pixmap.width()) / 2; + 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) { - 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 + 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_centered_offset() +{ + return centered_offset; +} + +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; + qDebug() << "centering image at center" << f_center << "final position" << f_pos; + return f_pos; +} + void AOLayer::combo_resize(int w, int h) { QSize f_size(w, h); @@ -160,8 +189,14 @@ void AOLayer::move_and_center(int ax, int ay) } } -void BackgroundLayer::load_image(QString p_filename) +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"); @@ -172,7 +207,7 @@ void BackgroundLayer::load_image(QString p_filename) #endif QString final_path = ao_app->get_image_suffix(ao_app->get_background_path(p_filename)); - if (final_path == last_path) + if (final_path == last_path && g_center == last_center) { // Don't restart background if background is unchanged return; @@ -326,7 +361,7 @@ void AOLayer::start_playback(QString p_image) force_continuous = true; } - if (((last_path == p_image) && (!force_continuous)) || p_image == "") + if (((last_path == p_image) && (!force_continuous) && (g_center == last_center)) || p_image == "") { return; } @@ -370,6 +405,7 @@ void AOLayer::start_playback(QString p_image) 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 diff --git a/src/aolayer.h b/src/aolayer.h index 0a44d8f..b5591e8 100644 --- a/src/aolayer.h +++ b/src/aolayer.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -81,6 +82,9 @@ public: // 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); @@ -88,10 +92,26 @@ public: // 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 movie_frames; @@ -114,6 +134,8 @@ protected: 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; @@ -124,21 +146,29 @@ protected: int duration = 0; - // Start playback of the movie (if animated). - void play(); - - // Freeze the movie at the current frame. - void freeze(); - // Retreive a pixmap adjused for mirroring/aspect ratio shenanigans from a // provided QImage QPixmap get_pixmap(QImage image); // Set the movie's frame to provided pixmap void set_frame(QPixmap f_pixmap); + + // 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(); @@ -164,7 +194,7 @@ class BackgroundLayer : public AOLayer Q_OBJECT public: BackgroundLayer(AOApplication *p_ao_app, QWidget *p_parent); - void load_image(QString p_filename); + void load_image(QString p_filename, int center = -1); }; class CharLayer : public AOLayer diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 89c5c75..5a1ac53 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -47,6 +47,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) ui_viewport->setObjectName("ui_viewport"); ui_vp_background = new BackgroundLayer(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->setObjectName("ui_vp_speedlines"); ui_vp_speedlines->stretch = true; @@ -57,8 +58,15 @@ Courtroom::Courtroom(AOApplication *p_ao_app) 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->hide(); + ui_vp_sidedummy_char = new CharLayer(ao_app, ui_viewport); + ui_vp_sidedummy_char->masked = false; + ui_vp_sidedummy_char->hide(); ui_vp_desk = new BackgroundLayer(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->setAttribute(Qt::WA_TransparentForMouseEvents); @@ -315,6 +323,11 @@ Courtroom::Courtroom(AOApplication *p_ao_app) ui_showname_enable->setText(tr("Shownames")); ui_showname_enable->setObjectName("ui_showname_enable"); + ui_slide_enable = new QCheckBox(this); + ui_slide_enable->setChecked(false); + ui_slide_enable->setText(tr("Slide")); + ui_slide_enable->setObjectName("ui_slide_enable"); + ui_immediate = new QCheckBox(this); ui_immediate->setText(tr("Immediate")); ui_immediate->hide(); @@ -487,10 +500,11 @@ Courtroom::Courtroom(AOApplication *p_ao_app) connect(ui_settings, &AOButton::clicked, this, &Courtroom::on_settings_clicked); connect(ui_switch_area_music, &AOButton::clicked, this, &Courtroom::on_switch_area_music_clicked); - connect(ui_pre, &AOButton::clicked, this, &Courtroom::on_pre_clicked); - connect(ui_flip, &AOButton::clicked, this, &Courtroom::on_flip_clicked); + connect(ui_pre, &AOButton::clicked, this, &Courtroom::focus_ic_input); + connect(ui_flip, &AOButton::clicked, this, &Courtroom::focus_ic_input); connect(ui_additive, &AOButton::clicked, this, &Courtroom::on_additive_clicked); - connect(ui_guard, &AOButton::clicked, this, &Courtroom::on_guard_clicked); + connect(ui_guard, &AOButton::clicked, this, &Courtroom::focus_ic_input); + connect(ui_slide_enable, &AOButton::clicked, this, &Courtroom::focus_ic_input); connect(ui_showname_enable, &AOButton::clicked, this, &Courtroom::on_showname_enable_clicked); @@ -507,6 +521,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) connect(ui_vp_evidence_display, &AOEvidenceDisplay::show_evidence_details, this, &Courtroom::show_evidence); + connect(transition_animation_group, &QParallelAnimationGroup::finished, this, &Courtroom::on_transition_finish); + set_widgets(); set_char_select(); @@ -762,6 +778,13 @@ void Courtroom::set_widgets() ui_vp_sideplayer_char->move_and_center(0, 0); ui_vp_sideplayer_char->combo_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()); + // the AO2 desk element ui_vp_desk->move_and_center(0, 0); ui_vp_desk->combo_resize(ui_viewport->width(), ui_viewport->height()); @@ -1077,6 +1100,9 @@ void Courtroom::set_widgets() set_size_and_pos(ui_showname_enable, "showname_enable"); ui_showname_enable->setToolTip(tr("Display customized shownames for all users when checked.")); + set_size_and_pos(ui_slide_enable, "slide_enable"); + ui_slide_enable->setToolTip(tr("Allow your messages to trigger slide animations when checked.")); + set_size_and_pos(ui_custom_objection, "custom_objection"); ui_custom_objection->setText(tr("Custom Shout!")); ui_custom_objection->setImage("custom"); @@ -1148,6 +1174,7 @@ void Courtroom::set_widgets() truncate_label_text(ui_pre, "pre"); truncate_label_text(ui_flip, "flip"); truncate_label_text(ui_showname_enable, "showname_enable"); + truncate_label_text(ui_slide_enable, "slide_enable"); // QLabel truncate_label_text(ui_music_label, "music_label"); @@ -1392,8 +1419,12 @@ void Courtroom::set_background(QString p_background, bool display) } for (const QString &pos : ao_app->read_design_ini("positions", ao_app->get_background_path("design.ini")).split(",")) { - if (file_exists(ao_app->get_image_suffix(ao_app->get_background_path(pos)))) - { + QString real_pos = pos.split(":")[0]; + QStringList overrides = {"def", "wit", "pro"}; + if ((file_exists(ao_app->get_image_suffix(ao_app->get_background_path(real_pos)))) || // Normal check, OR + (overrides.contains(pos) && // It's one of our subpos overrides, AND + file_exists(ao_app->get_image_suffix(ao_app->get_background_path("court"))) && // the "court" default image exists, AND + !ao_app->read_design_ini("court:" + pos + "/pos_center", ao_app->get_background_path("design.ini")).isEmpty())) { // config exists for this pos pos_list.append(pos); } } @@ -1489,7 +1520,7 @@ void Courtroom::set_pos_dropdown(QStringList pos_dropdowns) { QString pos = pos_dropdown_list.at(n); ui_pos_dropdown->addItem(pos); - QPixmap image = QPixmap(ao_app->get_image_suffix(ao_app->get_background_path(ao_app->get_pos_path(pos)))); + QPixmap image = QPixmap(ao_app->get_image_suffix(ao_app->get_background_path(ao_app->get_pos_path(pos).first))); if (!image.isNull()) { image = image.scaledToHeight(ui_pos_dropdown->iconSize().height()); @@ -1637,7 +1668,7 @@ void Courtroom::update_character(int p_cid, QString char_name, bool reset_emote) } ui_char_select_background->hide(); ui_ic_chat_message->setEnabled(m_cid != -1); - ui_ic_chat_message->setFocus(); + focus_ic_input(); update_audio_volume(); } @@ -2310,6 +2341,9 @@ void Courtroom::reset_ui() // Turn off our Preanim checkbox ui_pre->setChecked(false); } + + // Slides can't be sticky for nausea reasons. + ui_slide_enable->setChecked(false); } void Courtroom::chatmessage_enqueue(QStringList p_contents) @@ -2735,8 +2769,6 @@ void Courtroom::display_character() // Determine if we should flip the character or not ui_vp_player_char->set_flipped(m_chatmessage[FLIP].toInt() == 1); - // Move the character on the viewport according to the offsets - set_self_offset(m_chatmessage[SELF_OFFSET]); } void Courtroom::display_pair_character(QString other_charid, QString other_offset) @@ -2870,32 +2902,7 @@ void Courtroom::handle_ic_message() // Update the chatbox information initialize_chatbox(); - int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); - bool immediate = m_chatmessage[IMMEDIATE].toInt() == 1; - if (m_chatmessage[EMOTE] != "") - { - // Display our own character - display_character(); - - // Reset the pair character - ui_vp_sideplayer_char->stop(); - ui_vp_sideplayer_char->move(0, 0); - - // If the emote_mod is not zooming - if (emote_mod != ZOOM && emote_mod != PREANIM_ZOOM) - { - // Display the pair character - display_pair_character(m_chatmessage[OTHER_CHARID], m_chatmessage[OTHER_OFFSET]); - } - - // Parse the emote_mod part of the chat message - handle_emote_mod(emote_mod, immediate); - } - else - { - play_sfx(); - start_chat_ticking(); - } + do_transition(m_chatmessage[DESK_MOD], last_side, m_chatmessage[SIDE]); // if we have instant objections disabled, and queue is not empty, check if next message after this is an objection. if (!Options::getInstance().objectionSkipQueueEnabled() && chatmessage_queue.size() > 0) @@ -2956,6 +2963,196 @@ void Courtroom::do_screenshake() screenshake_animation_group->start(); } +void Courtroom::do_transition(QString p_desk_mod, QString old_pos, QString new_pos) { + + 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; + if (file_exists(ao_app->get_image_suffix(ao_app->get_background_path("court")))) { + if (legacy_pos.contains(old_pos)) { + t_old_pos = "court:" + old_pos; + } + if (legacy_pos.contains(new_pos)) { + t_new_pos = "court:" + new_pos; + } + } + + QPair old_pos_pair = ao_app->get_pos_path(t_old_pos); + QPair new_pos_pair = ao_app->get_pos_path(t_new_pos); + + int duration = ao_app->get_pos_transition_duration(t_old_pos, t_new_pos); + + // conditions to stop slide + if (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) { +#ifdef DEBUG_TRANSITION + qDebug() << "skipping transition - not applicable"; +#endif + post_transition_cleanup(); + return; + } +#ifdef DEBUG_TRANSITION + // for debugging animation + ui_vp_sideplayer_char->setStyleSheet("background-color:rgba(0, 0, 255, 128);"); + ui_vp_dummy_char->setStyleSheet("background-color:rgba(255, 0, 0, 128);"); + ui_vp_sidedummy_char->setStyleSheet("background-color:rgba(0, 255, 0, 128);"); + + qDebug() << "STARTING TRANSITION, CURRENT TIME:" << transition_animation_group->currentTime(); +#endif + + set_scene(p_desk_mod.toInt(), old_pos); + + QList affected_list = {ui_vp_background, ui_vp_desk, ui_vp_player_char}; + + 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) { + 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_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)") { + 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; + } + } + + 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); + + 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_sidedummy_char->move(starting_position.x(), starting_position.y()); + } + else { + ui_vp_sidedummy_char->stop(); + } + + 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())); +} + + +void Courtroom::on_transition_finish() { + transition_animation_group->clear(); + transition_animation_group->setCurrentTime(0); + QTimer::singleShot(TRANSITION_BOOKEND_DELAY, this, SLOT(post_transition_cleanup())); +} + +void Courtroom::post_transition_cleanup() { + + set_scene(m_chatmessage[DESK_MOD].toInt(), m_chatmessage[SIDE]); + + // Move the character on the viewport according to the offsets + set_self_offset(m_chatmessage[SELF_OFFSET], ui_vp_player_char); + + int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); + bool immediate = m_chatmessage[IMMEDIATE].toInt() == 1; + + + // 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) { + // Display the pair character + 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); +} + void Courtroom::do_flash() { if (!Options::getInstance().effectsEnabled()) @@ -3878,7 +4075,7 @@ void Courtroom::start_chat_ticking() switch (m_chatmessage[DESK_MOD].toInt()) { case DESK_EMOTE_ONLY_EX: - set_self_offset(m_chatmessage[SELF_OFFSET]); + set_self_offset(m_chatmessage[SELF_OFFSET], ui_vp_player_char); [[fallthrough]]; case DESK_EMOTE_ONLY: case DESK_SHOW: @@ -4331,8 +4528,11 @@ void Courtroom::play_sfx() void Courtroom::set_scene(bool show_desk, const QString f_side) { - ui_vp_background->load_image(ao_app->get_pos_path(f_side)); - ui_vp_desk->load_image(ao_app->get_pos_path(f_side, true)); + QPair bg_pair = ao_app->get_pos_path(f_side); + QPair desk_pair = ao_app->get_pos_path(f_side, true); + ui_vp_background->load_image(bg_pair.first, bg_pair.second); + ui_vp_desk->load_image(desk_pair.first, desk_pair.second); + last_side = f_side; if (show_desk) { @@ -4344,7 +4544,7 @@ void Courtroom::set_scene(bool show_desk, const QString f_side) } } -void Courtroom::set_self_offset(const QString &p_list) +void Courtroom::set_self_offset(const QString& p_list, AOLayer* p_layer) { { QStringList self_offsets = p_list.split("&"); int self_offset = self_offsets[0].toInt(); @@ -4357,7 +4557,7 @@ void Courtroom::set_self_offset(const QString &p_list) { self_offset_v = self_offsets[1].toInt(); } - ui_vp_player_char->move_and_center(ui_viewport->width() * self_offset / 100, ui_viewport->height() * self_offset_v / 100); + p_layer->move_and_center(ui_viewport->width() * self_offset / 100, ui_viewport->height() * self_offset_v / 100); } void Courtroom::set_ip_list(QString p_list) @@ -4381,7 +4581,7 @@ void Courtroom::set_mute(bool p_muted, int p_cid) else { ui_muted->hide(); - ui_ic_chat_message->setFocus(); + focus_ic_input(); } ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); @@ -5005,7 +5205,7 @@ void Courtroom::on_pos_remove_clicked() ui_pos_dropdown->blockSignals(false); current_side = ""; ui_pos_remove->hide(); - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::set_iniswap_dropdown() @@ -5052,7 +5252,7 @@ void Courtroom::set_iniswap_dropdown() void Courtroom::on_iniswap_dropdown_changed(int p_index) { - ui_ic_chat_message->setFocus(); + focus_ic_input(); QString iniswap = ui_iniswap_dropdown->itemText(p_index); QStringList swaplist; @@ -5190,7 +5390,7 @@ void Courtroom::set_sfx_dropdown() void Courtroom::on_sfx_dropdown_changed(int p_index) { custom_sfx = ""; - ui_ic_chat_message->setFocus(); + focus_ic_input(); if (p_index == 0) { ui_sfx_remove->hide(); @@ -5345,7 +5545,7 @@ void Courtroom::on_character_effects_edit_requested() void Courtroom::on_effects_dropdown_changed(int p_index) { effect = ui_effects_dropdown->itemText(p_index); - ui_ic_chat_message->setFocus(); + focus_ic_input(); } bool Courtroom::effects_dropdown_find_and_set(QString effect) @@ -5701,7 +5901,7 @@ void Courtroom::on_hold_it_clicked() objection_state = 1; } - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_objection_clicked() @@ -5721,7 +5921,7 @@ void Courtroom::on_objection_clicked() objection_state = 2; } - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_take_that_clicked() @@ -5741,7 +5941,7 @@ void Courtroom::on_take_that_clicked() objection_state = 3; } - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_custom_objection_clicked() @@ -5761,7 +5961,7 @@ void Courtroom::on_custom_objection_clicked() objection_state = 4; } - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::show_custom_objection_menu(const QPoint &pos) @@ -5814,7 +6014,7 @@ void Courtroom::on_realization_clicked() ui_realization->setImage("realization"); } - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_screenshake_clicked() @@ -5830,7 +6030,7 @@ void Courtroom::on_screenshake_clicked() ui_screenshake->setImage("screenshake"); } - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_mute_clicked() @@ -6033,13 +6233,13 @@ void Courtroom::on_text_color_changed(int p_color) text_color = 0; } } - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_music_slider_moved(int p_value) { music_player->setStreamVolume(p_value, 0); // Set volume on music layer - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_sfx_slider_moved(int p_value) @@ -6051,13 +6251,13 @@ void Courtroom::on_sfx_slider_moved(int p_value) music_player->setStreamVolume(p_value, i); } objection_player->setVolume(p_value); - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_blip_slider_moved(int p_value) { blip_player->setVolume(p_value); - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_log_limit_changed(int value) @@ -6084,7 +6284,7 @@ void Courtroom::on_witness_testimony_clicked() ao_app->send_server_packet(AOPacket("RT", {"testimony1"})); - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_cross_examination_clicked() @@ -6096,7 +6296,7 @@ void Courtroom::on_cross_examination_clicked() ao_app->send_server_packet(AOPacket("RT", {"testimony2"})); - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_not_guilty_clicked() @@ -6108,7 +6308,7 @@ void Courtroom::on_not_guilty_clicked() ao_app->send_server_packet(AOPacket("RT", {"judgeruling", "0"})); - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_guilty_clicked() @@ -6120,7 +6320,7 @@ void Courtroom::on_guilty_clicked() ao_app->send_server_packet(AOPacket("RT", {"judgeruling", "1"})); - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_change_character_clicked() @@ -6207,7 +6407,7 @@ void Courtroom::on_call_mod_clicked() ao_app->send_server_packet(AOPacket("ZZ")); } - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::on_settings_clicked() @@ -6215,15 +6415,6 @@ void Courtroom::on_settings_clicked() ao_app->call_settings_menu(); } -void Courtroom::on_pre_clicked() -{ - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_flip_clicked() -{ - ui_ic_chat_message->setFocus(); -} void Courtroom::on_additive_clicked() { @@ -6234,10 +6425,10 @@ void Courtroom::on_additive_clicked() ui_ic_chat_message->end(false); // move cursor to the end of the message // without selecting anything } - ui_ic_chat_message->setFocus(); + focus_ic_input(); } -void Courtroom::on_guard_clicked() +void Courtroom::focus_ic_input() { ui_ic_chat_message->setFocus(); } @@ -6245,7 +6436,7 @@ void Courtroom::on_guard_clicked() void Courtroom::on_showname_enable_clicked() { regenerate_ic_chatlog(); - ui_ic_chat_message->setFocus(); + focus_ic_input(); } void Courtroom::regenerate_ic_chatlog() diff --git a/src/courtroom.h b/src/courtroom.h index 0f49839..0370892 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -150,8 +150,9 @@ public: // sets desk and bg based on pos in chatmessage void set_scene(bool show_desk, QString f_side); - // sets ui_vp_player_char according to SELF_OFFSET, only a function bc it's used with desk_mod 4 and 5 - void set_self_offset(const QString &p_list); + // sets p_layer according to SELF_OFFSET, only a function bc it's used with + // desk_mod 4 and 5 + void set_self_offset(const QString &p_list, AOLayer *p_layer); // takes in serverD-formatted IP list as prints a converted version to server // OOC admittedly poorly named @@ -215,6 +216,9 @@ public: // Handle the stuff that comes when the character appears on screen and starts animating (preanims etc.) void handle_ic_message(); + // Start the logic for doing a courtroom pan slide + void do_transition(QString desk_mod, QString old_pos, QString new_pos); + // Display the character. void display_character(); @@ -308,6 +312,8 @@ private: QParallelAnimationGroup *screenshake_animation_group = new QParallelAnimationGroup; + QParallelAnimationGroup *transition_animation_group = new QParallelAnimationGroup; + bool next_character_is_not_special = false; // If true, write the // next character as it is. @@ -436,9 +442,15 @@ private: // Minumum and maximum number of parameters in the MS packet static const int MS_MINIMUM = 15; - static const int MS_MAXIMUM = 31; + static const int MS_MAXIMUM = 32; QString m_chatmessage[MS_MAXIMUM]; + /** + * @brief The amount of time to wait at the start and end of slide + * animations + */ + static const int TRANSITION_BOOKEND_DELAY = 300; + QString previous_ic_message; QString additive_previous; @@ -570,6 +582,11 @@ private: QString current_background = "default"; QString current_side; + // used for courtroom slide logic + QString last_side = ""; + int last_offset = 0; + int last_v_offset = 0; + QString last_music_search; QString last_area_search; @@ -595,6 +612,8 @@ private: 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; AOEvidenceDisplay *ui_vp_evidence_display; AOImage *ui_vp_chatbox; @@ -693,6 +712,8 @@ private: QCheckBox *ui_immediate; QCheckBox *ui_showname_enable; + QCheckBox *ui_slide_enable; + AOButton *ui_custom_objection; QMenu *custom_obj_menu; AOButton *ui_realization; @@ -784,6 +805,7 @@ 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); @@ -912,10 +934,8 @@ private Q_SLOTS: void on_call_mod_clicked(); void on_settings_clicked(); - void on_pre_clicked(); - void on_flip_clicked(); + void focus_ic_input(); void on_additive_clicked(); - void on_guard_clicked(); void on_showname_enable_clicked(); @@ -960,4 +980,8 @@ private Q_SLOTS: void preview_emote(QString emote); void update_emote_preview(); + + // After attempting to play a transition animation, clean up the viewport + // objects for everyone else and continue the IC processing callstack + void post_transition_cleanup(); }; diff --git a/src/datatypes.h b/src/datatypes.h index 9463a47..ac9c646 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -78,6 +78,7 @@ enum CHAT_MESSAGE ADDITIVE, EFFECTS, BLIPNAME, + SLIDE, }; enum EMOTE_MOD_TYPE diff --git a/src/options.cpp b/src/options.cpp index 8006076..d06c5e4 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -401,6 +401,16 @@ void Options::setNetworkedFrameSfxEnabled(bool value) config.setValue("framenetwork", value); } +bool Options::slidesEnabled() const +{ + return config.value("slides", true).toBool(); +} + +void Options::setSlidesEnabled(bool value) +{ + config.setValue("slides", value); +} + bool Options::colorLogEnabled() const { return config.value("colorlog", true).toBool(); diff --git a/src/options.h b/src/options.h index 088e34b..1ea7621 100644 --- a/src/options.h +++ b/src/options.h @@ -95,6 +95,11 @@ public: bool networkedFrameSfxEnabled() const; void setNetworkedFrameSfxEnabled(bool value); + // Returns the value of whether courtroom slide animations should be played + // on this client. + bool slidesEnabled() const; + void setSlidesEnabled(bool value); + // Returns the value of whether colored ic log should be a thing. // from the config.ini. bool colorLogEnabled() const; diff --git a/src/path_functions.cpp b/src/path_functions.cpp index d68535c..1860245 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -78,9 +78,34 @@ VPath AOApplication::get_default_background_path(QString p_file) return VPath("background/default/" + p_file); } -QString AOApplication::get_pos_path(const QString &pos, const bool desk) +QPair AOApplication::get_pos_path(const QString &pos, const bool desk) { // witness is default if pos is invalid + QString f_pos = pos; + // legacy overrides for new format if found + if (pos == "def" && file_exists(get_image_suffix(get_background_path("court")))) + { + f_pos = "court:def"; + } + else if (pos == "pro" && file_exists(get_image_suffix(get_background_path("court")))) + { + f_pos = "court:pro"; + } + else if (pos == "wit" && file_exists(get_image_suffix(get_background_path("court")))) + { + f_pos = "court:wit"; + } + QStringList f_pos_split = f_pos.split(":"); + int f_center = -1; + 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) + { + f_center = subpos_center; + } + } QString f_background; QString f_desk_image; if (file_exists(get_image_suffix(get_background_path("witnessempty")))) @@ -130,10 +155,10 @@ QString AOApplication::get_pos_path(const QString &pos, const bool desk) f_desk_image = "seancedesk"; } - if (file_exists(get_image_suffix(get_background_path(pos)))) // Unique pos path + if (file_exists(get_image_suffix(get_background_path(f_pos_split[0])))) // Unique pos path { - f_background = pos; - f_desk_image = pos + "_overlay"; + f_background = f_pos_split[0]; + f_desk_image = f_pos_split[0] + "_overlay"; } QString desk_override = read_design_ini("overlays/" + f_background, get_background_path("design.ini")); @@ -143,9 +168,9 @@ QString AOApplication::get_pos_path(const QString &pos, const bool desk) } if (desk) { - return f_desk_image; + return {f_desk_image, f_center}; } - return f_background; + return {f_background, f_center}; } VPath AOApplication::get_evidence_path(QString p_file) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index e7c4511..b123da3 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -912,3 +912,22 @@ bool AOApplication::get_pos_is_judge(const QString &p_pos) } return positions.contains(p_pos.trimmed()); } + +int AOApplication::get_pos_transition_duration(const QString &old_pos, const QString &new_pos) +{ + if (old_pos.split(":").size() < 2 || new_pos.split(":").size() < 2) + { + return -1; // no subpositions + } + + QString new_subpos = new_pos.split(":")[1]; + + bool ok; + int duration = read_design_ini(old_pos + "/slide_ms_" + new_subpos, get_background_path("design.ini")).toInt(&ok); + if (ok) + { + return duration; + } + else + return -1; // invalid +} diff --git a/src/widgets/aooptionsdialog.cpp b/src/widgets/aooptionsdialog.cpp index beb7e55..a0b7d7c 100644 --- a/src/widgets/aooptionsdialog.cpp +++ b/src/widgets/aooptionsdialog.cpp @@ -358,6 +358,7 @@ void AOOptionsDialog::setupUI() FROM_UI(QCheckBox, category_stop_cb); FROM_UI(QCheckBox, sfx_on_idle_cb); FROM_UI(QCheckBox, evidence_double_click_cb); + FROM_UI(QCheckBox, slides_cb); registerOption("theme_scaling_factor_sb", &Options::themeScalingFactor, &Options::setThemeScalingFactor); registerOption("animated_theme_cb", &Options::animatedThemeEnabled, &Options::setAnimatedThemeEnabled); @@ -400,6 +401,7 @@ void AOOptionsDialog::setupUI() registerOption("category_stop_cb", &Options::stopMusicOnCategoryEnabled, &Options::setStopMusicOnCategoryEnabled); registerOption("sfx_on_idle_cb", &Options::playSelectedSFXOnIdle, &Options::setPlaySelectedSFXOnIdle); registerOption("evidence_double_click_cb", &Options::evidenceDoubleClickEdit, &Options::setEvidenceDoubleClickEdit); + registerOption("slides_cb", &Options::slidesEnabled, &Options::setSlidesEnabled); // Callwords tab. This could just be a QLineEdit, but no, we decided to allow // people to put a billion entries in. diff --git a/src/widgets/aooptionsdialog.h b/src/widgets/aooptionsdialog.h index bccec58..b16be33 100644 --- a/src/widgets/aooptionsdialog.h +++ b/src/widgets/aooptionsdialog.h @@ -47,6 +47,7 @@ private: QPushButton *ui_theme_reload_button; QPushButton *ui_theme_folder_button; QCheckBox *ui_evidence_double_click_cb; + QCheckBox *ui_slides_cb; QCheckBox *ui_animated_theme_cb; QSpinBox *ui_stay_time_spinbox; QCheckBox *ui_instant_objection_cb;