aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/aoapplication.cpp8
-rw-r--r--src/aocharmovie.cpp332
-rw-r--r--src/aoclocklabel.cpp59
-rw-r--r--src/aoevidencedisplay.cpp10
-rw-r--r--src/aolayer.cpp601
-rw-r--r--src/aomovie.cpp100
-rw-r--r--src/aomusicplayer.cpp7
-rw-r--r--src/aooptionsdialog.cpp59
-rw-r--r--src/aopacket.cpp10
-rw-r--r--src/aoscene.cpp132
-rw-r--r--src/charselect.cpp74
-rw-r--r--src/courtroom.cpp1481
-rw-r--r--src/demoserver.cpp304
-rw-r--r--src/lobby.cpp12
-rw-r--r--src/networkmanager.cpp2
-rw-r--r--src/packet_distribution.cpp106
-rw-r--r--src/path_functions.cpp10
-rw-r--r--src/text_file_functions.cpp198
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>();