diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/aoapplication.cpp | 8 | ||||
| -rw-r--r-- | src/aocharmovie.cpp | 332 | ||||
| -rw-r--r-- | src/aoclocklabel.cpp | 59 | ||||
| -rw-r--r-- | src/aoevidencedisplay.cpp | 10 | ||||
| -rw-r--r-- | src/aolayer.cpp | 601 | ||||
| -rw-r--r-- | src/aomovie.cpp | 100 | ||||
| -rw-r--r-- | src/aomusicplayer.cpp | 7 | ||||
| -rw-r--r-- | src/aooptionsdialog.cpp | 59 | ||||
| -rw-r--r-- | src/aopacket.cpp | 10 | ||||
| -rw-r--r-- | src/aoscene.cpp | 132 | ||||
| -rw-r--r-- | src/charselect.cpp | 74 | ||||
| -rw-r--r-- | src/courtroom.cpp | 1481 | ||||
| -rw-r--r-- | src/demoserver.cpp | 304 | ||||
| -rw-r--r-- | src/lobby.cpp | 12 | ||||
| -rw-r--r-- | src/networkmanager.cpp | 2 | ||||
| -rw-r--r-- | src/packet_distribution.cpp | 106 | ||||
| -rw-r--r-- | src/path_functions.cpp | 10 | ||||
| -rw-r--r-- | src/text_file_functions.cpp | 198 |
18 files changed, 2269 insertions, 1236 deletions
diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index fa58ab84..34afb28e 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -45,6 +45,10 @@ void AOApplication::construct_lobby() if (is_discord_enabled()) discord->state_lobby(); + if (demo_server) + demo_server->deleteLater(); + demo_server = new DemoServer(); + w_lobby->show(); } @@ -182,6 +186,10 @@ void AOApplication::call_announce_menu(Courtroom *court) void CALLBACK AOApplication::BASSreset(HSTREAM handle, DWORD channel, DWORD data, void *user) { + UNUSED(handle); + UNUSED(channel); + UNUSED(data); + UNUSED(user); doBASSreset(); } diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp deleted file mode 100644 index 09a4b889..00000000 --- a/src/aocharmovie.cpp +++ /dev/null @@ -1,332 +0,0 @@ -#include "aocharmovie.h" - -#include "aoapplication.h" -#include "file_functions.h" -#include "misc_functions.h" - -AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) - : QLabel(p_parent) -{ - ao_app = p_ao_app; - preanim_timer = new QTimer(this); - preanim_timer->setSingleShot(true); - - ticker = new QTimer(this); - ticker->setTimerType(Qt::PreciseTimer); - ticker->setSingleShot(false); - connect(ticker, SIGNAL(timeout()), this, SLOT(movie_ticker())); - - // connect(m_movie, SIGNAL(frameChanged(int)), this, - // SLOT(frame_change(int))); - connect(preanim_timer, SIGNAL(timeout()), this, SLOT(preanim_done())); -} - -void AOCharMovie::load_image(QString p_char, QString p_emote, - QString emote_prefix) -{ -#ifdef DEBUG_CHARMOVIE - actual_time.restart(); -#endif - QString emote_path; - QList<QString> pathlist; - pathlist = { - ao_app->get_image_suffix(ao_app->get_character_path( - p_char, emote_prefix + p_emote)), // Default path - ao_app->get_image_suffix(ao_app->get_character_path( - p_char, emote_prefix + "/" + - p_emote)), // Path check if it's categorized into a folder - ao_app->get_image_suffix(ao_app->get_character_path( - p_char, p_emote)), // Just use the non-prefixed image, animated or not - ao_app->get_image_suffix( - ao_app->get_theme_path("placeholder")), // Theme placeholder path - ao_app->get_image_suffix(ao_app->get_default_theme_path( - "placeholder")), // Default theme placeholder path - }; - - for (QString path : pathlist) { - if (file_exists(path)) { - emote_path = path; - break; - } - } - - this->clear(); - ticker->stop(); - preanim_timer->stop(); - movie_frames.clear(); - movie_delays.clear(); - movie_effects.clear(); - - if (!file_exists(emote_path)) - return; - - m_reader->setFileName(emote_path); - - // set format to apng if png supports animation - if (emote_path.endsWith("png")) { - m_reader->setFormat("apng"); - if (!m_reader->supportsAnimation()) { - m_reader->setFormat("png"); - } - } - - QPixmap f_pixmap = this->get_pixmap(m_reader->read()); - int f_delay = m_reader->nextImageDelay(); - - frame = 0; - max_frames = m_reader->imageCount(); - - this->set_frame(f_pixmap); - this->show(); - if (max_frames > 1) { - movie_frames.append(f_pixmap); - movie_delays.append(f_delay); - } - - m_char = p_char; - m_emote = emote_prefix + p_emote; - - if (network_strings.size() > 0) // our FX overwritten by networked ones - this->load_network_effects(); - else // Use default ini FX - this->load_effects(); -#ifdef DEBUG_CHARMOVIE - qDebug() << max_frames << "Setting image to " << emote_path - << "Time taken to process image:" << actual_time.elapsed(); - - actual_time.restart(); -#endif -} - -void AOCharMovie::load_effects() -{ - movie_effects.clear(); - movie_effects.resize(max_frames); - for (int e_frame = 0; e_frame < max_frames; ++e_frame) { - QString effect = ao_app->get_screenshake_frame(m_char, m_emote, e_frame); - if (effect != "") { - movie_effects[e_frame].append("shake"); - } - - effect = ao_app->get_flash_frame(m_char, m_emote, e_frame); - if (effect != "") { - movie_effects[e_frame].append("flash"); - } - - effect = ao_app->get_sfx_frame(m_char, m_emote, e_frame); - if (effect != "") { - movie_effects[e_frame].append("sfx^" + effect); - } - } -} - -void AOCharMovie::load_network_effects() -{ - movie_effects.clear(); - movie_effects.resize(max_frames); - // Order is important!!! - QStringList effects_list = {"shake", "flash", "sfx^"}; - - // Determines which list is smaller - effects_list or network_strings - and - // uses it as basis for the loop. This way, incomplete network_strings would - // still be parsed, and excess/unaccounted for networked information is - // omitted. - int effects_size = qMin(effects_list.size(), network_strings.size()); - - for (int i = 0; i < effects_size; ++i) { - QString netstring = network_strings.at(i); - QStringList emote_splits = netstring.split("^"); - foreach (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) { - qDebug() << "Warning: out of bounds" << effects_list[i] << "frame" - << f_frame << "out of" << max_frames << "for" << m_char - << m_emote; - continue; - } - QString f_data = frame_split.at(1); - if (f_data != "") { - QString effect = effects_list[i]; - if (effect == "sfx^") // Currently the only frame result that feeds us - // data, let's yank it in. - effect += f_data; - qDebug() << effect << f_data << "frame" << f_frame << "for" << m_char - << m_emote; - movie_effects[f_frame].append(effect); - } - } - } - } -} - -void AOCharMovie::play() -{ - play_frame_effect(frame); - if (max_frames <= 1) { - if (play_once) - ticker->start(60); - } - else - ticker->start(this->get_frame_delay(movie_delays[frame])); -} - -void AOCharMovie::play_pre(QString p_char, QString p_emote, int duration) -{ - load_image(p_char, p_emote, ""); - // As much as I'd like to screw around with [Time] durations modifying the - // animation speed, I don't think I can reliably do that, not without looping - // through all frames in the image at least - which causes lag. So for now it - // simply ends the preanimation early instead. - play_once = true; - if (duration > - 0) // It's -1 if there's no definition in [Time] for it. In which case, it - // will let the animation run out in full. Duration 0 does the same. - preanim_timer->start(duration * - time_mod); // This timer will not fire if the animation - // finishes earlier than that - play(); -} - -void AOCharMovie::play_talking(QString p_char, QString p_emote) -{ - play_once = false; - load_image(p_char, p_emote, "(b)"); - play(); -} - -void AOCharMovie::play_idle(QString p_char, QString p_emote) -{ - play_once = false; - load_image(p_char, p_emote, "(a)"); - play(); -} - -void AOCharMovie::play_frame_effect(int frame) -{ - if (frame < max_frames) { - foreach (QString effect, movie_effects[frame]) { - if (effect == "shake") { - shake(); -#ifdef DEBUG_CHARMOVIE - qDebug() << "Attempting to play shake on frame" << frame; -#endif - } - - if (effect == "flash") { - flash(); -#ifdef DEBUG_CHARMOVIE - qDebug() << "Attempting to play flash on frame" << frame; -#endif - } - - if (effect.startsWith("sfx^")) { - QString sfx = effect.section("^", 1); - play_sfx(sfx); -#ifdef DEBUG_CHARMOVIE - qDebug() << "Attempting to play sfx" << sfx << "on frame" << frame; -#endif - } - } - } -} - -void AOCharMovie::stop() -{ - // for all intents and purposes, stopping is the same as hiding. at no point - // do we want a frozen gif to display - ticker->stop(); - preanim_timer->stop(); - this->hide(); -} - -QPixmap AOCharMovie::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; - auto transform_mode = Qt::FastTransformation; - if (f_pixmap.height() > f_h) // We are downscaling, use anti-aliasing. - transform_mode = Qt::SmoothTransformation; - - f_pixmap = f_pixmap.scaledToHeight(f_h, transform_mode); - this->resize(f_pixmap.size()); - - return f_pixmap; -} - -void AOCharMovie::set_frame(QPixmap f_pixmap) -{ - this->setPixmap(f_pixmap); - QLabel::move( - x + (f_w - f_pixmap.width()) / 2, - y + (f_h - f_pixmap.height())); // Always center horizontally, always put - // at the bottom vertically -} - -void AOCharMovie::combo_resize(int w, int h) -{ - QSize f_size(w, h); - f_w = w; - f_h = h; - this->resize(f_size); -} - -int AOCharMovie::get_frame_delay(int delay) -{ - return static_cast<int>(double(delay) * double(speed / 100)); -} - -void AOCharMovie::move(int ax, int ay) -{ - x = ax; - y = ay; - QLabel::move(x, y); -} - -void AOCharMovie::movie_ticker() -{ - ++frame; - if (frame >= max_frames) { - if (play_once) { - preanim_done(); - return; - } - else - frame = 0; - } - // qint64 difference = elapsed - movie_delays[frame]; - if (frame >= movie_frames.size()) { - m_reader->jumpToImage(frame); - movie_frames.resize(frame + 1); - movie_frames[frame] = this->get_pixmap(m_reader->read()); - movie_delays.resize(frame + 1); - movie_delays[frame] = m_reader->nextImageDelay(); - } - -#ifdef DEBUG_CHARMOVIE - qDebug() << frame << movie_delays[frame] - << "actual time taken from last frame:" << actual_time.restart(); -#endif - - this->set_frame(movie_frames[frame]); - play_frame_effect(frame); - ticker->setInterval(this->get_frame_delay(movie_delays[frame])); -} - -void AOCharMovie::preanim_done() -{ - ticker->stop(); - preanim_timer->stop(); - done(); -} diff --git a/src/aoclocklabel.cpp b/src/aoclocklabel.cpp new file mode 100644 index 00000000..4c9c4819 --- /dev/null +++ b/src/aoclocklabel.cpp @@ -0,0 +1,59 @@ +#include "aoclocklabel.h" + +AOClockLabel::AOClockLabel(QWidget *parent) : QLabel(parent) {} + +void AOClockLabel::start() +{ + timer.start(1000 / 60, this); +} + +void AOClockLabel::start(int msecs) +{ + this->set(msecs); + this->start(); +} + +void AOClockLabel::set(int msecs, bool update_text) +{ + target_time = QTime::currentTime().addMSecs(msecs); + if (update_text) + { + if (QTime::currentTime() >= target_time) + { + this->setText("00:00:00.000"); + } + else + { + QTime timeleft = QTime(0,0).addMSecs(QTime::currentTime().msecsTo(target_time)); + QString timestring = timeleft.toString("hh:mm:ss.zzz"); + this->setText(timestring); + } + } +} + +void AOClockLabel::pause() +{ + timer.stop(); +} + +void AOClockLabel::stop() +{ + this->setText("00:00:00.000"); + timer.stop(); +} + +void AOClockLabel::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == timer.timerId()) { + if (QTime::currentTime() >= target_time) + { + this->stop(); + return; + } + QTime timeleft = QTime(0,0).addMSecs(QTime::currentTime().msecsTo(target_time)); + QString timestring = timeleft.toString("hh:mm:ss.zzz"); + this->setText(timestring); + } else { + QWidget::timerEvent(event); + } +} diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index 2ffea2c9..f6dffd85 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -11,7 +11,7 @@ AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) evidence_icon = new QLabel(this); sfx_player = new AOSfxPlayer(this, ao_app); - evidence_movie = new AOMovie(this, ao_app); + evidence_movie = new InterfaceLayer(this, ao_app); connect(evidence_movie, SIGNAL(done()), this, SLOT(show_done())); } @@ -46,9 +46,11 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, evidence_icon->setPixmap(f_pixmap); evidence_icon->resize(f_pixmap.size()); evidence_icon->move(icon_dimensions.x, icon_dimensions.y); - - evidence_movie->play(gif_name); - sfx_player->play(ao_app->get_sfx("evidence_present")); + evidence_movie->static_duration = 320; + evidence_movie->max_duration = 1000; + evidence_movie->set_play_once(true); + evidence_movie->load_image(gif_name, ""); + sfx_player->play(ao_app->get_sfx("evidence_present", "default")); } void AOEvidenceDisplay::reset() diff --git a/src/aolayer.cpp b/src/aolayer.cpp new file mode 100644 index 00000000..f95773b0 --- /dev/null +++ b/src/aolayer.cpp @@ -0,0 +1,601 @@ +#include "aolayer.h" + +#include "aoapplication.h" +#include "file_functions.h" +#include "misc_functions.h" + +AOLayer::AOLayer(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) +{ + ao_app = p_ao_app; + + // used for culling images when their max_duration is exceeded + shfx_timer = new QTimer(this); + shfx_timer->setTimerType(Qt::PreciseTimer); + shfx_timer->setSingleShot(true); + connect(shfx_timer, SIGNAL(timeout()), this, SLOT(shfx_timer_done())); + + ticker = new QTimer(this); + ticker->setTimerType(Qt::PreciseTimer); + ticker->setSingleShot(false); + connect(ticker, SIGNAL(timeout()), this, SLOT(movie_ticker())); + + preanim_timer = new QTimer(this); + preanim_timer->setSingleShot(true); + connect(preanim_timer, SIGNAL(timeout()), this, SLOT(preanim_done())); +} + +BackgroundLayer::BackgroundLayer(QWidget *p_parent, AOApplication *p_ao_app) + : AOLayer(p_parent, p_ao_app) +{ +} +ForegroundLayer::ForegroundLayer(QWidget *p_parent, AOApplication *p_ao_app) + : AOLayer(p_parent, p_ao_app) +{ +} +CharLayer::CharLayer(QWidget *p_parent, AOApplication *p_ao_app) + : AOLayer(p_parent, p_ao_app) +{ +} +EffectLayer::EffectLayer(QWidget *p_parent, AOApplication *p_ao_app) + : AOLayer(p_parent, p_ao_app) +{ +} +InterjectionLayer::InterjectionLayer(QWidget *p_parent, AOApplication *p_ao_app) + : AOLayer(p_parent, p_ao_app) +{ +} +InterfaceLayer::InterfaceLayer(QWidget *p_parent, AOApplication *p_ao_app) + : AOLayer(p_parent, p_ao_app) +{ +} + +StickerLayer::StickerLayer(QWidget *p_parent, AOApplication *p_ao_app) + : AOLayer(p_parent, p_ao_app) +{ +} + +QString AOLayer::find_image(QList<QString> p_list) +{ + QString image_path; + for (QString path : p_list) { +#ifdef DEBUG_MOVIE + qDebug() << "checking path " << path; +#endif + if (file_exists(path)) { + image_path = path; +#ifdef DEBUG_MOVIE + qDebug() << "found path " << path; +#endif + break; + } + } + return image_path; +} + +QPixmap AOLayer::get_pixmap(QImage image) +{ + QPixmap f_pixmap; + if (m_flipped) + f_pixmap = QPixmap::fromImage(image.mirrored(true, false)); + else + f_pixmap = QPixmap::fromImage(image); + // auto aspect_ratio = Qt::KeepAspectRatio; + if (f_pixmap.height() > f_h) // We are downscaling, use anti-aliasing. + transform_mode = Qt::SmoothTransformation; + if (stretch) + f_pixmap = f_pixmap.scaled(f_w, f_h); + else + f_pixmap = f_pixmap.scaledToHeight(f_h, transform_mode); + this->resize(f_pixmap.size()); + + return f_pixmap; +} + +void AOLayer::set_frame(QPixmap f_pixmap) +{ + this->setPixmap(f_pixmap); + QLabel::move( + x + (f_w - f_pixmap.width()) / 2, + y + (f_h - f_pixmap.height())); // Always center horizontally, always put + // at the bottom vertically + this->setMask( + QRegion((f_pixmap.width() - f_w) / 2, (f_pixmap.height() - f_h) / 2, f_w, + f_h)); // make sure we don't escape the area we've been given +} + +void AOLayer::combo_resize(int w, int h) +{ + QSize f_size(w, h); + f_w = w; + f_h = h; + this->resize(f_size); +} + +int AOLayer::get_frame_delay(int delay) +{ + return static_cast<int>(double(delay) * double(speed / 100)); +} + +void AOLayer::move(int ax, int ay) +{ + x = ax; + y = ay; + QLabel::move(x, y); +} + +void BackgroundLayer::load_image(QString p_filename) +{ + play_once = false; + cull_image = false; + QString design_path = ao_app->get_background_path("design.ini"); + transform_mode = + ao_app->get_scaling(ao_app->read_design_ini("scaling", design_path)); + stretch = ao_app->read_design_ini("stretch", design_path).startsWith("true"); + qDebug() << "[BackgroundLayer] BG loaded: " << p_filename; + start_playback(ao_app->get_image_suffix(ao_app->get_background_path(p_filename))); +} + +void ForegroundLayer::load_image(QString p_filename, QString p_charname) +{ + play_once = false; + cull_image = false; + miscname = ao_app->get_char_shouts(p_charname); + qDebug() << "[ForegroundLayer] FG loaded: " << p_filename; + QList<QString> pathlist = { + ao_app->get_image_suffix(ao_app->get_character_path( + p_charname, p_filename)), // first check the character folder + ao_app->get_image_suffix(ao_app->get_theme_path( + "misc/" + miscname + "/" + + p_filename)), // then check our theme's misc directory + ao_app->get_image_suffix(ao_app->get_misc_path( + miscname, p_filename)), // then check our global misc folder + ao_app->get_image_suffix( + ao_app->get_theme_path(p_filename)), // then check the user's theme + ao_app->get_image_suffix(ao_app->get_default_theme_path( + p_filename))}; // and finally check the default theme + start_playback(find_image(pathlist)); +} + +void CharLayer::load_image(QString p_filename, QString p_charname, + int p_duration, bool p_is_preanim) +{ + duration = p_duration; + cull_image = false; + force_continuous = false; + transform_mode = ao_app->get_scaling( + ao_app->get_emote_property(p_charname, p_filename, "scaling")); + stretch = ao_app->get_emote_property(p_charname, p_filename, "stretch") + .startsWith(true); + if ((p_charname == last_char) && + ((p_filename == last_emote) || + (p_filename.mid(3, -1) == last_emote.mid(3, -1))) && + (!is_preanim) && (!was_preanim)) { + continuous = true; + force_continuous = true; + } + else { + continuous = false; + force_continuous = true; + } + prefix = ""; + current_emote = p_filename; + was_preanim = is_preanim; + m_char = p_charname; + m_emote = current_emote; + last_char = p_charname; + last_emote = current_emote; + last_prefix = prefix; + is_preanim = p_is_preanim; + if ((p_filename.left(3) == "(a)") || (p_filename.left(3) == "(b)")) { + prefix = p_filename.left(3); + current_emote = p_filename.mid(3, -1); + } + else if ((duration > 0) || (p_filename.left(3) == "(c)")) { + if (p_filename.left(3) == "(c)") { + prefix = "(c)"; + current_emote = p_filename.mid(3, -1); + } + is_preanim = true; + play_once = true; + preanim_timer->start(duration * tick_ms); + } + qDebug() << "[CharLayer] anim loaded: prefix " << prefix << " filename " + << current_emote << " from character: " << p_charname + << " continuous: " << continuous; + QList<QString> pathlist = { + ao_app->get_image_suffix(ao_app->get_character_path( + p_charname, prefix + current_emote)), // Default path + ao_app->get_image_suffix(ao_app->get_character_path( + p_charname, + prefix + "/" + current_emote)), // Path check if it's categorized + // into a folder + ao_app->get_image_suffix(ao_app->get_character_path( + p_charname, + current_emote)), // Just use the non-prefixed image, animated or not + ao_app->get_image_suffix( + ao_app->get_theme_path("placeholder")), // Theme placeholder path + ao_app->get_image_suffix(ao_app->get_default_theme_path( + "placeholder"))}; // Default theme placeholder path + this->start_playback(find_image(pathlist)); +} + +void InterjectionLayer::load_image(QString p_filename, QString p_charname, + QString p_miscname) +{ + continuous = false; + force_continuous = true; + play_once = true; + transform_mode = ao_app->get_misc_scaling(p_miscname); + QList<QString> pathlist = { + ao_app->get_image_suffix(ao_app->get_character_path( + p_charname, p_filename)), // Character folder + ao_app->get_image_suffix(ao_app->get_theme_path( + "misc/" + p_miscname + "/" + p_filename)), // Theme misc path + ao_app->get_image_suffix( + ao_app->get_misc_path(p_miscname, p_filename)), // Misc path + ao_app->get_image_suffix( + ao_app->get_theme_path(p_filename)), // Theme path + ao_app->get_image_suffix( + ao_app->get_default_theme_path(p_filename)), // Default theme path + ao_app->get_image_suffix( + ao_app->get_theme_path("placeholder")), // Placeholder path + ao_app->get_image_suffix(ao_app->get_default_theme_path( + "placeholder")), // Default placeholder path + }; + QString final_image = find_image(pathlist); + if (final_image == ao_app->get_theme_path("custom.png") || + final_image == ao_app->get_default_theme_path("custom.png") || + final_image == ao_app->get_theme_path("witnesstestimony.png") || + final_image == ao_app->get_default_theme_path("witnesstestimony.png") || + final_image == ao_app->get_theme_path("crossexamination.png") || + final_image == ao_app->get_default_theme_path("crossexamination.png")) + // stupid exceptions because themes are stupid + final_image = find_image( + {ao_app->get_image_suffix(ao_app->get_theme_path("placeholder")), + ao_app->get_image_suffix(ao_app->get_default_theme_path("placeholder"))}); + start_playback(final_image); +} + +void EffectLayer::load_image(QString p_filename, bool p_looping) +{ + if (p_looping) + play_once = false; + else + play_once = true; + continuous = false; + force_continuous = true; + start_playback(p_filename); // handled in its own file before we see it +} + +void InterfaceLayer::load_image(QString p_filename, QString p_miscname) +{ + transform_mode = ao_app->get_misc_scaling(p_miscname); + QList<QString> pathlist = { + ao_app->get_image_suffix(ao_app->get_theme_path( + "misc/" + p_miscname + "/" + + p_filename)), // first check our theme's misc directory + ao_app->get_image_suffix(ao_app->get_misc_path( + p_miscname, p_filename)), // then check our global misc folder + ao_app->get_image_suffix(ao_app->get_theme_path( + p_filename)), // then check the user's theme for a default image + ao_app->get_image_suffix(ao_app->get_default_theme_path( + p_filename))}; // and finally check the default theme + start_playback(find_image(pathlist)); +} + +void StickerLayer::load_image(QString p_charname) +{ + QString miscname = ao_app->get_char_shouts(p_charname); + transform_mode = ao_app->get_misc_scaling(miscname); + QList<QString> pathlist = { + ao_app->get_image_suffix(ao_app->get_base_path() + "misc/" + + miscname + "/sticker/" + p_charname), // Misc path + ao_app->get_image_suffix(ao_app->get_custom_theme_path(miscname, "sticker/" + p_charname)), // Custom theme path + ao_app->get_image_suffix(ao_app->get_theme_path("sticker/" + p_charname)), // Theme path + ao_app->get_image_suffix( + ao_app->get_default_theme_path("sticker/" + p_charname)), // Default theme path + ao_app->get_image_suffix( + ao_app->get_character_path(p_charname, "sticker")), // Character folder + ao_app->get_image_suffix( + ao_app->get_character_path(p_charname, "showname")), // Scuffed DRO way + }; + start_playback(find_image(pathlist)); +} + +void CharLayer::start_playback(QString p_image) +{ + movie_effects.clear(); + AOLayer::start_playback(p_image); + if (network_strings.size() > 0) // our FX overwritten by networked ones + load_network_effects(); + else // Use default ini FX + load_effects(); +} + +void AOLayer::start_playback(QString p_image) +{ +#ifdef DEBUG_MOVIE + actual_time.restart(); +#endif + this->clear(); + freeze(); + movie_frames.clear(); + movie_delays.clear(); + + if (!file_exists(p_image)) + return; + + QString scaling_override = + ao_app->read_design_ini("scaling", p_image + ".ini"); + if (scaling_override != "") + transform_mode = ao_app->get_scaling(scaling_override); + QString stretch_override = + ao_app->read_design_ini("stretch", p_image + ".ini"); + if (stretch_override != "") + stretch = stretch_override.startsWith("true"); + + qDebug() << "stretch:" << stretch << "filename:" << p_image; + m_reader.setFileName(p_image); + if (m_reader.loopCount() == 0) + play_once = true; + if ((last_path == p_image) && (!force_continuous)) + continuous = true; + else if ((last_path != p_image) && !force_continuous) + continuous = false; + if (!continuous) + frame = 0; + force_continuous = false; + last_max_frames = max_frames; + max_frames = m_reader.imageCount(); + if (((continuous) && (max_frames != last_max_frames)) || max_frames == 0) { + frame = 0; + continuous = false; + } + // CANTFIX: this causes a slight hitch + // The correct way of doing this would be to use QImageReader::jumpToImage() + // and populate missing data in the movie ticker when it's needed. This is + // unforunately completely impossible, because QImageReader::jumpToImage() is + // not implemented in any image format AO2 is equipped to use. Instead, the + // default behavior is used - that is, absolutely nothing. + if (continuous) { + for (int i = frame; i--;) { + if (i <= -1) + break; + QPixmap l_pixmap = this->get_pixmap(m_reader.read()); + int l_delay = m_reader.nextImageDelay(); + movie_frames.append(l_pixmap); + movie_delays.append(l_delay); + // qDebug() << "appending delay of " << l_delay; + } + } + // qDebug() << "CONT: " << continuous << " MAX: " << max_frames + // << " LAST MAX: " << last_max_frames << " FRAME: " << frame; + QPixmap f_pixmap = this->get_pixmap(m_reader.read()); + int f_delay = m_reader.nextImageDelay(); + + this->set_frame(f_pixmap); + this->show(); + if (max_frames > 1) { + movie_frames.append(f_pixmap); + movie_delays.append(f_delay); + } + else if (max_frames <= 1) { + duration = static_duration; + play_once = false; +#ifdef DEBUG_MOVIE + qDebug() << "max_frames is <= 1, using static duration"; +#endif + } + if (duration > 0 && cull_image == true) + shfx_timer->start(duration); + play(); +#ifdef DEBUG_MOVIE + qDebug() << max_frames << "Setting image to " << image_path + << "Time taken to process image:" << actual_time.elapsed(); + + actual_time.restart(); +#endif +} + +void CharLayer::play() +{ + play_frame_effect(frame); + AOLayer::play(); +} + +void AOLayer::play() +{ + if (max_frames <= 1) { + if (play_once) + ticker->start(tick_ms); + else + this->freeze(); + } + else + ticker->start(this->get_frame_delay(movie_delays[frame])); +} + +void AOLayer::set_play_once(bool p_play_once) { play_once = p_play_once; } +void AOLayer::set_cull_image(bool p_cull_image) { cull_image = p_cull_image; } +void AOLayer::set_static_duration(int p_static_duration) +{ + static_duration = p_static_duration; +} +void AOLayer::set_max_duration(int p_max_duration) +{ + max_duration = p_max_duration; +} + +void CharLayer::load_effects() +{ + movie_effects.clear(); + movie_effects.resize(max_frames); + for (int e_frame = 0; e_frame < max_frames; ++e_frame) { + QString effect = ao_app->get_screenshake_frame(m_char, m_emote, e_frame); + if (effect != "") { + movie_effects[e_frame].append("shake"); + } + + effect = ao_app->get_flash_frame(m_char, m_emote, e_frame); + if (effect != "") { + movie_effects[e_frame].append("flash"); + } + + effect = ao_app->get_sfx_frame(m_char, m_emote, e_frame); + if (effect != "") { + movie_effects[e_frame].append("sfx^" + effect); + } + } +} + +void CharLayer::load_network_effects() +{ + movie_effects.clear(); + movie_effects.resize(max_frames); + // Order is important!!! + QStringList effects_list = {"shake", "flash", "sfx^"}; + + // Determines which list is smaller - effects_list or network_strings - and + // uses it as basis for the loop. This way, incomplete network_strings would + // still be parsed, and excess/unaccounted for networked information is + // omitted. + int effects_size = qMin(effects_list.size(), network_strings.size()); + + for (int i = 0; i < effects_size; ++i) { + QString netstring = network_strings.at(i); + QStringList emote_splits = netstring.split("^"); + for (const QString &emote : emote_splits) { + QStringList parsed = emote.split("|"); + if (parsed.size() <= 0 || parsed.at(0) != m_emote) + continue; + foreach (QString frame_data, parsed) { + QStringList frame_split = frame_data.split("="); + if (frame_split.size() <= + 1) // We might still be hanging at the emote itself (entry 0). + continue; + int f_frame = frame_split.at(0).toInt(); + if (f_frame >= max_frames || f_frame < 0) { + qDebug() << "Warning: out of bounds" << effects_list[i] << "frame" + << f_frame << "out of" << max_frames << "for" << m_emote; + continue; + } + QString f_data = frame_split.at(1); + if (f_data != "") { + QString effect = effects_list[i]; + if (effect == "sfx^") // Currently the only frame result that feeds us + // data, let's yank it in. + effect += f_data; + qDebug() << effect << f_data << "frame" << f_frame << "for" + << m_emote; + movie_effects[f_frame].append(effect); + } + } + } + } +} + +void CharLayer::play_frame_effect(int p_frame) +{ + if (p_frame < max_frames) { + foreach (QString effect, movie_effects[p_frame]) { + if (effect == "shake") { + shake(); +#ifdef DEBUG_MOVIE + qDebug() << "Attempting to play shake on frame" << frame; +#endif + } + + if (effect == "flash") { + flash(); +#ifdef DEBUG_MOVIE + qDebug() << "Attempting to play flash on frame" << frame; +#endif + } + + if (effect.startsWith("sfx^")) { + QString sfx = effect.section("^", 1); + play_sfx(sfx); +#ifdef DEBUG_MOVIE + qDebug() << "Attempting to play sfx" << sfx << "on frame" << frame; +#endif + } + } + } +} + +void AOLayer::stop() +{ + // for all intents and purposes, stopping is the same as hiding. at no point + // do we want a frozen gif to display + this->freeze(); + this->hide(); +} + +void AOLayer::freeze() +{ + // aT nO pOiNt Do We WaNt A fRoZeN gIf To DiSpLaY + ticker->stop(); + preanim_timer->stop(); + shfx_timer->stop(); +} + +void CharLayer::movie_ticker() +{ + AOLayer::movie_ticker(); + play_frame_effect(frame); +} + +void AOLayer::movie_ticker() +{ + ++frame; + if ((frame >= max_frames) && (max_frames > 1)) { + if (play_once) { + if (cull_image) + this->stop(); + else + this->freeze(); + preanim_done(); + return; + } + else + frame = 0; + } + // qint64 difference = elapsed - movie_delays[frame]; + if (frame >= movie_frames.size()) { + movie_frames.append(this->get_pixmap(m_reader.read())); + movie_delays.append(m_reader.nextImageDelay()); + } + +#ifdef DEBUG_MOVIE + qDebug() << frame << movie_delays[frame] + << "actual time taken from last frame:" << actual_time.restart(); +#endif + + this->set_frame(movie_frames[frame]); + ticker->setInterval(this->get_frame_delay(movie_delays[frame])); +} + +void CharLayer::preanim_done() +{ + if (is_preanim) + AOLayer::preanim_done(); + else + return; +} + +void AOLayer::preanim_done() +{ + ticker->stop(); + preanim_timer->stop(); + done(); +} + +void AOLayer::shfx_timer_done() +{ + this->stop(); +#ifdef DEBUG_MOVIE + qDebug() << "shfx timer signaled done"; +#endif + // signal connected to courtroom object, let it figure out what to do + done(); +} diff --git a/src/aomovie.cpp b/src/aomovie.cpp deleted file mode 100644 index 196c1d3e..00000000 --- a/src/aomovie.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "aomovie.h" - -#include "courtroom.h" -#include "file_functions.h" -#include "misc_functions.h" - -AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) -{ - ao_app = p_ao_app; - - m_movie = new QMovie(); - m_movie->setCacheMode(QMovie::CacheAll); - - this->setMovie(m_movie); - - timer = new QTimer(this); - timer->setTimerType(Qt::PreciseTimer); - timer->setSingleShot(true); - - connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); - connect(timer, SIGNAL(timeout()), this, SLOT(timer_done())); -} - -void AOMovie::set_play_once(bool p_play_once) { play_once = p_play_once; } - -void AOMovie::play(QString p_image, QString p_char, QString p_custom_theme, - int duration) -{ - m_movie->stop(); - - QString shout_path = p_image; - if (!file_exists(p_image)) { - QList<QString> pathlist; - - pathlist = { - ao_app->get_image_suffix( - ao_app->get_character_path(p_char, p_image)), // Character folder - ao_app->get_image_suffix(ao_app->get_base_path() + "misc/" + - p_custom_theme + "/" + p_image), // Misc path - ao_app->get_image_suffix(ao_app->get_custom_theme_path( - p_custom_theme, p_image)), // Custom theme path - ao_app->get_image_suffix(ao_app->get_theme_path(p_image)), // Theme path - ao_app->get_image_suffix( - ao_app->get_default_theme_path(p_image)), // Default theme path - ao_app->get_image_suffix( - ao_app->get_theme_path("placeholder")), // Placeholder path - ao_app->get_image_suffix(ao_app->get_default_theme_path( - "placeholder")), // Default placeholder path - }; - - for (QString path : pathlist) { - if (file_exists(path)) { - shout_path = path; - break; - } - } - } - - m_movie->setFileName(shout_path); - - if (m_movie->loopCount() == 0) - play_once = true; - - this->show(); - m_movie->start(); - if (m_movie->frameCount() == 0 && duration > 0) - timer->start(duration); -} - -void AOMovie::stop() -{ - m_movie->stop(); - this->hide(); -} - -void AOMovie::frame_change(int n_frame) -{ - // If it's a "static movie" (only one frame - png image), we can't change - // frames - ignore this function (use timer instead). If the frame didn't reach - // the last frame or the movie is continuous, don't stop the movie. - if (m_movie->frameCount() == 0 || n_frame < (m_movie->frameCount() - 1) || - !play_once) - return; - // we need this or else the last frame wont show - timer->start(m_movie->nextFrameDelay()); -} - -void AOMovie::timer_done() -{ - this->stop(); - // signal connected to courtroom object, let it figure out what to do - done(); -} - -void AOMovie::combo_resize(int w, int h) -{ - QSize f_size(w, h); - this->resize(f_size); - m_movie->setScaledSize(f_size); -} diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index 585a7f42..b36de486 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -96,7 +96,7 @@ void AOMusicPlayer::play(QString p_song, int channel, bool loop, BASS_ChannelLock(oldstream, false); } - if (effect_flags & FADE_OUT) { + if (effect_flags & FADE_OUT & (m_volume[channel] != 0)) { // Fade out the other sample and stop it (due to -1) BASS_ChannelSlideAttribute(oldstream, BASS_ATTRIB_VOL | BASS_SLIDE_LOG, -1, 4000); @@ -116,6 +116,7 @@ void AOMusicPlayer::play(QString p_song, int channel, bool loop, BASS_ChannelSlideAttribute(newstream, BASS_ATTRIB_VOL, static_cast<float>(m_volume[channel] / 100.0f), 1000); + } else this->set_volume(m_volume[channel], channel); @@ -149,15 +150,17 @@ void AOMusicPlayer::set_volume(int p_value, int channel) void CALLBACK loopProc(HSYNC handle, DWORD channel, DWORD data, void *user) { + UNUSED(handle); + UNUSED(data); QWORD loop_start = *(static_cast<unsigned *>(user)); BASS_ChannelLock(channel, true); BASS_ChannelSetPosition(channel, loop_start, BASS_POS_BYTE); BASS_ChannelLock(channel, false); } + void AOMusicPlayer::set_looping(bool toggle, int channel) { - qDebug() << "Setting looping for channel" << channel << "to" << toggle; m_looping = toggle; if (!m_looping) { if (BASS_ChannelFlags(m_stream_list[channel], 0, 0) & BASS_SAMPLE_LOOP) diff --git a/src/aooptionsdialog.cpp b/src/aooptionsdialog.cpp index 314e9820..0fbee478 100644 --- a/src/aooptionsdialog.cpp +++ b/src/aooptionsdialog.cpp @@ -176,6 +176,61 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_log_ic_actions_cb); + + row += 1; + ui_stay_time_lbl = new QLabel(ui_form_layout_widget); + ui_stay_time_lbl->setText(tr("Text Stay Time:")); + ui_stay_time_lbl->setToolTip(tr( + "Minimum amount of time (in miliseconds) an IC message must stay on screen before " + "the next IC message is shown, acting as a 'queue'. Set to 0 to disable this behavior.")); + + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_stay_time_lbl); + + ui_stay_time_spinbox = new QSpinBox(ui_form_layout_widget); + ui_stay_time_spinbox->setMaximum(10000); + ui_stay_time_spinbox->setValue(p_ao_app->stay_time()); + + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_stay_time_spinbox); + + row += 1; + ui_desync_logs_lbl = new QLabel(ui_form_layout_widget); + ui_desync_logs_lbl->setText(tr("Desynchronize IC Logs:")); + ui_desync_logs_lbl->setToolTip( + tr("If ticked, log will show messages as-received, while viewport will parse according to the queue (Text Stay Time).")); + + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_desync_logs_lbl); + + ui_desync_logs_cb = new QCheckBox(ui_form_layout_widget); + ui_desync_logs_cb->setChecked(p_ao_app->get_log_timestamp()); + + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_desync_logs_cb); + + row += 1; + ui_instant_objection_lbl = new QLabel(ui_form_layout_widget); + ui_instant_objection_lbl->setText(tr("Instant Objection:")); + ui_instant_objection_lbl->setToolTip( + tr("If Text Stay Time is more than 0, instant objection will skip queued messages instead of waiting to catch up.")); + + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_instant_objection_lbl); + + ui_instant_objection_cb = new QCheckBox(ui_form_layout_widget); + ui_instant_objection_cb->setChecked(ao_app->is_instant_objection_enabled()); + + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_instant_objection_cb); + + row += 1; + ui_chat_ratelimit_lbl = new QLabel(ui_form_layout_widget); + ui_chat_ratelimit_lbl->setText(tr("Chat Rate Limit:")); + ui_chat_ratelimit_lbl->setToolTip(tr( + "Minimum amount of time (in miliseconds) that must pass before the next Enter key press will send your IC message.")); + + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_chat_ratelimit_lbl); + + ui_chat_ratelimit_spinbox = new QSpinBox(ui_form_layout_widget); + ui_chat_ratelimit_spinbox->setMaximum(5000); + ui_chat_ratelimit_spinbox->setValue(p_ao_app->get_chat_ratelimit()); + + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_chat_ratelimit_spinbox); row += 1; ui_log_names_divider = new QFrame(ui_form_layout_widget); ui_log_names_divider->setFrameShape(QFrame::HLine); @@ -779,6 +834,10 @@ void AOOptionsDialog::save_pressed() configini->setValue("log_margin", ui_log_margin_spinbox->value()); configini->setValue("log_timestamp", ui_log_timestamp_cb->isChecked()); configini->setValue("log_ic_actions", ui_log_ic_actions_cb->isChecked()); + configini->setValue("desync_logs", ui_desync_logs_cb->isChecked()); + configini->setValue("stay_time", ui_stay_time_spinbox->value()); + configini->setValue("instant_objection", ui_instant_objection_cb->isChecked()); + configini->setValue("chat_ratelimit", ui_chat_ratelimit_spinbox->value()); configini->setValue("default_username", ui_username_textbox->text()); configini->setValue("show_custom_shownames", ui_showname_cb->isChecked()); configini->setValue("master", ui_ms_textbox->text()); diff --git a/src/aopacket.cpp b/src/aopacket.cpp index bb6ac73b..a40d2ef7 100644 --- a/src/aopacket.cpp +++ b/src/aopacket.cpp @@ -8,9 +8,15 @@ AOPacket::AOPacket(QString p_packet_string) m_contents = packet_contents.mid(1, packet_contents.size()-2); // trims % } -QString AOPacket::to_string() +QString AOPacket::to_string(bool encoded) { - return m_header + "#" + m_contents.join("#") + "#%"; + QStringList contents = m_contents; + if (encoded) + contents.replaceInStrings("#", "<num>") + .replaceInStrings("%", "<percent>") + .replaceInStrings("$", "<dollar>") + .replaceInStrings("&", "<and>"); + return m_header + "#" + contents.join("#") + "#%"; } void AOPacket::net_encode() diff --git a/src/aoscene.cpp b/src/aoscene.cpp deleted file mode 100644 index 78d69acd..00000000 --- a/src/aoscene.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "aoscene.h" -#include "courtroom.h" -#include "file_functions.h" - -AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) -{ - m_parent = parent; - ao_app = p_ao_app; - m_movie = new QMovie(this); - m_movie->setCacheMode(QMovie::CacheAll); - last_image = ""; -} - -void AOScene::set_image(QString p_image) -{ - QString background_path = - ao_app->get_image_suffix(ao_app->get_background_path(p_image)); - if (!file_exists(background_path)) // If image is missing, clear current image - { - this->clear(); - this->setMovie(nullptr); - - m_movie->stop(); - last_image = ""; - return; - } - - if (!file_exists(background_path) || background_path != last_image) - { - this->clear(); - this->setMovie(nullptr); - - m_movie->stop(); - m_movie->setFileName(background_path); - } - - if (m_movie->isValid() && m_movie->frameCount() > 1) { - m_movie->jumpToNextFrame(); - float scale_factor = static_cast<float>(f_h) / - static_cast<float>(m_movie->frameRect().height()); - // preserve aspect ratio - int n_w = static_cast<int>(m_movie->frameRect().width() * scale_factor); - int n_h = static_cast<int>(m_movie->frameRect().height() * scale_factor); - - m_movie->setScaledSize(QSize(n_w, n_h)); - this->resize(m_movie->scaledSize()); - if (!file_exists(background_path) || background_path != last_image) - { - this->setMovie(m_movie); - m_movie->start(); - } - QLabel::move(x + (f_w - n_w) / 2, y + (f_h - n_h) / 2); // Center - } - else { - QPixmap background(background_path); - auto transform_mode = Qt::FastTransformation; - if (background.height() > f_h) // We are downscaling, use anti-aliasing. - transform_mode = Qt::SmoothTransformation; - - background = background.scaledToHeight(f_h, transform_mode); - this->resize(background.size()); - this->setPixmap(background); - QLabel::move( - x + (f_w - background.width()) / 2, - y + (f_h - background.height()) / - 2); // Always center horizontally, always center vertically - } - last_image = background_path; -} - -void AOScene::set_legacy_desk(QString p_image) -{ - - QString desk_path = - ao_app->get_image_suffix(ao_app->get_background_path(p_image)); - if (!file_exists(desk_path)) // If image is missing, clear current image - { - this->clear(); - this->setMovie(nullptr); - - m_movie->stop(); - last_image = ""; - return; - } - - if (file_exists(desk_path) && desk_path == last_image) - return; - - QPixmap f_desk(desk_path); - - // vanilla desks vary in both width and height. in order to make that work - // with viewport rescaling, some INTENSE math is needed. - int vp_width = m_parent->width(); - int vp_height = m_parent->height(); - - double h_modifier = vp_height / 192; - - int final_h = static_cast<int>(h_modifier * f_desk.height()); - - this->clear(); - this->setMovie(nullptr); - - m_movie->stop(); - m_movie->setFileName(desk_path); - - m_movie->setScaledSize(QSize(vp_width, final_h)); - - if (m_movie->isValid() && m_movie->frameCount() > 1) { - this->setMovie(m_movie); - m_movie->start(); - } - else { - this->resize(vp_width, final_h); - this->setPixmap(f_desk.scaled(vp_width, final_h)); - } - last_image = desk_path; -} - -void AOScene::combo_resize(int w, int h) -{ - QSize f_size(w, h); - f_w = w; - f_h = h; - this->resize(f_size); -} - -void AOScene::move(int ax, int ay) -{ - x = ax; - y = ay; - QLabel::move(x, y); -} diff --git a/src/charselect.cpp b/src/charselect.cpp index 33cc5176..abed0950 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -11,6 +11,16 @@ void Courtroom::construct_char_select() ui_char_select_background = new AOImage(this, ao_app); + ui_char_list = new QTreeWidget(ui_char_select_background); + ui_char_list->setColumnCount(2); + ui_char_list->setHeaderLabels({"Name", "ID"}); + ui_char_list->setHeaderHidden(true); + ui_char_list->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + ui_char_list->hideColumn(1); + ui_char_list->setDropIndicatorShown(true); + set_size_and_pos(ui_char_list, "char_list"); + + ui_char_buttons = new QWidget(ui_char_select_background); ui_selector = new AOImage(ui_char_select_background, ao_app); @@ -46,6 +56,9 @@ void Courtroom::construct_char_select() set_size_and_pos(ui_char_buttons, "char_buttons"); + connect(ui_char_list, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), + this, SLOT(on_char_list_double_clicked(QTreeWidgetItem *, int))); + connect(ui_back_to_lobby, SIGNAL(clicked()), this, SLOT(on_back_to_lobby_clicked())); @@ -126,6 +139,21 @@ void Courtroom::set_char_select_page() put_button_in_place(current_char_page * max_chars_on_page, chars_on_page); } +void Courtroom::on_char_list_double_clicked(QTreeWidgetItem *p_item, int column) +{ + UNUSED(column); + int cid = p_item->text(1).toInt(); + if (cid == -1 && !p_item->isExpanded()) { + p_item->setExpanded(true); + return; + } + else if (cid == -1) { + p_item->setExpanded(false); + return; + } + char_clicked(cid); +} + void Courtroom::char_clicked(int n_char) { if (n_char != -1) @@ -218,7 +246,32 @@ void Courtroom::character_loading_finished() char_button->set_image(char_list.at(n).name); char_button->setToolTip(char_list.at(n).name); ui_char_button_list.append(char_button); - + QString char_category = ao_app->get_category(char_list.at(n).name); + QList<QTreeWidgetItem*> matching_list = ui_char_list->findItems(char_category, Qt::MatchFixedString, 0); + // create the character tree item + QTreeWidgetItem *treeItem = new QTreeWidgetItem(); + treeItem->setText(0, char_list.at(n).name); + treeItem->setIcon(0, QIcon(ao_app->get_static_image_suffix( + ao_app->get_character_path(char_list.at(n).name, "char_icon")))); + treeItem->setText(1, QString::number(n)); + // category logic + QTreeWidgetItem *category; + if (char_category == "") // no category + ui_char_list->addTopLevelItem(treeItem); + else if (!matching_list.isEmpty()) { // our category already exists + category = matching_list[0]; + category->addChild(treeItem); + } + else { // we need to make a new category + category = new QTreeWidgetItem(); + category->setText(0, char_category); + category->setText(1, "-1"); + category->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless); + ui_char_list->insertTopLevelItem(0, category); + category->addChild(treeItem); + } + + connect(char_button, &AOCharButton::clicked, [this, n]() { this->char_clicked(n); }); @@ -241,7 +294,7 @@ void Courtroom::character_loading_finished() .arg(QString::number(ao_app->char_list_size))); } } - + ui_char_list->expandAll(); filter_character_list(); } @@ -250,24 +303,37 @@ void Courtroom::filter_character_list() ui_char_button_list_filtered.clear(); for (int i = 0; i < char_list.size(); i++) { AOCharButton *current_char = ui_char_button_list.at(i); + QTreeWidgetItem* current_char_list_item = ui_char_list->findItems(QString::number(i), Qt::MatchExactly | Qt::MatchRecursive, 1)[0]; + + // It seems passwording characters is unimplemented yet? // Until then, this will stay here, I suppose. // if (ui_char_passworded->isChecked() && character_is_passworded??) // continue; - if (!ui_char_taken->isChecked() && char_list.at(i).taken) + if (!ui_char_taken->isChecked() && char_list.at(i).taken) { + current_char_list_item->setHidden(true); continue; + } if (!char_list.at(i).name.contains(ui_char_search->text(), - Qt::CaseInsensitive)) + Qt::CaseInsensitive)) { + current_char_list_item->setHidden(true); continue; + } // We only really need to update the fact that a character is taken // for the buttons that actually appear. // You'd also update the passwordedness and etc. here later. current_char->reset(); + current_char_list_item->setHidden(false); current_char->set_taken(char_list.at(i).taken); + current_char_list_item->setText(0, char_list.at(i).name); + // reset disabled + current_char_list_item->setDisabled(false); + if (char_list.at(i).taken) // woops, we are taken + current_char_list_item->setDisabled(true); ui_char_button_list_filtered.append(current_char); } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 3bbf82a4..4b37e6f9 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -12,13 +12,16 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() qsrand(static_cast<uint>(QDateTime::currentMSecsSinceEpoch() / 1000)); keepalive_timer = new QTimer(this); - keepalive_timer->start(60000); + keepalive_timer->start(45000); chat_tick_timer = new QTimer(this); text_delay_timer = new QTimer(this); text_delay_timer->setSingleShot(true); + text_queue_timer = new QTimer(this); + text_queue_timer->setSingleShot(true); + sfx_delay_timer = new QTimer(this); sfx_delay_timer->setSingleShot(true); @@ -40,21 +43,22 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_background = new AOImage(this, ao_app); ui_viewport = new QWidget(this); - ui_vp_background = new AOScene(ui_viewport, ao_app); - ui_vp_speedlines = new AOMovie(ui_viewport, ao_app); - ui_vp_speedlines->set_play_once(false); - ui_vp_player_char = new AOCharMovie(ui_viewport, ao_app); - ui_vp_sideplayer_char = new AOCharMovie(ui_viewport, ao_app); + ui_vp_background = new BackgroundLayer(ui_viewport, ao_app); + ui_vp_speedlines = new ForegroundLayer(ui_viewport, ao_app); + ui_vp_player_char = new CharLayer(ui_viewport, ao_app); + ui_vp_sideplayer_char = new CharLayer(ui_viewport, ao_app); ui_vp_sideplayer_char->hide(); - ui_vp_desk = new AOScene(ui_viewport, ao_app); - ui_vp_legacy_desk = new AOScene(ui_viewport, ao_app); + ui_vp_desk = new BackgroundLayer(ui_viewport, ao_app); + + ui_vp_effect = new EffectLayer(this, ao_app); + ui_vp_effect->setAttribute(Qt::WA_TransparentForMouseEvents); ui_vp_evidence_display = new AOEvidenceDisplay(ui_viewport, ao_app); ui_vp_chatbox = new AOImage(this, ao_app); ui_vp_showname = new QLabel(ui_vp_chatbox); ui_vp_showname->setAlignment(Qt::AlignLeft); - ui_vp_chat_arrow = new AOMovie(ui_vp_chatbox, ao_app); + ui_vp_chat_arrow = new InterfaceLayer(this, ao_app); ui_vp_chat_arrow->set_play_once(false); ui_vp_message = new QTextEdit(this); @@ -63,14 +67,13 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_vp_message->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_vp_message->setReadOnly(true); - ui_vp_testimony = new AOMovie(this, ao_app); + ui_vp_testimony = new InterfaceLayer(this, ao_app); ui_vp_testimony->set_play_once(false); ui_vp_testimony->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_vp_effect = new AOMovie(this, ao_app); - ui_vp_effect->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_vp_wtce = new AOMovie(this, ao_app); + ui_vp_wtce = new InterjectionLayer(this, ao_app); + ui_vp_wtce->set_play_once(true); ui_vp_wtce->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_vp_objection = new AOMovie(this, ao_app); + ui_vp_objection = new InterjectionLayer(this, ao_app); ui_vp_objection->setAttribute(Qt::WA_TransparentForMouseEvents); ui_ic_chatlog = new QTextEdit(this); @@ -107,14 +110,22 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_music_list->header()->setStretchLastSection(false); ui_music_list->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui_music_list->setContextMenuPolicy(Qt::CustomContextMenu); + ui_music_list->setUniformRowHeights(true); + - ui_music_display = new AOMovie(this, ao_app); + ui_music_display = new InterfaceLayer(this, ao_app); ui_music_display->set_play_once(false); ui_music_display->setAttribute(Qt::WA_TransparentForMouseEvents); ui_music_name = new ScrollText(ui_music_display); ui_music_name->setText(tr("None")); ui_music_name->setAttribute(Qt::WA_TransparentForMouseEvents); + + for (int i = 0; i < max_clocks; i++) { + ui_clock[i] = new AOClockLabel(this); + ui_clock[i]->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_clock[i]->hide(); + } ui_ic_chat_name = new QLineEdit(this); ui_ic_chat_name->setFrame(false); @@ -129,6 +140,10 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() // todo: filter out \n from showing up as that commonly breaks the chatlog and // can be spammed to hell + ui_vp_sticker = new StickerLayer(ui_viewport, ao_app); + ui_vp_sticker->set_play_once(false); + ui_vp_sticker->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_muted = new AOImage(ui_ic_chat_message, ao_app); ui_muted->hide(); @@ -211,7 +226,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_showname_enable->setText(tr("Shownames")); ui_immediate = new QCheckBox(this); - ui_immediate->setText(tr("No Interrupt")); + ui_immediate->setText(tr("Immediate")); ui_immediate->hide(); ui_custom_objection = new AOButton(this, ao_app); @@ -266,6 +281,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() connect(keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); + connect(ui_vp_effect, SIGNAL(done()), this, SLOT(effect_done())); + connect(ui_vp_wtce, SIGNAL(done()), this, SLOT(effect_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); connect(ui_vp_player_char, SIGNAL(shake()), this, SLOT(do_screenshake())); connect(ui_vp_player_char, SIGNAL(flash()), this, SLOT(do_flash())); @@ -274,6 +291,10 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() connect(text_delay_timer, SIGNAL(timeout()), this, SLOT(start_chat_ticking())); + + connect(text_queue_timer, SIGNAL(timeout()), this, + SLOT(chatmessage_dequeue())); + connect(sfx_delay_timer, SIGNAL(timeout()), this, SLOT(play_sfx())); connect(chat_tick_timer, SIGNAL(timeout()), this, SLOT(chat_tick())); @@ -290,6 +311,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() connect(ui_sfx_dropdown, SIGNAL(currentIndexChanged(int)), this, SLOT(on_sfx_dropdown_changed(int))); + connect(ui_sfx_dropdown, SIGNAL(editTextChanged(QString)), this, + SLOT(on_sfx_dropdown_custom(QString))); connect(ui_sfx_dropdown, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(on_sfx_context_menu_requested(QPoint))); connect(ui_sfx_remove, SIGNAL(clicked()), this, @@ -521,14 +544,6 @@ void Courtroom::set_widgets() ui_vp_desk->move(0, 0); ui_vp_desk->combo_resize(ui_viewport->width(), ui_viewport->height()); - // the size of the ui_vp_legacy_desk element relies on various factors and is - // set in set_scene() - - double y_modifier = 147.0 / 192.0; - int final_y = static_cast<int>(y_modifier * ui_viewport->height()); - ui_vp_legacy_desk->move(0, final_y); - ui_vp_legacy_desk->hide(); - ui_vp_evidence_display->move(0, 0); ui_vp_evidence_display->combo_resize(ui_viewport->width(), ui_viewport->height()); @@ -542,11 +557,14 @@ void Courtroom::set_widgets() ui_vp_chat_arrow->hide(); } else { - ui_vp_chat_arrow->move(design_ini_result.x, design_ini_result.y); - ui_vp_chat_arrow->combo_resize(design_ini_result.width, - design_ini_result.height); + 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); } + // layering shenanigans with ui_vp_chatbox prevent us from doing the sensible + // 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(ui_viewport->x(), ui_viewport->y()); ui_vp_testimony->combo_resize(ui_viewport->width(), ui_viewport->height()); @@ -621,6 +639,17 @@ void Courtroom::set_widgets() set_size_and_pos(ui_music_list, "music_list"); ui_music_list->header()->setMinimumSectionSize(ui_music_list->width()); + QString music_list_indentation = ao_app->get_font_name("music_list_indent", "courtroom_design.ini"); + if (music_list_indentation == "") + ui_music_list->resetIndentation(); + else + ui_music_list->setIndentation(music_list_indentation.toInt()); + + QString music_list_animated = ao_app->get_font_name("music_list_animated", "courtroom_design.ini"); + if (music_list_animated == "1") + ui_music_list->setAnimated(true); + else + ui_music_list->setAnimated(false); set_size_and_pos(ui_music_name, "music_name"); @@ -629,7 +658,7 @@ void Courtroom::set_widgets() ao_app->get_element_dimensions("music_display", "courtroom_design.ini"); if (design_ini_result.width < 0 || design_ini_result.height < 0) { - qDebug() << "W: could not find \"music_name\" in courtroom_design.ini"; + qDebug() << "W: could not find \"music_display\" in courtroom_design.ini"; ui_music_display->hide(); } else { @@ -637,18 +666,21 @@ void Courtroom::set_widgets() ui_music_display->combo_resize(design_ini_result.width, design_ini_result.height); } + ui_music_display->load_image("music_display", ""); - ui_music_display->play("music_display"); - ui_music_display->set_play_once(false); + + for (int i = 0; i < max_clocks; i++) { + set_size_and_pos(ui_clock[i], "clock_" + QString::number(i)); + } if (is_ao2_bg) { set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message"); - set_size_and_pos(ui_vp_chatbox, "ao2_chatbox"); + // set_size_and_pos(ui_vp_chatbox, "ao2_chatbox"); set_size_and_pos(ui_ic_chat_name, "ao2_ic_chat_name"); } else { set_size_and_pos(ui_ic_chat_message, "ic_chat_message"); - set_size_and_pos(ui_vp_chatbox, "chatbox"); + // set_size_and_pos(ui_vp_chatbox, "chatbox"); set_size_and_pos(ui_ic_chat_name, "ic_chat_name"); } @@ -671,6 +703,10 @@ void Courtroom::set_widgets() ui_vp_message->y() + ui_vp_chatbox->y()); ui_vp_message->setTextInteractionFlags(Qt::NoTextInteraction); + ui_vp_sticker->move(0, 0); + ui_vp_sticker->combo_resize(ui_viewport->width(), + ui_viewport->height()); + ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); ui_muted->set_image("muted"); ui_muted->setToolTip(tr("Oops, you're muted!")); @@ -712,7 +748,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_sfx_dropdown, "sfx_dropdown"); ui_sfx_dropdown->setEditable(true); - ui_sfx_dropdown->setInsertPolicy(QComboBox::InsertAtBottom); + ui_sfx_dropdown->setInsertPolicy(QComboBox::NoInsert); ui_sfx_dropdown->setToolTip( tr("Set a sound effect to play on your next 'Preanim'. Leaving it on " "Default will use the emote-defined sound (if any).\n" @@ -844,7 +880,8 @@ void Courtroom::set_widgets() "animation plays concurrently.")); design_ini_result = - ao_app->get_element_dimensions("immediate", "courtroom_design.ini"); + ao_app->get_element_dimensions("immediate", "courtroom_design.ini"); + // If we don't have new-style naming, fall back to the old method if (design_ini_result.width < 0 || design_ini_result.height < 0) { set_size_and_pos(ui_immediate, "pre_no_interrupt"); @@ -989,6 +1026,9 @@ void Courtroom::set_fonts(QString p_char) set_font(ui_area_list, "", "area_list", p_char); set_font(ui_music_name, "", "music_name", p_char); + for (int i = 0; i < max_clocks; i++) + set_font(ui_clock[i], "", "clock_" + QString::number(i), p_char); + set_dropdowns(); } @@ -1097,6 +1137,24 @@ void Courtroom::set_size_and_pos(QWidget *p_widget, QString p_identifier) } } +void Courtroom::set_size_and_pos(QWidget *p_widget, QString p_identifier, + QString p_char) +{ + QString filename = "courtroom_design.ini"; + + pos_size_type design_ini_result = + ao_app->get_element_dimensions(p_identifier, filename, p_char); + + if (design_ini_result.width < 0 || design_ini_result.height < 0) { + qDebug() << "W: could not find \"" << p_identifier << "\" in " << filename; + p_widget->hide(); + } + else { + p_widget->move(design_ini_result.x, design_ini_result.y); + p_widget->resize(design_ini_result.width, design_ini_result.height); + } +} + void Courtroom::set_taken(int n_char, bool p_taken) { if (n_char >= char_list.size()) { @@ -1139,13 +1197,20 @@ void Courtroom::done_received() objection_player->set_volume(0); blip_player->set_volume(0); - set_char_select_page(); + if (char_list.size() > 0) + { + set_char_select_page(); + set_char_select(); + } + else + { + update_character(m_cid); + enter_courtroom(); + } set_mute_list(); set_pair_list(); - set_char_select(); - show(); ui_spectator->show(); @@ -1189,11 +1254,11 @@ void Courtroom::set_background(QString p_background, bool display) is_ao2_bg = true; if (is_ao2_bg) { - set_size_and_pos(ui_vp_chatbox, "ao2_chatbox"); + // set_size_and_pos(ui_vp_chatbox, "ao2_chatbox"); set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message"); } else { - set_size_and_pos(ui_vp_chatbox, "chatbox"); + // set_size_and_pos(ui_vp_chatbox, "chatbox"); set_size_and_pos(ui_ic_chat_message, "ic_chat_message"); } @@ -1205,10 +1270,13 @@ void Courtroom::set_background(QString p_background, bool display) ui_vp_effect->stop(); ui_vp_message->hide(); ui_vp_chatbox->hide(); - // Stop the chat arrow from animating ui_vp_chat_arrow->stop(); + // Clear the message queue + text_queue_timer->stop(); + chatmessage_queue.clear(); + text_state = 2; anim_state = 3; ui_vp_objection->stop(); @@ -1256,8 +1324,6 @@ void Courtroom::set_pos_dropdown(QStringList pos_dropdowns) ui_pos_dropdown->addItems(pos_dropdown_list); // Unblock the signals so the element can be used for setting pos again ui_pos_dropdown->blockSignals(false); - - qDebug() << pos_dropdown_list; } void Courtroom::update_character(int p_cid) @@ -1300,7 +1366,6 @@ void Courtroom::update_character(int p_cid) set_sfx_dropdown(); set_effects_dropdown(); - qDebug() << "update_character called"; if (newchar) // Avoid infinite loop of death and suffering set_iniswap_dropdown(); @@ -1373,6 +1438,12 @@ void Courtroom::update_character(int p_cid) } } } + if (is_ao2_bg) { + set_size_and_pos(ui_vp_chatbox, "ao2_chatbox", f_char); + } + else { + set_size_and_pos(ui_vp_chatbox, "chatbox", f_char); + } if (m_cid != -1) // there is no name at char_list -1, and we crash if we try // to find one @@ -1598,7 +1669,7 @@ void Courtroom::on_chat_return_pressed() return; ui_ic_chat_message->blockSignals(true); - QTimer::singleShot(600, this, + QTimer::singleShot(ao_app->get_chat_ratelimit(), this, [=] { ui_ic_chat_message->blockSignals(false); }); // MS# // deskmod# @@ -1657,12 +1728,6 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(current_side); packet_contents.append(get_char_sfx()); - if (ui_pre->isChecked() && !ao_app->is_stickysounds_enabled() && ui_sfx_dropdown->currentIndex() > 1) { - ui_sfx_dropdown->blockSignals(true); - ui_sfx_dropdown->setCurrentIndex(0); - ui_sfx_dropdown->blockSignals(false); - ui_sfx_remove->hide(); - } int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); @@ -1814,7 +1879,8 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(ui_additive->isChecked() ? "1" : "0"); } if (ao_app->effects_enabled) { - QString fx_sound = ao_app->get_effect_sound(effect, current_char); + QString fx_sound = + ao_app->get_effect_property(effect, current_char, "sound"); QString p_effect = ao_app->read_char_ini(current_char, "effects", "Options"); packet_contents.append(effect + "|" + p_effect + "|" + fx_sound); @@ -1829,17 +1895,6 @@ void Courtroom::on_chat_return_pressed() ao_app->send_server_packet(new AOPacket("MS", packet_contents)); } -void Courtroom::reset_ic() -{ - ui_vp_chat_arrow->stop(); - text_state = 0; - anim_state = 0; - evidence_presented = false; - ui_vp_objection->stop(); - chat_tick_timer->stop(); - ui_vp_evidence_display->reset(); -} - void Courtroom::reset_ui() { ui_ic_chat_message->clear(); @@ -1849,8 +1904,6 @@ void Courtroom::reset_ui() realization_state = 0; screenshake_state = 0; is_presenting_evidence = false; - if (!ao_app->is_stickypres_enabled()) - ui_pre->setChecked(false); ui_hold_it->set_image("holdit"); ui_objection->set_image("objection"); ui_take_that->set_image("takethat"); @@ -1858,56 +1911,245 @@ void Courtroom::reset_ui() ui_realization->set_image("realization"); ui_screenshake->set_image("screenshake"); ui_evidence_present->set_image("present"); + + if (ui_pre->isChecked() && !ao_app->is_stickysounds_enabled()) { + ui_sfx_dropdown->setCurrentIndex(0); + ui_sfx_remove->hide(); + custom_sfx = ""; + } + if (!ao_app->is_stickypres_enabled()) + ui_pre->setChecked(false); } -void Courtroom::handle_chatmessage(QStringList *p_contents) +void Courtroom::chatmessage_enqueue(QStringList p_contents) { // Instead of checking for whether a message has at least chatmessage_size // amount of packages, we'll check if it has at least 15. // That was the original chatmessage_size. - if (p_contents->size() < MS_MINIMUM) + if (p_contents.size() < MS_MINIMUM) + return; + + // Check the validity of the character ID we got + int f_char_id = p_contents[CHAR_ID].toInt(); + if (f_char_id < -1 || f_char_id >= char_list.size()) + return; + + // We muted this char, gtfo + if (mute_map.value(f_char_id)) + return; + + // Reset input UI elements if the char ID matches our client's char ID (most likely, this is our message coming back to us) + if (f_char_id == m_cid) { + reset_ui(); + } + + // User-created blankpost + if (p_contents[MESSAGE].trimmed().isEmpty()) { + // Turn it into true blankpost + p_contents[MESSAGE] = ""; + } + + bool is_objection = false; + // If the user wants to clear queue on objection + if (ao_app->is_instant_objection_enabled()) + { + int objection_mod = p_contents[OBJECTION_MOD].split("&")[0].toInt(); + is_objection = objection_mod >= 1 && objection_mod <= 5; + // If this is an objection, nuke the queue + if (is_objection) + chatmessage_queue.clear(); + } + + // Record the log I/O, log files should be accurate. + // If desynced logs are on, display the log IC immediately. + LogMode log_mode = ao_app->is_desyncrhonized_logs_enabled() ? DISPLAY_AND_IO : IO_ONLY; + log_chatmessage(p_contents[MESSAGE], f_char_id, p_contents[SHOWNAME], p_contents[TEXT_COLOR].toInt(), log_mode); + // Send this boi into the queue + chatmessage_queue.enqueue(p_contents); + + // Our settings disabled queue, or no message is being parsed right now and we're not waiting on one + bool start_queue = ao_app->stay_time() <= 0 || (text_state >= 2 && !text_queue_timer->isActive()); + // Objections also immediately play the message + if (start_queue || is_objection) + chatmessage_dequeue(); // Process the message instantly + // Otherwise, since a message is being parsed, chat_tick() should be called which will call dequeue once it's done. +} + +void Courtroom::chatmessage_dequeue() +{ + // Chat stopped being processed, indicate that the user can post their message now. + QString f_custom_theme; + if (ao_app->is_customchat_enabled()) { + QString f_char = m_chatmessage[CHAR_NAME]; + f_custom_theme = ao_app->get_chat(f_char); + } + ui_vp_chat_arrow->load_image("chat_arrow", f_custom_theme); + + // Nothing to parse in the queue + if (chatmessage_queue.isEmpty()) return; - int prev_char_id = m_chatmessage[CHAR_ID].toInt(); + // Stop the text queue timer + if (text_queue_timer->isActive()) + text_queue_timer->stop(); + + unpack_chatmessage(chatmessage_queue.dequeue()); +} + +void Courtroom::unpack_chatmessage(QStringList p_contents) +{ for (int n_string = 0; n_string < MS_MAXIMUM; ++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 // supports CCCC's IC features, or if it's just japing us. Also, don't // forget! A size 15 message will have indices from 0 to 14. - if (n_string < p_contents->size() && + if (n_string < p_contents.size() && (n_string < MS_MINIMUM || ao_app->cccc_ic_support_enabled)) { - m_chatmessage[n_string] = p_contents->at(n_string); + m_chatmessage[n_string] = p_contents.at(n_string); } else { m_chatmessage[n_string] = ""; } } - int f_char_id = m_chatmessage[CHAR_ID].toInt(); - const bool is_spectator = (f_char_id == -1); + if (!ao_app->is_desyncrhonized_logs_enabled()) { + // We have logs displaying as soon as we reach the message in our queue, which is a less confusing but also less accurate experience for the user. + log_chatmessage(m_chatmessage[MESSAGE], m_chatmessage[CHAR_ID].toInt(), m_chatmessage[SHOWNAME], m_chatmessage[TEXT_COLOR].toInt(), DISPLAY_ONLY); + } - if (f_char_id < -1 || f_char_id >= char_list.size()) - return; - if (mute_map.value(m_chatmessage[CHAR_ID].toInt())) - return; + // Process the callwords for this message + handle_callwords(); - QString f_displayname; - if (!is_spectator && - (m_chatmessage[SHOWNAME].isEmpty() || !ui_showname_enable->isChecked())) { - // If the users is not a spectator and showname is disabled, use the - // character's name - f_displayname = ao_app->get_showname(char_list.at(f_char_id).name); + // Reset the interface to make room for objection handling + ui_vp_chat_arrow->stop(); + text_state = 0; + anim_state = 0; + evidence_presented = false; + ui_vp_objection->stop(); + 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. + if (!handle_objection()) + handle_ic_message(); +} + +void Courtroom::log_chatmessage(QString f_message, int f_char_id, QString f_showname, int f_color, LogMode f_log_mode) +{ + // Display name will use the showname + QString f_displayname = f_showname; + if (f_char_id != -1) { + // Grab the char.ini showname + f_showname = ao_app->get_showname(char_list.at(f_char_id).name); + // If custom serversided shownames are not enabled + if (!ui_showname_enable->isChecked()) { + // Set the display name to the char.ini showname + f_displayname = f_showname; + } } - else { - // Otherwise, use the showname - f_displayname = m_chatmessage[SHOWNAME]; + // If display name is just whitespace, use the char.ini showname. + if (f_displayname.trimmed().isEmpty()) + f_displayname = f_showname; + + if (log_ic_actions) { + // Check if a custom objection is in use + int objection_mod = 0; + QString custom_objection = ""; + if (m_chatmessage[OBJECTION_MOD].contains("4&")) { + objection_mod = 4; + custom_objection = m_chatmessage[OBJECTION_MOD].split( + "4&")[1]; // takes the name of custom objection. + } + else { + objection_mod = m_chatmessage[OBJECTION_MOD].toInt(); + } + + QString f_char = m_chatmessage[CHAR_NAME]; + QString f_custom_theme = ao_app->get_char_shouts(f_char); + if (objection_mod <= 4 && objection_mod >= 1) { + QString shout_message; + switch (objection_mod) { + case 1: + shout_message = ao_app->read_char_ini(f_char, "holdit_message", "Shouts"); + if (shout_message == "") + shout_message = tr("HOLD IT!"); + break; + case 2: + shout_message = ao_app->read_char_ini(f_char, "objection_message", "Shouts"); + if (shout_message == "") + shout_message = tr("OBJECTION!"); + break; + case 3: + shout_message = ao_app->read_char_ini(f_char, "takethat_message", "Shouts"); + if (shout_message == "") + shout_message = tr("TAKE THAT!"); + break; + // case 4 is AO2 only + case 4: + if (custom_objection != "") { + shout_message = ao_app->read_char_ini(f_char, custom_objection.split('.')[0] + "_message", "Shouts"); + if (shout_message == "") + shout_message = custom_objection.split('.')[0]; + } + else { + shout_message = ao_app->read_char_ini(f_char, "custom_message", "Shouts"); + if (shout_message == "") + shout_message = tr("CUSTOM OBJECTION!"); + } + break; + } + switch (f_log_mode) { + case IO_ONLY: + log_ic_text(f_char, f_displayname, shout_message, tr("shouts")); + break; + case DISPLAY_AND_IO: + log_ic_text(f_char, f_displayname, shout_message, tr("shouts")); + [[fallthrough]]; + case DISPLAY_ONLY: + append_ic_text(shout_message, f_displayname, tr("shouts")); + break; + } + } + + // Obtain evidence ID we're trying to work with + int f_evi_id = m_chatmessage[EVIDENCE_ID].toInt(); + // If the evidence ID is in the valid range + if (f_evi_id > 0 && f_evi_id <= local_evidence_list.size()) { + // Obtain the evidence name + QString f_evi_name = local_evidence_list.at(f_evi_id - 1).name; + switch (f_log_mode) { + case IO_ONLY: + log_ic_text(f_showname, f_displayname, f_evi_name, tr("has presented evidence")); + break; + case DISPLAY_AND_IO: + log_ic_text(f_showname, f_displayname, f_evi_name, tr("has presented evidence")); + [[fallthrough]]; + case DISPLAY_ONLY: + append_ic_text(f_evi_name, f_displayname, tr("has presented evidence")); + break; + } + } } - // If chatblank is enabled, use the character's name for logs - if (f_displayname.trimmed().isEmpty()) - f_displayname = ao_app->get_showname(char_list.at(f_char_id).name); + // If the chat message isn't a blankpost, or the chatlog history is empty, or its last message isn't a blankpost + if (!f_message.isEmpty() || + ic_chatlog_history.isEmpty() || ic_chatlog_history.last().get_message() != "") { + switch (f_log_mode) { + case IO_ONLY: + log_ic_text(f_showname, f_displayname, f_message, "",f_color); + break; + case DISPLAY_AND_IO: + log_ic_text(f_showname, f_displayname, f_message, "",f_color); + [[fallthrough]]; + case DISPLAY_ONLY: + append_ic_text(f_message, f_displayname, "",f_color); + break; + } + } +} +bool Courtroom::handle_objection() +{ // Check if a custom objection is in use int objection_mod = 0; QString custom_objection = ""; @@ -1920,103 +2162,109 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) objection_mod = m_chatmessage[OBJECTION_MOD].toInt(); } - // Reset IC display - reset_ic(); - - // Reset UI elements after client message gets sent - if (m_chatmessage[CHAR_ID].toInt() == m_cid) { - reset_ui(); + if (is_ao2_bg) { + set_size_and_pos(ui_vp_chatbox, "ao2_chatbox", m_chatmessage[CHAR_NAME]); } - - QString f_charname = ""; - if (f_char_id >= 0) - f_charname = ao_app->get_showname(char_list.at(f_char_id).name); - - if (m_chatmessage[MESSAGE].trimmed().isEmpty()) // User-created blankpost - { - m_chatmessage[MESSAGE] = ""; // Turn it into true blankpost + else { + set_size_and_pos(ui_vp_chatbox, "chatbox", m_chatmessage[CHAR_NAME]); } - - QString f_char = m_chatmessage[CHAR_NAME]; - QString f_custom_theme = ao_app->get_char_shouts(f_char); + set_size_and_pos(ui_vp_showname, "showname", m_chatmessage[CHAR_NAME]); + set_size_and_pos(ui_vp_message, "message", m_chatmessage[CHAR_NAME]); + ui_vp_message->move(ui_vp_message->x() + ui_vp_chatbox->x(), + ui_vp_message->y() + ui_vp_chatbox->y()); + ui_vp_message->setTextInteractionFlags(Qt::NoTextInteraction); // if an objection is used if (objection_mod <= 4 && objection_mod >= 1) { - QString shout_message; + ui_vp_objection->set_static_duration(shout_static_time); + ui_vp_objection->set_max_duration(shout_max_time); + QString filename; switch (objection_mod) { case 1: - ui_vp_objection->play("holdit_bubble", f_char, f_custom_theme, 724); - objection_player->play("holdit", f_char, f_custom_theme); - shout_message = ao_app->read_char_ini(f_char, "holdit_message", "Shouts"); - if (shout_message == "") - shout_message = tr("HOLD IT!"); + filename = "holdit_bubble"; + objection_player->play("holdit", m_chatmessage[CHAR_NAME], + ao_app->get_char_shouts(m_chatmessage[CHAR_NAME])); break; case 2: - ui_vp_objection->play("objection_bubble", f_char, f_custom_theme, 724); - objection_player->play("objection", f_char, f_custom_theme); - shout_message = ao_app->read_char_ini(f_char, "objection_message", "Shouts"); - if (shout_message == "") - shout_message = tr("OBJECTION!"); + filename = "objection_bubble"; + objection_player->play("objection", m_chatmessage[CHAR_NAME], + ao_app->get_char_shouts(m_chatmessage[CHAR_NAME])); if (ao_app->objection_stop_music()) music_player->stop(); break; case 3: - ui_vp_objection->play("takethat_bubble", f_char, f_custom_theme, 724); - objection_player->play("takethat", f_char, f_custom_theme); - shout_message = ao_app->read_char_ini(f_char, "takethat_message", "Shouts"); - if (shout_message == "") - shout_message = tr("TAKE THAT!"); + filename = "takethat_bubble"; + objection_player->play("takethat", m_chatmessage[CHAR_NAME], + ao_app->get_char_shouts(m_chatmessage[CHAR_NAME])); break; // case 4 is AO2 only case 4: if (custom_objection != "") { - ui_vp_objection->play("custom_objections/" + custom_objection, f_char, - f_custom_theme, shout_stay_time); - objection_player->play("custom_objections/" + - custom_objection.split('.')[0], - f_char, f_custom_theme); - shout_message = ao_app->read_char_ini(f_char, custom_objection.split('.')[0] + "_message", "Shouts"); - if (shout_message == "") - shout_message = custom_objection.split('.')[0]; + filename = "custom_objections/" + custom_objection; + objection_player->play( + "custom_objections/" + custom_objection.split('.')[0], + m_chatmessage[CHAR_NAME], + ao_app->get_char_shouts(m_chatmessage[CHAR_NAME])); } else { - ui_vp_objection->play("custom", f_char, f_custom_theme, - shout_stay_time); - objection_player->play("custom", f_char, f_custom_theme); - shout_message = ao_app->read_char_ini(f_char, "custom_message", "Shouts"); - if (shout_message == "") - shout_message = tr("CUSTOM OBJECTION!"); + filename = "custom"; + objection_player->play( + "custom", m_chatmessage[CHAR_NAME], + ao_app->get_char_shouts(m_chatmessage[CHAR_NAME])); } + break; m_chatmessage[EMOTE_MOD] = 1; - break; } - log_ic_text(f_char, f_displayname, shout_message, - tr("shouts"),2); - append_ic_text(shout_message, f_displayname, tr("shouts")); + ui_vp_objection->load_image( + filename, m_chatmessage[CHAR_NAME], + ao_app->get_char_shouts(m_chatmessage[CHAR_NAME])); sfx_player->clear(); // Objection played! Cut all sfx. + return true; } - else - handle_chatmessage_2(); - - if (!m_chatmessage[MESSAGE].isEmpty() || ic_chatlog_history.isEmpty() || - ic_chatlog_history.last().get_message() != "") { - log_ic_text(f_charname, f_displayname, m_chatmessage[MESSAGE], "", - m_chatmessage[TEXT_COLOR].toInt()); - append_ic_text(m_chatmessage[MESSAGE], f_displayname, "", - m_chatmessage[TEXT_COLOR].toInt()); - } + display_character(); + return false; } -void Courtroom::objection_done() { handle_chatmessage_2(); } +void Courtroom::effect_done() +{ + ui_vp_effect->stop(); + ui_vp_wtce->stop(); +} -void Courtroom::handle_chatmessage_2() +void Courtroom::display_character() { + // Stop all previously playing animations, effects etc. ui_vp_speedlines->stop(); ui_vp_player_char->stop(); ui_vp_effect->stop(); // Clear all looping sfx to prevent obnoxiousness sfx_player->loop_clear(); + // Hide the message and chatbox and handle the emotes + ui_vp_message->hide(); + ui_vp_chatbox->hide(); + // Hide the face sticker + ui_vp_sticker->stop(); + // Initialize the correct pos (called SIDE here for some reason) with DESK_MOD to determine if we should hide the desk or not. + switch(m_chatmessage[DESK_MOD].toInt()) { + case 4: + set_self_offset(m_chatmessage[SELF_OFFSET]); + [[fallthrough]]; + case 2: + set_scene("1", m_chatmessage[SIDE]); + break; + case 5: + ui_vp_sideplayer_char->hide(); + ui_vp_player_char->move(0, 0); + [[fallthrough]]; + case 3: + set_scene("0", m_chatmessage[SIDE]); + break; + default: + set_scene(m_chatmessage[DESK_MOD], m_chatmessage[SIDE]); + break; + } + // Arrange the netstrings of the frame SFX for the character to know about if (!m_chatmessage[FRAME_SFX].isEmpty() && ao_app->is_frame_network_enabled()) { // ORDER IS IMPORTANT!! @@ -2028,249 +2276,145 @@ void Courtroom::handle_chatmessage_2() else ui_vp_player_char->network_strings.clear(); - int f_charid = m_chatmessage[CHAR_ID].toInt(); - if (f_charid >= 0 && - (m_chatmessage[SHOWNAME].isEmpty() || !ui_showname_enable->isChecked())) { - QString real_name = char_list.at(f_charid).name; - - QString f_showname = ao_app->get_showname(real_name); - - ui_vp_showname->setText(f_showname); - } - else { - ui_vp_showname->setText(m_chatmessage[SHOWNAME]); - } - - QString customchar; - if (ao_app->is_customchat_enabled()) - customchar = m_chatmessage[CHAR_NAME]; - if (ui_vp_showname->text().trimmed().isEmpty()) // Whitespace showname - { - ui_vp_chatbox->set_image("chatblank"); - } - else // Aw yeah dude do some showname magic - { - if (!ui_vp_chatbox->set_image("chat")) - ui_vp_chatbox->set_image("chatbox"); - - QFontMetrics fm(ui_vp_showname->font()); -// Gotta support the slow paced ubuntu 18 STUCK IN 5.9.5!! -#if QT_VERSION > QT_VERSION_CHECK(5, 11, 0) - int fm_width = fm.horizontalAdvance(ui_vp_showname->text()); -#else - int fm_width = fm.boundingRect((ui_vp_showname->text())).width(); -#endif - QString chatbox_path = ao_app->get_theme_path("chat"); - QString chatbox = ao_app->get_chat(customchar); - - if (chatbox != "" && ao_app->is_customchat_enabled()) { - chatbox_path = ao_app->get_base_path() + "misc/" + chatbox + "/chat"; - if (!ui_vp_chatbox->set_chatbox(chatbox_path)) - ui_vp_chatbox->set_chatbox(chatbox_path + "box"); - } - - // This should probably be called only if any change from the last chat - // arrow was actually detected. - pos_size_type design_ini_result = ao_app->get_element_dimensions( - "chat_arrow", "courtroom_design.ini", customchar); - if (design_ini_result.width < 0 || design_ini_result.height < 0) { - qDebug() << "W: could not find \"chat_arrow\" in courtroom_design.ini"; - ui_vp_chat_arrow->hide(); - } - else { - ui_vp_chat_arrow->move(design_ini_result.x, design_ini_result.y); - ui_vp_chat_arrow->combo_resize(design_ini_result.width, - design_ini_result.height); - } + // Determine if we should flip the character or not (what servers don't support flipping at this point?) + if (ao_app->flipping_enabled && m_chatmessage[FLIP].toInt() == 1) + ui_vp_player_char->set_flipped(true); + else + ui_vp_player_char->set_flipped(false); - pos_size_type default_width = ao_app->get_element_dimensions( - "showname", "courtroom_design.ini", customchar); - int extra_width = - ao_app - ->get_design_element("showname_extra_width", "courtroom_design.ini", - customchar) - .toInt(); - QString align = ao_app - ->get_design_element("showname_align", - "courtroom_design.ini", customchar) - .toLower(); - if (align == "right") - ui_vp_showname->setAlignment(Qt::AlignRight); - else if (align == "center") - ui_vp_showname->setAlignment(Qt::AlignHCenter); - else if (align == "justify") - ui_vp_showname->setAlignment(Qt::AlignHCenter); - else - ui_vp_showname->setAlignment(Qt::AlignLeft); + // Parse the character X offset + QStringList offsets = m_chatmessage[SELF_OFFSET].split("&"); + int offset_x = offsets[0].toInt(); + // Y offset is 0 by default unless we find that the server sent us the Y position as well + int offset_y = 0; + if (offsets.length() > 1) + offset_y = offsets[1].toInt(); + // Move the character on the viewport according to the offsets + ui_vp_player_char->move(ui_viewport->width() * offset_x / 100, ui_viewport->height() * offset_y / 100); +} - if (extra_width > 0) { - if (fm_width > default_width.width && - ui_vp_chatbox->set_chatbox( - chatbox_path + - "med")) // This text be big. Let's do some shenanigans. - { - ui_vp_showname->resize(default_width.width + extra_width, - ui_vp_showname->height()); - if (fm_width > ui_vp_showname->width() && - ui_vp_chatbox->set_chatbox(chatbox_path + - "big")) // Biggest possible size for us. - { - ui_vp_showname->resize( - static_cast<int>(default_width.width + (extra_width * 2)), - ui_vp_showname->height()); - } +void Courtroom::display_pair_character(QString other_charid, QString other_offset) +{ + // If pair information exists + if (!other_charid.isEmpty()) { + // Initialize the "ok" bool check to see if the toInt conversion succeeded + bool ok; + // Grab the charid of the pair + int charid = other_charid.split("^")[0].toInt(&ok); + // If the charid is an int and is valid... + if (ok && charid > -1) { + // Show the pair character + ui_vp_sideplayer_char->show(); + // Obtain the offsets, splitting it up by & char + QStringList offsets = other_offset.split("&"); + int offset_x; + int offset_y; + // If we only got one number... + if (offsets.length() <= 1) { + // That's just the X offset. Make Y offset 0. + offset_x = other_offset.toInt(); + offset_y = 0; } + else { + // We got two numbers, set x and y offsets! + offset_x = offsets[0].toInt(); + offset_y = offsets[1].toInt(); + } + // Move pair character according to the offsets + ui_vp_sideplayer_char->move(ui_viewport->width() * offset_x / 100, + ui_viewport->height() * offset_y / 100); + + // Flip the pair character + if (ao_app->flipping_enabled && m_chatmessage[OTHER_FLIP].toInt() == 1) + ui_vp_sideplayer_char->set_flipped(true); else - ui_vp_showname->resize(default_width.width, ui_vp_showname->height()); + ui_vp_sideplayer_char->set_flipped(false); - set_font(ui_vp_showname, "", "showname", customchar); - } - else { - ui_vp_showname->resize(default_width.width, ui_vp_showname->height()); + // Play the other pair character's idle animation + QString filename = "(a)" + m_chatmessage[OTHER_EMOTE]; + ui_vp_sideplayer_char->load_image(filename, m_chatmessage[OTHER_NAME], + 0, false); + } } - } - - ui_vp_message->hide(); - ui_vp_chatbox->hide(); - - QString font_name; - QString chatfont = ao_app->get_chat_font(m_chatmessage[CHAR_NAME]); - if (chatfont != "") - font_name = chatfont; - - int f_pointsize = 0; - int chatsize = ao_app->get_chat_size(m_chatmessage[CHAR_NAME]); - if (chatsize > 0) - f_pointsize = chatsize; - set_font(ui_vp_message, "", "message", customchar, font_name, f_pointsize); +} - int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); +void Courtroom::handle_emote_mod(int emote_mod, bool p_immediate) +{ // Deal with invalid emote modifiers if (emote_mod != 0 && emote_mod != 1 && emote_mod != 2 && emote_mod != 5 && emote_mod != 6) { + // If emote mod is 4... if (emote_mod == 4) emote_mod = 6; // Addresses issue with an old bug that sent the wrong // emote modifier for zoompre else - emote_mod = 0; + emote_mod = 0; // Reset emote mod to 0 } - if (ao_app->flipping_enabled && m_chatmessage[FLIP].toInt() == 1) - ui_vp_player_char->set_flipped(true); - else - ui_vp_player_char->set_flipped(false); - - QString side = m_chatmessage[SIDE]; - - // Making the second character appear. - if (m_chatmessage[OTHER_CHARID].isEmpty()) { - // If there is no second character, hide 'em - ui_vp_sideplayer_char->stop(); - ui_vp_sideplayer_char->move(0, 0); - } - else { - bool ok; - int got_other_charid = m_chatmessage[OTHER_CHARID].split("^")[0].toInt(&ok); - if (ok) { - if (got_other_charid > -1) { - // If there is, show them! - ui_vp_sideplayer_char->show(); - QStringList other_offsets = m_chatmessage[OTHER_OFFSET].split("&"); - int other_offset; - int other_offset_v; - if (other_offsets.length() <= 1) { - other_offset = m_chatmessage[OTHER_OFFSET].toInt(); - other_offset_v = 0; - } - else { - other_offset = other_offsets[0].toInt(); - other_offset_v = other_offsets[1].toInt(); - } - ui_vp_sideplayer_char->move(ui_viewport->width() * other_offset / 100, - ui_viewport->height() * other_offset_v / - 100); - - QStringList args = m_chatmessage[OTHER_CHARID].split("^"); - if (args.size() > - 1) // This ugly workaround is so we don't make an extra packet just - // for this purpose. Rewrite pairing when? - { - // Change the order of appearance based on the pair order variable - int order = args.at(1).toInt(); - switch (order) { - case 0: - ui_vp_sideplayer_char->stackUnder(ui_vp_player_char); - break; - case 1: - ui_vp_player_char->stackUnder(ui_vp_sideplayer_char); - break; - default: - break; - } - } - - // We should probably also play the other character's idle emote. - if (ao_app->flipping_enabled && m_chatmessage[OTHER_FLIP].toInt() == 1) - ui_vp_sideplayer_char->set_flipped(true); - else - ui_vp_sideplayer_char->set_flipped(false); - ui_vp_sideplayer_char->play_idle(m_chatmessage[OTHER_NAME], - m_chatmessage[OTHER_EMOTE]); - } - else { - // If the server understands other characters, but there - // really is no second character, hide 'em, and center the first. - ui_vp_sideplayer_char->hide(); - ui_vp_sideplayer_char->move(0, 0); - } - } - } - // Set ourselves according to SELF_OFFSET - - QStringList self_offsets = m_chatmessage[SELF_OFFSET].split("&"); - int self_offset = self_offsets[0].toInt(); - int self_offset_v; - if (self_offsets.length() <= 1) - self_offset_v = 0; - else - self_offset_v = self_offsets[1].toInt(); - ui_vp_player_char->move(ui_viewport->width() * self_offset / 100, - ui_viewport->height() * self_offset_v / 100); - - switch(m_chatmessage[DESK_MOD].toInt()) { - case 4: - ui_vp_sideplayer_char->hide(); - ui_vp_player_char->move(0, 0); - [[fallthrough]]; - case 2: - set_scene("0", m_chatmessage[SIDE]); - break; - case 5: - case 3: - set_scene("1", m_chatmessage[SIDE]); - break; - default: - set_scene(m_chatmessage[DESK_MOD], m_chatmessage[SIDE]); - break; - } + // Handle the emote mod switch (emote_mod) { case 1: case 2: case 6: + // Emotes 1, 2 and 6 all play preanim that makes the chatbox wait for it to finish. play_preanim(false); break; case 0: case 5: - if (m_chatmessage[IMMEDIATE].toInt() == 0) - handle_chatmessage_3(); + // If immediate is not ticked on... + if (!p_immediate) + { + // Skip preanim. + handle_ic_speaking(); + } else + { + // Emotes 0, 5 all play preanim alongside the chatbox, not waiting for the animation to finish. play_preanim(true); + } break; default: + // This should never happen, but if it does anyway, yell in the console about it. qDebug() << "W: invalid emote mod: " << QString::number(emote_mod); } } +void Courtroom::objection_done() { handle_ic_message(); } + +void Courtroom::handle_ic_message() +{ + // 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 + int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); + if (emote_mod != 5 && emote_mod != 6) { + // 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(m_chatmessage[EMOTE_MOD].toInt(), m_chatmessage[IMMEDIATE].toInt() == 1); + + // Update the chatbox information + initialize_chatbox(); + + // if we have instant objections disabled, and queue is not empty, check if next message after this is an objection. + if (!ao_app->is_instant_objection_enabled() && chatmessage_queue.size() > 0) + { + QStringList p_contents = chatmessage_queue.head(); + int objection_mod = p_contents[OBJECTION_MOD].split("&")[0].toInt(); + bool is_objection = objection_mod >= 1 && objection_mod <= 5; + // If this is an objection, we'll need to interrupt our current message. + if (is_objection) + text_queue_timer->start(objection_threshold); + } +} + void Courtroom::do_screenshake() { if (!ao_app->is_shake_enabled()) @@ -2322,7 +2466,11 @@ void Courtroom::do_flash() QString f_char = m_chatmessage[CHAR_NAME]; QString f_custom_theme = ao_app->get_char_shouts(f_char); - ui_vp_effect->play("realizationflash", f_char, f_custom_theme, 60); + ui_vp_effect->stretch = true; + ui_vp_effect->set_static_duration(60); + ui_vp_effect->set_max_duration(60); + ui_vp_effect->load_image( + ao_app->get_effect("realization", f_char, f_custom_theme), false); } void Courtroom::do_effect(QString fx_name, QString fx_sound, QString p_char, @@ -2339,131 +2487,231 @@ void Courtroom::do_effect(QString fx_name, QString fx_sound, QString p_char, // Only check if effects are disabled after playing the sound if it exists if (!ao_app->is_effects_enabled()) return; - + ui_vp_effect->transform_mode = ao_app->get_scaling( + ao_app->get_effect_property(fx_name, p_char, "scaling")); + ui_vp_effect->stretch = + ao_app->get_effect_property(fx_name, p_char, "stretch") + .startsWith("true"); ui_vp_effect->set_play_once( false); // The effects themselves dictate whether or not they're looping. // Static effects will linger. - ui_vp_effect->play(effect); // It will set_play_once to true if the filepath - // provided is not designed to loop more than once + ui_vp_effect->set_static_duration(0); + ui_vp_effect->set_max_duration(0); + ui_vp_effect->load_image(effect, false); } void Courtroom::play_char_sfx(QString sfx_name) { sfx_player->play(sfx_name); - // sfx_player->set_looping(false); - // if (ao_app->get_looping_sfx()) - // sfx_player->set_looping( - // ao_app->get_sfx_looping(current_char, current_emote) == "1"); } -void Courtroom::handle_chatmessage_3() +void Courtroom::initialize_chatbox() { - int f_evi_id = m_chatmessage[EVIDENCE_ID].toInt(); - QString f_side = m_chatmessage[SIDE]; - - QString f_showname; - int f_char_id = m_chatmessage[CHAR_ID].toInt(); - if (f_char_id > -1 && + int f_charid = m_chatmessage[CHAR_ID].toInt(); + if (f_charid >= 0 && f_charid < char_list.size() && (m_chatmessage[SHOWNAME].isEmpty() || !ui_showname_enable->isChecked())) { - f_showname = ao_app->get_showname(char_list.at(f_char_id).name); + QString real_name = char_list.at(f_charid).name; + ui_vp_player_char->set_static_duration(0); + QString f_showname = ao_app->get_showname(real_name); + + ui_vp_showname->setText(f_showname); } else { - f_showname = m_chatmessage[SHOWNAME]; + ui_vp_showname->setText(m_chatmessage[SHOWNAME]); + } + + QString customchar; + if (ao_app->is_customchat_enabled()) + customchar = m_chatmessage[CHAR_NAME]; + if (ui_vp_showname->text().trimmed().isEmpty()) // Whitespace showname + { + ui_vp_chatbox->set_image("chatblank"); } - if (f_showname.trimmed() - .isEmpty()) // Pure whitespace showname, get outta here. - f_showname = m_chatmessage[CHAR_NAME]; + else // Aw yeah dude do some showname magic + { + if (!ui_vp_chatbox->set_image("chat")) + ui_vp_chatbox->set_image("chatbox"); + + QFontMetrics fm(ui_vp_showname->font()); +// Gotta support the slow paced ubuntu 18 STUCK IN 5.9.5!! +#if QT_VERSION > QT_VERSION_CHECK(5, 11, 0) + int fm_width = fm.horizontalAdvance(ui_vp_showname->text()); +#else + int fm_width = fm.boundingRect((ui_vp_showname->text())).width(); +#endif + QString chatbox_path = ao_app->get_theme_path("chat"); + QString chatbox = ao_app->get_chat(customchar); - if (f_evi_id > 0 && f_evi_id <= local_evidence_list.size() && - !evidence_presented) { + if (chatbox != "" && ao_app->is_customchat_enabled()) { + chatbox_path = ao_app->get_theme_path("misc/" + chatbox + "/chat"); + if (!ui_vp_chatbox->set_chatbox(chatbox_path)) { + chatbox_path = ao_app->get_base_path() + "misc/" + chatbox + "/chat"; + if (!ui_vp_chatbox->set_chatbox(chatbox_path)) + ui_vp_chatbox->set_chatbox(chatbox_path + "box"); + } + } + + // This should probably be called only if any change from the last chat + // arrow was actually detected. + if (current_misc != last_misc) { + pos_size_type design_ini_result = ao_app->get_element_dimensions( + "chat_arrow", "courtroom_design.ini", customchar); + if (design_ini_result.width < 0 || design_ini_result.height < 0) { + qDebug() << "W: could not find \"chat_arrow\" in courtroom_design.ini"; + ui_vp_chat_arrow->hide(); + } + 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); + } + } + + pos_size_type default_width = ao_app->get_element_dimensions( + "showname", "courtroom_design.ini", customchar); + int extra_width = + ao_app + ->get_design_element("showname_extra_width", "courtroom_design.ini", + customchar) + .toInt(); + QString align = ao_app + ->get_design_element("showname_align", + "courtroom_design.ini", customchar) + .toLower(); + if (align == "right") + ui_vp_showname->setAlignment(Qt::AlignRight); + else if (align == "center") + ui_vp_showname->setAlignment(Qt::AlignHCenter); + else if (align == "justify") + ui_vp_showname->setAlignment(Qt::AlignHCenter); + else + ui_vp_showname->setAlignment(Qt::AlignLeft); + + if (extra_width > 0) { + if (fm_width > default_width.width && + ui_vp_chatbox->set_chatbox( + chatbox_path + + "med")) // This text be big. Let's do some shenanigans. + { + ui_vp_showname->resize(default_width.width + extra_width, + ui_vp_showname->height()); + if (fm_width > ui_vp_showname->width() && + ui_vp_chatbox->set_chatbox(chatbox_path + + "big")) // Biggest possible size for us. + { + ui_vp_showname->resize( + static_cast<int>(default_width.width + (extra_width * 2)), + ui_vp_showname->height()); + } + } + else + ui_vp_showname->resize(default_width.width, ui_vp_showname->height()); + + set_font(ui_vp_showname, "", "showname", customchar); + } + else { + ui_vp_showname->resize(default_width.width, ui_vp_showname->height()); + } + } + + QString font_name; + QString chatfont = ao_app->get_chat_font(m_chatmessage[CHAR_NAME]); + if (chatfont != "") + font_name = chatfont; + + int f_pointsize = 0; + int chatsize = ao_app->get_chat_size(m_chatmessage[CHAR_NAME]); + if (chatsize > 0) + f_pointsize = chatsize; + set_font(ui_vp_message, "", "message", customchar, font_name, f_pointsize); +} + +void Courtroom::handle_callwords() +{ + // Quickly check through the message for the word_call (callwords) sfx + QString f_message = m_chatmessage[MESSAGE]; + // Obtain the current call words (Really? It does File I/O on every single message???) + QStringList call_words = ao_app->get_call_words(); + // Loop through each word in the call words list + for (QString word : call_words) { + // If our message contains that specific call word + if (f_message.contains(word, Qt::CaseInsensitive)) { + // Play the call word sfx on the modcall_player sound container + modcall_player->play(ao_app->get_sfx("word_call")); + // Make the window flash + ao_app->alert(this); + // Break the loop so we don't spam sound effects + break; + } + } +} + +void Courtroom::display_evidence_image() +{ + QString side = m_chatmessage[SIDE]; + int f_evi_id = m_chatmessage[EVIDENCE_ID].toInt(); + if (f_evi_id > 0 && f_evi_id <= local_evidence_list.size()) { // shifted by 1 because 0 is no evidence per legacy standards QString f_image = local_evidence_list.at(f_evi_id - 1).image; QString f_evi_name = local_evidence_list.at(f_evi_id - 1).name; // def jud and hlp should display the evidence icon on the RIGHT side - bool is_left_side = !(f_side == "def" || f_side == "hlp" || - f_side == "jud" || f_side == "jur"); + bool is_left_side = !(side == "def" || side == "hlp" || + side == "jud" || side == "jur"); ui_vp_evidence_display->show_evidence(f_image, is_left_side, ui_sfx_slider->value()); - if (log_ic_actions) { - log_ic_text(m_chatmessage[CHAR_NAME], m_chatmessage[SHOWNAME], f_evi_name, - tr("has presented evidence"), - m_chatmessage[TEXT_COLOR].toInt()); - append_ic_text(f_evi_name, f_showname, tr("has presented evidence")); - } - evidence_presented = true; // we're done presenting evidence, and we - // don't want to do it twice } +} - int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); - +void Courtroom::handle_ic_speaking() +{ + // Display the evidence + display_evidence_image(); QString side = m_chatmessage[SIDE]; - - switch(m_chatmessage[DESK_MOD].toInt()) { - case 4: - set_self_offset(m_chatmessage[SELF_OFFSET]); - [[fallthrough]]; - case 2: - set_scene("1", m_chatmessage[SIDE]); - break; - case 5: - ui_vp_sideplayer_char->hide(); - ui_vp_player_char->move(0, 0); - [[fallthrough]]; - case 3: - set_scene("0", m_chatmessage[SIDE]); - break; - default: - set_scene(m_chatmessage[DESK_MOD], m_chatmessage[SIDE]); - break; - } + int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); + // emote_mod 5 is zoom and emote_mod 6 is zoom w/ preanim. if (emote_mod == 5 || emote_mod == 6) { + // Hide the desks ui_vp_desk->hide(); - ui_vp_legacy_desk->hide(); - - // Since we're zooming, hide the second character, and centre the first. - ui_vp_sideplayer_char->hide(); - ui_vp_player_char->move(0, 0); - QString f_char = m_chatmessage[CHAR_NAME]; - QString f_custom_theme = ao_app->get_char_shouts(f_char); + // Obtain character information for our character + QString filename; + // I still hate this hardcoding. If we're on pos pro, hlp and wit, use prosecution_speedlines. Otherwise, defense_speedlines. if (side == "pro" || side == "hlp" || side == "wit") - ui_vp_speedlines->play("prosecution_speedlines", f_char, f_custom_theme); + filename = "prosecution_speedlines"; else - ui_vp_speedlines->play("defense_speedlines", f_char, f_custom_theme); + filename = "defense_speedlines"; + ui_vp_speedlines->load_image(filename, m_chatmessage[CHAR_NAME]); } - // If this color is talking + // Check if this is a talking color (white text, etc.) color_is_talking = color_markdown_talking_list.at(m_chatmessage[TEXT_COLOR].toInt()); - + QString filename; + // If color is talking, and our state isn't already talking if (color_is_talking && text_state == 1 && - anim_state < 2) // Set it to talking as we're not on that already + anim_state < 2) { + // Stop the previous animation and play the talking animation ui_vp_player_char->stop(); - ui_vp_player_char->play_talking(m_chatmessage[CHAR_NAME], - m_chatmessage[EMOTE]); + 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 anim_state = 2; } - else if (anim_state < 3) // Set it to idle as we're not on that already + 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->play_idle(m_chatmessage[CHAR_NAME], - m_chatmessage[EMOTE]); + 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 anim_state = 3; } - QString f_message = m_chatmessage[MESSAGE]; - QStringList call_words = ao_app->get_call_words(); - - for (QString word : call_words) { - if (f_message.contains(word, Qt::CaseInsensitive)) { - modcall_player->play(ao_app->get_sfx("word_call")); - ao_app->alert(this); - - break; - } - } - + // Begin parsing through the chatbox message start_chat_ticking(); } @@ -2715,7 +2963,7 @@ void Courtroom::log_ic_text(QString p_name, QString p_showname, { chatlogpiece log_entry(p_name, p_showname, p_message, p_action, p_color); ic_chatlog_history.append(log_entry); - if (ao_app->get_auto_logging_enabled()) + if (ao_app->get_auto_logging_enabled() && !ao_app->log_filename.isEmpty()) ao_app->append_to_file(log_entry.get_full(), ao_app->log_filename, true); while (ic_chatlog_history.size() > log_maximum_blocks && @@ -2769,8 +3017,16 @@ void Courtroom::append_ic_text(QString p_text, QString p_name, QString p_action, // Make shout text bold else if (p_action == tr("shouts") && log_ic_actions) { ui_ic_chatlog->textCursor().insertText(" " + p_action + " ", normal); - if (log_colors) - ui_ic_chatlog->textCursor().insertHtml("<b>" + filter_ic_text(p_text, true, -1, 0) + "</b>"); + if (log_colors) { + ui_ic_chatlog->textCursor().insertHtml( + "<b>" + + filter_ic_text(p_text, true, -1, 0) + .replace( + "$c0", + ao_app->get_color("ic_chatlog_color", "courtroom_fonts.ini") + .name(QColor::HexRgb)) + + "</b>"); + } else ui_ic_chatlog->textCursor().insertText(" " + p_text, italics); } @@ -2849,11 +3105,10 @@ void Courtroom::play_preanim(bool immediate) { QString f_char = m_chatmessage[CHAR_NAME]; QString f_preanim = m_chatmessage[PRE_EMOTE]; - // all time values in char.inis are multiplied by a constant(time_mod) to get // the actual time int ao2_duration = ao_app->get_ao2_preanim_duration(f_char, f_preanim); - int text_delay = ao_app->get_text_delay(f_char, f_preanim) * time_mod; + int stay_time = ao_app->get_text_delay(f_char, f_preanim) * time_mod; int sfx_delay = m_chatmessage[SFX_DELAY].toInt() * time_mod; int preanim_duration; @@ -2872,28 +3127,30 @@ void Courtroom::play_preanim(bool immediate) else anim_state = 1; preanim_done(); - qDebug() << "could not find " + anim_to_find; + qDebug() << "W: could not find " + anim_to_find; return; } - - ui_vp_player_char->play_pre(f_char, f_preanim, preanim_duration); + 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); if (immediate) anim_state = 4; else anim_state = 1; - if (text_delay >= 0) - text_delay_timer->start(text_delay); + if (stay_time >= 0) + text_delay_timer->start(stay_time); if (immediate) - handle_chatmessage_3(); + handle_ic_speaking(); } void Courtroom::preanim_done() { anim_state = 1; - handle_chatmessage_3(); + qDebug() << "preanim over, anim_state set to 1"; + handle_ic_speaking(); } void Courtroom::start_chat_ticking() @@ -2941,6 +3198,8 @@ void Courtroom::start_chat_ticking() ui_vp_chatbox->show(); ui_vp_message->show(); + ui_vp_sticker->load_image(m_chatmessage[CHAR_NAME]); + if (m_chatmessage[ADDITIVE] != "1") { ui_vp_message->clear(); real_tick_pos = 0; @@ -2964,6 +3223,8 @@ void Courtroom::start_chat_ticking() // means text is currently ticking text_state = 1; + + c_played = false; } void Courtroom::chat_tick() @@ -2975,13 +3236,30 @@ 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()) { text_state = 2; if (anim_state < 3) { - anim_state = 3; - ui_vp_player_char->play_idle(m_chatmessage[CHAR_NAME], - m_chatmessage[EMOTE]); + 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)) { + anim_state = 5; + ui_vp_player_char->set_play_once(true); + filename = "(c)" + m_chatmessage[EMOTE]; + c_played = true; + } + else { + anim_state = 3; + 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); } QString f_char; QString f_custom_theme; @@ -2989,15 +3267,33 @@ void Courtroom::chat_tick() f_char = m_chatmessage[CHAR_NAME]; f_custom_theme = ao_app->get_chat(f_char); } - ui_vp_chat_arrow->play( - "chat_arrow", f_char, - 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()); + ui_vp_chat_arrow->load_image("chat_arrow",f_custom_theme); // Chat stopped being processed, indicate that. + additive_previous = + additive_previous + + filter_ic_text(f_message, true, -1, m_chatmessage[TEXT_COLOR].toInt()); + QString f_message_filtered = filter_ic_text(f_message, true, -1, m_chatmessage[TEXT_COLOR].toInt()); for (int c = 0; c < max_colors; ++c) { f_message_filtered = f_message_filtered.replace("$c" + QString::number(c), char_color_rgb_list.at(c).name(QColor::HexRgb)); } additive_previous = additive_previous + f_message_filtered; real_tick_pos = ui_vp_message->toPlainText().size(); + + + // If we're not already waiting on the next message, start the timer. We could be overriden if there's an objection planned. + int delay = ao_app->stay_time(); + if (delay > 0 && !text_queue_timer->isActive()) + text_queue_timer->start(delay); + + // if we have instant objections disabled, and queue is not empty, check if next message after this is an objection. + if (!ao_app->is_instant_objection_enabled() && chatmessage_queue.size() > 0) + { + QStringList p_contents = chatmessage_queue.head(); + int objection_mod = p_contents[OBJECTION_MOD].split("&")[0].toInt(); + bool is_objection = objection_mod >= 1 && objection_mod <= 5; + // If this is an objection, we'll need to interrupt our current message. + if (is_objection) + chatmessage_dequeue(); + } return; } @@ -3171,16 +3467,20 @@ void Courtroom::chat_tick() // to avoid interrupting a non-interrupted preanim) { ui_vp_player_char->stop(); - ui_vp_player_char->play_talking(m_chatmessage[CHAR_NAME], - m_chatmessage[EMOTE]); + 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; } 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->play_idle(m_chatmessage[CHAR_NAME], - m_chatmessage[EMOTE]); + 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; } // Continue ticking @@ -3265,19 +3565,15 @@ void Courtroom::set_scene(QString f_desk_mod, QString f_side) f_background = f_side; f_desk_image = f_side + "_overlay"; } - - ui_vp_background->set_image(f_background); - ui_vp_desk->set_image(f_desk_image); - ui_vp_legacy_desk->set_legacy_desk(f_desk_image); + ui_vp_background->load_image(f_background); + ui_vp_desk->load_image(f_desk_image); if (f_desk_mod == "0" || (f_desk_mod != "1" && (f_side == "jud" || f_side == "hld" || f_side == "hlp"))) { ui_vp_desk->hide(); - ui_vp_legacy_desk->hide(); } else { - ui_vp_legacy_desk->hide(); ui_vp_desk->show(); } } @@ -3402,13 +3698,11 @@ void Courtroom::handle_song(QStringList *p_contents) if (!mute_map.value(n_char)) { if (f_song == "~stop.mp3") { - log_ic_text(str_char, str_show, "", tr("has stopped the music"), - m_chatmessage[TEXT_COLOR].toInt()); + log_ic_text(str_char, str_show, "", tr("has stopped the music")); append_ic_text("", str_show, tr("has stopped the music")); } else { - log_ic_text(str_char, str_show, f_song, tr("has played a song"), - m_chatmessage[TEXT_COLOR].toInt()); + log_ic_text(str_char, str_show, f_song, tr("has played a song")); append_ic_text(f_song_clear, str_show, tr("has played a song")); } music_player->play(f_song, channel, looping, effect_flags); @@ -3427,31 +3721,40 @@ void Courtroom::handle_song(QStringList *p_contents) void Courtroom::handle_wtce(QString p_wtce, int variant) { QString sfx_file = "courtroom_sounds.ini"; - + QString sfx_name; + QString filename; + ui_vp_wtce->set_static_duration(wtce_static_time); + ui_vp_wtce->set_max_duration(wtce_max_time); // witness testimony if (p_wtce == "testimony1") { - sfx_player->play(ao_app->get_sfx("witness_testimony")); - ui_vp_wtce->play("witnesstestimony", "", "", 1500); - ui_vp_testimony->play("testimony"); + sfx_name = "witness_testimony"; + filename = "witnesstestimony"; + ui_vp_testimony->load_image("testimony", ""); } // cross examination else if (p_wtce == "testimony2") { - sfx_player->play(ao_app->get_sfx("cross_examination")); - ui_vp_wtce->play("crossexamination", "", "", 1500); + sfx_name = "cross_examination"; + filename = "crossexamination"; ui_vp_testimony->stop(); } else if (p_wtce == "judgeruling") { + ui_vp_wtce->set_static_duration(verdict_static_time); + ui_vp_wtce->set_max_duration(verdict_max_time); if (variant == 0) { - sfx_player->play(ao_app->get_sfx("not_guilty")); - ui_vp_wtce->play("notguilty", "", "", 3000); + sfx_name = "not_guilty"; + filename = "notguilty"; ui_vp_testimony->stop(); } else if (variant == 1) { - sfx_player->play(ao_app->get_sfx("guilty")); - ui_vp_wtce->play("guilty", "", "", 3000); + sfx_name = "guilty"; + filename = "guilty"; ui_vp_testimony->stop(); } } + QString bg_misc = ao_app->read_design_ini("misc", ao_app->get_background_path("design.ini")); + sfx_player->play(ao_app->get_sfx(sfx_name, bg_misc)); + ui_vp_wtce->load_image(filename, "", bg_misc); + ui_vp_wtce->set_play_once(true); } void Courtroom::set_hp_bar(int p_bar, int p_state) @@ -3522,9 +3825,6 @@ void Courtroom::on_ooc_return_pressed() { QString ooc_message = ui_ooc_chat_message->text(); - if (ooc_message == "" || ui_ooc_chat_name->text() == "") - return; - if (ooc_message.startsWith("/pos")) { if (ooc_message == "/pos jud") { toggle_judge_buttons(true); @@ -4049,28 +4349,28 @@ void Courtroom::set_sfx_dropdown() ui_sfx_remove->hide(); return; } - QStringList soundlist = ao_app->get_list_file( + // Initialzie character sound list first. Will be empty if not found. + sound_list = ao_app->get_list_file( ao_app->get_character_path(current_char, "soundlist.ini")); - if (soundlist.size() <= 0) { - soundlist = ao_app->get_list_file( - ao_app->get_theme_path("character_soundlist.ini")); - if (soundlist.size() <= 0) { - soundlist = ao_app->get_list_file( - ao_app->get_default_theme_path("character_soundlist.ini")); - } - } + // Append default sound list after the character sound list. + sound_list += ao_app->get_list_file( + ao_app->get_base_path() + "soundlist.ini"); - if (soundlist.size() <= 0) { - ui_sfx_dropdown->hide(); - ui_sfx_remove->hide(); - return; + QStringList display_sounds; + for (QString sound : sound_list) { + QStringList unpacked = sound.split("="); + QString display = unpacked[0].trimmed(); + if (unpacked.size() > 1) + display = unpacked[1].trimmed(); + + display_sounds.append(display); } - soundlist.prepend("Nothing"); - soundlist.prepend("Default"); + display_sounds.prepend("Nothing"); + display_sounds.prepend("Default"); ui_sfx_dropdown->show(); - ui_sfx_dropdown->addItems(soundlist); + ui_sfx_dropdown->addItems(display_sounds); ui_sfx_dropdown->setCurrentIndex(0); ui_sfx_remove->hide(); ui_sfx_dropdown->blockSignals(false); @@ -4078,37 +4378,16 @@ void Courtroom::set_sfx_dropdown() void Courtroom::on_sfx_dropdown_changed(int p_index) { + UNUSED(p_index); ui_ic_chat_message->setFocus(); + ui_sfx_remove->hide(); + custom_sfx = ""; +} - QStringList soundlist; - for (int i = 2; i < ui_sfx_dropdown->count(); ++i) { - QString entry = ui_sfx_dropdown->itemText(i); - if (!soundlist.contains(entry)) - soundlist.append(entry); - } - - QStringList defaultlist = - ao_app->get_list_file(ao_app->get_theme_path("character_soundlist.ini")); - if (defaultlist.size() <= 0) { - defaultlist = ao_app->get_list_file( - ao_app->get_default_theme_path("character_soundlist.ini")); - } - - if (defaultlist.size() > 0 && - defaultlist.toSet().subtract(soundlist.toSet()).size() > - 0) // There's a difference from the default configuration - ao_app->write_to_file( - soundlist.join("\n"), - ao_app->get_character_path(current_char, - "soundlist.ini")); // Create a new sound list - - ui_sfx_dropdown->blockSignals(true); - ui_sfx_dropdown->setCurrentIndex(p_index); - ui_sfx_dropdown->blockSignals(false); - if (p_index > 1) - ui_sfx_remove->show(); - else - ui_sfx_remove->hide(); +void Courtroom::on_sfx_dropdown_custom(QString p_sfx) +{ + ui_sfx_remove->show(); + custom_sfx = p_sfx; } void Courtroom::on_sfx_context_menu_requested(const QPoint &pos) @@ -4121,40 +4400,27 @@ void Courtroom::on_sfx_context_menu_requested(const QPoint &pos) menu->addAction(QString("Edit " + current_char + "/soundlist.ini"), this, SLOT(on_sfx_edit_requested())); else - menu->addAction(QString("Edit theme's character_soundlist.ini"), this, + menu->addAction(QString("Edit global soundlist.ini"), this, SLOT(on_sfx_edit_requested())); - if (ui_sfx_dropdown->currentIndex() > 1) - menu->addAction(QString("Remove " + ui_sfx_dropdown->itemText( - ui_sfx_dropdown->currentIndex())), - this, SLOT(on_sfx_remove_clicked())); + if (!custom_sfx.isEmpty()) + menu->addAction(QString("Clear Edit Text"), this, SLOT(on_sfx_remove_clicked())); menu->popup(ui_sfx_dropdown->mapToGlobal(pos)); } + void Courtroom::on_sfx_edit_requested() { QString p_path = ao_app->get_character_path(current_char, "soundlist.ini"); if (!file_exists(p_path)) { - p_path = ao_app->get_theme_path("character_soundlist.ini"); - if (!file_exists(p_path)) { - p_path = ao_app->get_default_theme_path("character_soundlist.ini"); - if (!file_exists(p_path)) { - return; - } + p_path = ao_app->get_base_path() + "soundlist.ini"; } - } QDesktopServices::openUrl(QUrl::fromLocalFile(p_path)); } void Courtroom::on_sfx_remove_clicked() { - if (ui_sfx_dropdown->count() <= 0) { - ui_sfx_remove->hide(); // We're not supposed to see it. Do this or the - // client will crash - return; - } - if (ui_sfx_dropdown->currentIndex() > 1) { - ui_sfx_dropdown->removeItem(ui_sfx_dropdown->currentIndex()); - on_sfx_dropdown_changed(0); // Reset back to original - } + ui_sfx_remove->hide(); + ui_sfx_dropdown->setCurrentIndex(0); + custom_sfx = ""; } void Courtroom::set_effects_dropdown() @@ -4257,19 +4523,21 @@ bool Courtroom::effects_dropdown_find_and_set(QString effect) QString Courtroom::get_char_sfx() { - QString sfx = ui_sfx_dropdown->itemText(ui_sfx_dropdown->currentIndex()); - if (sfx == "Nothing") - return "1"; - if (sfx != "" && sfx != "Default") - return sfx; - return ao_app->get_sfx_name(current_char, current_emote); + if (!custom_sfx.isEmpty()) + return custom_sfx; + int index = ui_sfx_dropdown->currentIndex(); + if (index == 0) // Default + return ao_app->get_sfx_name(current_char, current_emote); + if (index == 1) // Nothing + return "1"; + QString sfx = sound_list[index-2].split("=")[0].trimmed(); + if (sfx == "") + return "1"; + return sfx; } int Courtroom::get_char_sfx_delay() { - // QString sfx = ui_sfx_dropdown->itemText(ui_sfx_dropdown->currentIndex()); - // if (sfx != "" && sfx != "Default") - // return 0; //todo: a way to define this return ao_app->get_sfx_delay(current_char, current_emote); } @@ -4472,12 +4740,12 @@ void Courtroom::music_stop() void Courtroom::on_area_list_double_clicked(QTreeWidgetItem *p_item, int column) { column = 0; // The metadata + UNUSED(column); // so gcc shuts up QString p_area = p_item->text(0); QStringList packet_contents; packet_contents.append(p_area); packet_contents.append(QString::number(m_cid)); - qDebug() << packet_contents; ao_app->send_server_packet(new AOPacket("MC", packet_contents), false); } @@ -4709,18 +4977,18 @@ void Courtroom::set_text_color_dropdown() QColor color = ao_app->get_chat_color("c" + QString::number(c), current_char); color_rgb_list.append(color); - color_markdown_start_list.append(ao_app->get_chat_markdown( + color_markdown_start_list.append(ao_app->get_chat_markup( "c" + QString::number(c) + "_start", current_char)); - color_markdown_end_list.append(ao_app->get_chat_markdown( + color_markdown_end_list.append(ao_app->get_chat_markup( "c" + QString::number(c) + "_end", current_char)); color_markdown_remove_list.append( - ao_app->get_chat_markdown("c" + QString::number(c) + "_remove", - current_char) == "1"); + ao_app->get_chat_markup("c" + QString::number(c) + "_remove", + current_char) == "1"); color_markdown_talking_list.append( - ao_app->get_chat_markdown("c" + QString::number(c) + "_talking", - current_char) != "0"); + ao_app->get_chat_markup("c" + QString::number(c) + "_talking", + current_char) != "0"); - QString color_name = ao_app->get_chat_markdown( + QString color_name = ao_app->get_chat_markup( "c" + QString::number(c) + "_name", current_char); if (color_name.isEmpty()) // Not defined { @@ -4971,12 +5239,16 @@ void Courtroom::on_showname_enable_clicked() void Courtroom::regenerate_ic_chatlog() { ui_ic_chatlog->clear(); + last_ic_message = ""; foreach (chatlogpiece item, ic_chatlog_history) { - append_ic_text(item.get_message(), - ui_showname_enable->isChecked() ? item.get_showname() - : item.get_name(), + QString message = item.get_message(); + QString name = ui_showname_enable->isChecked() ? item.get_showname() + : item.get_name(); + append_ic_text(message, + name, item.get_action(), item.get_chat_color(), item.get_datetime().toLocalTime()); + last_ic_message = name + ":" + message; } } @@ -5007,10 +5279,21 @@ void Courtroom::on_switch_area_music_clicked() void Courtroom::ping_server() { + ping_timer.start(); + is_pinging = true; ao_app->send_server_packet( new AOPacket("CH#" + QString::number(m_cid) + "#%")); } +qint64 Courtroom::pong() +{ + if (!is_pinging) + return -1; + + is_pinging = false; + return ping_timer.elapsed(); +} + void Courtroom::on_casing_clicked() { if (ao_app->casing_alerts_enabled) { @@ -5050,6 +5333,54 @@ void Courtroom::announce_case(QString title, bool def, bool pro, bool jud, } } +void Courtroom::start_clock(int id) +{ + if (id >= 0 && id < max_clocks && ui_clock[id] != nullptr) + { + ui_clock[id]->start(); + } +} + +void Courtroom::start_clock(int id, qint64 msecs) +{ + if (id >= 0 && id < max_clocks && ui_clock[id] != nullptr) + { + ui_clock[id]->start(static_cast<int>(msecs)); + } +} + +void Courtroom::set_clock(int id, qint64 msecs) +{ + if (id >= 0 && id < max_clocks && ui_clock[id] != nullptr) + { + ui_clock[id]->set(static_cast<int>(msecs), true); + } +} + +void Courtroom::pause_clock(int id) +{ + if (id >= 0 && id < max_clocks && ui_clock[id] != nullptr) + { + ui_clock[id]->pause(); + } +} + +void Courtroom::stop_clock(int id) +{ + if (id >= 0 && id < max_clocks && ui_clock[id] != nullptr) + { + ui_clock[id]->stop(); + } +} + +void Courtroom::set_clock_visibility(int id, bool visible) +{ + if (id >= 0 && id < max_clocks && ui_clock[id] != nullptr) + { + ui_clock[id]->setVisible(visible); + } +} + void Courtroom::truncate_label_text(QWidget *p_widget, QString p_identifier) { QString filename = "courtroom_design.ini"; diff --git a/src/demoserver.cpp b/src/demoserver.cpp new file mode 100644 index 00000000..fcb5003d --- /dev/null +++ b/src/demoserver.cpp @@ -0,0 +1,304 @@ +#include "demoserver.h" +#include "lobby.h" + +DemoServer::DemoServer(QObject *parent) : QObject(parent) +{ + timer = new QTimer(this); + timer->setTimerType(Qt::PreciseTimer); + timer->setSingleShot(true); + + tcp_server = new QTcpServer(this); + connect(tcp_server, &QTcpServer::newConnection, this, &DemoServer::accept_connection); + connect(timer, &QTimer::timeout, this, &DemoServer::playback); +} + +void DemoServer::start_server() +{ + if (server_started) return; + if (!tcp_server->listen(QHostAddress::LocalHost, 0)) { + qCritical() << "Could not start demo playback server..."; + qDebug() << tcp_server->errorString(); + return; + } + this->port = tcp_server->serverPort(); + qDebug() << "Server started"; + server_started = true; +} + +void DemoServer::destroy_connection() +{ + QTcpSocket* temp_socket = tcp_server->nextPendingConnection(); + connect(temp_socket, &QAbstractSocket::disconnected, temp_socket, &QObject::deleteLater); + temp_socket->disconnectFromHost(); + return; +} + +void DemoServer::accept_connection() +{ + QString path = QFileDialog::getOpenFileName(nullptr, tr("Load Demo"), "logs/", tr("Demo Files (*.demo)")); + if (path.isEmpty()) + { + destroy_connection(); + return; + } + load_demo(path); + + if (demo_data.isEmpty()) + { + destroy_connection(); + return; + } + + if (demo_data.head().startsWith("SC#")) + { + sc_packet = demo_data.dequeue(); + AOPacket sc(sc_packet); + num_chars = sc.get_contents().length(); + } + else + { + sc_packet = "SC#%"; + num_chars = 0; + } + + if (client_sock) { + // Client is already connected... + qDebug() << "Multiple connections to demo server disallowed."; + QTcpSocket* temp_socket = tcp_server->nextPendingConnection(); + connect(temp_socket, &QAbstractSocket::disconnected, temp_socket, &QObject::deleteLater); + temp_socket->disconnectFromHost(); + return; + } + client_sock = tcp_server->nextPendingConnection(); + connect(client_sock, &QAbstractSocket::disconnected, this, &DemoServer::client_disconnect); + connect(client_sock, &QAbstractSocket::readyRead, this, &DemoServer::recv_data); + client_sock->write("decryptor#NOENCRYPT#%"); +} + +void DemoServer::recv_data() +{ + QString in_data = QString::fromUtf8(client_sock->readAll()); + + // Copypasted from NetworkManager + if (!in_data.endsWith("%")) { + partial_packet = true; + temp_packet += in_data; + return; + } + + else { + if (partial_packet) { + in_data = temp_packet + in_data; + temp_packet = ""; + partial_packet = false; + } + } + + QStringList packet_list = + in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); + + for (QString packet : packet_list) { + AOPacket ao_packet(packet); + handle_packet(ao_packet); + } +} + +void DemoServer::handle_packet(AOPacket packet) +{ + packet.net_decode(); + + // This code is literally a barebones AO server + // It is wise to do it this way, because I can + // avoid touching any of this disgusting shit + // related to hardcoding this stuff in. + + // Also, at some point, I will make akashit + // into a shared library. + + QString header = packet.get_header(); + QStringList contents = packet.get_contents(); + + if (header == "HI") { + client_sock->write("ID#0#DEMOINTERNAL#0#%"); + } + else if (header == "ID") { + QStringList feature_list = { + "noencryption", "yellowtext", "prezoom", + "flipping", "customobjections", "fastloading", + "deskmod", "evidence", "cccc_ic_support", + "arup", "casing_alerts", "modcall_reason", + "looping_sfx", "additive", "effects", + "y_offset", "expanded_desk_mods"}; + client_sock->write("PN#0#1#%"); + client_sock->write("FL#"); + client_sock->write(feature_list.join('#').toUtf8()); + client_sock->write("#%"); + } + else if (header == "askchaa") { + client_sock->write("SI#"); + client_sock->write(QString::number(num_chars).toUtf8()); + client_sock->write("#0#1#%"); + } + else if (header == "RC") { + client_sock->write(sc_packet.toUtf8()); + } + else if (header == "RM") { + client_sock->write("SM#%"); + } + else if (header == "RD") { + client_sock->write("DONE#%"); + } + else if (header == "CC") { + client_sock->write("PV#0#CID#-1#%"); + client_sock->write("CT#DEMO#Demo file loaded. Send /play or > in OOC to begin playback.#1#%"); + } + else if (header == "CT") { + if (contents[1].startsWith("/load")) + { + QString path = QFileDialog::getOpenFileName(nullptr, tr("Load Demo"), "logs/", tr("Demo Files (*.demo)")); + if (path.isEmpty()) + return; + load_demo(path); + client_sock->write("CT#DEMO#Demo file loaded. Send /play or > in OOC to begin playback.#1#%"); + } + else if (contents[1].startsWith("/play") || contents[1] == ">") + { + if (timer->interval() != 0 && !timer->isActive()) + { + timer->start(); + client_sock->write("CT#DEMO#Resuming playback.#1#%"); + } + else + { + if (demo_data.isEmpty() && p_path != "") + load_demo(p_path); + playback(); + } + } + else if (contents[1].startsWith("/pause") || contents[1] == "|") + { + int timeleft = timer->remainingTime(); + timer->stop(); + timer->setInterval(timeleft); + client_sock->write("CT#DEMO#Pausing playback.#1#%"); + } + else if (contents[1].startsWith("/max_wait")) + { + QStringList args = contents[1].split(" "); + if (args.size() > 1) + { + bool ok; + int p_max_wait = args.at(1).toInt(&ok); + if (ok) + { + if (p_max_wait < 0) + p_max_wait = -1; + max_wait = p_max_wait; + client_sock->write("CT#DEMO#Setting max_wait to "); + client_sock->write(QString::number(max_wait).toUtf8()); + client_sock->write(" milliseconds.#1#%"); + } + else + { + client_sock->write("CT#DEMO#Not a valid integer!#1#%"); + } + } + else + { + client_sock->write("CT#DEMO#Current max_wait is "); + client_sock->write(QString::number(max_wait).toUtf8()); + client_sock->write(" milliseconds.#1#%"); + } + } + else if (contents[1].startsWith("/min_wait")) + { + QStringList args = contents[1].split(" "); + if (args.size() > 1) + { + bool ok; + int p_min_wait = args.at(1).toInt(&ok); + if (ok) + { + if (p_min_wait < 0) + p_min_wait = -1; + min_wait = p_min_wait; + client_sock->write("CT#DEMO#Setting min_wait to "); + client_sock->write(QString::number(min_wait).toUtf8()); + client_sock->write(" milliseconds.#1#%"); + } + else + { + client_sock->write("CT#DEMO#Not a valid integer!#1#%"); + } + } + else + { + client_sock->write("CT#DEMO#Current min_wait is "); + client_sock->write(QString::number(min_wait).toUtf8()); + client_sock->write(" milliseconds.#1#%"); + } + } + else if (contents[1].startsWith("/help")) + { + client_sock->write("CT#DEMO#Available commands:\nload, play, pause, max_wait, min_wait, help#1#%"); + } + } +} + +void DemoServer::load_demo(QString filename) +{ + QFile demo_file(filename); + demo_file.open(QIODevice::ReadOnly); + if (!demo_file.isOpen()) + return; + demo_data.clear(); + p_path = filename; + QTextStream demo_stream(&demo_file); + QString line = demo_stream.readLine(); + while (!line.isNull()) { + if (!line.endsWith("%")) { + line += "\n"; + } + demo_data.enqueue(line); + line = demo_stream.readLine(); + } +} + +void DemoServer::playback() +{ + if (demo_data.isEmpty()) + return; + + QString current_packet = demo_data.dequeue(); + // We reset the elapsed time with this packet + if (current_packet.startsWith("MS#")) + elapsed_time = 0; + + while (!current_packet.startsWith("wait") && !demo_data.isEmpty()) { + client_sock->write(current_packet.toUtf8()); + current_packet = demo_data.dequeue(); + } + if (!demo_data.isEmpty()) { + AOPacket wait_packet = AOPacket(current_packet); + + int duration = wait_packet.get_contents().at(0).toInt(); + if (max_wait != -1 && duration + elapsed_time > max_wait) + duration = qMax(0, max_wait - elapsed_time); + // We use elapsed_time to make sure that the packet we're using min_wait on is "priority" (e.g. IC) + if (elapsed_time == 0 && min_wait != -1 && duration < min_wait) + duration = min_wait; + elapsed_time += duration; + timer->start(duration); + } + else + { + client_sock->write("CT#DEMO#Reached the end of the demo file. Send /play or > in OOC to restart, or /load to open a new file.#1#%"); + timer->setInterval(0); + } +} + +void DemoServer::client_disconnect() +{ + client_sock->deleteLater(); + client_sock = nullptr; +} diff --git a/src/lobby.cpp b/src/lobby.cpp index 3579ff1a..39ddfa31 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -3,6 +3,7 @@ #include "aoapplication.h" #include "aosfxplayer.h" #include "debug_functions.h" +#include "demoserver.h" #include "networkmanager.h" #include <QImageReader> @@ -28,6 +29,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_server_list = new QTreeWidget(this); ui_server_list->setHeaderLabels({"#", "Name"}); //, "Players"}); ui_server_list->hideColumn(0); + ui_server_list->setHeaderHidden(true); ui_server_search = new QLineEdit(this); ui_server_search->setFrame(false); @@ -441,7 +443,15 @@ void Lobby::on_server_list_clicked(QTreeWidgetItem *p_item, int column) ui_connect->setEnabled(false); - ao_app->net_manager->connect_to_server(f_server); + if (f_server.port == 99999 && f_server.ip == "127.0.0.1") { + // Demo playback server selected + ao_app->demo_server->start_server(); + server_type demo_server; + demo_server.ip = "127.0.0.1"; + demo_server.port = ao_app->demo_server->port; + ao_app->net_manager->connect_to_server(demo_server); + } + else ao_app->net_manager->connect_to_server(f_server); } } diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp index cf89d0ab..5e29e219 100644 --- a/src/networkmanager.cpp +++ b/src/networkmanager.cpp @@ -131,7 +131,7 @@ void NetworkManager::on_srv_lookup() qDebug() << "Connecting to " << record.target() << ":" << record.port(); #endif ms_socket->connectToHost(record.target(), record.port()); - QTime timer; + QElapsedTimer timer; timer.start(); do { ao_app->processEvents(); diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 7819cd50..2da6981c 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -102,6 +102,19 @@ end: delete p_packet; } +void AOApplication::append_to_demofile(QString packet_string) +{ + if (get_auto_logging_enabled() && !log_filename.isEmpty()) + { + QString path = log_filename.left(log_filename.size()).replace(".log", ".demo"); + append_to_file(packet_string, path, true); + if (!demo_timer.isValid()) + demo_timer.start(); + else + append_to_file("wait#"+ QString::number(demo_timer.restart()) + "#%", path, true); + } +} + void AOApplication::server_packet_received(AOPacket *p_packet) { p_packet->net_decode(); @@ -164,6 +177,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) else w_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1), "0"); + + append_to_demofile(p_packet->to_string(true)); } } else if (header == "FL") { @@ -232,7 +247,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) evidence_list_size = f_contents.at(1).toInt(); music_list_size = f_contents.at(2).toInt(); - if (char_list_size < 1 || evidence_list_size < 0 || music_list_size < 0) + if (char_list_size < 0 || evidence_list_size < 0 || music_list_size < 0) goto end; loaded_chars = 0; @@ -245,7 +260,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) courtroom_loaded = false; - QString window_title = tr("Attorney Online 2"); + window_title = tr("Attorney Online 2"); int selected_server = w_lobby->get_selected_server(); QString server_address = "", server_name = ""; @@ -255,7 +270,6 @@ void AOApplication::server_packet_received(AOPacket *p_packet) server_name = info.name; server_address = QString("%1:%2").arg(info.ip, QString::number(info.port)); - qDebug() << server_address; window_title += ": " + server_name; } } @@ -265,7 +279,6 @@ void AOApplication::server_packet_received(AOPacket *p_packet) server_name = info.name; server_address = QString("%1:%2").arg(info.ip, QString::number(info.port)); - qDebug() << server_address; window_title += ": " + server_name; } } @@ -283,7 +296,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Remove any characters not accepted in folder names for the server_name // here - if (AOApplication::get_auto_logging_enabled()) { + if (AOApplication::get_auto_logging_enabled() && server_name != "Demo playback") { this->log_filename = QDateTime::currentDateTime().toUTC().toString( "'logs/" + server_name.remove(QRegExp("[\\\\/:*?\"<>|\']")) + "/'yyyy-MM-dd hh-mm-ss t'.log'"); @@ -292,6 +305,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QDateTime::currentDateTime().toUTC().toString(), log_filename, true); } + else + this->log_filename = ""; QCryptographicHash hash(QCryptographicHash::Algorithm::Sha256); hash.addData(server_address.toUtf8()); @@ -312,7 +327,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "SC") { - if (!courtroom_constructed) + if (!courtroom_constructed || courtroom_loaded) goto end; for (int n_element = 0; n_element < f_contents.size(); ++n_element) { @@ -344,9 +359,10 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } send_server_packet(new AOPacket("RM#%")); + append_to_demofile(p_packet->to_string(true)); } else if (header == "SM") { - if (!courtroom_constructed) + if (!courtroom_constructed || courtroom_loaded) goto end; bool musics_time = false; @@ -445,6 +461,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) 2) // We have a pos included in the background packet! w_courtroom->set_side(f_contents.at(1)); w_courtroom->set_background(f_contents.at(0), f_contents.size() >= 2); + append_to_demofile(p_packet->to_string(true)); } } else if (header == "SP") { @@ -454,6 +471,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (courtroom_constructed) // We were sent a "set position" packet { w_courtroom->set_side(f_contents.at(0)); + append_to_demofile(p_packet->to_string(true)); } } else if (header == "SD") // Send pos dropdown @@ -475,27 +493,37 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "MS") { if (courtroom_constructed && courtroom_loaded) - w_courtroom->handle_chatmessage(&p_packet->get_contents()); + { + w_courtroom->chatmessage_enqueue(p_packet->get_contents()); + append_to_demofile(p_packet->to_string(true)); + } } else if (header == "MC") { if (courtroom_constructed && courtroom_loaded) + { w_courtroom->handle_song(&p_packet->get_contents()); + append_to_demofile(p_packet->to_string(true)); + } } else if (header == "RT") { if (f_contents.size() < 1) goto end; if (courtroom_constructed) { - if (f_contents.size() == 1) - w_courtroom->handle_wtce(f_contents.at(0), 0); - else if (f_contents.size() == 2) { - w_courtroom->handle_wtce(f_contents.at(0), f_contents.at(1).toInt()); + if (f_contents.size() == 1) + w_courtroom->handle_wtce(f_contents.at(0), 0); + else if (f_contents.size() == 2) { + w_courtroom->handle_wtce(f_contents.at(0), f_contents.at(1).toInt()); + append_to_demofile(p_packet->to_string(true)); } } } else if (header == "HP") { if (courtroom_constructed && f_contents.size() > 1) + { w_courtroom->set_hp_bar(f_contents.at(0).toInt(), f_contents.at(1).toInt()); + append_to_demofile(p_packet->to_string(true)); + } } else if (header == "LE") { if (courtroom_constructed) { @@ -576,6 +604,60 @@ void AOApplication::server_packet_received(AOPacket *p_packet) f_contents.at(4) == "1", f_contents.at(5) == "1"); } + else if (header == "TI") { // Timer packet + if (!courtroom_constructed || f_contents.size() < 2) + goto end; + + // Timer ID is reserved as argument 0 + int id = f_contents.at(0).toInt(); + + // Type 0 = start/resume/sync timer at time + // Type 1 = pause timer at time + // Type 2 = show timer + // Type 3 = hide timer + int type = f_contents.at(1).toInt(); + + if (type == 0 || type == 1) + { + if (f_contents.size() < 2) + goto end; + + // The time as displayed on the clock, in milliseconds. + // If the number received is negative, stop the timer. + qint64 timer_value = f_contents.at(2).toLongLong(); + qDebug() << "timer:" << timer_value; + if (timer_value > 0) + { + if (type == 0) + { + timer_value -= latency / 2; + w_courtroom->start_clock(id, timer_value); + } + else + { + w_courtroom->pause_clock(id); + w_courtroom->set_clock(id, timer_value); + } + } + else + { + w_courtroom->stop_clock(id); + } + } + else if (type == 2) + w_courtroom->set_clock_visibility(id, true); + else if (type == 3) + w_courtroom->set_clock_visibility(id, false); + } + else if (header == "CHECK") { + if (!courtroom_constructed) + goto end; + + qint64 ping_time = w_courtroom->pong(); + qDebug() << "ping:" << ping_time; + if (ping_time != -1) + latency = ping_time; + } end: diff --git a/src/path_functions.cpp b/src/path_functions.cpp index c6c73a8b..728f5a40 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -65,6 +65,16 @@ QString AOApplication::get_character_path(QString p_char, QString p_file) return get_case_sensitive_path(path); } +QString AOApplication::get_misc_path(QString p_misc, QString p_file) +{ + QString path = get_base_path() + "misc/" + p_misc + "/" + p_file; +#ifndef CASE_SENSITIVE_FILESYSTEM + return path; +#else + return get_case_sensitive_path(path); +#endif +} + QString AOApplication::get_sounds_path(QString p_file) { QString path = get_base_path() + "sounds/general/" + p_file; diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 3524d87e..0128f565 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -46,6 +46,18 @@ int AOApplication::get_max_log_size() return result; } +int AOApplication::stay_time() +{ + int result = configini->value("stay_time", 200).toInt(); + return result; +} + +int AOApplication::get_chat_ratelimit() +{ + int result = configini->value("chat_ratelimit", 300).toInt(); + return result; +} + bool AOApplication::get_log_goes_downwards() { QString result = @@ -55,8 +67,7 @@ bool AOApplication::get_log_goes_downwards() bool AOApplication::get_log_newline() { - QString result = - configini->value("log_newline", "false").value<QString>(); + QString result = configini->value("log_newline", "false").value<QString>(); return result.startsWith("true"); } @@ -68,8 +79,7 @@ int AOApplication::get_log_margin() bool AOApplication::get_log_timestamp() { - QString result = - configini->value("log_timestamp", "false").value<QString>(); + QString result = configini->value("log_timestamp", "false").value<QString>(); return result.startsWith("true"); } @@ -172,6 +182,10 @@ bool AOApplication::write_to_file(QString p_text, QString p_file, bool make_dir) bool AOApplication::append_to_file(QString p_text, QString p_file, bool make_dir) { + if(!file_exists(p_file)) //Don't create a newline if file didn't exist before now + { + return write_to_file(p_text, p_file, make_dir); + } QString path = QFileInfo(p_file).path(); // Create the dir if it doesn't exist yet if (make_dir) { @@ -243,6 +257,13 @@ QVector<server_type> AOApplication::read_serverlist_txt() f_server_list.append(f_server); } + server_type demo_server; + demo_server.ip = "127.0.0.1"; + demo_server.port = 99999; + demo_server.name = "Demo playback"; + demo_server.desc = "Play back demos you have previously recorded"; + f_server_list.append(demo_server); + return f_server_list; } @@ -259,6 +280,13 @@ QString AOApplication::read_design_ini(QString p_identifier, } } +Qt::TransformationMode AOApplication::get_scaling(QString p_scaling) +{ + if (p_scaling == "smooth") + return Qt::SmoothTransformation; + return Qt::FastTransformation; +} + QPoint AOApplication::get_button_spacing(QString p_identifier, QString p_file) { QString design_ini_path = get_theme_path(p_file); @@ -292,28 +320,12 @@ pos_size_type AOApplication::get_element_dimensions(QString p_identifier, QString p_file, QString p_char) { - QString char_ini_path = - get_base_path() + "misc/" + get_chat(p_char) + "/" + p_file; - QString design_ini_path = get_theme_path(p_file); - QString default_path = get_default_theme_path(p_file); - QString f_result = read_design_ini(p_identifier, char_ini_path); - pos_size_type return_value; - return_value.x = 0; return_value.y = 0; return_value.width = -1; return_value.height = -1; - - if (f_result == "") { - f_result = read_design_ini(p_identifier, design_ini_path); - if (f_result == "") { - f_result = read_design_ini(p_identifier, default_path); - - if (f_result == "") - return return_value; - } - } + QString f_result = get_design_element(p_identifier, p_file, p_char); QStringList sub_line_elements = f_result.split(","); @@ -330,17 +342,16 @@ pos_size_type AOApplication::get_element_dimensions(QString p_identifier, QString AOApplication::get_design_element(QString p_identifier, QString p_file, QString p_char) { - QString char_ini_path = - get_base_path() + "misc/" + get_chat(p_char) + "/" + p_file; - QString design_ini_path = get_theme_path(p_file); - QString default_path = get_default_theme_path(p_file); - QString f_result = read_design_ini(p_identifier, char_ini_path); - if (f_result == "") { - f_result = read_design_ini(p_identifier, design_ini_path); - if (f_result == "") - f_result = read_design_ini(p_identifier, default_path); + QStringList paths{get_theme_path("misc/" + get_chat(p_char) + "/" + + p_file), // user theme overrides base/misc + get_base_path() + "misc/" + get_chat(p_char) + "/" + p_file, + get_theme_path(p_file), get_default_theme_path(p_file)}; + for (const QString &path : paths) { + QString value = read_design_ini(p_identifier, path); + if (!value.isEmpty()) + return value; } - return f_result; + return ""; } QString AOApplication::get_font_name(QString p_identifier, QString p_file) { @@ -460,34 +471,30 @@ QString AOApplication::get_tagged_stylesheet(QString target_tag, QString p_file) return f_text; } -QString AOApplication::get_chat_markdown(QString p_identifier, QString p_chat) +QString AOApplication::get_chat_markup(QString p_identifier, QString p_chat) { - QString design_ini_path = - get_base_path() + "misc/" + get_chat(p_chat) + "/config.ini"; - QString default_path = get_base_path() + "misc/default/config.ini"; - QString f_result = read_design_ini(p_identifier, design_ini_path); + QStringList paths{get_theme_path("misc/" + get_chat(p_chat) + "/config.ini"), + get_base_path() + "misc/" + get_chat(p_chat) + + "/config.ini", + get_base_path() + "misc/default/config.ini", + get_theme_path("misc/default/config.ini")}; - if (f_result == "") - f_result = read_design_ini(p_identifier, default_path); + for (const QString &path : paths) { + QString value = read_design_ini(p_identifier, path); + if (!value.isEmpty()) { + return value.toLatin1(); + } + } - return f_result.toLatin1(); + return ""; } QColor AOApplication::get_chat_color(QString p_identifier, QString p_chat) { QColor return_color(255, 255, 255); - - QString design_ini_path = - get_base_path() + "misc/" + get_chat(p_chat) + "/config.ini"; - QString default_path = get_base_path() + "misc/default/config.ini"; - QString f_result = read_design_ini(p_identifier, design_ini_path); - - if (f_result == "") { - f_result = read_design_ini(p_identifier, default_path); - - if (f_result == "") - return return_color; - } + QString f_result = get_chat_markup(p_identifier, p_chat); + if (f_result == "") + return return_color; QStringList color_list = f_result.split(","); @@ -501,23 +508,21 @@ QColor AOApplication::get_chat_color(QString p_identifier, QString p_chat) return return_color; } -QString AOApplication::get_sfx(QString p_identifier) +QString AOApplication::get_sfx(QString p_identifier, QString p_misc) { - QString design_ini_path = get_theme_path("courtroom_sounds.ini"); - QString default_path = get_default_theme_path("courtroom_sounds.ini"); - QString f_result = read_design_ini(p_identifier, design_ini_path); + QStringList paths{get_theme_path("misc/" + p_misc + "/courtroom_sounds.ini"), + get_misc_path(p_misc, "courtroom_sounds.ini"), + get_theme_path("courtroom_sounds.ini"), + get_default_theme_path("courtroom_sounds.ini")}; QString return_sfx = ""; - if (f_result == "") { - f_result = read_design_ini(p_identifier, default_path); - - if (f_result == "") - return return_sfx; + for (const QString &path : paths) { + QString value = read_design_ini(p_identifier, path); + if (!value.isEmpty()) { + return value.toLatin1(); + } } - - return_sfx = f_result; - return return_sfx; } @@ -644,6 +649,37 @@ QString AOApplication::get_blips(QString p_char) return f_result; } +QString AOApplication::get_emote_property(QString p_char, QString p_emote, + QString p_property) +{ + QString f_result = + read_char_ini(p_char, p_emote, p_property); // per-emote override + if (f_result == "") + f_result = read_char_ini(p_char, p_property, + "Options"); // global for this character + return f_result; +} + +Qt::TransformationMode AOApplication::get_misc_scaling(QString p_miscname) +{ + if (p_miscname != "") { + QString misc_transform_mode = read_design_ini( + "scaling", get_theme_path("misc/" + p_miscname + "/config.ini")); + if (misc_transform_mode == "") + misc_transform_mode = + read_design_ini("scaling", get_misc_path(p_miscname, "config.ini")); + if (misc_transform_mode == "smooth") + return Qt::SmoothTransformation; + } + return Qt::FastTransformation; +} + +QString AOApplication::get_category(QString p_char) +{ + QString f_result = read_char_ini(p_char, "category", "Options"); + return f_result; +} + QString AOApplication::get_chat(QString p_char) { if (p_char == "default") @@ -854,7 +890,7 @@ QString AOApplication::get_flash_frame(QString p_char, QString p_emote, int AOApplication::get_text_delay(QString p_char, QString p_emote) { - QString f_result = read_char_ini(p_char, p_emote, "TextDelay"); + QString f_result = read_char_ini(p_char, p_emote, "stay_time"); if (f_result == "") return -1; @@ -875,7 +911,7 @@ QStringList AOApplication::get_theme_effects() QStringList lines = read_file(p_path).split("\n"); foreach (QString effect, lines) { - effect = effect.split("=")[0].trimmed(); + effect = effect.split("=")[0].trimmed().split("_")[0]; if (!effect.isEmpty() && !effects.contains(effect)) effects.append(effect); } @@ -893,7 +929,7 @@ QStringList AOApplication::get_effects(QString p_char) QStringList lines = read_file(p_path).split("\n"); foreach (QString effect, lines) { - effect = effect.split("=")[0].trimmed(); + effect = effect.split("=")[0].trimmed().split("_")[0]; if (!effect.isEmpty() && !effects.contains(effect)) effects.append(effect); } @@ -928,25 +964,33 @@ QString AOApplication::get_effect(QString effect, QString p_char, return p_path; } -QString AOApplication::get_effect_sound(QString fx_name, QString p_char) +QString AOApplication::get_effect_property(QString fx_name, QString p_char, + QString p_property) { + QString f_property; + if (p_property == "sound") + f_property = fx_name; + else + f_property = fx_name + "_" + p_property; QString p_effect = read_char_ini(p_char, "effects", "Options"); QString p_path = get_base_path() + "misc/" + p_effect + "/effects.ini"; QString design_ini_path = get_theme_path("effects/effects.ini"); QString default_path = get_default_theme_path("effects/effects.ini"); - QString f_result = read_design_ini(fx_name, p_path); + QString f_result = read_design_ini(f_property, p_path); if (f_result == "") { - f_result = read_design_ini(fx_name, design_ini_path); + f_result = read_design_ini(f_property, design_ini_path); if (f_result == "") { - f_result = read_design_ini(fx_name, default_path); + f_result = read_design_ini(f_property, default_path); } } - if (fx_name == "realization") { + if (fx_name == "realization" && p_property == "sound") { f_result = get_custom_realization(p_char); } + qDebug() << "got" << f_property << "of" << fx_name << "==" << f_result; + return f_result; } @@ -979,6 +1023,18 @@ bool AOApplication::objection_stop_music() return result.startsWith("true"); } +bool AOApplication::is_instant_objection_enabled() +{ + QString result = configini->value("instant_objection", "true").value<QString>(); + return result.startsWith("true"); +} + +bool AOApplication::is_desyncrhonized_logs_enabled() +{ + QString result = configini->value("desync_logs", "false").value<QString>(); + return result.startsWith("true"); +} + bool AOApplication::is_discord_enabled() { QString result = configini->value("discord", "true").value<QString>(); |
