From e5012261d17a6a1978ef7bcf91f1ce7807fd9415 Mon Sep 17 00:00:00 2001 From: Ibrahim Jomaa Date: Sun, 8 Sep 2019 16:37:58 -0400 Subject: [PATCH 001/842] Improve character image resizing Character images, when forced to scale down to below their original size, are now less pixelated. --- aocharmovie.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/aocharmovie.cpp b/aocharmovie.cpp index 7fa3cc88a..728d1d94c 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -154,9 +154,18 @@ void AOCharMovie::frame_change(int n_frame) { if (movie_frames.size() > n_frame) { - QPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(n_frame)); + QPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(n_frame)); + auto aspect_ratio = Qt::KeepAspectRatio; - this->setPixmap(f_pixmap.scaled(this->width(), this->height())); + if (f_pixmap.size().width() > f_pixmap.size().height()) + aspect_ratio = Qt::KeepAspectRatioByExpanding; + + if (f_pixmap.size().width() > this->size().width() || f_pixmap.size().height() > this->size().height()) + this->setPixmap(f_pixmap.scaled(this->width(), this->height(), aspect_ratio, Qt::SmoothTransformation)); + else + this->setPixmap(f_pixmap.scaled(this->width(), this->height(), aspect_ratio, Qt::FastTransformation)); + + QLabel::move(x + (this->width() - this->pixmap()->width())/2, y); } if (m_movie->frameCount() - 1 == n_frame && play_once) From 0417c485e36bd92f703f7e8b6cc2e8aec05b489e Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 27 Jan 2020 12:17:44 -0500 Subject: [PATCH 002/842] Refresh background immediately after receiving BN packet as opposed to on next IC message --- packet_distribution.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packet_distribution.cpp b/packet_distribution.cpp index 2f030918f..a428a7fde 100644 --- a/packet_distribution.cpp +++ b/packet_distribution.cpp @@ -556,8 +556,10 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (f_contents.size() < 1) goto end; - if (courtroom_constructed) + if (courtroom_constructed) { w_courtroom->set_background(f_contents.at(0)); + w_courtroom->set_scene(); + } } //server accepting char request(CC) packet else if (header == "PV") From 55ee103ddf02a70ad4b6165aba71e64c7545fccf Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 30 Jan 2020 22:48:07 -0500 Subject: [PATCH 003/842] start of variant --- aoapplication.h | 8 + aobutton.cpp | 31 ++-- aobutton.h | 2 + aocharmovie.cpp | 4 +- aoevidencebutton.cpp | 13 +- aoevidencedisplay.cpp | 13 +- aoimage.cpp | 24 +-- aoimage.h | 3 + aolabel.cpp | 11 +- aomovie.cpp | 12 +- aoshoutplayer.cpp | 6 +- courtroom.cpp | 239 ++++++++++++------------ courtroom.h | 1 + path_functions.cpp | 6 + text_file_functions.cpp | 398 +++++++++++++++++++--------------------- 15 files changed, 373 insertions(+), 398 deletions(-) diff --git a/aoapplication.h b/aoapplication.h index b28ea7af9..138a55dae 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -96,6 +96,7 @@ class AOApplication : public QApplication QString get_base_path(); QString get_data_path(); QString get_theme_path(); + QString get_theme_variant_path(); QString get_default_theme_path(); QString get_character_path(QString p_character); QString get_demothings_path(); @@ -156,6 +157,9 @@ class AOApplication : public QApplication //Returns the value of p_identifier in the design.ini file in p_design_path QString read_design_ini(QString p_identifier, QString p_design_path); + //Returns the value of p_identifier from p_file in either a theme variant subfolder, a theme folder, or default theme folder + QString read_theme_ini(QString p_identifier, QString p_file); + //Helper function for returning an int in a file inside of the theme folder int get_design_ini_value(QString p_identifier, QString p_design_file); @@ -255,12 +259,16 @@ class AOApplication : public QApplication //Returns p_char's gender QString get_gender(QString p_char); + //Get the location of p_image, which is either in a theme variant subfolder, a theme folder, or default theme folder + QString get_image_path(QString p_image); + private: const int RELEASE = 2; const int MAJOR_VERSION = 4; const int MINOR_VERSION = 8; QString current_theme = "default"; + QString theme_variant = "dr2"; QVector server_list; QVector favorite_list; diff --git a/aobutton.cpp b/aobutton.cpp index 6ba97caa0..b2c07ad9a 100644 --- a/aobutton.cpp +++ b/aobutton.cpp @@ -18,19 +18,22 @@ AOButton::~AOButton() void AOButton::set_image(QString p_image) { QString f_image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); - QString image_path = ao_app->get_theme_path() + p_image; - QString hover_image_path = ao_app->get_theme_path() + f_image_name + "_hover.png"; - QString default_image_path = ao_app->get_default_theme_path() + p_image; - - if (file_exists(image_path)) - { - if(file_exists(hover_image_path)) - this->setStyleSheet("QPushButton {border-image:url(\"" + image_path + "\");}" - "QPushButton:hover {border-image:url(\"" + hover_image_path + "\");}"); - else - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - } - + QString some_image_path = ao_app->get_image_path(p_image); + QString some_hover_image_path = ao_app->get_image_path(p_image + "_hover.png"); + + if (file_exists(some_image_path)) + { + image_path = some_image_path; + + if (file_exists(some_hover_image_path)) + this->setStyleSheet("QPushButton {border-image:url(\"" + some_image_path + "\");}" + "QPushButton:hover {border-image:url(\"" + some_hover_image_path + "\");}"); + else + this->setStyleSheet("border-image:url(\"" + some_image_path + "\")"); + } else - this->setStyleSheet("border-image:url(\"" + default_image_path + "\")"); + { + image_path = ""; + this->setStyleSheet("border-image:url(\"" + some_image_path + "\")"); + } } diff --git a/aobutton.h b/aobutton.h index 23e6311ca..c96938310 100644 --- a/aobutton.h +++ b/aobutton.h @@ -16,6 +16,8 @@ class AOButton : public QPushButton AOApplication *ao_app = nullptr; void set_image(QString p_image); + + QString image_path = ""; }; #endif // AOBUTTON_H diff --git a/aocharmovie.cpp b/aocharmovie.cpp index 0c144a0ea..e0876fd48 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -27,8 +27,7 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bo QString original_path = ao_app->get_character_path(p_char) + emote_prefix + p_emote.toLower(); // .gif QString alt_path = ao_app->get_character_path(p_char) + p_emote.toLower(); // .png - QString placeholder_path = ao_app->get_theme_path() + "placeholder"; // .gif - QString placeholder_default_path = ao_app->get_default_theme_path() + "placeholder"; // .gif + QString placeholder_path = ao_app->get_image_path("placeholder"); // .gif QString gif_path; // if (file_exists(original_path)) @@ -43,7 +42,6 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bo f_vec.push_back(original_path); f_vec.push_back(alt_path); f_vec.push_back(placeholder_path); - f_vec.push_back(placeholder_default_path); for(auto &f_file : f_vec) { diff --git a/aoevidencebutton.cpp b/aoevidencebutton.cpp index 7c8abb66d..f3bc6b407 100644 --- a/aoevidencebutton.cpp +++ b/aoevidencebutton.cpp @@ -54,18 +54,9 @@ void AOEvidenceButton::set_image(QString p_image) void AOEvidenceButton::set_theme_image(QString p_image) { - QString theme_image_path = ao_app->get_theme_path() + p_image; - QString default_image_path = ao_app->get_default_theme_path() + p_image; - - QString final_image_path; - - if (file_exists(theme_image_path)) - final_image_path = theme_image_path; - else - final_image_path = default_image_path; - + QString path = ao_app->get_image_path(p_image); this->setText(""); - this->setStyleSheet("border-image:url(\"" + final_image_path + "\")"); + this->setStyleSheet("border-image:url(\"" + path + "\")"); } void AOEvidenceButton::set_selected(bool p_selected) diff --git a/aoevidencedisplay.cpp b/aoevidencedisplay.cpp index cbe37c0e5..66593f251 100644 --- a/aoevidencedisplay.cpp +++ b/aoevidencedisplay.cpp @@ -46,19 +46,10 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid evidence_icon->move(icon_dimensions.x, icon_dimensions.y); evidence_icon->resize(icon_dimensions.width, icon_dimensions.height); - evidence_icon->setPixmap(f_pixmap.scaled(evidence_icon->width(), evidence_icon->height(), Qt::IgnoreAspectRatio)); - QString f_default_gif_path = ao_app->get_default_theme_path() + gif_name; - QString f_gif_path = ao_app->get_theme_path() + gif_name; - - if (file_exists(f_gif_path)) - final_gif_path = f_gif_path; - else - final_gif_path = f_default_gif_path; - - evidence_movie->setFileName(final_gif_path); - + QString f_path = ao_app->get_image_path(gif_name); + evidence_movie->setFileName(f_path); if(evidence_movie->frameCount() < 1) return; diff --git a/aoimage.cpp b/aoimage.cpp index 03e6ebcb3..c441a5a5e 100644 --- a/aoimage.cpp +++ b/aoimage.cpp @@ -16,19 +16,16 @@ AOImage::~AOImage() void AOImage::set_image(QString p_image) { - QString theme_image_path = ao_app->get_theme_path() + p_image; - QString default_image_path = ao_app->get_default_theme_path() + p_image; + QString f_path = ao_app->get_image_path(p_image); + QPixmap f_pixmap(f_path); - QString final_image_path; + this->setPixmap(f_pixmap.scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); - if (file_exists(theme_image_path)) - final_image_path = theme_image_path; + // Store final path if the path exists + if (file_exists(f_path)) + image_path = f_path; else - final_image_path = default_image_path; - - QPixmap f_pixmap(final_image_path); - - this->setPixmap(f_pixmap.scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); + image_path = ""; } void AOImage::set_image_from_path(QString p_path) @@ -43,6 +40,11 @@ void AOImage::set_image_from_path(QString p_path) final_path = default_path; QPixmap f_pixmap(final_path); - this->setPixmap(f_pixmap.scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); + + // Store final path if the path exists + if (file_exists(final_path)) + image_path = final_path; + else + image_path = ""; } diff --git a/aoimage.h b/aoimage.h index edc1efe48..d423d55d3 100644 --- a/aoimage.h +++ b/aoimage.h @@ -16,8 +16,11 @@ class AOImage : public QLabel AOApplication *ao_app = nullptr; void set_image(QString p_image); + void set_image_variant(QString p_image, QString p_variant); void set_image_from_path(QString p_path); void set_size_and_pos(QString identifier); + + QString image_path = ""; }; #endif // AOIMAGE_H diff --git a/aolabel.cpp b/aolabel.cpp index 0e893751d..36b35c736 100644 --- a/aolabel.cpp +++ b/aolabel.cpp @@ -9,13 +9,6 @@ AOLabel::AOLabel(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) void AOLabel::set_image(QString p_image) { - QString image_path = ao_app->get_theme_path() + p_image; - QString default_image_path = ao_app->get_default_theme_path() + p_image; - - if (file_exists(image_path)) - { - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - } - else - this->setStyleSheet("border-image:url(\"" + default_image_path + "\")"); + QString f_path = ao_app->get_image_path(p_image); + setStyleSheet("border-image:url(\"" + f_path + "\")"); } diff --git a/aomovie.cpp b/aomovie.cpp index 72cbb435a..90385ad40 100644 --- a/aomovie.cpp +++ b/aomovie.cpp @@ -38,17 +38,13 @@ void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) QString overlay_path = ao_app->get_character_path(p_char) + "overlay/" + p_file; QString custom_theme_path = ao_app->get_base_path() + "themes/" + p_custom_theme + "/" + p_file; - QString theme_path = ao_app->get_theme_path() + p_file; - QString default_theme_path = ao_app->get_default_theme_path() + p_file; - QString placeholder_path = ao_app->get_theme_path() + "placeholder"; - QString default_placeholder_path = ao_app->get_default_theme_path() + "placeholder"; + QString some_theme_path = ao_app->get_image_path(p_file); + QString some_placeholder_path = ao_app->get_image_path("placeholder"); f_vec.push_back(overlay_path); f_vec.push_back(custom_theme_path); - f_vec.push_back(theme_path); - f_vec.push_back(default_theme_path); - f_vec.push_back(placeholder_path); - f_vec.push_back(default_placeholder_path); + f_vec.push_back(some_theme_path); + f_vec.push_back(some_placeholder_path); for(auto &f_file : f_vec) { diff --git a/aoshoutplayer.cpp b/aoshoutplayer.cpp index c82a29ee7..16aecf062 100644 --- a/aoshoutplayer.cpp +++ b/aoshoutplayer.cpp @@ -12,19 +12,15 @@ void AOShoutPlayer::play(QString p_name, QString p_char) QString f_file; QString char_path = ao_app->get_character_path(p_char) + p_name.toLower(); - QString theme_path = ao_app->get_theme_path() + p_name.toLower(); - QString default_theme_path = ao_app->get_default_theme_path() + p_name.toLower(); + QString theme_path = ao_app->get_image_path(p_name.toLower()); qDebug() << char_path; qDebug() << theme_path; - qDebug() << default_theme_path; if(file_exists(char_path)) f_file = char_path; else if (file_exists(theme_path)) f_file = theme_path; - else if (file_exists(default_theme_path)) - f_file = default_theme_path; else f_file = ""; diff --git a/courtroom.cpp b/courtroom.cpp index 25f8ccfbd..810c03e93 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -643,7 +643,8 @@ void Courtroom::set_widgets() ui_shout_down->set_image("shoutdown.png"); ui_shout_down->hide(); - if (ao_app->read_design_ini( "enable_single_shout", ao_app->get_theme_path() + cc_config_ini ) == "true" && ui_shouts.size() > 0) // courtroom_config.ini necessary + check for crash + // courtroom_config.ini necessary + check for crash + if (ao_app->read_theme_ini("enable_single_shout", cc_config_ini) == "true" && ui_shouts.size() > 0) { for(auto & shout : ui_shouts) move_widget(shout, "bullet"); @@ -670,7 +671,7 @@ void Courtroom::set_widgets() ui_effect_down->set_image("effectdown.png"); ui_effect_down->hide(); - if (ao_app->read_design_ini( "enable_single_effect", ao_app->get_theme_path() + cc_config_ini ) == "true" && ui_effects.size() > 0) // check to prevent crashing + if (ao_app->read_theme_ini("enable_single_effect", cc_config_ini) == "true" && ui_effects.size() > 0) // check to prevent crashing { for(auto & effect : ui_effects) move_widget(effect, "effect"); @@ -692,7 +693,7 @@ void Courtroom::set_widgets() ui_wtce_down->set_image("wtcedown.png"); ui_wtce_down->hide(); - if( ao_app->read_design_ini( "enable_single_wtce", ao_app->get_theme_path() + cc_config_ini ) == "true" ) // courtroom_config.ini necessary + if (ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true" ) // courtroom_config.ini necessary { for(auto & wtce : ui_wtce) move_widget(wtce, "wtce"); qDebug() << "AA: single wtce"; @@ -712,95 +713,53 @@ void Courtroom::set_widgets() set_size_and_pos(ui_note_button, "note_button"); set_size_and_pos(ui_switch_area_music, "switch_area_music"); -// ui_switch_area_music->setText("A/M"); - if(ao_app->read_design_ini("enable_button_images", ao_app->get_theme_path() + cc_config_ini) == "true") + // Set the default values for the buttons, then try and determine if they should be replaced by images + ui_change_character->setText("Change character"); + ui_change_character->setStyleSheet(""); + + ui_reload_theme->setText("Reload theme"); + ui_reload_theme->setStyleSheet(""); + + ui_call_mod->setText("Call mod"); + ui_call_mod->setStyleSheet(""); + + ui_switch_area_music->setText("A/M"); + ui_switch_area_music->setStyleSheet(""); + + ui_confirm_theme->setText("^"); + ui_confirm_theme->setStyleSheet(""); + + ui_note_button->setText("><:"); + ui_note_button->setStyleSheet(""); + + if (ao_app->read_theme_ini("enable_button_images", cc_config_ini) == "true") { - if(file_exists(ao_app->get_theme_path() + "changecharacter.png")) - { - ui_change_character->set_image("changecharacter.png"); + // Set files, ask questions later + // set_image first tries the theme variant folder, then the theme folder, then falls back to the default theme + ui_change_character->set_image("changecharacter.png"); + if (!ui_change_character->image_path.isEmpty()) ui_change_character->setText(""); - } - else - { - ui_change_character->setStyleSheet(""); - ui_change_character->setText("Change character"); - } - if(file_exists(ao_app->get_theme_path() + "reloadtheme.png")) - { - ui_reload_theme->set_image("reloadtheme.png"); + ui_reload_theme->set_image("reloadtheme.png"); + if (!ui_reload_theme->image_path.isEmpty()) ui_reload_theme->setText(""); - } - else - { - ui_reload_theme->setStyleSheet(""); - ui_reload_theme->setText("Reload theme"); - } - if(file_exists(ao_app->get_theme_path() + "callmod.png")) - { - ui_call_mod->set_image("callmod.png"); + ui_call_mod->set_image("callmod.png"); + if (!ui_call_mod->image_path.isEmpty()) ui_call_mod->setText(""); - } - else - { - ui_call_mod->setStyleSheet(""); - ui_call_mod->setText("Call mod"); - } - if(file_exists(ao_app->get_theme_path() + "switch_area_music.png")) - { - ui_switch_area_music->set_image("switch_area_music.png"); + ui_switch_area_music->set_image("switch_area_music.png"); + if (!ui_switch_area_music->image_path.isEmpty()) ui_switch_area_music->setText(""); - } - else - { - ui_switch_area_music->setStyleSheet(""); - ui_switch_area_music->setText("A/M"); - } - if(file_exists(ao_app->get_theme_path() + "confirmtheme.png")) - { - ui_confirm_theme->set_image("confirmtheme.png"); + ui_confirm_theme->set_image("confirmtheme.png"); + if (!ui_confirm_theme->image_path.isEmpty()) ui_confirm_theme->setText(""); - } - else - { - ui_confirm_theme->setStyleSheet(""); - ui_confirm_theme->setText("^"); - } - if(file_exists(ao_app->get_theme_path() + "notebutton.png")) - { - ui_note_button->set_image("notebutton.png"); + ui_note_button->set_image("notebutton.png"); + if (!ui_note_button->image_path.isEmpty()) ui_note_button->setText(""); - } - else - { - ui_note_button->setStyleSheet(""); - ui_note_button->setText("><:"); - } - } - else - { - ui_change_character->setText("Change character"); - ui_change_character->setStyleSheet(""); - - ui_reload_theme->setText("Reload theme"); - ui_reload_theme->setStyleSheet(""); - - ui_call_mod->setText("Call mod"); - ui_call_mod->setStyleSheet(""); - - ui_switch_area_music->setText("A/M"); - ui_switch_area_music->setStyleSheet(""); - - ui_confirm_theme->setText("^"); - ui_confirm_theme->setStyleSheet(""); - - ui_note_button->setText("><:"); - ui_note_button->setStyleSheet(""); } set_size_and_pos(ui_theme_list, "theme_list"); @@ -819,39 +778,29 @@ void Courtroom::set_widgets() set_size_and_pos(ui_label_images[i], label_images[i].toLower() + "_image"); } - if(ao_app->read_design_ini("enable_label_images", ao_app->get_theme_path() + cc_config_ini) == "true") + if (ao_app->read_theme_ini("enable_label_images", cc_config_ini) == "true") { - for(int i = 0 ; i < ui_checks.size(); ++i) // loop through checks + for (int i = 0 ; i < ui_checks.size(); ++i) // loop through checks { QString image = label_images[i].toLower() + ".png"; - QString path = ao_app->get_theme_path() + image; - if(file_exists(path)) - { - ui_label_images[i]->set_image(image); + ui_label_images[i]->set_image(image); + + if (!ui_label_images[i]->image_path.isEmpty()) ui_checks[i]->setText(""); - } else - { - ui_label_images[i]->set_image(""); ui_checks[i]->setText(label_images[i]); - } } for(int i = 0 ; i < ui_labels.size(); ++i) // now through labels.......... { int j = i + ui_checks.size(); QString image = label_images[j].toLower() + ".png"; - QString path = ao_app->get_theme_path() + image; - if(file_exists(path)) - { - ui_label_images[j]->set_image(image); + ui_label_images[j]->set_image(image); + + if (!ui_label_images[j]->image_path.isEmpty()) ui_labels[i]->setText(""); - } else - { - ui_label_images[j]->set_image(""); ui_labels[i]->setText(label_images[j]); - } } } else @@ -1064,7 +1013,7 @@ void Courtroom::set_wtce() if(is_judge && ui_wtce.size() > 0) // check to prevent crashing { - if( ao_app->read_design_ini( "enable_single_wtce", ao_app->get_theme_path() + cc_config_ini ) == "true" ) + if( ao_app->read_theme_ini("enable_single_wtce", cc_config_ini ) == "true" ) { ui_wtce[m_wtce_current]->show(); ui_wtce_up->show(); @@ -1090,7 +1039,7 @@ void Courtroom::handle_music_anim() QFont f_font = ui_vp_music_name->font(); QFontMetrics fm(f_font); int dist; - if ( ao_app->read_design_ini( "enable_const_music_speed", ao_app->get_theme_path() + cc_config_ini ) == "true") + if ( ao_app->read_theme_ini( "enable_const_music_speed", cc_config_ini ) == "true") dist = res_b.width; else dist = fm.width(ui_vp_music_name->toPlainText()); int time = static_cast(1000000*dist/speed); @@ -1945,7 +1894,7 @@ void Courtroom::handle_chatmessage_2() // handles IC QString f_color = ao_app->read_char_ini(real_name, "color", "[Options]", "[Time]"); if (f_color == "") - f_color = "rgb(" + ao_app->read_design_ini("showname_color" , ao_app->get_theme_path() + "courtroom_fonts.ini") + ")"; + f_color = "rgb(" + ao_app->read_theme_ini("showname_color" , "courtroom_fonts.ini") + ")"; int bold = ao_app->get_font_size("showname_bold", fonts_ini); // is the font bold or not? // taken directly from function up there lol // kinda hacky QString is_bold = ""; @@ -1962,7 +1911,7 @@ void Courtroom::handle_chatmessage_2() // handles IC QString chatbox = ao_app->get_chat(m_chatmessage[CHAR_NAME]); if (chatbox == "") - if (ao_app->read_design_ini("daynight_theme", ao_app->get_theme_path() + cc_config_ini) == "true") + if (ao_app->read_theme_ini("daynight_theme", cc_config_ini) == "true") { if (current_clock < 0) ui_vp_chatbox->set_image("chatmed.png"); @@ -2061,7 +2010,7 @@ void Courtroom::handle_chatmessage_3() QVector exts = {".png", ".jpg", ".bmp"}; QString ext = file_exists(ao_app->get_character_path(f_char) + "showname", exts); - if(ext != "" && !chatmessage_is_empty && ao_app->read_design_ini("enable_showname_image", ao_app->get_theme_path() + cc_config_ini) == "true") + if(ext != "" && !chatmessage_is_empty && ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") { ui_vp_showname->hide(); QString path = ao_app->get_character_path(f_char) + "showname" + ext; @@ -2097,8 +2046,7 @@ void Courtroom::handle_chatmessage_3() QString overlay_name = overlay.at(0); QString overlay_sfx = overlay.at(1); - QString design_ini = ao_app->get_theme_path() + cc_config_ini; - bool do_it = ao_app->read_design_ini("non_vanilla_effects", design_ini) == "true"; + bool do_it = ao_app->read_theme_ini("non_vanilla_effects", cc_config_ini) == "true"; if (effect == 1 && !do_it) { @@ -2159,7 +2107,7 @@ void Courtroom::append_ic_text(QString p_text, QString p_name) { QTextCharFormat bold; QTextCharFormat normal; - QString color = ao_app->read_design_ini("chatlog_showname", ao_app->get_theme_path() + fonts_ini); + QString color = ao_app->read_theme_ini("chatlog_showname", fonts_ini); bold.setFontWeight(QFont::Bold); normal.setFontWeight(QFont::Normal); const QTextCursor old_cursor = ui_ic_chatlog->textCursor(); @@ -2248,7 +2196,7 @@ void Courtroom::append_system_text(QString p_text) QTextCharFormat bold; QTextCharFormat normal; - QString color = ao_app->read_design_ini("system_msg", ao_app->get_theme_path() + fonts_ini); + QString color = ao_app->read_theme_ini("system_msg", fonts_ini); bold.setFontWeight(QFont::Bold); normal.setFontWeight(QFont::Normal); const QTextCursor old_cursor = ui_ic_chatlog->textCursor(); @@ -2492,7 +2440,7 @@ void Courtroom::chat_tick() ui_vp_message->insertHtml("" + f_character + ""); } - else if(ao_app->read_design_ini("enable_highlighting", ao_app->get_theme_path() + cc_config_ini) == "true") + else if (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "true") { bool found = false; QVector f_vec = ao_app->get_highlight_color(); @@ -2523,7 +2471,7 @@ void Courtroom::chat_tick() } } -// else if(ao_app->read_design_ini("enable_highlighting", ao_app->get_theme_path() + cc_config_ini) == "extra") +// else if(ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "extra") // { // QString ch = parsed_message.at(tick_pos); // ui_vp_message->insertHtml(ch); @@ -2872,36 +2820,84 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) void Courtroom::check_shouts() { QString char_path = ao_app->get_character_path(current_char); + QString theme_variant_path = ao_app->get_theme_variant_path(); QString theme_path = ao_app->get_theme_path(); for(int i = 0; i < ui_shouts.size(); ++i) { - if(file_exists(char_path + shout_names.at(i) + ".gif") || file_exists(theme_path + shout_names.at(i) + ".gif") || file_exists(theme_path + shout_names.at(i) + ".apng")) - shouts_enabled[i] = true; - else shouts_enabled[i] = false; + QStringList paths{ + char_path + shout_names.at(i) + ".gif", + theme_variant_path + shout_names.at(i) + ".gif", + theme_variant_path + shout_names.at(i) + ".apng", + theme_path + shout_names.at(i) + ".gif", + theme_path + shout_names.at(i) + ".apng" + }; + + // Assume the shout does not exist until a matching file is found + shouts_enabled[i] = false; + for (QString path : paths) + { + if (file_exists(path)) + { + shouts_enabled[i] = true; + break; + } + } } } void Courtroom::check_effects() { QString char_path = ao_app->get_character_path(current_char); + QString theme_variant_path = ao_app->get_theme_variant_path(); QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < effect_names.size(); ++i) + for(int i = 0; i < ui_shouts.size(); ++i) { - if(file_exists(theme_path + effect_names.at(i) + ".gif") || file_exists(theme_path + effect_names.at(i) + ".apng")) - effects_enabled[i] = true; - else effects_enabled[i] = false; + QStringList paths{ + char_path + effect_names.at(i) + ".gif", + theme_variant_path + effect_names.at(i) + ".gif", + theme_variant_path + effect_names.at(i) + ".apng", + theme_path + effect_names.at(i) + ".gif", + theme_path + effect_names.at(i) + ".apng" + }; + + // Assume the effect does not exist until a matching file is found + effects_enabled[i] = false; + for (QString path : paths) + { + if (file_exists(path)) + { + effects_enabled[i] = true; + break; + } + } } } void Courtroom::check_wtce() { QString char_path = ao_app->get_character_path(current_char); + QString theme_variant_path = ao_app->get_theme_variant_path(); QString theme_path = ao_app->get_theme_path(); for(int i = 0; i < ui_wtce.size(); ++i) { - if(file_exists(char_path + wtce_names.at(i) + ".gif") || file_exists(theme_path + wtce_names.at(i) + ".gif") || file_exists(theme_path + wtce_names.at(i) + ".apng")) - wtce_enabled[i] = true; - else wtce_enabled[i] = false; + QStringList paths{ + char_path + wtce_names.at(i) + ".gif", + theme_variant_path + wtce_names.at(i) + ".gif", + theme_variant_path + wtce_names.at(i) + ".apng", + theme_path + wtce_names.at(i) + ".gif", + theme_path + wtce_names.at(i) + ".apng" + }; + + // Assume the judge button does not exist until a matching file is found + wtce_enabled[i] = false; + for (QString path : paths) + { + if (file_exists(path)) + { + wtce_enabled[i] = true; + break; + } + } } } @@ -3203,7 +3199,7 @@ void Courtroom::on_cycle_clicked() break; } - if(ao_app->read_design_ini("enable_cycle_ding", ao_app->get_theme_path() + cc_config_ini) == "true") + if (ao_app->read_theme_ini("enable_cycle_ding", cc_config_ini) == "true") m_cycle_player->play(ao_app->get_sfx("cycle")); set_shouts(); @@ -3236,9 +3232,10 @@ void Courtroom::cycle_wtce(int p_index) void Courtroom::reset_effect_buttons() { - for(int i = 0; i < effect_names.size(); ++i) // effect names does not necessarily have the same size as ui effects + // effect names does not necessarily have the same size as ui effects + for(int i = 0; i < effect_names.size(); ++i) { -// qDebug() << effect_names << i; + // qDebug() << effect_names << i; ui_effects[i]->set_image(effect_names.at(i) + ".png"); } diff --git a/courtroom.h b/courtroom.h index 27393ef21..5740d9b72 100644 --- a/courtroom.h +++ b/courtroom.h @@ -265,6 +265,7 @@ class Courtroom : public QMainWindow QString rpc_ini = "configs/rpccharlist.ini"; QString file_select_ini = "configs/filesabstract.ini"; QString shownames_ini = "configs/shownames.ini"; + //theme files locations QString design_ini = "courtroom_design.ini"; QString fonts_ini = "courtroom_fonts.ini"; diff --git a/path_functions.cpp b/path_functions.cpp index 6e772dbb7..0b2d25302 100644 --- a/path_functions.cpp +++ b/path_functions.cpp @@ -56,6 +56,12 @@ QString AOApplication::get_theme_path() return get_base_path() + "themes/" + current_theme.toLower() + "/"; } +QString AOApplication::get_theme_variant_path() +{ + return get_base_path() + "themes/" + current_theme.toLower() + "/" + + theme_variant.toLower() + "/"; +} + QString AOApplication::get_default_theme_path() { return get_base_path() + "themes/default/"; diff --git a/text_file_functions.cpp b/text_file_functions.cpp index 52676fc12..a9219b007 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -303,34 +303,23 @@ QString AOApplication::read_design_ini(QString p_identifier, QString p_design_pa int AOApplication::get_design_ini_value(QString p_identifier, QString p_file) { - QString design_ini_path = get_theme_path() + p_file; - QString result = read_design_ini(p_identifier, design_ini_path); - int r = result.toInt(); - if (result == "") r = 0; - return r; + QString result = read_theme_ini(p_identifier, p_file); + if (result.isEmpty()) + return 0; + return result.toInt(); } QPoint AOApplication::get_button_spacing(QString p_identifier, QString 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, design_ini_path); - QPoint return_value; - return_value.setX(0); return_value.setY(0); - if (f_result == "") - { - f_result = read_design_ini(p_identifier, default_path); - - if (f_result == "") - return return_value; - } + QString f_result = read_theme_ini(p_identifier, p_file); + if (f_result.isEmpty()) + return return_value; QStringList sub_line_elements = f_result.split(","); - if (sub_line_elements.size() < 2) return return_value; @@ -342,24 +331,15 @@ QPoint AOApplication::get_button_spacing(QString p_identifier, QString p_file) pos_size_type AOApplication::get_element_dimensions(QString p_identifier, QString 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, design_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, default_path); - - if (f_result == "") - return return_value; - } + QString f_result = read_theme_ini(p_identifier, p_file); + if (f_result.isEmpty()) + return return_value; QStringList sub_line_elements = f_result.split(","); @@ -376,36 +356,20 @@ pos_size_type AOApplication::get_element_dimensions(QString p_identifier, QStrin int AOApplication::get_font_size(QString p_identifier, QString 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, design_ini_path); - - if (f_result == "") - { - f_result = read_design_ini(p_identifier, default_path); - - if (f_result == "") - return 10; - } + QString f_result = read_theme_ini(p_identifier, p_file); + if (f_result.isEmpty()) + return 10; return f_result.toInt(); } QColor AOApplication::get_color(QString p_identifier, QString 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, design_ini_path); - QColor return_color(255, 255, 255); - if (f_result == "") - { - f_result = read_design_ini(p_identifier, default_path); - - if (f_result == "") - return return_color; - } + QString f_result = read_theme_ini(p_identifier, p_file); + if (f_result.isEmpty()) + return return_color; QStringList color_list = f_result.split(","); @@ -421,242 +385,233 @@ QColor AOApplication::get_color(QString p_identifier, QString p_file) QString AOApplication::get_font_name(QString p_identifier, QString 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, design_ini_path); + QString f_result = read_theme_ini(p_identifier, p_file); - if(f_result == "") - { - f_result = read_design_ini(p_identifier, default_path); + if (f_result.isEmpty()) + qDebug() << "Failure retreiving font name"; - if(f_result == "") - { - qDebug() << "Failure retreiving font name"; - return f_result; - } - } + return f_result; +} - return f_result; +QString AOApplication::get_sfx(QString p_identifier) +{ + return read_theme_ini(p_identifier, "courtroom_sounds.ini"); } QString AOApplication::get_stylesheet(QString target_tag, QString p_file) { - QString design_ini_path = get_theme_path() + p_file; + QStringList paths{get_theme_variant_path() + p_file, get_theme_path() + p_file}; - QFile design_ini; - - design_ini.setFileName(design_ini_path); - - if(!design_ini.open(QIODevice::ReadOnly)) - return ""; - - QTextStream in(&design_ini); - - QString f_text; - - bool tag_found = false; - - while(!in.atEnd()) + for (QString path : paths) { - QString line = in.readLine(); + QFile design_ini; + design_ini.setFileName(path); + if (!design_ini.open(QIODevice::ReadOnly)) + continue; - if (line.startsWith(target_tag, Qt::CaseInsensitive)) + QTextStream in(&design_ini); + QString f_text; + bool tag_found = false; + + while (!in.atEnd()) { - tag_found = true; - continue; - } + QString line = in.readLine(); + if (line.startsWith(target_tag, Qt::CaseInsensitive)) + { + tag_found = true; + continue; + } - if(tag_found) + if (tag_found) { - if((line.startsWith("[") && line.endsWith("]"))) - break; + if ((line.startsWith("[") && line.endsWith("]"))) + break; f_text.append(line); } + } + + design_ini.close(); + if (!f_text.isEmpty()) + return f_text; } - design_ini.close(); - return f_text; + // Default value in case everything fails, return an empty string + return ""; } QVector AOApplication::get_highlight_color() { - QString design_ini_path = get_theme_path() + "courtroom_config.ini"; - - QFile design_ini; - - design_ini.setFileName(design_ini_path); - - QVector f_vec; - - if(!design_ini.open(QIODevice::ReadOnly)) - return f_vec; + QString p_file = "courtroom_config.ini"; + QStringList paths{get_theme_variant_path() + p_file, get_theme_path() + p_file}; - QTextStream in(&design_ini); + for (QString path : paths) + { + QVector f_vec; - bool tag_found = false; + QFile design_ini; + design_ini.setFileName(path); + if (!design_ini.open(QIODevice::ReadOnly)) + continue; - while(!in.atEnd()) - { - QString line = in.readLine(); + QTextStream in(&design_ini); + bool tag_found = false; - if (line.startsWith("[HIGHLIGHTS]", Qt::CaseInsensitive)) + while (!in.atEnd()) { - tag_found = true; - continue; - } + QString line = in.readLine(); - if(tag_found) + if (line.startsWith("[HIGHLIGHTS]", Qt::CaseInsensitive)) + { + tag_found = true; + continue; + } + + if (tag_found) { if((line.startsWith("[") && line.endsWith("]"))) - break; + break; f_vec.append(line.split("=")); } + } + + design_ini.close(); + if (!f_vec.isEmpty()) + return f_vec; } - design_ini.close(); + // Default value in case everything fails, return an empty vector + QVector f_vec; return f_vec; } QString AOApplication::get_spbutton(QString p_tag, int index) { - QString design_ini_path = get_theme_path() + "courtroom_config.ini"; - - QFile design_ini; - - design_ini.setFileName(design_ini_path); - - QString res = ""; + QString p_file = "courtroom_config.ini"; + QStringList paths{get_theme_variant_path() + p_file, get_theme_path() + p_file}; - if(!design_ini.open(QIODevice::ReadOnly)) - return res; + for (QString path : paths) + { + QString res = ""; - QTextStream in(&design_ini); + QFile design_ini; + design_ini.setFileName(path); + if (!design_ini.open(QIODevice::ReadOnly)) + continue; - bool tag_found = false; + QTextStream in(&design_ini); + bool tag_found = false; - while(!in.atEnd()) - { - QString line = in.readLine(); - - if(line.startsWith(p_tag, Qt::CaseInsensitive)) + while (!in.atEnd()) { - tag_found = true; - continue; - } + QString line = in.readLine(); - if(tag_found) + if (line.startsWith(p_tag, Qt::CaseInsensitive)) { - if((line.startsWith("[") && line.endsWith("]"))) + tag_found = true; + continue; + } + + if (tag_found) + { + if ((line.startsWith("[") && line.endsWith("]"))) break; QStringList line_contents = line.split("="); - if(line_contents.at(0).trimmed() == QString::number(index)) + if (line_contents.at(0).trimmed() == QString::number(index)) res = line_contents.at(1); } + } + + design_ini.close(); + if (!res.isEmpty()) + return res; } - design_ini.close(); - return res; + // Default value in case everything fails, return an empty string + return ""; } QStringList AOApplication::get_effect(int index) { - QString design_ini_path = get_theme_path() + "courtroom_config.ini"; - - QFile design_ini; - - design_ini.setFileName(design_ini_path); + QString p_file = "courtroom_config.ini"; + QStringList paths{get_theme_variant_path() + p_file, get_theme_path() + p_file}; - QStringList res; - - if(!design_ini.open(QIODevice::ReadOnly)) - return res; - - QTextStream in(&design_ini); + for (QString path : paths) + { + QStringList res; - bool tag_found = false; + QFile design_ini; + design_ini.setFileName(path); + if (!design_ini.open(QIODevice::ReadOnly)) + continue; - while(!in.atEnd()) - { - QString line = in.readLine(); + QTextStream in(&design_ini); + bool tag_found = false; - if(line.startsWith("[EFFECTS]", Qt::CaseInsensitive)) + while (!in.atEnd()) { - tag_found = true; - continue; - } + QString line = in.readLine(); - if(tag_found) + if (line.startsWith("[EFFECTS]", Qt::CaseInsensitive)) { - if((line.startsWith("[") && line.endsWith("]"))) - break; + tag_found = true; + continue; + } + + if (tag_found) + { + if ((line.startsWith("[") && line.endsWith("]"))) + break; QStringList line_contents = line.split("="); - if(line_contents.at(0).trimmed() == QString::number(index)) + if (line_contents.at(0).trimmed() == QString::number(index)) res = line_contents.at(1).split(","); - if(res.size() == 1) + if (res.size() == 1) res.append("1"); } + } + + design_ini.close(); + if (!res.isEmpty()) + return res; } - design_ini.close(); + // Default value in case everything fails, return an empty string list + QStringList res; return res; } -QString AOApplication::get_sfx(QString p_identifier) +QStringList AOApplication::get_sfx_list() { - 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 return_value; + QFile base_sfx_list_ini; + QFile char_sfx_list_ini; - QString return_sfx = ""; + base_sfx_list_ini.setFileName(get_base_path() + "configs/sounds.ini"); + char_sfx_list_ini.setFileName(get_character_path(get_current_char()) + "sounds.ini"); - if (f_result == "") + if (!char_sfx_list_ini.open(QIODevice::ReadOnly) && !base_sfx_list_ini.open(QIODevice::ReadOnly)) { - f_result = read_design_ini(p_identifier, default_path); - - if (f_result == "") - return return_sfx; + return return_value; } - return_sfx = f_result; - - return return_sfx; -} - -QStringList AOApplication::get_sfx_list() -{ - QStringList return_value; - - QFile base_sfx_list_ini; - - QFile char_sfx_list_ini; + QTextStream in_a(&base_sfx_list_ini); + QTextStream in_b(&char_sfx_list_ini); - base_sfx_list_ini.setFileName(get_base_path() + "configs/sounds.ini"); - char_sfx_list_ini.setFileName(get_character_path(get_current_char()) + "sounds.ini"); - - if (!char_sfx_list_ini.open(QIODevice::ReadOnly) && !base_sfx_list_ini.open(QIODevice::ReadOnly)) - { - return return_value; - } - - QTextStream in_a(&base_sfx_list_ini); - QTextStream in_b(&char_sfx_list_ini); - - while (!in_a.atEnd()) - { - QString line = in_a.readLine(); - return_value.append(line); - } + while (!in_a.atEnd()) + { + QString line = in_a.readLine(); + return_value.append(line); + } - while (!in_b.atEnd()) - { - QString line = in_b.readLine(); - return_value.append(line); - } + while (!in_b.atEnd()) + { + QString line = in_b.readLine(); + return_value.append(line); + } - return return_value; + return return_value; } //returns whatever is to the right of "search_line =" within target_tag and terminator_tag, trimmed @@ -938,8 +893,41 @@ bool AOApplication::get_blank_blip() return f_result.startsWith("true"); } +QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) +{ + // Try to obtain a theme ini from either the current theme variant folder, + // the current theme folder or the default theme folder + QString variant_design_ini_path = get_theme_variant_path() + 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, variant_design_ini_path); + if (!f_result.isEmpty()) + return f_result; + + f_result = read_design_ini(p_identifier, design_ini_path); + if (!f_result.isEmpty()) + return f_result; + + f_result = read_design_ini(p_identifier, default_path); + if (!f_result.isEmpty()) + return f_result; + return ""; +} +QString AOApplication::get_image_path(QString p_image) +{ + QString theme_variant_image_path = get_theme_variant_path() + p_image; + QString theme_image_path = get_theme_path() + p_image; + QString default_image_path = get_default_theme_path() + p_image; + QString final_image_path; + if (file_exists(theme_variant_image_path)) + return theme_variant_image_path; + else if (file_exists(theme_image_path)) + return theme_image_path; + return default_image_path; +} From 4da2f5d686b70f886f79975b61df8976e21b3c1f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 31 Jan 2020 06:15:08 -0500 Subject: [PATCH 004/842] Fix charmovie and movie using wrong paths for files --- aocharmovie.cpp | 30 ++++++++++-------------------- aomovie.cpp | 24 +++++++++++++----------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/aocharmovie.cpp b/aocharmovie.cpp index e0876fd48..9f1be7e2f 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -23,27 +23,17 @@ AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_ void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bool show) { - QStringList f_vec; - - QString original_path = ao_app->get_character_path(p_char) + emote_prefix + p_emote.toLower(); // .gif - QString alt_path = ao_app->get_character_path(p_char) + p_emote.toLower(); // .png - QString placeholder_path = ao_app->get_image_path("placeholder"); // .gif QString gif_path; - -// if (file_exists(original_path)) -// gif_path = original_path; -// else if (file_exists(alt_path)) -// gif_path = alt_path; -// else if (file_exists(placeholder_path)) -// gif_path = placeholder_path; -// else -// gif_path = placeholder_default_path; - - f_vec.push_back(original_path); - f_vec.push_back(alt_path); - f_vec.push_back(placeholder_path); - - for(auto &f_file : f_vec) + QStringList f_vec; + QStringList f_paths{ + ao_app->get_character_path(p_char) + emote_prefix + p_emote.toLower(), // .gif + ao_app->get_character_path(p_char) + p_emote.toLower(), // .png + ao_app->get_theme_variant_path() + "placeholder", // .gif + ao_app->get_theme_path() + "placeholder", // .gif + ao_app->get_default_theme_path() + "placeholder" // .gif + }; + + for(auto &f_file : f_paths) { bool found = false; for (auto &ext : decltype(f_vec){".apng", ".gif", ".png"}) diff --git a/aomovie.cpp b/aomovie.cpp index 90385ad40..12379acba 100644 --- a/aomovie.cpp +++ b/aomovie.cpp @@ -36,17 +36,18 @@ void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) f_vec.push_back(custom_path); - QString overlay_path = ao_app->get_character_path(p_char) + "overlay/" + p_file; - QString custom_theme_path = ao_app->get_base_path() + "themes/" + p_custom_theme + "/" + p_file; - QString some_theme_path = ao_app->get_image_path(p_file); - QString some_placeholder_path = ao_app->get_image_path("placeholder"); - - f_vec.push_back(overlay_path); - f_vec.push_back(custom_theme_path); - f_vec.push_back(some_theme_path); - f_vec.push_back(some_placeholder_path); - - for(auto &f_file : f_vec) + QStringList f_paths{ + ao_app->get_character_path(p_char) + "overlay/" + p_file, + ao_app->get_base_path() + "themes/" + p_custom_theme + "/" + p_file, + ao_app->get_theme_variant_path() + p_file, + ao_app->get_theme_path() + p_file, + ao_app->get_default_theme_path() + p_file, + ao_app->get_theme_variant_path() + "placeholder", + ao_app->get_theme_path() + "placeholder", + ao_app->get_default_theme_path() + "placeholder" + }; + + for(auto &f_file : f_paths) { bool found = false; for (auto &ext : decltype(f_vec){".apng", ".gif", ".png"}) @@ -64,6 +65,7 @@ void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) break; } + qDebug() << file_path; m_movie->setFileName(file_path); this->show(); From a7096e54f7af06a7a0e1e5e75eaffaa4ffc6e652 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 31 Jan 2020 21:38:54 -0500 Subject: [PATCH 005/842] Add support for client command /variant+"VA" packets force variant change --- aoapplication.cpp | 5 +++++ aoapplication.h | 4 +++- courtroom.cpp | 19 +++++++++++++++++++ courtroom.h | 3 +++ packet_distribution.cpp | 7 +++++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/aoapplication.cpp b/aoapplication.cpp index 35259bc2f..707391c46 100644 --- a/aoapplication.cpp +++ b/aoapplication.cpp @@ -99,6 +99,11 @@ void AOApplication::reload_theme() current_theme = read_theme(); } +void AOApplication::set_theme_variant(QString theme_variant) +{ + this->theme_variant = theme_variant; +} + void AOApplication::set_favorite_list() { favorite_list = read_serverlist_txt(); diff --git a/aoapplication.h b/aoapplication.h index 138a55dae..383d421ba 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -150,6 +150,8 @@ class AOApplication : public QApplication //Overwrites config.ini with new theme void write_theme(QString theme); + //Set the theme variant + void set_theme_variant(QString theme_variant); //Returns the contents of serverlist.txt QVector read_serverlist_txt(); @@ -268,7 +270,7 @@ class AOApplication : public QApplication const int MINOR_VERSION = 8; QString current_theme = "default"; - QString theme_variant = "dr2"; + QString theme_variant = ""; QVector server_list; QVector favorite_list; diff --git a/courtroom.cpp b/courtroom.cpp index 810c03e93..102f1001f 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1059,6 +1059,12 @@ void Courtroom::handle_clock(QString time) ui_vp_clock->play(string); } +void Courtroom::handle_theme_variant(QString theme_variant) +{ + ao_app->set_theme_variant(theme_variant); + on_reload_theme_clicked(); +} + void Courtroom::set_window_title(QString p_title) { this->setWindowTitle(p_title); @@ -2968,6 +2974,19 @@ void Courtroom::on_ooc_return_pressed() { m_sfx_player->play(ao_app->get_sfx("coinflip")); } + else if (ooc_message.startsWith("/variant")) + { + int space_location = ooc_message.indexOf(" "); + QString variant; + + if (space_location == -1) + variant = ""; + else + variant = ooc_message.mid(space_location+1); + + handle_theme_variant(variant); + } + QStringList packet_contents; packet_contents.append(ui_ooc_chat_name->text()); packet_contents.append(ooc_message); diff --git a/courtroom.h b/courtroom.h index 5740d9b72..3c0222371 100644 --- a/courtroom.h +++ b/courtroom.h @@ -184,6 +184,9 @@ class Courtroom : public QMainWindow //handle server-side clock animation and display void handle_clock(QString time); + //handle request to change theme variant + void handle_theme_variant(QString theme_variant); + void play_preanim(); QString parse_message(QString message); diff --git a/packet_distribution.cpp b/packet_distribution.cpp index a428a7fde..5e1312f45 100644 --- a/packet_distribution.cpp +++ b/packet_distribution.cpp @@ -665,6 +665,13 @@ void AOApplication::server_packet_received(AOPacket *p_packet) { w_courtroom->handle_clock(f_contents.at(1)); } + else if (header == "VA") + { + if (courtroom_constructed) + { + w_courtroom->handle_theme_variant(f_contents.at(0)); + } + } end: From 337cde9e4f43063e78ff91eab335c2daa5d998fd Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 22 Feb 2020 19:24:43 -0500 Subject: [PATCH 006/842] Add ! tag support for removing shouts/wtce/effects --- aomovie.cpp | 11 ++- courtroom.cpp | 235 +++++++++++++++++++++++++++++--------------------- courtroom.h | 7 +- 3 files changed, 147 insertions(+), 106 deletions(-) diff --git a/aomovie.cpp b/aomovie.cpp index 12379acba..196a0a37e 100644 --- a/aomovie.cpp +++ b/aomovie.cpp @@ -23,20 +23,23 @@ void AOMovie::set_play_once(bool p_play_once) void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) { m_movie->stop(); - QVector f_vec; - QString file_path = ""; + // Remove ! at the beginning of p_file if needed + // This is an indicator that the file is not selectable in the current theme (variant) but + // is still usable by other people + if (p_file.length() > 0 && p_file.at(0) == "!") + p_file = p_file.remove(0, 1); + QString custom_path; if (p_file == "custom") custom_path = ao_app->get_character_path(p_char) + p_file; else custom_path = ao_app->get_character_path(p_char) + p_file + "_bubble"; - f_vec.push_back(custom_path); - QStringList f_paths{ + custom_path, ao_app->get_character_path(p_char) + "overlay/" + p_file, ao_app->get_base_path() + "themes/" + p_custom_theme + "/" + p_file, ao_app->get_theme_variant_path() + p_file, diff --git a/courtroom.cpp b/courtroom.cpp index 102f1001f..f836c79bf 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -174,22 +174,27 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_sfx_label = new AOLabel(this, ao_app); ui_blip_label = new AOLabel(this, ao_app); - - int shout_number = ao_app->get_design_ini_value("shout_number", cc_config_ini); - shouts_enabled.resize(shout_number); - ui_shouts.resize(shout_number); - - for(int i = 0; i < ui_shouts.size(); ++i) - { - ui_shouts[i] = new AOButton(this, ao_app); - ui_shouts[i]->setProperty("shout_id", i+1); - } + load_shouts(); // Reads from theme, deletes old shouts if needed and creates new ones ui_shout_up = new AOButton(this, ao_app); ui_shout_up->setProperty("cycle_id", 1); ui_shout_down = new AOButton(this, ao_app); ui_shout_down->setProperty("cycle_id", 0); + load_effects(); + + ui_effect_down = new AOButton(this, ao_app); + ui_effect_down->setProperty("cycle_id", 2); + ui_effect_up = new AOButton(this, ao_app); + ui_effect_up->setProperty("cycle_id", 3); + + load_wtce(); + + ui_wtce_up = new AOButton(this, ao_app); + ui_wtce_up->setProperty("cycle_id", 5); + ui_wtce_down = new AOButton(this, ao_app); + ui_wtce_down->setProperty("cycle_id", 4); + ui_ooc_toggle = new AOButton(this, ao_app); ui_change_character = new AOButton(this, ao_app); @@ -228,35 +233,6 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_labels.push_back(ui_blip_label); // - int effect_number = ao_app->get_design_ini_value("effect_number", cc_config_ini); - effects_enabled.resize(effect_number); - ui_effects.resize(effect_number); - for(int i = 0; i < ui_effects.size(); ++i) - { - ui_effects[i] = new AOButton(this, ao_app); - ui_effects[i]->setProperty("effect_id", i+1); - } - - ui_effect_down = new AOButton(this, ao_app); - ui_effect_down->setProperty("cycle_id", 2); - ui_effect_up = new AOButton(this, ao_app); - ui_effect_up->setProperty("cycle_id", 3); - - int wtce_number = ao_app->get_design_ini_value("wtce_number", cc_config_ini); - wtce_enabled.resize(wtce_number); - ui_wtce.resize(wtce_number); - - for(int i = 0; i < ui_wtce.size(); ++i) - { - ui_wtce[i] = new AOButton(this, ao_app); - ui_wtce[i]->setProperty("wtce_id", i+1); - } - - ui_wtce_up = new AOButton(this, ao_app); - ui_wtce_up->setProperty("cycle_id", 5); - ui_wtce_down = new AOButton(this, ao_app); - ui_wtce_down->setProperty("cycle_id", 4); - ui_mute = new AOButton(this, ao_app); ui_defense_plus = new AOButton(this, ao_app); @@ -328,21 +304,16 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_music_list_double_clicked(QModelIndex))); connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_area_list_double_clicked(QModelIndex))); - for(auto & shout : ui_shouts) - connect(shout, SIGNAL(clicked(bool)), this, SLOT(on_shout_clicked())); + // connection for buttons now happen in load_shouts(), load_effects(), load_wtce() + // for(auto & shout : ui_shouts) + // connect(shout, SIGNAL(clicked(bool)), this, SLOT(on_shout_clicked())); connect(ui_shout_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_shout_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - for(auto & effect : ui_effects) - connect(effect, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked())); - connect(ui_effect_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_effect_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - for(auto & wtce : ui_wtce) - connect(wtce, SIGNAL(clicked(bool)), this, SLOT(on_wtce_clicked())); - connect(ui_wtce_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_wtce_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); @@ -705,7 +676,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_call_mod, "call_mod"); - set_size_and_pos(ui_change_character, "change_character"); set_size_and_pos(ui_reload_theme, "reload_theme"); set_size_and_pos(ui_call_mod, "call_mod"); @@ -995,6 +965,87 @@ void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) } } +void Courtroom::load_shouts() +{ + // Close any existing shouts to prevent memory leaks + for (int i=0; iclose(); + delete ui_shouts[i]; + } + + // And create new shouts + int shout_number = ao_app->get_design_ini_value("shout_number", cc_config_ini); + shouts_enabled.resize(shout_number); + ui_shouts.resize(shout_number); + + for (int i=0; isetProperty("shout_id", i+1); + ui_shouts[i]->stackUnder(ui_shout_up); + ui_shouts[i]->stackUnder(ui_shout_down); + } + + // And connect their actions + for (auto & shout : ui_shouts) + connect(shout, SIGNAL(clicked(bool)), this, SLOT(on_shout_clicked())); +} + +void Courtroom::load_effects() +{ + // Close any existing effects to prevent memory leaks + for (int i=0; iclose(); + delete ui_effects[i]; + } + + // And create new effects + int effect_number = ao_app->get_design_ini_value("effect_number", cc_config_ini); + effects_enabled.resize(effect_number); + ui_effects.resize(effect_number); + + for (int i=0; isetProperty("effect_id", i+1); + ui_effects[i]->stackUnder(ui_effect_up); + ui_effects[i]->stackUnder(ui_effect_down); + } + + // And connect their actions + for (auto & effect : ui_effects) + connect(effect, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked())); +} + +void Courtroom::load_wtce() +{ + // Close any existing wtce buttons to prevent memory leaks + for (int i=0; iclose(); + delete ui_wtce[i]; + } + + // And create new wtce buttons + int wtce_number = ao_app->get_design_ini_value("wtce_number", cc_config_ini); + wtce_enabled.resize(wtce_number); + ui_wtce.resize(wtce_number); + + for (int i=0; isetProperty("wtce_id", i+1); + ui_wtce[i]->stackUnder(ui_wtce_up); + ui_wtce[i]->stackUnder(ui_wtce_down); + } + + // And connect their actions + for (auto & wtce : ui_wtce) + connect(wtce, SIGNAL(clicked(bool)), this, SLOT(on_wtce_clicked())); +} + void Courtroom::set_shouts() { for(auto & shout : ui_shouts) shout->hide(); @@ -1271,55 +1322,28 @@ void Courtroom::enter_courtroom(int p_cid) ui_prosecution_plus->hide(); } - check_shouts(); - check_effects(); - check_wtce(); - - if (ao_app->custom_objection_enabled) - { + // Update widgets first, then check if everything is valid + // This will also handle showing the correct shouts, effects and wtce buttons, and cycling + // through them if the buttons that are supposed to be displayed do not exist + qDebug() << "setting widgets"; + set_widgets(); - for(int i = 0; i < ui_shouts.size(); ++i) - { - if(shouts_enabled[i]) - { - ui_shouts[i]->show(); - } - else - { - ui_shouts[i]->hide(); - } - } - } - else - { - for(int i = 3; i < ui_shouts.size(); i++) // the non-official shouts - ui_shouts[i]->hide(); - } + qDebug() << "checking shouts"; + check_shouts(); + if (!shouts_enabled[m_shout_state]) + cycle_shout(1); - for(int i = 0; i < ui_effects.size(); ++i) - { - if(effects_enabled[i]) - { - ui_effects[i]->show(); - } - else - { - ui_effects[i]->hide(); - } - } + qDebug() << "checking effects"; + check_effects(); + if (!effects_enabled[m_effect_current]) + cycle_effect(1); - for(int i = 0; i < ui_wtce.size(); ++i) - { - if(wtce_enabled[i] && is_judge) - { - ui_wtce[i]->show(); - } - else - { - ui_wtce[i]->hide(); - } - } + qDebug() << "checking wtce"; + check_wtce(); + if (is_judge && !wtce_enabled[m_wtce_current]) + cycle_wtce(1); + qDebug() << "checked all"; if (ao_app->flipping_enabled) ui_flip->show(); else @@ -1340,8 +1364,6 @@ void Courtroom::enter_courtroom(int p_cid) testimony_in_progress = false; - set_widgets(); - //ui_server_chatlog->setHtml(ui_server_chatlog->toHtml()); ui_char_select_background->hide(); @@ -2901,9 +2923,12 @@ void Courtroom::check_wtce() if (file_exists(path)) { wtce_enabled[i] = true; + qDebug() << i << " judge button exists " << path; break; } + qDebug() << " does not exist " << path; } + qDebug() << i << " no judge button for " << wtce_names.at(i); } } @@ -2986,6 +3011,11 @@ void Courtroom::on_ooc_return_pressed() handle_theme_variant(variant); } + else if (ooc_message.startsWith("/delbullet")) { + int last = ui_shouts.size()-1; + ui_shouts[last]->close(); + ui_shouts.resize(last); + } QStringList packet_contents; packet_contents.append(ui_ooc_chat_name->text()); @@ -3244,7 +3274,10 @@ void Courtroom::cycle_effect(int p_index) void Courtroom::cycle_wtce(int p_index) { int n = ui_wtce.size(); - do { m_wtce_current = (m_wtce_current - p_index + n) % n; } while( !wtce_enabled[m_wtce_current] ); + do { + m_wtce_current = (m_wtce_current - p_index + n) % n; + //qDebug() << m_wtce_current; + } while( !wtce_enabled[m_wtce_current] ); set_wtce(); } @@ -3409,11 +3442,17 @@ void Courtroom::on_change_character_clicked() void Courtroom::on_reload_theme_clicked() { ao_app->reload_theme(); - + qDebug() << "loading shouts"; + load_shouts(); + qDebug() << "loading effects"; + load_effects(); + qDebug() << "loading wtce"; + load_wtce(); + qDebug() << "loaded everything"; //to update status on the background set_background(current_background); enter_courtroom(m_cid); - + qDebug() << "entered courtroom"; anim_state = 4; text_state = 3; } diff --git a/courtroom.h b/courtroom.h index 3c0222371..017dbbf71 100644 --- a/courtroom.h +++ b/courtroom.h @@ -655,19 +655,18 @@ private slots: void on_cycle_clicked(); void cycle_shout(int p_index); - void cycle_effect(int p_index); - void cycle_wtce(int p_index); void on_add_button_clicked(); - void on_delete_button_clicked(); void on_set_file_button_clicked(); - void on_file_selected(); + void load_shouts(); + void load_effects(); + void load_wtce(); /** * @brief reset the shout button's texture to default * DOES NOT MODIFY OBJECTION_STATE From d48ab720aaf415efb8e56a0e3f38b91d05465cc7 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 22 Feb 2020 19:32:09 -0500 Subject: [PATCH 007/842] Remove leftover debugging messages and code --- courtroom.cpp | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index f836c79bf..15ab5bc26 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -304,10 +304,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_music_list_double_clicked(QModelIndex))); connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_area_list_double_clicked(QModelIndex))); - // connection for buttons now happen in load_shouts(), load_effects(), load_wtce() - // for(auto & shout : ui_shouts) - // connect(shout, SIGNAL(clicked(bool)), this, SLOT(on_shout_clicked())); - + // connect events for shout/effect/wtce buttons happen in load_shouts(), load_effects(), load_wtce() connect(ui_shout_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_shout_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); @@ -1325,25 +1322,20 @@ void Courtroom::enter_courtroom(int p_cid) // Update widgets first, then check if everything is valid // This will also handle showing the correct shouts, effects and wtce buttons, and cycling // through them if the buttons that are supposed to be displayed do not exist - qDebug() << "setting widgets"; set_widgets(); - qDebug() << "checking shouts"; check_shouts(); if (!shouts_enabled[m_shout_state]) cycle_shout(1); - qDebug() << "checking effects"; check_effects(); if (!effects_enabled[m_effect_current]) cycle_effect(1); - qDebug() << "checking wtce"; check_wtce(); if (is_judge && !wtce_enabled[m_wtce_current]) cycle_wtce(1); - qDebug() << "checked all"; if (ao_app->flipping_enabled) ui_flip->show(); else @@ -2923,12 +2915,9 @@ void Courtroom::check_wtce() if (file_exists(path)) { wtce_enabled[i] = true; - qDebug() << i << " judge button exists " << path; break; } - qDebug() << " does not exist " << path; } - qDebug() << i << " no judge button for " << wtce_names.at(i); } } @@ -3011,11 +3000,6 @@ void Courtroom::on_ooc_return_pressed() handle_theme_variant(variant); } - else if (ooc_message.startsWith("/delbullet")) { - int last = ui_shouts.size()-1; - ui_shouts[last]->close(); - ui_shouts.resize(last); - } QStringList packet_contents; packet_contents.append(ui_ooc_chat_name->text()); @@ -3274,10 +3258,7 @@ void Courtroom::cycle_effect(int p_index) void Courtroom::cycle_wtce(int p_index) { int n = ui_wtce.size(); - do { - m_wtce_current = (m_wtce_current - p_index + n) % n; - //qDebug() << m_wtce_current; - } while( !wtce_enabled[m_wtce_current] ); + do { m_wtce_current = (m_wtce_current - p_index + n) % n; } while ( !wtce_enabled[m_wtce_current] ); set_wtce(); } @@ -3442,17 +3423,13 @@ void Courtroom::on_change_character_clicked() void Courtroom::on_reload_theme_clicked() { ao_app->reload_theme(); - qDebug() << "loading shouts"; load_shouts(); - qDebug() << "loading effects"; load_effects(); - qDebug() << "loading wtce"; load_wtce(); - qDebug() << "loaded everything"; + //to update status on the background set_background(current_background); enter_courtroom(m_cid); - qDebug() << "entered courtroom"; anim_state = 4; text_state = 3; } From 3da129f2ccb20b6147b27d540ab1849892adcdac Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 23 Feb 2020 09:48:31 -0500 Subject: [PATCH 008/842] Add text outline support for VP messages. Enable with enable_vp_outline=true --- courtroom.cpp | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 15ab5bc26..0978daa60 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -2378,6 +2378,7 @@ QString Courtroom::parse_message(QString message) void Courtroom::start_chat_ticking() { ui_vp_message->clear(); + set_text_color(); rainbow_counter = 0; //we need to ensure that the text isn't already ticking because this function can be called by two logic paths @@ -2410,6 +2411,9 @@ void Courtroom::chat_tick() { //note: this is called fairly often(every 60 ms when char is talking) //do not perform heavy operations here + QTextCharFormat vp_message_format = ui_vp_message->currentCharFormat(); + if (ao_app->read_theme_ini("enable_vp_outline", cc_config_ini) == "true") + vp_message_format.setTextOutline(QPen(Qt::black, 1)); QString f_message = m_chatmessage[MESSAGE]; // QString parsed_message = parse_message(f_message); @@ -2457,8 +2461,12 @@ void Courtroom::chat_tick() } ++rainbow_counter; + // Apply color to the next character + QColor text_color; + text_color.setNamedColor(html_color); + vp_message_format.setForeground(text_color); - ui_vp_message->insertHtml("" + f_character + ""); + ui_vp_message->textCursor().insertText(f_character, vp_message_format); } else if (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "true") { @@ -2477,8 +2485,17 @@ void Courtroom::chat_tick() } } - //qDebug() << "character = " << f_character << "color=" << m_string_color; - ui_vp_message->insertHtml("" + f_character + ""); + // Apply color to the next character + if (m_string_color.isEmpty()) + vp_message_format.setForeground(Qt::white); + else + { + QColor textColor; + textColor.setNamedColor(m_string_color); + vp_message_format.setForeground(textColor); + } + + ui_vp_message->textCursor().insertText(f_character, vp_message_format); for(const auto& col : f_vec) { @@ -2488,18 +2505,13 @@ void Courtroom::chat_tick() m_string_color = m_color_stack.top(); break; } - } } -// else if(ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "extra") -// { -// QString ch = parsed_message.at(tick_pos); -// ui_vp_message->insertHtml(ch); -// } else { - ui_vp_message->insertHtml(f_character); + ui_vp_message->textCursor().insertText(f_character, vp_message_format); } + QScrollBar *scroll = ui_vp_message->verticalScrollBar(); scroll->setValue(scroll->maximum()); From 595cea0d0ec21a63832d39841e37521871610069 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 23 Feb 2020 10:46:59 -0500 Subject: [PATCH 009/842] Fix outlines not disappearing after switching to theme with no outlines --- courtroom.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/courtroom.cpp b/courtroom.cpp index 0978daa60..e51bb0332 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -2414,6 +2414,8 @@ void Courtroom::chat_tick() QTextCharFormat vp_message_format = ui_vp_message->currentCharFormat(); if (ao_app->read_theme_ini("enable_vp_outline", cc_config_ini) == "true") vp_message_format.setTextOutline(QPen(Qt::black, 1)); + else + vp_message_format.setTextOutline(Qt::NoPen); QString f_message = m_chatmessage[MESSAGE]; // QString parsed_message = parse_message(f_message); From 89257aee1daf2a0f76bae1e4d96e0c48545bc55a Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 26 Feb 2020 23:00:58 -0500 Subject: [PATCH 010/842] Fix color option in itembox being discarded --- courtroom.cpp | 45 ++++++++++++++++++++++++++------------------- courtroom.h | 1 + 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index e51bb0332..eb970e70e 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -2472,7 +2472,7 @@ void Courtroom::chat_tick() } else if (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "true") { - bool found = false; + bool highlight_found = false; QVector f_vec = ao_app->get_highlight_color(); if(m_color_stack.isEmpty()) m_color_stack.push(""); @@ -2482,14 +2482,14 @@ void Courtroom::chat_tick() { m_color_stack.push(col[1].trimmed()); m_string_color = m_color_stack.top(); - found = true; + highlight_found = true; break; } } // Apply color to the next character if (m_string_color.isEmpty()) - vp_message_format.setForeground(Qt::white); + vp_message_format.setForeground(m_base_string_color); else { QColor textColor; @@ -2497,17 +2497,24 @@ void Courtroom::chat_tick() vp_message_format.setForeground(textColor); } - ui_vp_message->textCursor().insertText(f_character, vp_message_format); + ui_vp_message->setAlignment(Qt::AlignCenter); + QString m_future_string_color = m_string_color; for(const auto& col : f_vec) { - if(f_character == col[0].trimmed()[1] && !found) + if(f_character == col[0].trimmed()[1] && !highlight_found) { if(m_color_stack.size() > 1) m_color_stack.pop(); - m_string_color = m_color_stack.top(); + m_future_string_color = m_color_stack.top(); + highlight_found = true; break; } } + + if (!highlight_found) + ui_vp_message->textCursor().insertText(f_character, vp_message_format); + + m_string_color = m_future_string_color; } else { @@ -2671,34 +2678,34 @@ void Courtroom::set_scene() void Courtroom::set_text_color() { + switch (m_chatmessage[TEXT_COLOR].toInt()) { case GREEN: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: rgb(0, 255, 0)"); + ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); + m_base_string_color.setRgb(0, 255, 0); break; case RED: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: red"); + ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); + m_base_string_color.setNamedColor("red"); break; case ORANGE: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: orange"); + ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); + m_base_string_color.setNamedColor("orange"); break; case BLUE: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: rgb(45, 150, 255)"); + ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); + m_base_string_color.setRgb(45, 150, 255); break; case YELLOW: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: yellow"); + ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); + m_base_string_color.setNamedColor("yellow"); break; default: qDebug() << "W: undefined text color: " << m_chatmessage[TEXT_COLOR]; case WHITE: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: white"); - + ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); + m_base_string_color.setNamedColor("white"); } } diff --git a/courtroom.h b/courtroom.h index 017dbbf71..390d038be 100644 --- a/courtroom.h +++ b/courtroom.h @@ -284,6 +284,7 @@ class Courtroom : public QMainWindow QString previous_ic_message = ""; + QColor m_base_string_color; QString m_string_color = ""; QStack m_color_stack; From 11784dddf2e24028606cd19cd05f0412a9487563 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 26 Feb 2020 23:15:57 -0500 Subject: [PATCH 011/842] Change outline parameter name to "enable_vp_message_outline"+Remove leftover code --- courtroom.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index eb970e70e..18835b68d 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -2412,7 +2412,7 @@ void Courtroom::chat_tick() //note: this is called fairly often(every 60 ms when char is talking) //do not perform heavy operations here QTextCharFormat vp_message_format = ui_vp_message->currentCharFormat(); - if (ao_app->read_theme_ini("enable_vp_outline", cc_config_ini) == "true") + if (ao_app->read_theme_ini("enable_vp_message_outline", cc_config_ini) == "true") vp_message_format.setTextOutline(QPen(Qt::black, 1)); else vp_message_format.setTextOutline(Qt::NoPen); @@ -2497,7 +2497,6 @@ void Courtroom::chat_tick() vp_message_format.setForeground(textColor); } - ui_vp_message->setAlignment(Qt::AlignCenter); QString m_future_string_color = m_string_color; for(const auto& col : f_vec) From 2924164eae3d7088dead8d6fc7675e3ae61913bb Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 27 Feb 2020 19:46:59 -0500 Subject: [PATCH 012/842] Basic chronometer --- Attorney_Online_remake.pro | 2 ++ aotimer.cpp | 41 ++++++++++++++++++++++++ aotimer.h | 65 ++++++++++++++++++++++++++++++++++++++ courtroom.cpp | 2 ++ courtroom.h | 2 ++ 5 files changed, 112 insertions(+) create mode 100644 aotimer.cpp create mode 100644 aotimer.h diff --git a/Attorney_Online_remake.pro b/Attorney_Online_remake.pro index a42821f65..aee1a8044 100644 --- a/Attorney_Online_remake.pro +++ b/Attorney_Online_remake.pro @@ -16,6 +16,7 @@ TEMPLATE = app VERSION = 2.4.8.0 SOURCES += main.cpp\ + aotimer.cpp \ lobby.cpp \ text_file_functions.cpp \ path_functions.cpp \ @@ -60,6 +61,7 @@ SOURCES += main.cpp\ HEADERS += lobby.h \ aoimage.h \ + aotimer.h \ file_functions.h \ aobutton.h \ debug_functions.h \ diff --git a/aotimer.cpp b/aotimer.cpp new file mode 100644 index 000000000..39fd607f0 --- /dev/null +++ b/aotimer.cpp @@ -0,0 +1,41 @@ +#include "aotimer.h" +#include + +AOTimer::AOTimer(QWidget* p_parent, AOApplication *p_ao_app) : QWidget(p_parent) +{ + // Original source: https://stackoverflow.com/questions/36679708/how-to-make-a-chronometer-in-qt-c + ao_app = p_ao_app; + + ui_timer_label = new AOLabel(this, ao_app); + AOButton *start_button = new AOButton(this, ao_app); + AOButton *end_button = new AOButton(this, ao_app); + + start_button->setText("Start"); + end_button->setText("End"); + ui_timer_label->setText(QTime(0, 0).toString()); + start_button->move(0, 0); + end_button->move(0, 40); + ui_timer_label->move(0, 80); + ui_timer_label->resize(200, 200); + this->resize(200, 200); + start_button->raise(); + end_button->raise(); + ui_timer_label->raise(); + + connect(&t, SIGNAL(timeout()), this, SLOT(updateTime())); + connect(start_button, SIGNAL(clicked()), this, SLOT(start())); + connect(end_button, SIGNAL(clicked()), &t, SLOT(stop())); +} + +void AOTimer::updateTime() +{ + ui_timer_label->setText(c.getTime().toString("mm:ss.zzz")); +} + +void AOTimer::start() +{ + ui_timer_label->setText(QTime(0, 0).toString("mm:ss.zzz")); + c.restart(); + t.start(16); + qDebug() << "started timer"; +} diff --git a/aotimer.h b/aotimer.h new file mode 100644 index 000000000..773c1e1b0 --- /dev/null +++ b/aotimer.h @@ -0,0 +1,65 @@ +#ifndef AOTIMER_H +#define AOTIMER_H + +#include +#include +#include +#include +#include +#include + +class Chronometer { + QElapsedTimer t; + public: + void restart() { t.restart(); } + QTime getTime() { return QTime(0,0).addMSecs(t.elapsed()); } +}; + +class AOTimer : public QWidget +{ + Q_OBJECT + +public: + AOTimer(QWidget* p_parent, AOApplication *p_ao_app); + +private: + AOApplication *ao_app = nullptr; + AOLabel *ui_timer_label; + + Chronometer c; + QTimer t; + +public slots: + void updateTime(); + void start(); +}; + + +#endif // AOTIMER_H + + +/* + * #ifndef AOTEXTEDIT_H +#define AOTEXTEDIT_H + +#include + +class AOTextEdit : public QPlainTextEdit +{ + Q_OBJECT +public: + AOTextEdit(QWidget *parent); + +protected: + void mouseDoubleClickEvent(QMouseEvent *e); + +signals: + void double_clicked(); + +private slots: + void on_enter_pressed(); + +}; + +#endif // AOTEXTEDIT_H +*/ diff --git a/courtroom.cpp b/courtroom.cpp index 18835b68d..93c1ce31d 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -268,6 +268,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_vp_notepad = new QTextEdit(this); ui_vp_notepad->setFrameStyle(QFrame::NoFrame); + ui_timer = new AOTimer(this, ao_app); + construct_evidence(); construct_char_select(); diff --git a/courtroom.h b/courtroom.h index 390d038be..eff7db203 100644 --- a/courtroom.h +++ b/courtroom.h @@ -24,6 +24,7 @@ #include "aonotepad.h" #include "aonotearea.hpp" #include "aolabel.hpp" +#include "aotimer.h" #include "datatypes.h" #include @@ -417,6 +418,7 @@ class Courtroom : public QMainWindow QWidget *ui_vp_music_area; AOMovie *ui_vp_clock; + AOTimer *ui_timer; QTextEdit* ui_ic_chatlog = nullptr; QVector m_ic_records; From c0f166874fe7e13149ee7aed0c793d3f9dcc2a05 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 18 Mar 2020 23:10:56 -0300 Subject: [PATCH 013/842] Add support for server-side timer manipulation packets --- aotimer.cpp | 155 ++++++++++++++++++++++++++++++++++------ aotimer.h | 66 ++++++++--------- courtroom.cpp | 32 ++++++++- courtroom.h | 7 +- packet_distribution.cpp | 31 ++++++++ 5 files changed, 232 insertions(+), 59 deletions(-) diff --git a/aotimer.cpp b/aotimer.cpp index 39fd607f0..0444b394a 100644 --- a/aotimer.cpp +++ b/aotimer.cpp @@ -3,39 +3,154 @@ AOTimer::AOTimer(QWidget* p_parent, AOApplication *p_ao_app) : QWidget(p_parent) { - // Original source: https://stackoverflow.com/questions/36679708/how-to-make-a-chronometer-in-qt-c + // Adapted from: https://stackoverflow.com/questions/36679708/how-to-make-a-chronometer-in-qt-c ao_app = p_ao_app; ui_timer_label = new AOLabel(this, ao_app); - AOButton *start_button = new AOButton(this, ao_app); + /* + AOButton *set_button = new AOButton(this, ao_app); + AOButton *resume_button = new AOButton(this, ao_app); AOButton *end_button = new AOButton(this, ao_app); + AOButton *concentrate = new AOButton(this, ao_app); + AOButton *normal = new AOButton(this, ao_app); + AOButton *fast_forward = new AOButton(this, ao_app); - start_button->setText("Start"); - end_button->setText("End"); - ui_timer_label->setText(QTime(0, 0).toString()); - start_button->move(0, 0); + set_button->setText("set"); + resume_button->setText("Resume"); + end_button->setText("Pause"); + concentrate->setText("Concentrate"); + normal->setText("Normal"); + fast_forward->setText("Fast Forward"); + + set_button->move(0, 0); + resume_button->move(0, 20); end_button->move(0, 40); - ui_timer_label->move(0, 80); - ui_timer_label->resize(200, 200); - this->resize(200, 200); - start_button->raise(); + concentrate->move(0, 60); + normal->move(0, 80); + fast_forward->move(0, 100);*/ + + ui_timer_label->move(0, 120); + ui_timer_label->resize(200, 100); + this->resize(200, 400); + ui_timer_label->setStyleSheet("QLabel { color : white; }"); + + /* + set_button->raise(); + resume_button->raise(); end_button->raise(); + concentrate->raise(); + normal->raise(); + fast_forward->raise(); ui_timer_label->raise(); + */ + + connect(&firing_timer, SIGNAL(timeout()), this, SLOT(update_time())); + + /* + connect(set_button, SIGNAL(clicked()), this, SLOT(set())); + connect(resume_button, SIGNAL(clicked()), this, SLOT(resume())); + connect(concentrate, SIGNAL(clicked()), this, SLOT(set_concentrate_mode())); + connect(normal, SIGNAL(clicked()), this, SLOT(set_normal_mode())); + connect(fast_forward, SIGNAL(clicked()), this, SLOT(set_fast_forward_mode())); + connect(end_button, SIGNAL(clicked()), &firing_timer, SLOT(stop())); + */ + + set_time(start_time); + old_manual_timer.set_time(start_time); + set_timestep_length(manual_timer_timestep_length); + redraw(); +} + +void AOTimer::update_time() +{ + manual_timer.perform_timestep(); - connect(&t, SIGNAL(timeout()), this, SLOT(updateTime())); - connect(start_button, SIGNAL(clicked()), this, SLOT(start())); - connect(end_button, SIGNAL(clicked()), &t, SLOT(stop())); + // Check for underflow/overflow + + // This one checks overflows, as the updated manual timer would appear to have a past time + // compared to the manual timer a step ago (represented by old_manual_timer) + if (manual_timer.get_timestep_length() > 0) + { + if (manual_timer.get_time().operator<(old_manual_timer.get_time())) + { + set_time(QTime(0, 0)); + firing_timer.stop(); + redraw(); + return; + } + } + // This one checks underflows, as the updated manual timer would appear to have a future time + // compared to the manual timer a step ago (represented by old_manual_timer) + else if (manual_timer.get_timestep_length() < 0) + { + if (manual_timer.get_time().operator>(old_manual_timer.get_time())) + { + set_time(QTime(0, 0)); + firing_timer.stop(); + redraw(); + return; + } + } + + // If no overflow/underflow, now update the old manual timer so it matches the current timer + // Redraw and return + old_manual_timer.perform_timestep(); + redraw(); +} + +void AOTimer::set() +{ + set_time(start_time); +} + +void AOTimer::resume() +{ + firing_timer.start(firing_timer_length); +} + +void AOTimer::pause() +{ + firing_timer.stop(); +} + +void AOTimer::redraw() +{ + ui_timer_label->setText(manual_timer.get_time().toString("mm:ss.zzz")); +} + +void AOTimer::set_time(QTime new_time) +{ + manual_timer.set_time(new_time); + old_manual_timer.set_time(new_time); + redraw(); +} + +void AOTimer::set_timestep_length(int new_timestep_length) +{ + manual_timer.set_timestep_length(new_timestep_length); + old_manual_timer.set_timestep_length(new_timestep_length); +} + +void AOTimer::set_firing_length(int new_firing_length) +{ + firing_timer_length = new_firing_length; + //firing_timer.start(firing_timer_length); +} + +void AOTimer::set_concentrate_mode() +{ + set_timestep_length(-12); + set_firing_length(120); } -void AOTimer::updateTime() +void AOTimer::set_normal_mode() { - ui_timer_label->setText(c.getTime().toString("mm:ss.zzz")); + set_timestep_length(-12); + set_firing_length(12); } -void AOTimer::start() +void AOTimer::set_fast_forward_mode() { - ui_timer_label->setText(QTime(0, 0).toString("mm:ss.zzz")); - c.restart(); - t.start(16); - qDebug() << "started timer"; + set_timestep_length(-24); + set_firing_length(12); } diff --git a/aotimer.h b/aotimer.h index 773c1e1b0..d0b1a1107 100644 --- a/aotimer.h +++ b/aotimer.h @@ -8,11 +8,17 @@ #include #include -class Chronometer { - QElapsedTimer t; +class ManualTimer { + QTime current_time; + int timestep_length; + public: - void restart() { t.restart(); } - QTime getTime() { return QTime(0,0).addMSecs(t.elapsed()); } + QTime get_time() { return current_time; } + int get_timestep_length() { return timestep_length; } + + void set_time(QTime new_time) { current_time = new_time; } + void set_timestep_length(int new_timestep_length) { timestep_length = new_timestep_length ;} + void perform_timestep() {current_time = current_time.addMSecs(timestep_length);} }; class AOTimer : public QWidget @@ -26,40 +32,26 @@ class AOTimer : public QWidget AOApplication *ao_app = nullptr; AOLabel *ui_timer_label; - Chronometer c; - QTimer t; - -public slots: - void updateTime(); - void start(); -}; - - -#endif // AOTIMER_H - - -/* - * #ifndef AOTEXTEDIT_H -#define AOTEXTEDIT_H + ManualTimer old_manual_timer; // Pre-update manual timer + ManualTimer manual_timer; + QTimer firing_timer; -#include - -class AOTextEdit : public QPlainTextEdit -{ - Q_OBJECT -public: - AOTextEdit(QWidget *parent); - -protected: - void mouseDoubleClickEvent(QMouseEvent *e); - -signals: - void double_clicked(); - -private slots: - void on_enter_pressed(); + QTime start_time = QTime(0, 5); + int manual_timer_timestep_length = -12; + int firing_timer_length = 12; +public slots: + void update_time(); + void set(); + void resume(); + void pause(); + void redraw(); + void set_time(QTime new_time); + void set_timestep_length(int new_timestep_length); + void set_firing_length(int new_firing_length); + void set_concentrate_mode(); + void set_normal_mode(); + void set_fast_forward_mode(); }; -#endif // AOTEXTEDIT_H -*/ +#endif diff --git a/courtroom.cpp b/courtroom.cpp index 93c1ce31d..fa8733a7b 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -268,7 +268,11 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_vp_notepad = new QTextEdit(this); ui_vp_notepad->setFrameStyle(QFrame::NoFrame); - ui_timer = new AOTimer(this, ao_app); + ui_timers.resize(num_timers); + for (int i=0; i= num_timers) + return; + + ui_timers[timer_id]->resume(); +} + +void Courtroom::timer_set(int timer_id, int new_time, int timestep_length, int firing_interval) +{ + if (timer_id >= num_timers) + return; + + ui_timers[timer_id]->set_time(QTime(0, 0).addMSecs(new_time)); + ui_timers[timer_id]->set_timestep_length(timestep_length); + ui_timers[timer_id]->set_firing_length(firing_interval); +} + +void Courtroom::timer_pause(int timer_id) +{ + if (timer_id >= num_timers) + return; + + ui_timers[timer_id]->pause(); +} diff --git a/courtroom.h b/courtroom.h index eff7db203..230d5f9df 100644 --- a/courtroom.h +++ b/courtroom.h @@ -206,6 +206,10 @@ class Courtroom : public QMainWindow void check_effects(); void check_wtce(); + void timer_resume(int timer_id); + void timer_set(int timer_id, int new_time, int timestep_length, int firing_interval); + void timer_pause(int timer_id); + private: AOApplication *ao_app = nullptr; @@ -368,6 +372,7 @@ class Courtroom : public QMainWindow bool server_ooc = true; int current_clock = -1; + int num_timers = 1; QString current_background = "gs4"; @@ -418,7 +423,7 @@ class Courtroom : public QMainWindow QWidget *ui_vp_music_area; AOMovie *ui_vp_clock; - AOTimer *ui_timer; + QVector ui_timers; QTextEdit* ui_ic_chatlog = nullptr; QVector m_ic_records; diff --git a/packet_distribution.cpp b/packet_distribution.cpp index 5e1312f45..42791ab02 100644 --- a/packet_distribution.cpp +++ b/packet_distribution.cpp @@ -672,6 +672,37 @@ void AOApplication::server_packet_received(AOPacket *p_packet) w_courtroom->handle_theme_variant(f_contents.at(0)); } } + else if (header == "TR") + { + // Timer resume + if (f_contents.size() != 1) + goto end; + + int timer_id = f_contents.at(0).toInt(); + w_courtroom->timer_resume(timer_id); + + } + else if (header == "TS") + { + // Timer set + if (f_contents.size() != 4) + goto end; + + int timer_id = f_contents.at(0).toInt(); + int new_time = f_contents.at(1).toInt(); + int timestep_length = f_contents.at(2).toInt(); + int firing_length = f_contents.at(3).toInt(); + w_courtroom->timer_set(timer_id, new_time, timestep_length, firing_length); + } + else if (header == "TP") + { + // Timer pause + if (f_contents.size() != 1) + goto end; + + int timer_id = f_contents.at(0).toInt(); + w_courtroom->timer_pause(timer_id); + } end: From 0e4a65124dbd371a6dc6daf2068e4d45e6c81b8f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 21 May 2020 19:22:08 -0400 Subject: [PATCH 014/842] Remove no longer needed unnecessary HTML conversion (which broke special chars) --- courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courtroom.cpp b/courtroom.cpp index fa8733a7b..162fc4858 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -2440,7 +2440,7 @@ void Courtroom::chat_tick() else { QString f_character = f_message.at(tick_pos); - f_character = f_character.toHtmlEscaped(); + //f_character = f_character.toHtmlEscaped(); //qDebug() << f_character; if (f_character == " ") From 99a4424cceaa4e1d1977212ef3d5f5843950c55b Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 22 May 2020 22:07:40 -0400 Subject: [PATCH 015/842] Add support for timers that can change via theme --- aotimer.cpp | 13 ++--- aotimer.h | 3 +- courtroom.cpp | 152 +++++++++++++++++++++++++++++++++++++++++++++++--- courtroom.h | 9 ++- 4 files changed, 158 insertions(+), 19 deletions(-) diff --git a/aotimer.cpp b/aotimer.cpp index 0444b394a..db0e4d170 100644 --- a/aotimer.cpp +++ b/aotimer.cpp @@ -1,12 +1,10 @@ #include "aotimer.h" #include -AOTimer::AOTimer(QWidget* p_parent, AOApplication *p_ao_app) : QWidget(p_parent) +AOTimer::AOTimer(QWidget* p_parent, AOApplication *p_ao_app) : AOLabel(p_parent, p_ao_app) { // Adapted from: https://stackoverflow.com/questions/36679708/how-to-make-a-chronometer-in-qt-c ao_app = p_ao_app; - - ui_timer_label = new AOLabel(this, ao_app); /* AOButton *set_button = new AOButton(this, ao_app); AOButton *resume_button = new AOButton(this, ao_app); @@ -29,10 +27,9 @@ AOTimer::AOTimer(QWidget* p_parent, AOApplication *p_ao_app) : QWidget(p_parent) normal->move(0, 80); fast_forward->move(0, 100);*/ - ui_timer_label->move(0, 120); - ui_timer_label->resize(200, 100); - this->resize(200, 400); - ui_timer_label->setStyleSheet("QLabel { color : white; }"); + //this->move(0, 0); + //this->resize(this->width(), this->height()); + this->setStyleSheet("QLabel { color : white; }"); /* set_button->raise(); @@ -115,7 +112,7 @@ void AOTimer::pause() void AOTimer::redraw() { - ui_timer_label->setText(manual_timer.get_time().toString("mm:ss.zzz")); + setText(manual_timer.get_time().toString("mm:ss.zzz")); } void AOTimer::set_time(QTime new_time) diff --git a/aotimer.h b/aotimer.h index d0b1a1107..b999aa4a3 100644 --- a/aotimer.h +++ b/aotimer.h @@ -21,7 +21,7 @@ class ManualTimer { void perform_timestep() {current_time = current_time.addMSecs(timestep_length);} }; -class AOTimer : public QWidget +class AOTimer : public AOLabel { Q_OBJECT @@ -30,7 +30,6 @@ class AOTimer : public QWidget private: AOApplication *ao_app = nullptr; - AOLabel *ui_timer_label; ManualTimer old_manual_timer; // Pre-update manual timer ManualTimer manual_timer; diff --git a/courtroom.cpp b/courtroom.cpp index 162fc4858..f7238ca99 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -268,8 +268,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_vp_notepad = new QTextEdit(this); ui_vp_notepad->setFrameStyle(QFrame::NoFrame); - ui_timers.resize(num_timers); - for (int i=0; imove(0, 0); ui_background->resize(m_courtroom_width, m_courtroom_height); ui_background->set_image("courtroombackground.png"); @@ -888,7 +886,51 @@ void Courtroom::set_widgets() contains_add_button = true; } + // Redraw the new correct number of timers. + /* + int new_timer_number = ao_app->read_theme_ini("timer_number", cc_config_ini).toInt(); + // Note we use the fact that, if timer_number is not there, read_theme_ini returns an empty + // string, which .toInt() would fail to convert and by documentation transform to 0. + + // Decide what to do if the new theme has a different amount of timers than the old one + if (new_timer_number < timer_number) + { + // Hide old timers if there are any. + for (int i=new_timer_number; ihide(); + } + } + else if (timer_number < new_timer_number) + { + // Create new timers if needed + if (ui_timers.size() < new_timer_number) + { + ui_timers.resize(new_timer_number); + for (int i=timer_number; ishow(); + set_size_and_pos(ui_timers[i], "timer_"+QString::number(i)); + // Note that show is deliberately placed before set_size_and_pos + // That is because we want to retain the functionality set_size_and_pos includes where + // hides a widget if it is unable to find a position for it, and does not change its + // visibility otherwise. + } + timer_number = new_timer_number; +*/ + timer_number = correct_numbered_items(ui_timers, "timer_number", "timer"); set_dropdowns(); + set_fonts(); } void Courtroom::set_fonts() @@ -903,6 +945,11 @@ void Courtroom::set_fonts() set_font(ui_sfx_list, "sfx_list"); set_font(ui_vp_music_name, "music_name"); set_font(ui_vp_notepad, "notepad"); + for (int i=0; i 5) + return; + + int timer_id = (size > 1 ? arguments[1].toInt() : 0); + int new_time = (size > 2 ? arguments[2].toInt() : 300)*1000; + int timestep_length = (size > 3 ? arguments[3].toDouble() : -.016)*1000; + int firing_length = (size > 4 ? arguments[4].toDouble() : .016)*1000; + timer_set(timer_id, new_time, timestep_length, firing_length); + } + else if (ooc_message.startsWith("/tp")) + { + // Timer pause + int space_location = ooc_message.indexOf(" "); + int timer_id; + if (space_location == -1) + timer_id = 0; + else + timer_id = ooc_message.mid(space_location+1).toInt(); + timer_pause(timer_id); + } QStringList packet_contents; packet_contents.append(ui_ooc_chat_name->text()); packet_contents.append(ooc_message); @@ -3615,7 +3701,7 @@ void Courtroom::set_bullets() void Courtroom::timer_resume(int timer_id) { - if (timer_id >= num_timers) + if (timer_id >= timer_number) return; ui_timers[timer_id]->resume(); @@ -3623,7 +3709,7 @@ void Courtroom::timer_resume(int timer_id) void Courtroom::timer_set(int timer_id, int new_time, int timestep_length, int firing_interval) { - if (timer_id >= num_timers) + if (timer_id >= timer_number) return; ui_timers[timer_id]->set_time(QTime(0, 0).addMSecs(new_time)); @@ -3633,8 +3719,60 @@ void Courtroom::timer_set(int timer_id, int new_time, int timestep_length, int f void Courtroom::timer_pause(int timer_id) { - if (timer_id >= num_timers) + if (timer_id >= timer_number) return; ui_timers[timer_id]->pause(); } + +template +int Courtroom::correct_numbered_items(QVector &item_vector, QString config_item_number, + QString item_name) +{ + // &item_vector must be a vector of size at least 1! + + // Redraw the new correct number of items. + int new_item_number = ao_app->read_theme_ini(config_item_number, cc_config_ini).toInt(); + int current_item_number = item_vector.size(); + // Note we use the fact that, if config_item_number is not there, read_theme_ini returns an + // empty string, which .toInt() would fail to convert and by documentation transform to 0. + + // Decide what to do if the new theme has a different amount of items than the old one + if (new_item_number < current_item_number) + { + // Hide old timers if there are any. + for (int i=new_item_number; ihide(); + } + } + else if (current_item_number < new_item_number) + { + // Create new items + item_vector.resize(new_item_number); + qDebug() << current_item_number << ' ' << new_item_number; + for (int i=current_item_number; istackUnder(item_vector[i-1]); + // index i-1 exists as i >= current_item_number == item_vector.size() >= 1 + } + } + // Note that by design we do *not* destroy items (or similarly, the size of item_vector + // is non-decreasing. This is because we want to allow for items to, say, run in the + // background as invisible. + // With that said, we can now properly format our new items + qDebug() << "Created"; + for (int i=0; ishow(); + set_size_and_pos(item_vector[i], item_name+"_"+QString::number(i)); + // Note that show is deliberately placed before set_size_and_pos + // That is because we want to retain the functionality set_size_and_pos includes where + // hides a widget if it is unable to find a position for it, and does not change its + // visibility otherwise. + } + qDebug() << "Showed all"; + return new_item_number; +} diff --git a/courtroom.h b/courtroom.h index 230d5f9df..ec3f6d84a 100644 --- a/courtroom.h +++ b/courtroom.h @@ -207,9 +207,14 @@ class Courtroom : public QMainWindow void check_wtce(); void timer_resume(int timer_id); - void timer_set(int timer_id, int new_time, int timestep_length, int firing_interval); + void timer_set(int timer_id, int new_time, int timestep_length, + int firing_interval); void timer_pause(int timer_id); + template + int correct_numbered_items(QVector &item_vector, QString config_item_number, + QString item_name); + private: AOApplication *ao_app = nullptr; @@ -372,7 +377,7 @@ class Courtroom : public QMainWindow bool server_ooc = true; int current_clock = -1; - int num_timers = 1; + int timer_number = 0; QString current_background = "gs4"; From 8f2ff1a05c234d7527ad8ea581d562ead11c1870 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 24 May 2020 20:27:44 -0400 Subject: [PATCH 016/842] Implement step-respecting firing interval change+Add more server packet support --- aotimer.cpp | 73 +++++++++++------------------------ aotimer.h | 4 +- courtroom.cpp | 84 +++++++++++++---------------------------- courtroom.h | 13 ++++--- packet_distribution.cpp | 34 +++++++++++++---- 5 files changed, 86 insertions(+), 122 deletions(-) diff --git a/aotimer.cpp b/aotimer.cpp index db0e4d170..32dd8ee8f 100644 --- a/aotimer.cpp +++ b/aotimer.cpp @@ -5,53 +5,9 @@ AOTimer::AOTimer(QWidget* p_parent, AOApplication *p_ao_app) : AOLabel(p_parent, { // Adapted from: https://stackoverflow.com/questions/36679708/how-to-make-a-chronometer-in-qt-c ao_app = p_ao_app; - /* - AOButton *set_button = new AOButton(this, ao_app); - AOButton *resume_button = new AOButton(this, ao_app); - AOButton *end_button = new AOButton(this, ao_app); - AOButton *concentrate = new AOButton(this, ao_app); - AOButton *normal = new AOButton(this, ao_app); - AOButton *fast_forward = new AOButton(this, ao_app); - - set_button->setText("set"); - resume_button->setText("Resume"); - end_button->setText("Pause"); - concentrate->setText("Concentrate"); - normal->setText("Normal"); - fast_forward->setText("Fast Forward"); - - set_button->move(0, 0); - resume_button->move(0, 20); - end_button->move(0, 40); - concentrate->move(0, 60); - normal->move(0, 80); - fast_forward->move(0, 100);*/ - - //this->move(0, 0); - //this->resize(this->width(), this->height()); this->setStyleSheet("QLabel { color : white; }"); - - /* - set_button->raise(); - resume_button->raise(); - end_button->raise(); - concentrate->raise(); - normal->raise(); - fast_forward->raise(); - ui_timer_label->raise(); - */ - connect(&firing_timer, SIGNAL(timeout()), this, SLOT(update_time())); - /* - connect(set_button, SIGNAL(clicked()), this, SLOT(set())); - connect(resume_button, SIGNAL(clicked()), this, SLOT(resume())); - connect(concentrate, SIGNAL(clicked()), this, SLOT(set_concentrate_mode())); - connect(normal, SIGNAL(clicked()), this, SLOT(set_normal_mode())); - connect(fast_forward, SIGNAL(clicked()), this, SLOT(set_fast_forward_mode())); - connect(end_button, SIGNAL(clicked()), &firing_timer, SLOT(stop())); - */ - set_time(start_time); old_manual_timer.set_time(start_time); set_timestep_length(manual_timer_timestep_length); @@ -60,6 +16,7 @@ AOTimer::AOTimer(QWidget* p_parent, AOApplication *p_ao_app) : AOLabel(p_parent, void AOTimer::update_time() { + time_spent_in_timestep = 0; manual_timer.perform_timestep(); // Check for underflow/overflow @@ -93,6 +50,7 @@ void AOTimer::update_time() // Redraw and return old_manual_timer.perform_timestep(); redraw(); + firing_timer.start(firing_timer_length); } void AOTimer::set() @@ -128,26 +86,41 @@ void AOTimer::set_timestep_length(int new_timestep_length) old_manual_timer.set_timestep_length(new_timestep_length); } -void AOTimer::set_firing_length(int new_firing_length) +void AOTimer::set_firing_interval(int new_firing_interval) { - firing_timer_length = new_firing_length; - //firing_timer.start(firing_timer_length); + /* + * Update the firing interval of the steptimer for all future timesteps. If a timestep + * has started when this function is scheduled to run, the current timestep will be + * readapted to finish in this new firing interval as follows: + * - Assume length x of the 'current' timestep with orig has elapsed. + * - Then, the current timestep will be adapted to have length max(0, new_interval-x) + */ + + // Update time spent so far and new future firing interval + time_spent_in_timestep += (firing_timer_length - firing_timer.remainingTime()); + firing_timer_length = new_firing_interval; + // For this timestep however, the firing interval will be shorter than firing_timer_length + // to account for the fact the timer may have already been running a bit in this timestep + if (new_firing_interval < time_spent_in_timestep) + firing_timer.start(0); + else + firing_timer.start(new_firing_interval-time_spent_in_timestep); } void AOTimer::set_concentrate_mode() { set_timestep_length(-12); - set_firing_length(120); + set_firing_interval(120); } void AOTimer::set_normal_mode() { set_timestep_length(-12); - set_firing_length(12); + set_firing_interval(12); } void AOTimer::set_fast_forward_mode() { set_timestep_length(-24); - set_firing_length(12); + set_firing_interval(12); } diff --git a/aotimer.h b/aotimer.h index b999aa4a3..8184552b8 100644 --- a/aotimer.h +++ b/aotimer.h @@ -36,8 +36,10 @@ class AOTimer : public AOLabel QTimer firing_timer; QTime start_time = QTime(0, 5); + // All of this is in miliseconds int manual_timer_timestep_length = -12; int firing_timer_length = 12; + int time_spent_in_timestep = 0; public slots: void update_time(); @@ -47,7 +49,7 @@ public slots: void redraw(); void set_time(QTime new_time); void set_timestep_length(int new_timestep_length); - void set_firing_length(int new_firing_length); + void set_firing_interval(int new_firing_interval); void set_concentrate_mode(); void set_normal_mode(); void set_fast_forward_mode(); diff --git a/courtroom.cpp b/courtroom.cpp index f7238ca99..06f48fbc0 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -886,49 +886,7 @@ void Courtroom::set_widgets() contains_add_button = true; } - // Redraw the new correct number of timers. - /* - int new_timer_number = ao_app->read_theme_ini("timer_number", cc_config_ini).toInt(); - // Note we use the fact that, if timer_number is not there, read_theme_ini returns an empty - // string, which .toInt() would fail to convert and by documentation transform to 0. - - // Decide what to do if the new theme has a different amount of timers than the old one - if (new_timer_number < timer_number) - { - // Hide old timers if there are any. - for (int i=new_timer_number; ihide(); - } - } - else if (timer_number < new_timer_number) - { - // Create new timers if needed - if (ui_timers.size() < new_timer_number) - { - ui_timers.resize(new_timer_number); - for (int i=timer_number; ishow(); - set_size_and_pos(ui_timers[i], "timer_"+QString::number(i)); - // Note that show is deliberately placed before set_size_and_pos - // That is because we want to retain the functionality set_size_and_pos includes where - // hides a widget if it is unable to find a position for it, and does not change its - // visibility otherwise. - } - timer_number = new_timer_number; -*/ - timer_number = correct_numbered_items(ui_timers, "timer_number", "timer"); + timer_number = adapt_numbered_items(ui_timers, "timer_number", "timer"); set_dropdowns(); set_fonts(); } @@ -3083,7 +3041,7 @@ void Courtroom::on_ooc_return_pressed() timer_id = 0; else timer_id = ooc_message.mid(space_location+1).toInt(); - timer_resume(timer_id); + resume_timer(timer_id); } else if (ooc_message.startsWith("/ts")) { @@ -3098,8 +3056,10 @@ void Courtroom::on_ooc_return_pressed() int timer_id = (size > 1 ? arguments[1].toInt() : 0); int new_time = (size > 2 ? arguments[2].toInt() : 300)*1000; int timestep_length = (size > 3 ? arguments[3].toDouble() : -.016)*1000; - int firing_length = (size > 4 ? arguments[4].toDouble() : .016)*1000; - timer_set(timer_id, new_time, timestep_length, firing_length); + int firing_interval = (size > 4 ? arguments[4].toDouble() : .016)*1000; + set_timer_time(timer_id, new_time); + set_timer_timestep(timer_id, timestep_length); + set_timer_firing(timer_id, firing_interval); } else if (ooc_message.startsWith("/tp")) { @@ -3111,7 +3071,7 @@ void Courtroom::on_ooc_return_pressed() timer_id = 0; else timer_id = ooc_message.mid(space_location+1).toInt(); - timer_pause(timer_id); + pause_timer(timer_id); } QStringList packet_contents; packet_contents.append(ui_ooc_chat_name->text()); @@ -3699,7 +3659,7 @@ void Courtroom::set_bullets() } while(thing != ""); } -void Courtroom::timer_resume(int timer_id) +void Courtroom::resume_timer(int timer_id) { if (timer_id >= timer_number) return; @@ -3707,17 +3667,31 @@ void Courtroom::timer_resume(int timer_id) ui_timers[timer_id]->resume(); } -void Courtroom::timer_set(int timer_id, int new_time, int timestep_length, int firing_interval) +void Courtroom::set_timer_time(int timer_id, int new_time) { if (timer_id >= timer_number) return; ui_timers[timer_id]->set_time(QTime(0, 0).addMSecs(new_time)); +} + +void Courtroom::set_timer_timestep(int timer_id, int timestep_length) +{ + if (timer_id >= timer_number) + return; + ui_timers[timer_id]->set_timestep_length(timestep_length); - ui_timers[timer_id]->set_firing_length(firing_interval); } -void Courtroom::timer_pause(int timer_id) +void Courtroom::set_timer_firing(int timer_id, int firing_interval) +{ + if (timer_id >= timer_number) + return; + + ui_timers[timer_id]->set_firing_interval(firing_interval); +} + +void Courtroom::pause_timer(int timer_id) { if (timer_id >= timer_number) return; @@ -3726,7 +3700,7 @@ void Courtroom::timer_pause(int timer_id) } template -int Courtroom::correct_numbered_items(QVector &item_vector, QString config_item_number, +int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_item_number, QString item_name) { // &item_vector must be a vector of size at least 1! @@ -3740,7 +3714,7 @@ int Courtroom::correct_numbered_items(QVector &item_vector, QString config_i // Decide what to do if the new theme has a different amount of items than the old one if (new_item_number < current_item_number) { - // Hide old timers if there are any. + // Hide old items if there are any. for (int i=new_item_number; ihide(); @@ -3750,7 +3724,6 @@ int Courtroom::correct_numbered_items(QVector &item_vector, QString config_i { // Create new items item_vector.resize(new_item_number); - qDebug() << current_item_number << ' ' << new_item_number; for (int i=current_item_number; i &item_vector, QString config_i // is non-decreasing. This is because we want to allow for items to, say, run in the // background as invisible. // With that said, we can now properly format our new items - qDebug() << "Created"; for (int i=0; ishow(); set_size_and_pos(item_vector[i], item_name+"_"+QString::number(i)); // Note that show is deliberately placed before set_size_and_pos @@ -3773,6 +3744,5 @@ int Courtroom::correct_numbered_items(QVector &item_vector, QString config_i // hides a widget if it is unable to find a position for it, and does not change its // visibility otherwise. } - qDebug() << "Showed all"; return new_item_number; } diff --git a/courtroom.h b/courtroom.h index ec3f6d84a..c0426eae6 100644 --- a/courtroom.h +++ b/courtroom.h @@ -206,14 +206,15 @@ class Courtroom : public QMainWindow void check_effects(); void check_wtce(); - void timer_resume(int timer_id); - void timer_set(int timer_id, int new_time, int timestep_length, - int firing_interval); - void timer_pause(int timer_id); + void resume_timer(int timer_id); + void set_timer_time(int timer_id, int new_time); + void set_timer_timestep(int timer_id, int timestep_length); + void set_timer_firing(int timer_id, int firing_interval); + void pause_timer(int timer_id); template - int correct_numbered_items(QVector &item_vector, QString config_item_number, - QString item_name); + int adapt_numbered_items(QVector &item_vector, QString config_item_number, + QString item_name); private: AOApplication *ao_app = nullptr; diff --git a/packet_distribution.cpp b/packet_distribution.cpp index 42791ab02..0ce0f9436 100644 --- a/packet_distribution.cpp +++ b/packet_distribution.cpp @@ -679,20 +679,38 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; int timer_id = f_contents.at(0).toInt(); - w_courtroom->timer_resume(timer_id); + w_courtroom->resume_timer(timer_id); } - else if (header == "TS") + else if (header == "TST") { - // Timer set - if (f_contents.size() != 4) + // Timer set time + if (f_contents.size() != 2) goto end; int timer_id = f_contents.at(0).toInt(); int new_time = f_contents.at(1).toInt(); - int timestep_length = f_contents.at(2).toInt(); - int firing_length = f_contents.at(3).toInt(); - w_courtroom->timer_set(timer_id, new_time, timestep_length, firing_length); + w_courtroom->set_timer_time(timer_id, new_time); + } + else if (header == "TSS") + { + // Timer set timeStep length + if (f_contents.size() != 2) + goto end; + + int timer_id = f_contents.at(0).toInt(); + int timestep_length = f_contents.at(1).toInt(); + w_courtroom->set_timer_timestep(timer_id, timestep_length); + } + else if (header == "TSF") + { + // Timer set Firing interval + if (f_contents.size() != 2) + goto end; + + int timer_id = f_contents.at(0).toInt(); + int firing_interval = f_contents.at(1).toInt(); + w_courtroom->set_timer_firing(timer_id, firing_interval); } else if (header == "TP") { @@ -701,7 +719,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; int timer_id = f_contents.at(0).toInt(); - w_courtroom->timer_pause(timer_id); + w_courtroom->pause_timer(timer_id); } end: From 2194aad965c22912dfd96b08136e21bd84664bbb Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 24 May 2020 21:07:29 -0400 Subject: [PATCH 017/842] Fix minor style issues --- courtroom.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 06f48fbc0..ba7aee270 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -269,10 +269,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_vp_notepad->setFrameStyle(QFrame::NoFrame); ui_timers.resize(1); - for (int i=0; i<1; i++) - { - ui_timers[i] = new AOTimer(this, ao_app); - } + ui_timers[0] = new AOTimer(this, ao_app); construct_evidence(); @@ -905,7 +902,6 @@ void Courtroom::set_fonts() set_font(ui_vp_notepad, "notepad"); for (int i=0; i Date: Mon, 25 May 2020 08:32:06 -0400 Subject: [PATCH 018/842] Start work on free blocks --- courtroom.cpp | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++ courtroom.h | 13 ++++++-- 2 files changed, 92 insertions(+), 3 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index ba7aee270..ab8f9ac70 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -271,6 +271,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_timers.resize(1); ui_timers[0] = new AOTimer(this, ao_app); + load_free_blocks(); // Done last so they are guaranteed to be at bottom + construct_evidence(); construct_char_select(); @@ -435,6 +437,14 @@ void Courtroom::set_widgets() wtce_names.append(name.trimmed()); } + free_block_names.clear(); + for(int i = 1; i <= ui_free_blocks.size(); ++i) + { + QString name = ao_app->get_spbutton("[FREE BLOCKS]", i); + if(!name.isEmpty()) + free_block_names.append(name.trimmed()); + } + ui_background->move(0, 0); ui_background->resize(m_courtroom_width, m_courtroom_height); ui_background->set_image("courtroombackground.png"); @@ -669,6 +679,13 @@ void Courtroom::set_widgets() set_wtce(); } + for(int i = 0; i < free_block_names.size(); ++i) + { + qDebug() << "Setting up free block " << i << " " << free_block_names[i]; + set_size_and_pos(ui_free_blocks[i], "free_block_" + free_block_names[i]); + } + set_free_blocks(); + set_size_and_pos(ui_ooc_toggle, "ooc_toggle"); ui_ooc_toggle->setText("Server"); @@ -1050,6 +1067,30 @@ void Courtroom::load_wtce() connect(wtce, SIGNAL(clicked(bool)), this, SLOT(on_wtce_clicked())); } +void Courtroom::load_free_blocks() +{ + // Close any existing free blocks to prevent memory leaks + for (int i=0; iclose(); + delete ui_free_blocks[i]; + } + + // And create new free block buttons + int free_block_number = ao_app->get_design_ini_value("free_block_number", cc_config_ini); + free_blocks_enabled.resize(free_block_number); + ui_free_blocks.resize(free_block_number); + + for (int i=0; isetProperty("free_block_id", i+1); + ui_free_blocks[i]->set_play_once(false); + ui_free_blocks[i]->stackUnder(ui_vp_music_display_a); + } + +} + void Courtroom::set_shouts() { for(auto & shout : ui_shouts) shout->hide(); @@ -1083,6 +1124,15 @@ void Courtroom::set_wtce() } } +void Courtroom::set_free_blocks() +{ + for (int i=0; iplay("free_block_" + free_block_names[i]); + } +} + void Courtroom::handle_music_anim() { QString file_a = design_ini; @@ -1329,6 +1379,7 @@ void Courtroom::enter_courtroom(int p_cid) // Update widgets first, then check if everything is valid // This will also handle showing the correct shouts, effects and wtce buttons, and cycling // through them if the buttons that are supposed to be displayed do not exist + // It will also take care of free blocks set_widgets(); check_shouts(); @@ -1343,6 +1394,8 @@ void Courtroom::enter_courtroom(int p_cid) if (is_judge && !wtce_enabled[m_wtce_current]) cycle_wtce(1); + check_free_blocks(); + if (ao_app->flipping_enabled) ui_flip->show(); else @@ -2948,6 +3001,34 @@ void Courtroom::check_wtce() } } +void Courtroom::check_free_blocks() +{ + QString char_path = ao_app->get_character_path(current_char); + QString theme_variant_path = ao_app->get_theme_variant_path(); + QString theme_path = ao_app->get_theme_path(); + for(int i = 0; i < ui_free_blocks.size(); ++i) + { + QStringList paths{ + char_path + free_block_names.at(i) + ".gif", + theme_variant_path + free_block_names.at(i) + ".gif", + theme_variant_path + free_block_names.at(i) + ".apng", + theme_path + free_block_names.at(i) + ".gif", + theme_path + free_block_names.at(i) + ".apng" + }; + + // Assume the free block does not exist until a matching file is found + free_blocks_enabled[i] = false; + for (QString path : paths) + { + if (file_exists(path)) + { + free_blocks_enabled[i] = true; + break; + } + } + } +} + void Courtroom::mod_called(QString p_ip) { ui_server_chatlog->append(p_ip); @@ -3494,6 +3575,7 @@ void Courtroom::on_reload_theme_clicked() load_shouts(); load_effects(); load_wtce(); + load_free_blocks(); //to update status on the background set_background(current_background); diff --git a/courtroom.h b/courtroom.h index c0426eae6..02fee03f4 100644 --- a/courtroom.h +++ b/courtroom.h @@ -148,10 +148,9 @@ class Courtroom : public QMainWindow void move_widget(QWidget *p_widget, QString p_identifier); void set_shouts(); - void set_effects(); - void set_wtce(); + void set_free_blocks(); //these are for OOC chat void append_ms_chatmessage(QString f_name, QString f_message); @@ -201,10 +200,11 @@ class Courtroom : public QMainWindow void check_connection_received(); - //checks whether shout/effect files are found + //checks whether shout/effect/wtce/free block files are found void check_shouts(); void check_effects(); void check_wtce(); + void check_free_blocks(); void resume_timer(int timer_id); void set_timer_time(int timer_id, int new_time); @@ -485,6 +485,8 @@ class Courtroom : public QMainWindow QVector ui_effects; //holds all the shout buttons objects QVector ui_wtce; + //holds all the free block objects + QVector ui_free_blocks; //holds all the names for sound files for the shouts // QVector shout_names = {"holdit", "objection", "takethat", "custom", "gotit", "crossswords", "counteralt"}; @@ -498,10 +500,14 @@ class Courtroom : public QMainWindow //QVector shout_names = {"witnesstestimony", "crossexamination", "investigation", "nonstop"}; QVector wtce_names; + //holds all the names for free blocks + QVector free_block_names; + //holds whether the animation file exists for a determined shout/effect QVector shouts_enabled; QVector effects_enabled; QVector wtce_enabled; + QVector free_blocks_enabled; // AOButton* ui_shout_hold_it = nullptr; // 1 // AOButton* ui_shout_objection = nullptr; // 2 @@ -681,6 +687,7 @@ private slots: void load_shouts(); void load_effects(); void load_wtce(); + void load_free_blocks(); /** * @brief reset the shout button's texture to default * DOES NOT MODIFY OBJECTION_STATE From c37e9c4c2ee6292efa57f03f3dbf03f4be2d5906 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 26 May 2020 12:22:54 -0400 Subject: [PATCH 019/842] Move widget management functions to new file --- Attorney_Online_remake.pro | 3 +- courtroom.cpp | 1815 ++++++------------------------------ courtroom.h | 3 + courtroom_widgets.cpp | 1305 ++++++++++++++++++++++++++ 4 files changed, 1585 insertions(+), 1541 deletions(-) create mode 100644 courtroom_widgets.cpp diff --git a/Attorney_Online_remake.pro b/Attorney_Online_remake.pro index aee1a8044..e7dd507a0 100644 --- a/Attorney_Online_remake.pro +++ b/Attorney_Online_remake.pro @@ -57,7 +57,8 @@ SOURCES += main.cpp\ aoshoutplayer.cpp \ aonotearea.cpp \ aonotepicker.cpp \ - aolabel.cpp + aolabel.cpp \ + courtroom_widgets.cpp HEADERS += lobby.h \ aoimage.h \ diff --git a/courtroom.cpp b/courtroom.cpp index ab8f9ac70..6fa53f299 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -28,1262 +28,12 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, 0, NULL); BASS_PluginLoad("bassopus.dll", BASS_UNICODE); - keepalive_timer = new QTimer(this); - keepalive_timer->start(60000); - - chat_tick_timer = new QTimer(this); - - text_delay_timer = new QTimer(this); - text_delay_timer->setSingleShot(true); - - sfx_delay_timer = new QTimer(this); - sfx_delay_timer->setSingleShot(true); - - realization_timer = new QTimer(this); - realization_timer->setSingleShot(true); - - testimony_show_timer = new QTimer(this); - testimony_show_timer->setSingleShot(true); - - testimony_hide_timer = new QTimer(this); - testimony_hide_timer->setSingleShot(true); - - char_button_mapper = new QSignalMapper(this); - - m_blip_player = new AOBlipPlayer(this, ao_app); - m_blip_player->set_volume(0); - m_sfx_player = new AOSfxPlayer(this, ao_app); - m_sfx_player->set_volume(0); - m_shout_player = new AOShoutPlayer(this, ao_app); - m_shout_player->set_volume(0); - m_mod_player = new AOSfxPlayer(this, ao_app); - m_mod_player->set_volume(50); - m_cycle_player = new AOSfxPlayer(this, ao_app); - m_cycle_player->set_volume(0); - m_music_player = new AOMusicPlayer(this, ao_app); - m_music_player->set_volume(0); - - 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_desk = new AOScene(ui_viewport, ao_app); - ui_vp_legacy_desk = new AOScene(ui_viewport, ao_app); - - ui_vp_music_display_a = new AOImage(this, ao_app); - ui_vp_music_display_b = new AOImage(this, ao_app); - ui_vp_music_area = new QWidget(ui_vp_music_display_a); - ui_vp_music_name = new QTextEdit(ui_vp_music_area); - ui_vp_music_name->setText("DANGANRONPA ONLINE"); - ui_vp_music_name->setFrameStyle(QFrame::NoFrame); - ui_vp_music_name->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_vp_music_name->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_vp_music_name->setReadOnly(true); - music_anim = new QPropertyAnimation(ui_vp_music_name, "geometry", this); - - ui_vp_clock = new AOMovie(this, ao_app); - ui_vp_clock->set_play_once(true); - - ui_vp_evidence_display = new AOEvidenceDisplay(this, ao_app); - - ui_vp_chatbox = new AOImage(this, ao_app); - ui_vp_showname = new QLabel(ui_vp_chatbox); - ui_vp_message = new QTextEdit(ui_vp_chatbox); - ui_vp_message->setFrameStyle(QFrame::NoFrame); - ui_vp_message->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_vp_message->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_vp_message->setReadOnly(true); - - ui_vp_showname_image = new AOImage(this, ao_app); - - ui_vp_testimony = new AOImage(this, ao_app); - ui_vp_effect = new AOMovie(this, ao_app); - ui_vp_wtce = new AOMovie(this, ao_app); - ui_vp_objection = new AOMovie(this, ao_app); - - ui_ic_chatlog = new QTextEdit(this); - ui_ic_chatlog->setReadOnly(true); - - ui_ms_chatlog = new AOTextArea(this); - ui_ms_chatlog->setReadOnly(true); - ui_ms_chatlog->setOpenExternalLinks(true); - ui_ms_chatlog->hide(); - - ui_server_chatlog = new AOTextArea(this); - ui_server_chatlog->setReadOnly(true); - ui_server_chatlog->setOpenExternalLinks(true); - - ui_mute_list = new QListWidget(this); - ui_area_list = new QListWidget(this); - ui_music_list = new QListWidget(this); - ui_sfx_list = new QListWidget(this); - - ui_ic_chat_message = new QLineEdit(this); - ui_ic_chat_message->setFrame(false); - - ui_muted = new AOImage(ui_ic_chat_message, ao_app); - ui_muted->hide(); - - ui_ooc_chat_message = new QLineEdit(this); - ui_ooc_chat_message->setFrame(false); - - ui_ooc_chat_name = new QLineEdit(this); - ui_ooc_chat_name->setFrame(false); - ui_ooc_chat_name->setPlaceholderText("Name"); - - //ui_area_password = new QLineEdit(this); - //ui_area_password->setFrame(false); - ui_music_search = new QLineEdit(this); - ui_music_search->setFrame(false); - - ui_sfx_search = new QLineEdit(this); - ui_sfx_search->setFrame(false); - - ui_note_area = new AONoteArea(this, ao_app); - ui_note_area->add_button = new AOButton(ui_note_area, ao_app); - ui_note_area->m_layout = new QVBoxLayout(ui_note_area); - note_scroll_area = new QScrollArea(this); - - note_scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - note_scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - note_scroll_area->setWidgetResizable(false); - - ui_set_notes = new AOButton(this, ao_app); - - construct_emotes(); - - ui_emote_left = new AOButton(this, ao_app); - ui_emote_right = new AOButton(this, ao_app); - - ui_emote_dropdown = new QComboBox(this); - ui_pos_dropdown = new QComboBox(this); - ui_pos_dropdown->addItem("wit"); - ui_pos_dropdown->addItem("def"); - ui_pos_dropdown->addItem("pro"); - ui_pos_dropdown->addItem("jud"); - ui_pos_dropdown->addItem("hld"); - ui_pos_dropdown->addItem("hlp"); - - ui_defense_bar = new AOImage(this, ao_app); - ui_prosecution_bar = new AOImage(this, ao_app); - - ui_music_label = new AOLabel(this, ao_app); - ui_sfx_label = new AOLabel(this, ao_app); - ui_blip_label = new AOLabel(this, ao_app); - - load_shouts(); // Reads from theme, deletes old shouts if needed and creates new ones - - ui_shout_up = new AOButton(this, ao_app); - ui_shout_up->setProperty("cycle_id", 1); - ui_shout_down = new AOButton(this, ao_app); - ui_shout_down->setProperty("cycle_id", 0); - - load_effects(); - - ui_effect_down = new AOButton(this, ao_app); - ui_effect_down->setProperty("cycle_id", 2); - ui_effect_up = new AOButton(this, ao_app); - ui_effect_up->setProperty("cycle_id", 3); - - load_wtce(); - - ui_wtce_up = new AOButton(this, ao_app); - ui_wtce_up->setProperty("cycle_id", 5); - ui_wtce_down = new AOButton(this, ao_app); - ui_wtce_down->setProperty("cycle_id", 4); - - ui_ooc_toggle = new AOButton(this, ao_app); - - ui_change_character = new AOButton(this, ao_app); - ui_reload_theme = new AOButton(this, ao_app); - ui_call_mod = new AOButton(this, ao_app); - ui_switch_area_music = new AOButton(this, ao_app); - - ui_theme_list = new QComboBox(this); - ui_confirm_theme = new AOButton(this, ao_app); - ui_note_button = new AOButton(this, ao_app); - - ui_label_images.resize(7); - for(int i = 0; i < ui_label_images.size(); ++i) - { - ui_label_images[i] = new AOImage(this, ao_app); - } - - ui_pre = new QCheckBox(this); - ui_pre->setText("Pre"); - ui_flip = new QCheckBox(this); - ui_flip->setText("Flip"); - ui_flip->hide(); - ui_guard = new QCheckBox(this); - ui_guard->setText("Guard"); - ui_guard->hide(); - ui_hidden = new QCheckBox(this); - ui_hidden->setText("Hidden"); - - // filling vectors with existing label/checkbox pointers - ui_checks.push_back(ui_pre); - ui_checks.push_back(ui_flip); - ui_checks.push_back(ui_guard); - ui_checks.push_back(ui_hidden); - ui_labels.push_back(ui_music_label); - ui_labels.push_back(ui_sfx_label); - ui_labels.push_back(ui_blip_label); - // - - ui_mute = new AOButton(this, ao_app); - - ui_defense_plus = new AOButton(this, ao_app); - ui_defense_minus = new AOButton(this, ao_app); - - ui_prosecution_plus = new AOButton(this, ao_app); - ui_prosecution_minus = new AOButton(this, ao_app); - - ui_text_color = new QComboBox(this); - ui_text_color->addItem("White"); - ui_text_color->addItem("Green"); - ui_text_color->addItem("Red"); - ui_text_color->addItem("Orange"); - ui_text_color->addItem("Blue"); - if (ao_app->yellow_text_enabled) - ui_text_color->addItem("Yellow"); - - ui_music_slider = new QSlider(Qt::Horizontal, this); - ui_music_slider->setRange(0, 100); - ui_music_slider->setValue(ao_app->get_default_music()); - - ui_sfx_slider = new QSlider(Qt::Horizontal, this); - ui_sfx_slider->setRange(0, 100); - ui_sfx_slider->setValue(ao_app->get_default_sfx()); - - ui_blip_slider = new QSlider(Qt::Horizontal, this); - ui_blip_slider->setRange(0, 100); - ui_blip_slider->setValue(ao_app->get_default_blip()); - - ui_evidence_button = new AOButton(this, ao_app); - - ui_vp_notepad_image = new AOImage(this, ao_app); - ui_vp_notepad = new QTextEdit(this); - ui_vp_notepad->setFrameStyle(QFrame::NoFrame); - - ui_timers.resize(1); - ui_timers[0] = new AOTimer(this, ao_app); - - load_free_blocks(); // Done last so they are guaranteed to be at bottom - - construct_evidence(); - - construct_char_select(); - - connect(keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); - - connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); - connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); - - connect(text_delay_timer, SIGNAL(timeout()), this, SLOT(start_chat_ticking())); - connect(sfx_delay_timer, SIGNAL(timeout()), this, SLOT(play_sfx())); - - connect(chat_tick_timer, SIGNAL(timeout()), this, SLOT(chat_tick())); - - connect(realization_timer, SIGNAL(timeout()), this, SLOT(realization_done())); - - connect(testimony_show_timer, SIGNAL(timeout()), this, SLOT(hide_testimony())); - connect(testimony_hide_timer, SIGNAL(timeout()), this, SLOT(show_testimony())); - - connect(ui_emote_left, SIGNAL(clicked()), this, SLOT(on_emote_left_clicked())); - connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); - - connect(ui_emote_dropdown, SIGNAL(activated(int)), this, SLOT(on_emote_dropdown_changed(int))); - connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); - - connect(ui_mute_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_mute_list_clicked(QModelIndex))); - - connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_chat_return_pressed())); - - connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); - - connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_music_list_clicked())); - connect(ui_area_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_area_list_clicked())); - connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_music_list_double_clicked(QModelIndex))); - connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_area_list_double_clicked(QModelIndex))); - - // connect events for shout/effect/wtce buttons happen in load_shouts(), load_effects(), load_wtce() - connect(ui_shout_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - connect(ui_shout_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - - connect(ui_effect_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - connect(ui_effect_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - - connect(ui_wtce_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - connect(ui_wtce_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - - connect(ui_mute, SIGNAL(clicked()), this, SLOT(on_mute_clicked())); - - connect(ui_defense_minus, SIGNAL(clicked()), this, SLOT(on_defense_minus_clicked())); - connect(ui_defense_plus, SIGNAL(clicked()), this, SLOT(on_defense_plus_clicked())); - connect(ui_prosecution_minus, SIGNAL(clicked()), this, SLOT(on_prosecution_minus_clicked())); - connect(ui_prosecution_plus, SIGNAL(clicked()), this, SLOT(on_prosecution_plus_clicked())); - - connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, SLOT(on_text_color_changed(int))); - - connect(ui_music_slider, SIGNAL(valueChanged(int)), this, SLOT(on_music_slider_moved(int))); - connect(ui_sfx_slider, SIGNAL(valueChanged(int)), this, SLOT(on_sfx_slider_moved(int))); - connect(ui_blip_slider, SIGNAL(valueChanged(int)), this, SLOT(on_blip_slider_moved(int))); - - connect(ui_ooc_toggle, SIGNAL(clicked()), this, SLOT(on_ooc_toggle_clicked())); - - connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); - connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited(QString))); - - connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); - connect(ui_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); - connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); - - connect(ui_switch_area_music, SIGNAL(clicked()), this, SLOT(on_switch_area_music_clicked())); - - connect(ui_confirm_theme, SIGNAL(clicked()), this, SLOT(on_confirm_theme_clicked())); - connect(ui_note_button, SIGNAL(clicked()), this, SLOT(on_note_button_clicked())); - - connect(ui_vp_notepad, SIGNAL(textChanged()), this, SLOT(on_note_text_changed())); - - connect(ui_pre, SIGNAL(clicked()), this, SLOT(on_pre_clicked())); - connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); - connect(ui_guard, SIGNAL(clicked()), this, SLOT(on_guard_clicked())); - - connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); - - connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_sfx_list_clicked())); - - connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); - - connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, SLOT(on_add_button_clicked())); - connect(ui_set_notes, SIGNAL(clicked(bool)), this, SLOT(on_set_notes_clicked())); - set_widgets(); - - set_bullets(); - - set_char_select(); -} - -void Courtroom::set_mute_list() -{ - mute_map.clear(); - - //maps which characters are muted based on cid, none are muted by default - for (int n_cid = 0 ; n_cid < char_list.size() ; n_cid++) - { - mute_map.insert(n_cid, false); - } - - QStringList sorted_mute_list; - - for (char_type i_char : char_list) - sorted_mute_list.append(i_char.name); - - sorted_mute_list.sort(); - - for (QString i_name : sorted_mute_list) - { - //mute_map.insert(i_name, false); - ui_mute_list->addItem(i_name); - } -} - -void Courtroom::set_widgets() -{ - blip_rate = ao_app->read_blip_rate(); - blank_blip = ao_app->get_blank_blip(); - - QString filename = design_ini; - pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", filename); - - if (f_courtroom.width < 0 || f_courtroom.height < 0) - { - qDebug() << "W: did not find courtroom width or height in " << filename; - - this->resize(714, 668); - } - else - { - m_courtroom_width = f_courtroom.width; - m_courtroom_height = f_courtroom.height; - - this->resize(f_courtroom.width, f_courtroom.height); - } - - shout_names.clear(); - for(int i = 1; i <= ui_shouts.size(); ++i) - { - QString name = ao_app->get_spbutton("[SHOUTS]", i); - if(!name.isEmpty()) - shout_names.append(name.trimmed()); - } - - effect_names.clear(); - for(int i = 1; i <= ui_effects.size(); ++i) - { - QStringList names = ao_app->get_effect(i); - if(!names.isEmpty()) - effect_names.append(names.at(0).trimmed()); - } - - wtce_names.clear(); - for(int i = 1; i <= ui_wtce.size(); ++i) - { - QString name = ao_app->get_spbutton("[WTCE]", i); - if(!name.isEmpty()) - wtce_names.append(name.trimmed()); - } - - free_block_names.clear(); - for(int i = 1; i <= ui_free_blocks.size(); ++i) - { - QString name = ao_app->get_spbutton("[FREE BLOCKS]", i); - if(!name.isEmpty()) - free_block_names.append(name.trimmed()); - } - - ui_background->move(0, 0); - ui_background->resize(m_courtroom_width, m_courtroom_height); - ui_background->set_image("courtroombackground.png"); - - set_size_and_pos(ui_viewport, "viewport"); - - ui_vp_background->move(0, 0); - ui_vp_background->resize(ui_viewport->width(), ui_viewport->height()); - - ui_vp_speedlines->move(0, 0); - ui_vp_speedlines->combo_resize(ui_viewport->width(), ui_viewport->height()); - - ui_vp_player_char->move(0, 0); - ui_vp_player_char->combo_resize(ui_viewport->width(), ui_viewport->height()); - - //the AO2 desk element - ui_vp_desk->move(0, 0); - ui_vp_desk->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(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->resize(ui_viewport->width(), ui_viewport->height()); - - set_size_and_pos(ui_vp_notepad_image, "notepad_image"); - ui_vp_notepad_image->set_image("notepad_image.png"); - ui_vp_notepad_image->hide(); - - set_size_and_pos(ui_vp_notepad, "notepad"); - ui_vp_notepad->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_vp_notepad->verticalScrollBar()->hide(); - ui_vp_notepad->verticalScrollBar()->resize(0, 0); - ui_vp_notepad->hide(); - - set_size_and_pos(ui_vp_showname, "showname"); - - set_size_and_pos(ui_vp_showname_image, "showname_image"); - ui_vp_showname_image->hide(); - - set_size_and_pos(ui_vp_message, "message"); - ui_vp_message->setTextInteractionFlags(Qt::NoTextInteraction); - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: white"); - - ui_vp_testimony->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_testimony->resize(ui_viewport->width(), ui_viewport->height()); - ui_vp_testimony->set_image("testimony.png"); - ui_vp_testimony->hide(); - - ui_vp_effect->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_effect->resize(ui_viewport->width(), ui_viewport->height()); - ui_vp_effect->hide(); - - ui_vp_wtce->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_wtce->combo_resize(ui_viewport->width(), ui_viewport->height()); - - ui_vp_objection->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_objection->combo_resize(ui_viewport->width(), ui_viewport->height()); - - set_size_and_pos(ui_ic_chatlog, "ic_chatlog"); - - set_size_and_pos(ui_ms_chatlog, "ms_chatlog"); - - set_size_and_pos(ui_server_chatlog, "server_chatlog"); - - set_size_and_pos(ui_mute_list, "mute_list"); - ui_mute_list->hide(); - - set_size_and_pos(ui_area_list, "area_list"); - ui_area_list->hide(); -// ui_area_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - - set_size_and_pos(ui_music_list, "music_list"); - - set_size_and_pos(ui_sfx_list, "sfx_list"); - - 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"); - } - 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_music_area, "music_area"); - ui_vp_music_area->show(); - set_size_and_pos(ui_vp_music_name, "music_name"); - - set_size_and_pos(ui_vp_music_display_a, "music_display_a"); - ui_vp_music_display_a->set_image("music_display_a.png"); - ui_vp_music_display_a->show(); - - set_size_and_pos(ui_vp_music_display_b, "music_display_b"); - ui_vp_music_display_b->set_image("music_display_b.png"); - ui_vp_music_display_b->show(); - - set_size_and_pos(ui_vp_clock, "clock"); - ui_vp_clock->show(); - - ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); - - ui_vp_chatbox->set_image("chatmed.png"); - ui_vp_chatbox->hide(); - - ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); - ui_muted->set_image("muted.png"); - - set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message"); - ui_ooc_chat_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - - set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name"); - ui_ooc_chat_name->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - - //set_size_and_pos(ui_area_password, "area_password"); - set_size_and_pos(ui_music_search, "music_search"); - - set_size_and_pos(ui_sfx_search, "sfx_search"); - - set_size_and_pos(ui_emotes, "emotes"); - - set_size_and_pos(ui_emote_left, "emote_left"); - ui_emote_left->set_image("arrow_left.png"); - - set_size_and_pos(ui_emote_right, "emote_right"); - ui_emote_right->set_image("arrow_right.png"); - - set_size_and_pos(ui_emote_dropdown, "emote_dropdown"); - - set_size_and_pos(ui_pos_dropdown, "pos_dropdown"); - - set_size_and_pos(ui_defense_bar, "defense_bar"); - ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + ".png"); - - set_size_and_pos(ui_prosecution_bar, "prosecution_bar"); - ui_prosecution_bar->set_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); - - set_size_and_pos(ui_music_label, "music_label"); - ui_music_label->setText("Music"); - set_size_and_pos(ui_sfx_label, "sfx_label"); - ui_sfx_label->setText("Sfx"); - set_size_and_pos(ui_blip_label, "blip_label"); - ui_blip_label->setText("Blips"); - -// set_size_and_pos(ui_shouts[0], "hold_it"); -// ui_shouts[0]->show(); -// set_size_and_pos(ui_shouts[1], "objection"); -// ui_shouts[1]->show(); -// set_size_and_pos(ui_shouts[2], "take_that"); -// ui_shouts[2]->show(); -// set_size_and_pos(ui_shouts[3], "custom_objection"); -// set_size_and_pos(ui_shouts[4], "got_it"); -// set_size_and_pos(ui_shouts[5], "cross_swords"); -// set_size_and_pos(ui_shouts[6], "counter_alt"); - for(int i = 0; i < shout_names.size(); ++i) - { - set_size_and_pos(ui_shouts[i], shout_names[i]); - } -// ui_shouts[0]->show(); -// ui_shouts[1]->show(); -// ui_shouts[2]->show(); - reset_shout_buttons(); - - set_size_and_pos(ui_shout_up, "shout_up"); - ui_shout_up->set_image("shoutup.png"); - ui_shout_up->hide(); - set_size_and_pos(ui_shout_down, "shout_down"); - ui_shout_down->set_image("shoutdown.png"); - ui_shout_down->hide(); - - // courtroom_config.ini necessary + check for crash - if (ao_app->read_theme_ini("enable_single_shout", cc_config_ini) == "true" && ui_shouts.size() > 0) - { - for(auto & shout : ui_shouts) move_widget(shout, "bullet"); - - set_shouts(); - - ui_shout_up->show(); - ui_shout_down->show(); - } - -// set_size_and_pos(ui_effects[0], "effect_flash"); -// set_size_and_pos(ui_effects[1], "effect_gloom"); -// set_size_and_pos(ui_effects[2], "effect_question"); -// set_size_and_pos(ui_effects[3], "effect_pow"); - for(int i = 0; i < effect_names.size(); ++i) - { - set_size_and_pos(ui_effects[i], effect_names[i]); - } - reset_effect_buttons(); - - set_size_and_pos(ui_effect_up, "effect_up"); - ui_effect_up->set_image("effectup.png"); - ui_effect_up->hide(); - set_size_and_pos(ui_effect_down, "effect_down"); - ui_effect_down->set_image("effectdown.png"); - ui_effect_down->hide(); - - if (ao_app->read_theme_ini("enable_single_effect", cc_config_ini) == "true" && ui_effects.size() > 0) // check to prevent crashing - { - for(auto & effect : ui_effects) move_widget(effect, "effect"); - - set_effects(); - - ui_effect_up->show(); - ui_effect_down->show(); - } - - for(int i = 0; i < wtce_names.size(); ++i) - { - set_size_and_pos(ui_wtce[i], wtce_names[i]); - } - - set_size_and_pos(ui_wtce_up, "wtce_up"); - ui_wtce_up->set_image("wtceup.png"); - ui_wtce_up->hide(); - set_size_and_pos(ui_wtce_down, "wtce_down"); - ui_wtce_down->set_image("wtcedown.png"); - ui_wtce_down->hide(); - - if (ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true" ) // courtroom_config.ini necessary - { - for(auto & wtce : ui_wtce) move_widget(wtce, "wtce"); - qDebug() << "AA: single wtce"; - set_wtce(); - } - - for(int i = 0; i < free_block_names.size(); ++i) - { - qDebug() << "Setting up free block " << i << " " << free_block_names[i]; - set_size_and_pos(ui_free_blocks[i], "free_block_" + free_block_names[i]); - } - set_free_blocks(); - - set_size_and_pos(ui_ooc_toggle, "ooc_toggle"); - ui_ooc_toggle->setText("Server"); - - set_size_and_pos(ui_call_mod, "call_mod"); - - set_size_and_pos(ui_change_character, "change_character"); - set_size_and_pos(ui_reload_theme, "reload_theme"); - set_size_and_pos(ui_call_mod, "call_mod"); - set_size_and_pos(ui_confirm_theme, "confirm_theme"); - set_size_and_pos(ui_note_button, "note_button"); - - set_size_and_pos(ui_switch_area_music, "switch_area_music"); - - // Set the default values for the buttons, then try and determine if they should be replaced by images - ui_change_character->setText("Change character"); - ui_change_character->setStyleSheet(""); - - ui_reload_theme->setText("Reload theme"); - ui_reload_theme->setStyleSheet(""); - - ui_call_mod->setText("Call mod"); - ui_call_mod->setStyleSheet(""); - - ui_switch_area_music->setText("A/M"); - ui_switch_area_music->setStyleSheet(""); - - ui_confirm_theme->setText("^"); - ui_confirm_theme->setStyleSheet(""); - - ui_note_button->setText("><:"); - ui_note_button->setStyleSheet(""); - - if (ao_app->read_theme_ini("enable_button_images", cc_config_ini) == "true") - { - // Set files, ask questions later - // set_image first tries the theme variant folder, then the theme folder, then falls back to the default theme - ui_change_character->set_image("changecharacter.png"); - if (!ui_change_character->image_path.isEmpty()) - ui_change_character->setText(""); - - ui_reload_theme->set_image("reloadtheme.png"); - if (!ui_reload_theme->image_path.isEmpty()) - ui_reload_theme->setText(""); - - ui_call_mod->set_image("callmod.png"); - if (!ui_call_mod->image_path.isEmpty()) - ui_call_mod->setText(""); - - ui_switch_area_music->set_image("switch_area_music.png"); - if (!ui_switch_area_music->image_path.isEmpty()) - ui_switch_area_music->setText(""); - - ui_confirm_theme->set_image("confirmtheme.png"); - if (!ui_confirm_theme->image_path.isEmpty()) - ui_confirm_theme->setText(""); - - ui_note_button->set_image("notebutton.png"); - if (!ui_note_button->image_path.isEmpty()) - ui_note_button->setText(""); - } - - set_size_and_pos(ui_theme_list, "theme_list"); - - set_size_and_pos(ui_pre, "pre"); - ui_pre->setText("Pre"); - - set_size_and_pos(ui_flip, "flip"); - - set_size_and_pos(ui_guard, "guard"); - - set_size_and_pos(ui_hidden, "hidden"); - - for(int i = 0; i < ui_label_images.size(); ++i) - { - set_size_and_pos(ui_label_images[i], label_images[i].toLower() + "_image"); - } - - if (ao_app->read_theme_ini("enable_label_images", cc_config_ini) == "true") - { - for (int i = 0 ; i < ui_checks.size(); ++i) // loop through checks - { - QString image = label_images[i].toLower() + ".png"; - ui_label_images[i]->set_image(image); - - if (!ui_label_images[i]->image_path.isEmpty()) - ui_checks[i]->setText(""); - else - ui_checks[i]->setText(label_images[i]); - } - - for(int i = 0 ; i < ui_labels.size(); ++i) // now through labels.......... - { - int j = i + ui_checks.size(); - QString image = label_images[j].toLower() + ".png"; - ui_label_images[j]->set_image(image); - - if (!ui_label_images[j]->image_path.isEmpty()) - ui_labels[i]->setText(""); - else - ui_labels[i]->setText(label_images[j]); - } - } - else - { - for(int i = 0; i < ui_checks.size(); ++i) //same thing - { - ui_checks[i]->setText(label_images[i]); - ui_label_images[i]->set_image(""); - } - - for(int i = 0; i < ui_labels.size(); ++i) //same thing - { - int j = i + ui_checks.size(); - ui_labels[i]->setText(label_images[j]); - ui_label_images[j]->set_image(""); - } - } - - set_size_and_pos(ui_mute, "mute_button"); - ui_mute->set_image("mute.png"); - - set_size_and_pos(ui_defense_plus, "defense_plus"); - ui_defense_plus->set_image("defplus.png"); - - set_size_and_pos(ui_defense_minus, "defense_minus"); - ui_defense_minus->set_image("defminus.png"); - - set_size_and_pos(ui_prosecution_plus, "prosecution_plus"); - ui_prosecution_plus->set_image("proplus.png"); - - set_size_and_pos(ui_prosecution_minus, "prosecution_minus"); - ui_prosecution_minus->set_image("prominus.png"); - - set_size_and_pos(ui_text_color, "text_color"); - - set_size_and_pos(ui_music_slider, "music_slider"); - set_size_and_pos(ui_sfx_slider, "sfx_slider"); - set_size_and_pos(ui_blip_slider, "blip_slider"); - - set_size_and_pos(ui_evidence_button, "evidence_button"); - ui_evidence_button->set_image("evidencebutton.png"); - - set_size_and_pos(ui_evidence, "evidence_background"); - ui_evidence->set_image("evidencebackground.png"); - - set_size_and_pos(ui_evidence_name, "evidence_name"); - - set_size_and_pos(ui_evidence_buttons, "evidence_buttons"); - - set_size_and_pos(ui_evidence_left, "evidence_left"); - ui_evidence_left->set_image("arrow_left.png"); - - set_size_and_pos(ui_evidence_right, "evidence_right"); - ui_evidence_right->set_image("arrow_right.png"); - - set_size_and_pos(ui_evidence_present, "evidence_present"); - ui_evidence_present->set_image("present_disabled.png"); - - set_size_and_pos(ui_evidence_overlay, "evidence_overlay"); - ui_evidence_overlay->set_image("evidenceoverlay.png"); - - set_size_and_pos(ui_evidence_delete, "evidence_delete"); - ui_evidence_delete->set_image("deleteevidence.png"); - - set_size_and_pos(ui_evidence_image_name, "evidence_image_name"); - - set_size_and_pos(ui_evidence_image_button, "evidence_image_button"); - - set_size_and_pos(ui_evidence_x, "evidence_x"); - ui_evidence_x->set_image("evidencex.png"); - - set_size_and_pos(ui_evidence_description, "evidence_description"); - - ui_selector->set_image("char_selector.png"); - ui_selector->hide(); - - set_size_and_pos(ui_back_to_lobby, "back_to_lobby"); - ui_back_to_lobby->setText("Back to Lobby"); - - set_size_and_pos(ui_char_password, "char_password"); - - set_size_and_pos(ui_char_buttons, "char_buttons"); - - set_size_and_pos(ui_char_select_left, "char_select_left"); - ui_char_select_left->set_image("arrow_left.png"); - - set_size_and_pos(ui_char_select_right, "char_select_right"); - ui_char_select_right->set_image("arrow_right.png"); - - set_size_and_pos(ui_spectator, "spectator"); - - handle_music_anim(); - - set_size_and_pos(ui_set_notes, "set_notes_button"); - ui_set_notes->set_image("set_notes.png"); - ui_note_area->m_layout->setSpacing(10); - set_size_and_pos(ui_note_area, "note_area"); - set_size_and_pos(note_scroll_area, "note_area"); - note_scroll_area->setWidget(ui_note_area); - ui_note_area->set_image("note_area.png"); - ui_note_area->add_button->set_image("add_button.png"); - ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - ui_note_area->setLayout(ui_note_area->m_layout); - ui_note_area->show(); - note_scroll_area->hide(); - - list_note_files(); - - if(!contains_add_button) - { - ui_note_area->m_layout->addWidget(ui_note_area->add_button); - contains_add_button = true; - } - - timer_number = adapt_numbered_items(ui_timers, "timer_number", "timer"); - set_dropdowns(); - set_fonts(); -} - -void Courtroom::set_fonts() -{ - set_font(ui_vp_showname, "showname"); - set_font(ui_vp_message, "message"); - set_font(ui_ic_chatlog, "ic_chatlog"); - set_font(ui_ms_chatlog, "ms_chatlog"); - set_font(ui_server_chatlog, "server_chatlog"); - set_font(ui_music_list, "music_list"); - set_font(ui_area_list, "area_list"); - set_font(ui_sfx_list, "sfx_list"); - set_font(ui_vp_music_name, "music_name"); - set_font(ui_vp_notepad, "notepad"); - for (int i=0; iget_font_size(p_identifier, design_file); - QString class_name = widget->metaObject()->className(); - - QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); - - widget->setFont(QFont(font_name, f_weight)); - - QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); - - int bold = ao_app->get_font_size(p_identifier + "_bold", design_file); // is the font bold or not? - - QString is_bold = ""; - if(bold == 1) is_bold = "bold"; - - QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + - "color: rgba(" + - QString::number(f_color.red()) + ", " + - QString::number(f_color.green()) + ", " + - QString::number(f_color.blue()) + ", 255);\n" - "font: " + is_bold + "; }"; - - widget->setStyleSheet(style_sheet_string); -} - -void Courtroom::set_dropdown(QWidget *widget, QString target_tag) -{ - QString f_file = "courtroom_stylesheets.css"; - QString style_sheet_string = ao_app->get_stylesheet(target_tag, f_file); - if (style_sheet_string != "") - widget->setStyleSheet(style_sheet_string); -} - -void Courtroom::set_dropdowns() -{ - set_dropdown(ui_text_color, "[TEXT COLOR]"); - set_dropdown(ui_pos_dropdown, "[POS DROPDOWN]"); - set_dropdown(ui_theme_list, "[THEME LIST]"); - set_dropdown(ui_emote_dropdown, "[EMOTE DROPDOWN]"); - set_dropdown(ui_mute_list, "[MUTE LIST]"); - set_dropdown(ui_ic_chat_message, "[IC LINE]"); - set_dropdown(ui_ooc_chat_message, "[OOC LINE]"); -} - -void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) -{ - QString filename = design_ini; - - pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - - 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); - } -} - -void Courtroom::load_shouts() -{ - // Close any existing shouts to prevent memory leaks - for (int i=0; iclose(); - delete ui_shouts[i]; - } - - // And create new shouts - int shout_number = ao_app->get_design_ini_value("shout_number", cc_config_ini); - shouts_enabled.resize(shout_number); - ui_shouts.resize(shout_number); - - for (int i=0; isetProperty("shout_id", i+1); - ui_shouts[i]->stackUnder(ui_shout_up); - ui_shouts[i]->stackUnder(ui_shout_down); - } - - // And connect their actions - for (auto & shout : ui_shouts) - connect(shout, SIGNAL(clicked(bool)), this, SLOT(on_shout_clicked())); -} - -void Courtroom::load_effects() -{ - // Close any existing effects to prevent memory leaks - for (int i=0; iclose(); - delete ui_effects[i]; - } - - // And create new effects - int effect_number = ao_app->get_design_ini_value("effect_number", cc_config_ini); - effects_enabled.resize(effect_number); - ui_effects.resize(effect_number); - - for (int i=0; isetProperty("effect_id", i+1); - ui_effects[i]->stackUnder(ui_effect_up); - ui_effects[i]->stackUnder(ui_effect_down); - } - - // And connect their actions - for (auto & effect : ui_effects) - connect(effect, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked())); -} - -void Courtroom::load_wtce() -{ - // Close any existing wtce buttons to prevent memory leaks - for (int i=0; iclose(); - delete ui_wtce[i]; - } - - // And create new wtce buttons - int wtce_number = ao_app->get_design_ini_value("wtce_number", cc_config_ini); - wtce_enabled.resize(wtce_number); - ui_wtce.resize(wtce_number); - - for (int i=0; isetProperty("wtce_id", i+1); - ui_wtce[i]->stackUnder(ui_wtce_up); - ui_wtce[i]->stackUnder(ui_wtce_down); - } - - // And connect their actions - for (auto & wtce : ui_wtce) - connect(wtce, SIGNAL(clicked(bool)), this, SLOT(on_wtce_clicked())); -} - -void Courtroom::load_free_blocks() -{ - // Close any existing free blocks to prevent memory leaks - for (int i=0; iclose(); - delete ui_free_blocks[i]; - } - - // And create new free block buttons - int free_block_number = ao_app->get_design_ini_value("free_block_number", cc_config_ini); - free_blocks_enabled.resize(free_block_number); - ui_free_blocks.resize(free_block_number); - - for (int i=0; isetProperty("free_block_id", i+1); - ui_free_blocks[i]->set_play_once(false); - ui_free_blocks[i]->stackUnder(ui_vp_music_display_a); - } - -} - -void Courtroom::set_shouts() -{ - for(auto & shout : ui_shouts) shout->hide(); - if (ui_shouts.size() > 0) ui_shouts[m_shout_state]->show(); // check to prevent crashing -} - -void Courtroom::set_effects() -{ - for(auto & effect : ui_effects) effect->hide(); - if (ui_effects.size() > 0) ui_effects[m_effect_current]->show(); // check to prevent crashing -} - -void Courtroom::set_wtce() -{ - for(auto & wtce : ui_wtce) wtce->hide(); - - if(is_judge && ui_wtce.size() > 0) // check to prevent crashing - { - if( ao_app->read_theme_ini("enable_single_wtce", cc_config_ini ) == "true" ) - { - ui_wtce[m_wtce_current]->show(); - ui_wtce_up->show(); - ui_wtce_down->show(); - } - else - { - for(auto & wtce : ui_wtce) wtce->show(); - ui_wtce_up->hide(); - ui_wtce_down->hide(); - } - } -} - -void Courtroom::set_free_blocks() -{ - for (int i=0; iplay("free_block_" + free_block_names[i]); - } -} - -void Courtroom::handle_music_anim() -{ - QString file_a = design_ini; - QString file_b = fonts_ini; - pos_size_type res_a = ao_app->get_element_dimensions("music_name", file_a); - pos_size_type res_b = ao_app->get_element_dimensions("music_area", file_a); - float speed = static_cast(ao_app->get_font_size("music_name_speed", file_b)); - - QFont f_font = ui_vp_music_name->font(); - QFontMetrics fm(f_font); - int dist; - if ( ao_app->read_theme_ini( "enable_const_music_speed", cc_config_ini ) == "true") - dist = res_b.width; - else dist = fm.width(ui_vp_music_name->toPlainText()); - int time = static_cast(1000000*dist/speed); - music_anim->setLoopCount(-1); - music_anim->setDuration(time); - music_anim->setStartValue(QRect(res_b.width + res_b.x, res_a.y, res_a.width, res_a.height)); - music_anim->setEndValue(QRect(-dist + res_a.x, res_a.y, res_a.width, res_a.height)); - music_anim->start(); -} - -void Courtroom::handle_clock(QString time) -{ - current_clock = time.toInt(); - - QString string = "hours\\" + time; // expected to be 0, 1, 2... - qDebug() << "hours:" << string; - ui_vp_clock->play(string); -} - -void Courtroom::handle_theme_variant(QString theme_variant) -{ - ao_app->set_theme_variant(theme_variant); - on_reload_theme_clicked(); -} - -void Courtroom::set_window_title(QString p_title) -{ - this->setWindowTitle(p_title); -} - -void Courtroom::set_char_rpc() -{ - rpc_char_list.clear(); - - QFile config_file(ao_app->get_base_path() + rpc_ini); - if (!config_file.open(QIODevice::ReadOnly)) - { qDebug() << "Error reading" << ao_app->get_base_path() + rpc_ini; return; } - - QTextStream in(&config_file); - - while(!in.atEnd()) - { - QString f_line = in.readLine().trimmed(); - - QStringList line_elements = f_line.split("-"); - - rpc_char_list.append(line_elements.at(1).trimmed().toLower()); - } - - config_file.close(); -} - -void Courtroom::set_size_and_pos(QWidget *p_widget, QString p_identifier) -{ - QString filename = design_ini; - - - pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - - 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()) - { - qDebug() << "W: set_taken attempted to set an index bigger than char_list size"; - return; - } - - char_type f_char; - f_char.name = char_list.at(n_char).name; - f_char.description = char_list.at(n_char).description; - f_char.taken = p_taken; - f_char.evidence_string = char_list.at(n_char).evidence_string; - - char_list.replace(n_char, f_char); -} - -void Courtroom::done_received() -{ - m_cid = -1; - - m_music_player->set_volume(0); - m_sfx_player->set_volume(0); - m_shout_player->set_volume(0); - m_blip_player->set_volume(0); - - set_char_select_page(); - - set_mute_list(); + create_widgets(); + connect_widgets(); + set_widgets(); + set_bullets(); set_char_select(); - - show(); - - ui_spectator->show(); -} - -void Courtroom::set_background(QString p_background) -{ - testimony_in_progress = false; - - current_background = p_background; - QString bg_path = get_background_path(); - QVector exts{".apng", ".gif", ".png"}; - - QString ini_path = ao_app->get_background_path() + "backgrounds.ini"; - - if (file_exists(ini_path)) - { - is_ao2_bg = true; - } - else - { - is_ao2_bg = file_exists(bg_path + "defensedesk", exts) != "" && - file_exists(bg_path + "prosecutiondesk", exts) != "" && - file_exists(bg_path + "stand", exts) != ""; - } - - if (is_ao2_bg) - { - 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_ic_chat_message, "ic_chat_message"); - } } void Courtroom::enter_courtroom(int p_cid) @@ -1303,7 +53,8 @@ void Courtroom::enter_courtroom(int p_cid) { f_char = ao_app->get_char_name(char_list.at(m_cid).name); QString r_char = f_char; - QRegularExpression re(QString::fromUtf8("[-`~!@#$%^&*()—+=|:;<>«»,.?/{}\'\"\\[\\]]")); // regex for removing non letter (except _) characters + // regex for removing non letter (except _) characters + QRegularExpression re(QString::fromUtf8("[-`~!@#$%^&*()—+=|:;<>«»,.?/{}\'\"\\[\\]]")); r_char.remove(re); if(!rpc_char_list.contains(f_char.toLower())) @@ -1394,42 +145,280 @@ void Courtroom::enter_courtroom(int p_cid) if (is_judge && !wtce_enabled[m_wtce_current]) cycle_wtce(1); - check_free_blocks(); + check_free_blocks(); + + if (ao_app->flipping_enabled) + ui_flip->show(); + else + ui_flip->hide(); + + list_music(); + list_areas(); + list_sfx(); + list_themes(); + + ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors + + m_music_player->set_volume(ui_music_slider->value()); + m_sfx_player->set_volume(ui_sfx_slider->value()); + m_cycle_player->set_volume(ui_sfx_slider->value()); + m_shout_player->set_volume(ui_sfx_slider->value()); + m_blip_player->set_volume(ui_blip_slider->value()); + + testimony_in_progress = false; + + //ui_server_chatlog->setHtml(ui_server_chatlog->toHtml()); + + ui_char_select_background->hide(); + + bool ok; // ok will be set to false and this will return 0 if goes bad + chat_tick_interval = ao_app->read_config("chat_tick_interval").toInt(&ok, 10); + if(!ok) chat_tick_interval = 60; + + ui_ic_chat_message->setEnabled(m_cid != -1); + ui_ic_chat_message->setFocus(); + + set_bullets(); +} + +void Courtroom::done_received() +{ + m_cid = -1; + + m_music_player->set_volume(0); + m_sfx_player->set_volume(0); + m_shout_player->set_volume(0); + m_blip_player->set_volume(0); + + set_char_select_page(); + + set_mute_list(); + + set_char_select(); + + show(); + + ui_spectator->show(); +} + +void Courtroom::set_window_title(QString p_title) +{ + this->setWindowTitle(p_title); +} + + + +void Courtroom::set_scene() +{ + if (testimony_in_progress) + show_testimony(); + + //witness is default if pos is invalid + QString f_background = "witnessempty"; + QString f_desk_image = "stand"; + QString f_desk_mod = m_chatmessage[DESK_MOD]; + QString f_side = m_chatmessage[SIDE]; + QString ini_path = ao_app->get_background_path() + "backgrounds.ini"; + + if (file_exists(ini_path)) + { + f_background = ao_app->read_design_ini(f_side, ini_path); + f_desk_image = ao_app->read_design_ini(f_side + "_desk", ini_path); + + if (f_desk_mod == "0") // keeping a bit of the functionality for now + { + ui_vp_desk->hide(); + ui_vp_legacy_desk->hide(); + } + } + else + { + if (f_side == "def") + { + f_background = "defenseempty"; + if (is_ao2_bg) + f_desk_image = "defensedesk"; + else + f_desk_image = "bancodefensa"; + } + else if (f_side == "pro") + { + f_background = "prosecutorempty"; + if (is_ao2_bg) + f_desk_image = "prosecutiondesk"; + else + f_desk_image = "bancoacusacion"; + } + else if (f_side == "jud") + { + f_background = "judgestand"; + f_desk_image = "judgedesk"; + } + else if (f_side == "hld") + { + f_background = "helperstand"; + f_desk_image = "helperdesk"; + } + else if (f_side == "hlp") + { + f_background = "prohelperstand"; + f_desk_image = "prohelperdesk"; + } + else + { + if (is_ao2_bg) + f_desk_image = "stand"; + else + f_desk_image = "estrado"; + } + + 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 if (is_ao2_bg || (f_side == "jud" || + f_side == "hld" || + f_side == "hlp")) + { + ui_vp_legacy_desk->hide(); + ui_vp_desk->show(); + } + else + { + if (f_side == "wit") + { + ui_vp_desk->show(); + ui_vp_legacy_desk->hide(); + } + else + { + ui_vp_desk->hide(); + ui_vp_legacy_desk->show(); + } + } + } + + 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); +} + +void Courtroom::set_char_rpc() +{ + rpc_char_list.clear(); + + QFile config_file(ao_app->get_base_path() + rpc_ini); + if (!config_file.open(QIODevice::ReadOnly)) + { qDebug() << "Error reading" << ao_app->get_base_path() + rpc_ini; return; } + + QTextStream in(&config_file); + + while(!in.atEnd()) + { + QString f_line = in.readLine().trimmed(); + + QStringList line_elements = f_line.split("-"); + + rpc_char_list.append(line_elements.at(1).trimmed().toLower()); + } - if (ao_app->flipping_enabled) - ui_flip->show(); - else - ui_flip->hide(); + config_file.close(); +} - list_music(); - list_areas(); - list_sfx(); - list_themes(); +void Courtroom::set_taken(int n_char, bool p_taken) +{ + if (n_char >= char_list.size()) + { + qDebug() << "W: set_taken attempted to set an index bigger than char_list size"; + return; + } - ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors + char_type f_char; + f_char.name = char_list.at(n_char).name; + f_char.description = char_list.at(n_char).description; + f_char.taken = p_taken; + f_char.evidence_string = char_list.at(n_char).evidence_string; - m_music_player->set_volume(ui_music_slider->value()); - m_sfx_player->set_volume(ui_sfx_slider->value()); - m_cycle_player->set_volume(ui_sfx_slider->value()); - m_shout_player->set_volume(ui_sfx_slider->value()); - m_blip_player->set_volume(ui_blip_slider->value()); + char_list.replace(n_char, f_char); +} +void Courtroom::set_background(QString p_background) +{ testimony_in_progress = false; - //ui_server_chatlog->setHtml(ui_server_chatlog->toHtml()); + current_background = p_background; + QString bg_path = get_background_path(); + QVector exts{".apng", ".gif", ".png"}; - ui_char_select_background->hide(); + QString ini_path = ao_app->get_background_path() + "backgrounds.ini"; - bool ok; - chat_tick_interval = ao_app->read_config("chat_tick_interval").toInt(&ok, 10); // ok will be set to false and this will return 0 if goes bad - if(!ok) chat_tick_interval = 60; + if (file_exists(ini_path)) + { + is_ao2_bg = true; + } + else + { + if (file_exists(bg_path + "defensedesk", exts) == "") + is_ao2_bg = false; + else if (file_exists(bg_path + "prosecutiondesk", exts) == "") + is_ao2_bg = false; + else if (file_exists(bg_path + "stand", exts) == "") + is_ao2_bg = false; + is_ao2_bg = true; ; + } - ui_ic_chat_message->setEnabled(m_cid != -1); - ui_ic_chat_message->setFocus(); + if (is_ao2_bg) + { + 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_ic_chat_message, "ic_chat_message"); + } +} - set_bullets(); +void Courtroom::handle_music_anim() +{ + QString file_a = design_ini; + QString file_b = fonts_ini; + pos_size_type res_a = ao_app->get_element_dimensions("music_name", file_a); + pos_size_type res_b = ao_app->get_element_dimensions("music_area", file_a); + float speed = static_cast(ao_app->get_font_size("music_name_speed", file_b)); + + QFont f_font = ui_vp_music_name->font(); + QFontMetrics fm(f_font); + int dist; + if ( ao_app->read_theme_ini( "enable_const_music_speed", cc_config_ini ) == "true") + dist = res_b.width; + else dist = fm.width(ui_vp_music_name->toPlainText()); + int time = static_cast(1000000*dist/speed); + music_anim->setLoopCount(-1); + music_anim->setDuration(time); + music_anim->setStartValue(QRect(res_b.width + res_b.x, res_a.y, res_a.width, res_a.height)); + music_anim->setEndValue(QRect(-dist + res_a.x, res_a.y, res_a.width, res_a.height)); + music_anim->start(); +} + +void Courtroom::handle_clock(QString time) +{ + current_clock = time.toInt(); + + QString string = "hours\\" + time; // expected to be 0, 1, 2... + qDebug() << "hours:" << string; + ui_vp_clock->play(string); } +void Courtroom::handle_theme_variant(QString theme_variant) +{ + ao_app->set_theme_variant(theme_variant); + on_reload_theme_clicked(); +} void Courtroom::list_music() { ui_music_list->clear(); @@ -1976,7 +965,8 @@ void Courtroom::handle_chatmessage_2() // handles IC if (f_color == "") f_color = "rgb(" + ao_app->read_theme_ini("showname_color" , "courtroom_fonts.ini") + ")"; - int bold = ao_app->get_font_size("showname_bold", fonts_ini); // is the font bold or not? // taken directly from function up there lol // kinda hacky + // is the font bold or not? // taken directly from function up there lol // kinda hacky + int bold = ao_app->get_font_size("showname_bold", fonts_ini); QString is_bold = ""; if(bold == 1) is_bold = "bold"; @@ -2631,108 +1621,12 @@ void Courtroom::play_sfx() if (sfx_name == "1") return; - QString f_ext = file_exists(ao_app->get_base_path() + "/sounds/general/" + sfx_name, QVector{ "", ".ogg", ".wav", ".mp3"}); - - m_sfx_player->play(sfx_name + f_ext); -} - -void Courtroom::set_scene() -{ - if (testimony_in_progress) - show_testimony(); - - //witness is default if pos is invalid - QString f_background = "witnessempty"; - QString f_desk_image = "stand"; - QString f_desk_mod = m_chatmessage[DESK_MOD]; - QString f_side = m_chatmessage[SIDE]; - QString ini_path = ao_app->get_background_path() + "backgrounds.ini"; - - if (file_exists(ini_path)) - { - f_background = ao_app->read_design_ini(f_side, ini_path); - f_desk_image = ao_app->read_design_ini(f_side + "_desk", ini_path); - - if (f_desk_mod == "0") // keeping a bit of the functionality for now - { - ui_vp_desk->hide(); - ui_vp_legacy_desk->hide(); - } - } - else - { - if (f_side == "def") - { - f_background = "defenseempty"; - if (is_ao2_bg) - f_desk_image = "defensedesk"; - else - f_desk_image = "bancodefensa"; - } - else if (f_side == "pro") - { - f_background = "prosecutorempty"; - if (is_ao2_bg) - f_desk_image = "prosecutiondesk"; - else - f_desk_image = "bancoacusacion"; - } - else if (f_side == "jud") - { - f_background = "judgestand"; - f_desk_image = "judgedesk"; - } - else if (f_side == "hld") - { - f_background = "helperstand"; - f_desk_image = "helperdesk"; - } - else if (f_side == "hlp") - { - f_background = "prohelperstand"; - f_desk_image = "prohelperdesk"; - } - else - { - if (is_ao2_bg) - f_desk_image = "stand"; - else - f_desk_image = "estrado"; - } + QVector extensions{ "", ".ogg", ".wav", ".mp3"}; + QString general_path = ao_app->get_base_path() + "/sounds/general/"; - 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 if (is_ao2_bg || (f_side == "jud" || - f_side == "hld" || - f_side == "hlp")) - { - ui_vp_legacy_desk->hide(); - ui_vp_desk->show(); - } - else - { - if (f_side == "wit") - { - ui_vp_desk->show(); - ui_vp_legacy_desk->hide(); - } - else - { - ui_vp_desk->hide(); - ui_vp_legacy_desk->show(); - } - } - } + QString f_ext = file_exists(general_path + sfx_name, extensions); - 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); + m_sfx_player->play(sfx_name + f_ext); } void Courtroom::set_text_color() @@ -2917,118 +1811,6 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) } } -void Courtroom::check_shouts() -{ - QString char_path = ao_app->get_character_path(current_char); - QString theme_variant_path = ao_app->get_theme_variant_path(); - QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < ui_shouts.size(); ++i) - { - QStringList paths{ - char_path + shout_names.at(i) + ".gif", - theme_variant_path + shout_names.at(i) + ".gif", - theme_variant_path + shout_names.at(i) + ".apng", - theme_path + shout_names.at(i) + ".gif", - theme_path + shout_names.at(i) + ".apng" - }; - - // Assume the shout does not exist until a matching file is found - shouts_enabled[i] = false; - for (QString path : paths) - { - if (file_exists(path)) - { - shouts_enabled[i] = true; - break; - } - } - } -} - -void Courtroom::check_effects() -{ - QString char_path = ao_app->get_character_path(current_char); - QString theme_variant_path = ao_app->get_theme_variant_path(); - QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < ui_shouts.size(); ++i) - { - QStringList paths{ - char_path + effect_names.at(i) + ".gif", - theme_variant_path + effect_names.at(i) + ".gif", - theme_variant_path + effect_names.at(i) + ".apng", - theme_path + effect_names.at(i) + ".gif", - theme_path + effect_names.at(i) + ".apng" - }; - - // Assume the effect does not exist until a matching file is found - effects_enabled[i] = false; - for (QString path : paths) - { - if (file_exists(path)) - { - effects_enabled[i] = true; - break; - } - } - } -} - -void Courtroom::check_wtce() -{ - QString char_path = ao_app->get_character_path(current_char); - QString theme_variant_path = ao_app->get_theme_variant_path(); - QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < ui_wtce.size(); ++i) - { - QStringList paths{ - char_path + wtce_names.at(i) + ".gif", - theme_variant_path + wtce_names.at(i) + ".gif", - theme_variant_path + wtce_names.at(i) + ".apng", - theme_path + wtce_names.at(i) + ".gif", - theme_path + wtce_names.at(i) + ".apng" - }; - - // Assume the judge button does not exist until a matching file is found - wtce_enabled[i] = false; - for (QString path : paths) - { - if (file_exists(path)) - { - wtce_enabled[i] = true; - break; - } - } - } -} - -void Courtroom::check_free_blocks() -{ - QString char_path = ao_app->get_character_path(current_char); - QString theme_variant_path = ao_app->get_theme_variant_path(); - QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < ui_free_blocks.size(); ++i) - { - QStringList paths{ - char_path + free_block_names.at(i) + ".gif", - theme_variant_path + free_block_names.at(i) + ".gif", - theme_variant_path + free_block_names.at(i) + ".apng", - theme_path + free_block_names.at(i) + ".gif", - theme_path + free_block_names.at(i) + ".apng" - }; - - // Assume the free block does not exist until a matching file is found - free_blocks_enabled[i] = false; - for (QString path : paths) - { - if (file_exists(path)) - { - free_blocks_enabled[i] = true; - break; - } - } - } -} - void Courtroom::mod_called(QString p_ip) { ui_server_chatlog->append(p_ip); @@ -3620,7 +2402,8 @@ void Courtroom::on_spectator_clicked() void Courtroom::on_call_mod_clicked() { QMessageBox::StandardButton reply; - reply = QMessageBox::warning(this, "Warning", "Are you sure you want to call a mod? They will get angry at you if the reason is no good.", QMessageBox::Yes|QMessageBox::No, QMessageBox::No); + QString warning = "Are you sure you want to call a mod? They will get angry at you if the reason is no good."; + reply = QMessageBox::warning(this, "Warning", warning, QMessageBox::Yes|QMessageBox::No, QMessageBox::No); if(reply == QMessageBox::Yes) { @@ -3776,51 +2559,3 @@ void Courtroom::pause_timer(int timer_id) ui_timers[timer_id]->pause(); } - -template -int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_item_number, - QString item_name) -{ - // &item_vector must be a vector of size at least 1! - - // Redraw the new correct number of items. - int new_item_number = ao_app->read_theme_ini(config_item_number, cc_config_ini).toInt(); - int current_item_number = item_vector.size(); - // Note we use the fact that, if config_item_number is not there, read_theme_ini returns an - // empty string, which .toInt() would fail to convert and by documentation transform to 0. - - // Decide what to do if the new theme has a different amount of items than the old one - if (new_item_number < current_item_number) - { - // Hide old items if there are any. - for (int i=new_item_number; ihide(); - } - } - else if (current_item_number < new_item_number) - { - // Create new items - item_vector.resize(new_item_number); - for (int i=current_item_number; istackUnder(item_vector[i-1]); - // index i-1 exists as i >= current_item_number == item_vector.size() >= 1 - } - } - // Note that by design we do *not* destroy items (or similarly, the size of item_vector - // is non-decreasing. This is because we want to allow for items to, say, run in the - // background as invisible. - // With that said, we can now properly format our new items - for (int i=0; ishow(); - set_size_and_pos(item_vector[i], item_name+"_"+QString::number(i)); - // Note that show is deliberately placed before set_size_and_pos - // That is because we want to retain the functionality set_size_and_pos includes where - // hides a widget if it is unable to find a position for it, and does not change its - // visibility otherwise. - } - return new_item_number; -} diff --git a/courtroom.h b/courtroom.h index 02fee03f4..bd9e98b72 100644 --- a/courtroom.h +++ b/courtroom.h @@ -599,6 +599,9 @@ class Courtroom : public QMainWindow AOButton *ui_spectator; + void create_widgets(); + void connect_widgets(); + void construct_char_select(); void set_char_select(); void set_char_select_page(); diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp new file mode 100644 index 000000000..0a5223344 --- /dev/null +++ b/courtroom_widgets.cpp @@ -0,0 +1,1305 @@ +#include "courtroom.h" + +#include "aoapplication.h" +#include "lobby.h" +#include "hardware_functions.h" +#include "file_functions.h" +#include "datatypes.h" +#include "debug_functions.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void Courtroom::create_widgets() +{ + keepalive_timer = new QTimer(this); + keepalive_timer->start(60000); + + chat_tick_timer = new QTimer(this); + + text_delay_timer = new QTimer(this); + text_delay_timer->setSingleShot(true); + + sfx_delay_timer = new QTimer(this); + sfx_delay_timer->setSingleShot(true); + + realization_timer = new QTimer(this); + realization_timer->setSingleShot(true); + + testimony_show_timer = new QTimer(this); + testimony_show_timer->setSingleShot(true); + + testimony_hide_timer = new QTimer(this); + testimony_hide_timer->setSingleShot(true); + + char_button_mapper = new QSignalMapper(this); + + m_blip_player = new AOBlipPlayer(this, ao_app); + m_blip_player->set_volume(0); + m_sfx_player = new AOSfxPlayer(this, ao_app); + m_sfx_player->set_volume(0); + m_shout_player = new AOShoutPlayer(this, ao_app); + m_shout_player->set_volume(0); + m_mod_player = new AOSfxPlayer(this, ao_app); + m_mod_player->set_volume(50); + m_cycle_player = new AOSfxPlayer(this, ao_app); + m_cycle_player->set_volume(0); + m_music_player = new AOMusicPlayer(this, ao_app); + m_music_player->set_volume(0); + + 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_desk = new AOScene(ui_viewport, ao_app); + ui_vp_legacy_desk = new AOScene(ui_viewport, ao_app); + + ui_vp_music_display_a = new AOImage(this, ao_app); + ui_vp_music_display_b = new AOImage(this, ao_app); + ui_vp_music_area = new QWidget(ui_vp_music_display_a); + ui_vp_music_name = new QTextEdit(ui_vp_music_area); + ui_vp_music_name->setText("DANGANRONPA ONLINE"); + ui_vp_music_name->setFrameStyle(QFrame::NoFrame); + ui_vp_music_name->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_music_name->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_music_name->setReadOnly(true); + music_anim = new QPropertyAnimation(ui_vp_music_name, "geometry", this); + + ui_vp_clock = new AOMovie(this, ao_app); + ui_vp_clock->set_play_once(true); + + ui_vp_evidence_display = new AOEvidenceDisplay(this, ao_app); + + ui_vp_chatbox = new AOImage(this, ao_app); + ui_vp_showname = new QLabel(ui_vp_chatbox); + ui_vp_message = new QTextEdit(ui_vp_chatbox); + ui_vp_message->setFrameStyle(QFrame::NoFrame); + ui_vp_message->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_message->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_message->setReadOnly(true); + + ui_vp_showname_image = new AOImage(this, ao_app); + + ui_vp_testimony = new AOImage(this, ao_app); + ui_vp_effect = new AOMovie(this, ao_app); + ui_vp_wtce = new AOMovie(this, ao_app); + ui_vp_objection = new AOMovie(this, ao_app); + + ui_ic_chatlog = new QTextEdit(this); + ui_ic_chatlog->setReadOnly(true); + + ui_ms_chatlog = new AOTextArea(this); + ui_ms_chatlog->setReadOnly(true); + ui_ms_chatlog->setOpenExternalLinks(true); + ui_ms_chatlog->hide(); + + ui_server_chatlog = new AOTextArea(this); + ui_server_chatlog->setReadOnly(true); + ui_server_chatlog->setOpenExternalLinks(true); + + ui_mute_list = new QListWidget(this); + ui_area_list = new QListWidget(this); + ui_music_list = new QListWidget(this); + ui_sfx_list = new QListWidget(this); + + ui_ic_chat_message = new QLineEdit(this); + ui_ic_chat_message->setFrame(false); + + ui_muted = new AOImage(ui_ic_chat_message, ao_app); + ui_muted->hide(); + + ui_ooc_chat_message = new QLineEdit(this); + ui_ooc_chat_message->setFrame(false); + + ui_ooc_chat_name = new QLineEdit(this); + ui_ooc_chat_name->setFrame(false); + ui_ooc_chat_name->setPlaceholderText("Name"); + + //ui_area_password = new QLineEdit(this); + //ui_area_password->setFrame(false); + ui_music_search = new QLineEdit(this); + ui_music_search->setFrame(false); + + ui_sfx_search = new QLineEdit(this); + ui_sfx_search->setFrame(false); + + ui_note_area = new AONoteArea(this, ao_app); + ui_note_area->add_button = new AOButton(ui_note_area, ao_app); + ui_note_area->m_layout = new QVBoxLayout(ui_note_area); + note_scroll_area = new QScrollArea(this); + + note_scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + note_scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + note_scroll_area->setWidgetResizable(false); + + ui_set_notes = new AOButton(this, ao_app); + + construct_emotes(); + + ui_emote_left = new AOButton(this, ao_app); + ui_emote_right = new AOButton(this, ao_app); + + ui_emote_dropdown = new QComboBox(this); + ui_pos_dropdown = new QComboBox(this); + ui_pos_dropdown->addItem("wit"); + ui_pos_dropdown->addItem("def"); + ui_pos_dropdown->addItem("pro"); + ui_pos_dropdown->addItem("jud"); + ui_pos_dropdown->addItem("hld"); + ui_pos_dropdown->addItem("hlp"); + + ui_defense_bar = new AOImage(this, ao_app); + ui_prosecution_bar = new AOImage(this, ao_app); + + ui_music_label = new AOLabel(this, ao_app); + ui_sfx_label = new AOLabel(this, ao_app); + ui_blip_label = new AOLabel(this, ao_app); + + load_shouts(); // Reads from theme, deletes old shouts if needed and creates new ones + + ui_shout_up = new AOButton(this, ao_app); + ui_shout_up->setProperty("cycle_id", 1); + ui_shout_down = new AOButton(this, ao_app); + ui_shout_down->setProperty("cycle_id", 0); + + load_effects(); + + ui_effect_down = new AOButton(this, ao_app); + ui_effect_down->setProperty("cycle_id", 2); + ui_effect_up = new AOButton(this, ao_app); + ui_effect_up->setProperty("cycle_id", 3); + + load_wtce(); + + ui_wtce_up = new AOButton(this, ao_app); + ui_wtce_up->setProperty("cycle_id", 5); + ui_wtce_down = new AOButton(this, ao_app); + ui_wtce_down->setProperty("cycle_id", 4); + + ui_ooc_toggle = new AOButton(this, ao_app); + + ui_change_character = new AOButton(this, ao_app); + ui_reload_theme = new AOButton(this, ao_app); + ui_call_mod = new AOButton(this, ao_app); + ui_switch_area_music = new AOButton(this, ao_app); + + ui_theme_list = new QComboBox(this); + ui_confirm_theme = new AOButton(this, ao_app); + ui_note_button = new AOButton(this, ao_app); + + ui_label_images.resize(7); + for(int i = 0; i < ui_label_images.size(); ++i) + { + ui_label_images[i] = new AOImage(this, ao_app); + } + + ui_pre = new QCheckBox(this); + ui_pre->setText("Pre"); + ui_flip = new QCheckBox(this); + ui_flip->setText("Flip"); + ui_flip->hide(); + ui_guard = new QCheckBox(this); + ui_guard->setText("Guard"); + ui_guard->hide(); + ui_hidden = new QCheckBox(this); + ui_hidden->setText("Hidden"); + + // filling vectors with existing label/checkbox pointers + ui_checks.push_back(ui_pre); + ui_checks.push_back(ui_flip); + ui_checks.push_back(ui_guard); + ui_checks.push_back(ui_hidden); + ui_labels.push_back(ui_music_label); + ui_labels.push_back(ui_sfx_label); + ui_labels.push_back(ui_blip_label); + // + + ui_mute = new AOButton(this, ao_app); + + ui_defense_plus = new AOButton(this, ao_app); + ui_defense_minus = new AOButton(this, ao_app); + + ui_prosecution_plus = new AOButton(this, ao_app); + ui_prosecution_minus = new AOButton(this, ao_app); + + ui_text_color = new QComboBox(this); + ui_text_color->addItem("White"); + ui_text_color->addItem("Green"); + ui_text_color->addItem("Red"); + ui_text_color->addItem("Orange"); + ui_text_color->addItem("Blue"); + if (ao_app->yellow_text_enabled) + ui_text_color->addItem("Yellow"); + + ui_music_slider = new QSlider(Qt::Horizontal, this); + ui_music_slider->setRange(0, 100); + ui_music_slider->setValue(ao_app->get_default_music()); + + ui_sfx_slider = new QSlider(Qt::Horizontal, this); + ui_sfx_slider->setRange(0, 100); + ui_sfx_slider->setValue(ao_app->get_default_sfx()); + + ui_blip_slider = new QSlider(Qt::Horizontal, this); + ui_blip_slider->setRange(0, 100); + ui_blip_slider->setValue(ao_app->get_default_blip()); + + ui_evidence_button = new AOButton(this, ao_app); + + ui_vp_notepad_image = new AOImage(this, ao_app); + ui_vp_notepad = new QTextEdit(this); + ui_vp_notepad->setFrameStyle(QFrame::NoFrame); + + ui_timers.resize(1); + ui_timers[0] = new AOTimer(this, ao_app); + + construct_evidence(); + + load_free_blocks(); // Done last so they are guaranteed to be at bottom + + construct_char_select(); +} + +void Courtroom::connect_widgets() +{ + connect(keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); + + connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); + connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); + + connect(text_delay_timer, SIGNAL(timeout()), this, SLOT(start_chat_ticking())); + connect(sfx_delay_timer, SIGNAL(timeout()), this, SLOT(play_sfx())); + + connect(chat_tick_timer, SIGNAL(timeout()), this, SLOT(chat_tick())); + + connect(realization_timer, SIGNAL(timeout()), this, SLOT(realization_done())); + + connect(testimony_show_timer, SIGNAL(timeout()), this, SLOT(hide_testimony())); + connect(testimony_hide_timer, SIGNAL(timeout()), this, SLOT(show_testimony())); + + connect(ui_emote_left, SIGNAL(clicked()), this, SLOT(on_emote_left_clicked())); + connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); + + connect(ui_emote_dropdown, SIGNAL(activated(int)), this, SLOT(on_emote_dropdown_changed(int))); + connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); + + connect(ui_mute_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_mute_list_clicked(QModelIndex))); + + connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_chat_return_pressed())); + + connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); + + connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_music_list_clicked())); + connect(ui_area_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_area_list_clicked())); + connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_music_list_double_clicked(QModelIndex))); + connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_area_list_double_clicked(QModelIndex))); + + // connect events for shout/effect/wtce buttons happen in load_shouts(), load_effects(), load_wtce() + connect(ui_shout_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + connect(ui_shout_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + + connect(ui_effect_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + connect(ui_effect_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + + connect(ui_wtce_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + connect(ui_wtce_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + + connect(ui_mute, SIGNAL(clicked()), this, SLOT(on_mute_clicked())); + + connect(ui_defense_minus, SIGNAL(clicked()), this, SLOT(on_defense_minus_clicked())); + connect(ui_defense_plus, SIGNAL(clicked()), this, SLOT(on_defense_plus_clicked())); + connect(ui_prosecution_minus, SIGNAL(clicked()), this, SLOT(on_prosecution_minus_clicked())); + connect(ui_prosecution_plus, SIGNAL(clicked()), this, SLOT(on_prosecution_plus_clicked())); + + connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, SLOT(on_text_color_changed(int))); + + connect(ui_music_slider, SIGNAL(valueChanged(int)), this, SLOT(on_music_slider_moved(int))); + connect(ui_sfx_slider, SIGNAL(valueChanged(int)), this, SLOT(on_sfx_slider_moved(int))); + connect(ui_blip_slider, SIGNAL(valueChanged(int)), this, SLOT(on_blip_slider_moved(int))); + + connect(ui_ooc_toggle, SIGNAL(clicked()), this, SLOT(on_ooc_toggle_clicked())); + + connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); + connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited(QString))); + + connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); + connect(ui_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); + connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); + + connect(ui_switch_area_music, SIGNAL(clicked()), this, SLOT(on_switch_area_music_clicked())); + + connect(ui_confirm_theme, SIGNAL(clicked()), this, SLOT(on_confirm_theme_clicked())); + connect(ui_note_button, SIGNAL(clicked()), this, SLOT(on_note_button_clicked())); + + connect(ui_vp_notepad, SIGNAL(textChanged()), this, SLOT(on_note_text_changed())); + + connect(ui_pre, SIGNAL(clicked()), this, SLOT(on_pre_clicked())); + connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); + connect(ui_guard, SIGNAL(clicked()), this, SLOT(on_guard_clicked())); + + connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); + + connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_sfx_list_clicked())); + + connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); + + connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, SLOT(on_add_button_clicked())); + connect(ui_set_notes, SIGNAL(clicked(bool)), this, SLOT(on_set_notes_clicked())); +} + +void Courtroom::set_widgets() +{ + blip_rate = ao_app->read_blip_rate(); + blank_blip = ao_app->get_blank_blip(); + + QString filename = design_ini; + pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", filename); + + if (f_courtroom.width < 0 || f_courtroom.height < 0) + { + qDebug() << "W: did not find courtroom width or height in " << filename; + + this->resize(714, 668); + } + else + { + m_courtroom_width = f_courtroom.width; + m_courtroom_height = f_courtroom.height; + + this->resize(f_courtroom.width, f_courtroom.height); + } + + shout_names.clear(); + for(int i = 1; i <= ui_shouts.size(); ++i) + { + QString name = ao_app->get_spbutton("[SHOUTS]", i); + if(!name.isEmpty()) + shout_names.append(name.trimmed()); + } + + effect_names.clear(); + for(int i = 1; i <= ui_effects.size(); ++i) + { + QStringList names = ao_app->get_effect(i); + if(!names.isEmpty()) + effect_names.append(names.at(0).trimmed()); + } + + wtce_names.clear(); + for(int i = 1; i <= ui_wtce.size(); ++i) + { + QString name = ao_app->get_spbutton("[WTCE]", i); + if(!name.isEmpty()) + wtce_names.append(name.trimmed()); + } + + free_block_names.clear(); + for(int i = 1; i <= ui_free_blocks.size(); ++i) + { + QString name = ao_app->get_spbutton("[FREE BLOCKS]", i); + if(!name.isEmpty()) + free_block_names.append(name.trimmed()); + } + + ui_background->move(0, 0); + ui_background->resize(m_courtroom_width, m_courtroom_height); + ui_background->set_image("courtroombackground.png"); + + set_size_and_pos(ui_viewport, "viewport"); + + ui_vp_background->move(0, 0); + ui_vp_background->resize(ui_viewport->width(), ui_viewport->height()); + + ui_vp_speedlines->move(0, 0); + ui_vp_speedlines->combo_resize(ui_viewport->width(), ui_viewport->height()); + + ui_vp_player_char->move(0, 0); + ui_vp_player_char->combo_resize(ui_viewport->width(), ui_viewport->height()); + + //the AO2 desk element + ui_vp_desk->move(0, 0); + ui_vp_desk->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(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->resize(ui_viewport->width(), ui_viewport->height()); + + set_size_and_pos(ui_vp_notepad_image, "notepad_image"); + ui_vp_notepad_image->set_image("notepad_image.png"); + ui_vp_notepad_image->hide(); + + set_size_and_pos(ui_vp_notepad, "notepad"); + ui_vp_notepad->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_notepad->verticalScrollBar()->hide(); + ui_vp_notepad->verticalScrollBar()->resize(0, 0); + ui_vp_notepad->hide(); + + set_size_and_pos(ui_vp_showname, "showname"); + + set_size_and_pos(ui_vp_showname_image, "showname_image"); + ui_vp_showname_image->hide(); + + set_size_and_pos(ui_vp_message, "message"); + ui_vp_message->setTextInteractionFlags(Qt::NoTextInteraction); + ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "color: white"); + + ui_vp_testimony->move(ui_viewport->x(), ui_viewport->y()); + ui_vp_testimony->resize(ui_viewport->width(), ui_viewport->height()); + ui_vp_testimony->set_image("testimony.png"); + ui_vp_testimony->hide(); + + ui_vp_effect->move(ui_viewport->x(), ui_viewport->y()); + ui_vp_effect->resize(ui_viewport->width(), ui_viewport->height()); + ui_vp_effect->hide(); + + ui_vp_wtce->move(ui_viewport->x(), ui_viewport->y()); + ui_vp_wtce->combo_resize(ui_viewport->width(), ui_viewport->height()); + + ui_vp_objection->move(ui_viewport->x(), ui_viewport->y()); + ui_vp_objection->combo_resize(ui_viewport->width(), ui_viewport->height()); + + set_size_and_pos(ui_ic_chatlog, "ic_chatlog"); + + set_size_and_pos(ui_ms_chatlog, "ms_chatlog"); + + set_size_and_pos(ui_server_chatlog, "server_chatlog"); + + set_size_and_pos(ui_mute_list, "mute_list"); + ui_mute_list->hide(); + + set_size_and_pos(ui_area_list, "area_list"); + ui_area_list->hide(); +// ui_area_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); + + set_size_and_pos(ui_music_list, "music_list"); + + set_size_and_pos(ui_sfx_list, "sfx_list"); + + 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"); + } + 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_music_area, "music_area"); + ui_vp_music_area->show(); + set_size_and_pos(ui_vp_music_name, "music_name"); + + set_size_and_pos(ui_vp_music_display_a, "music_display_a"); + ui_vp_music_display_a->set_image("music_display_a.png"); + ui_vp_music_display_a->show(); + + set_size_and_pos(ui_vp_music_display_b, "music_display_b"); + ui_vp_music_display_b->set_image("music_display_b.png"); + ui_vp_music_display_b->show(); + + set_size_and_pos(ui_vp_clock, "clock"); + ui_vp_clock->show(); + + ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); + + ui_vp_chatbox->set_image("chatmed.png"); + ui_vp_chatbox->hide(); + + ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); + ui_muted->set_image("muted.png"); + + set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message"); + ui_ooc_chat_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); + + set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name"); + ui_ooc_chat_name->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); + + //set_size_and_pos(ui_area_password, "area_password"); + set_size_and_pos(ui_music_search, "music_search"); + + set_size_and_pos(ui_sfx_search, "sfx_search"); + + set_size_and_pos(ui_emotes, "emotes"); + + set_size_and_pos(ui_emote_left, "emote_left"); + ui_emote_left->set_image("arrow_left.png"); + + set_size_and_pos(ui_emote_right, "emote_right"); + ui_emote_right->set_image("arrow_right.png"); + + set_size_and_pos(ui_emote_dropdown, "emote_dropdown"); + + set_size_and_pos(ui_pos_dropdown, "pos_dropdown"); + + set_size_and_pos(ui_defense_bar, "defense_bar"); + ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + ".png"); + + set_size_and_pos(ui_prosecution_bar, "prosecution_bar"); + ui_prosecution_bar->set_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); + + set_size_and_pos(ui_music_label, "music_label"); + ui_music_label->setText("Music"); + set_size_and_pos(ui_sfx_label, "sfx_label"); + ui_sfx_label->setText("Sfx"); + set_size_and_pos(ui_blip_label, "blip_label"); + ui_blip_label->setText("Blips"); + +// set_size_and_pos(ui_shouts[0], "hold_it"); +// ui_shouts[0]->show(); +// set_size_and_pos(ui_shouts[1], "objection"); +// ui_shouts[1]->show(); +// set_size_and_pos(ui_shouts[2], "take_that"); +// ui_shouts[2]->show(); +// set_size_and_pos(ui_shouts[3], "custom_objection"); +// set_size_and_pos(ui_shouts[4], "got_it"); +// set_size_and_pos(ui_shouts[5], "cross_swords"); +// set_size_and_pos(ui_shouts[6], "counter_alt"); + for(int i = 0; i < shout_names.size(); ++i) + { + set_size_and_pos(ui_shouts[i], shout_names[i]); + } +// ui_shouts[0]->show(); +// ui_shouts[1]->show(); +// ui_shouts[2]->show(); + reset_shout_buttons(); + + set_size_and_pos(ui_shout_up, "shout_up"); + ui_shout_up->set_image("shoutup.png"); + ui_shout_up->hide(); + set_size_and_pos(ui_shout_down, "shout_down"); + ui_shout_down->set_image("shoutdown.png"); + ui_shout_down->hide(); + + // courtroom_config.ini necessary + check for crash + if (ao_app->read_theme_ini("enable_single_shout", cc_config_ini) == "true" && ui_shouts.size() > 0) + { + for(auto & shout : ui_shouts) move_widget(shout, "bullet"); + + set_shouts(); + + ui_shout_up->show(); + ui_shout_down->show(); + } + +// set_size_and_pos(ui_effects[0], "effect_flash"); +// set_size_and_pos(ui_effects[1], "effect_gloom"); +// set_size_and_pos(ui_effects[2], "effect_question"); +// set_size_and_pos(ui_effects[3], "effect_pow"); + for(int i = 0; i < effect_names.size(); ++i) + { + set_size_and_pos(ui_effects[i], effect_names[i]); + } + reset_effect_buttons(); + + set_size_and_pos(ui_effect_up, "effect_up"); + ui_effect_up->set_image("effectup.png"); + ui_effect_up->hide(); + set_size_and_pos(ui_effect_down, "effect_down"); + ui_effect_down->set_image("effectdown.png"); + ui_effect_down->hide(); + + if (ao_app->read_theme_ini("enable_single_effect", cc_config_ini) == "true" && ui_effects.size() > 0) // check to prevent crashing + { + for(auto & effect : ui_effects) move_widget(effect, "effect"); + + set_effects(); + + ui_effect_up->show(); + ui_effect_down->show(); + } + + for(int i = 0; i < wtce_names.size(); ++i) + { + set_size_and_pos(ui_wtce[i], wtce_names[i]); + } + + set_size_and_pos(ui_wtce_up, "wtce_up"); + ui_wtce_up->set_image("wtceup.png"); + ui_wtce_up->hide(); + set_size_and_pos(ui_wtce_down, "wtce_down"); + ui_wtce_down->set_image("wtcedown.png"); + ui_wtce_down->hide(); + + if (ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true" ) // courtroom_config.ini necessary + { + for(auto & wtce : ui_wtce) move_widget(wtce, "wtce"); + qDebug() << "AA: single wtce"; + set_wtce(); + } + + for(int i = 0; i < free_block_names.size(); ++i) + { + set_size_and_pos(ui_free_blocks[i], "free_block_" + free_block_names[i]); + } + set_free_blocks(); + + set_size_and_pos(ui_ooc_toggle, "ooc_toggle"); + ui_ooc_toggle->setText("Server"); + + set_size_and_pos(ui_call_mod, "call_mod"); + + set_size_and_pos(ui_change_character, "change_character"); + set_size_and_pos(ui_reload_theme, "reload_theme"); + set_size_and_pos(ui_call_mod, "call_mod"); + set_size_and_pos(ui_confirm_theme, "confirm_theme"); + set_size_and_pos(ui_note_button, "note_button"); + + set_size_and_pos(ui_switch_area_music, "switch_area_music"); + + // Set the default values for the buttons, then try and determine if they should be replaced by images + ui_change_character->setText("Change character"); + ui_change_character->setStyleSheet(""); + + ui_reload_theme->setText("Reload theme"); + ui_reload_theme->setStyleSheet(""); + + ui_call_mod->setText("Call mod"); + ui_call_mod->setStyleSheet(""); + + ui_switch_area_music->setText("A/M"); + ui_switch_area_music->setStyleSheet(""); + + ui_confirm_theme->setText("^"); + ui_confirm_theme->setStyleSheet(""); + + ui_note_button->setText("><:"); + ui_note_button->setStyleSheet(""); + + if (ao_app->read_theme_ini("enable_button_images", cc_config_ini) == "true") + { + // Set files, ask questions later + // set_image first tries the theme variant folder, then the theme folder, then falls back to the default theme + ui_change_character->set_image("changecharacter.png"); + if (!ui_change_character->image_path.isEmpty()) + ui_change_character->setText(""); + + ui_reload_theme->set_image("reloadtheme.png"); + if (!ui_reload_theme->image_path.isEmpty()) + ui_reload_theme->setText(""); + + ui_call_mod->set_image("callmod.png"); + if (!ui_call_mod->image_path.isEmpty()) + ui_call_mod->setText(""); + + ui_switch_area_music->set_image("switch_area_music.png"); + if (!ui_switch_area_music->image_path.isEmpty()) + ui_switch_area_music->setText(""); + + ui_confirm_theme->set_image("confirmtheme.png"); + if (!ui_confirm_theme->image_path.isEmpty()) + ui_confirm_theme->setText(""); + + ui_note_button->set_image("notebutton.png"); + if (!ui_note_button->image_path.isEmpty()) + ui_note_button->setText(""); + } + + set_size_and_pos(ui_theme_list, "theme_list"); + + set_size_and_pos(ui_pre, "pre"); + ui_pre->setText("Pre"); + + set_size_and_pos(ui_flip, "flip"); + + set_size_and_pos(ui_guard, "guard"); + + set_size_and_pos(ui_hidden, "hidden"); + + for(int i = 0; i < ui_label_images.size(); ++i) + { + set_size_and_pos(ui_label_images[i], label_images[i].toLower() + "_image"); + } + + if (ao_app->read_theme_ini("enable_label_images", cc_config_ini) == "true") + { + for (int i = 0 ; i < ui_checks.size(); ++i) // loop through checks + { + QString image = label_images[i].toLower() + ".png"; + ui_label_images[i]->set_image(image); + + if (!ui_label_images[i]->image_path.isEmpty()) + ui_checks[i]->setText(""); + else + ui_checks[i]->setText(label_images[i]); + } + + for(int i = 0 ; i < ui_labels.size(); ++i) // now through labels.......... + { + int j = i + ui_checks.size(); + QString image = label_images[j].toLower() + ".png"; + ui_label_images[j]->set_image(image); + + if (!ui_label_images[j]->image_path.isEmpty()) + ui_labels[i]->setText(""); + else + ui_labels[i]->setText(label_images[j]); + } + } + else + { + for(int i = 0; i < ui_checks.size(); ++i) //same thing + { + ui_checks[i]->setText(label_images[i]); + ui_label_images[i]->set_image(""); + } + + for(int i = 0; i < ui_labels.size(); ++i) //same thing + { + int j = i + ui_checks.size(); + ui_labels[i]->setText(label_images[j]); + ui_label_images[j]->set_image(""); + } + } + + set_size_and_pos(ui_mute, "mute_button"); + ui_mute->set_image("mute.png"); + + set_size_and_pos(ui_defense_plus, "defense_plus"); + ui_defense_plus->set_image("defplus.png"); + + set_size_and_pos(ui_defense_minus, "defense_minus"); + ui_defense_minus->set_image("defminus.png"); + + set_size_and_pos(ui_prosecution_plus, "prosecution_plus"); + ui_prosecution_plus->set_image("proplus.png"); + + set_size_and_pos(ui_prosecution_minus, "prosecution_minus"); + ui_prosecution_minus->set_image("prominus.png"); + + set_size_and_pos(ui_text_color, "text_color"); + + set_size_and_pos(ui_music_slider, "music_slider"); + set_size_and_pos(ui_sfx_slider, "sfx_slider"); + set_size_and_pos(ui_blip_slider, "blip_slider"); + + set_size_and_pos(ui_evidence_button, "evidence_button"); + ui_evidence_button->set_image("evidencebutton.png"); + + set_size_and_pos(ui_evidence, "evidence_background"); + ui_evidence->set_image("evidencebackground.png"); + + set_size_and_pos(ui_evidence_name, "evidence_name"); + + set_size_and_pos(ui_evidence_buttons, "evidence_buttons"); + + set_size_and_pos(ui_evidence_left, "evidence_left"); + ui_evidence_left->set_image("arrow_left.png"); + + set_size_and_pos(ui_evidence_right, "evidence_right"); + ui_evidence_right->set_image("arrow_right.png"); + + set_size_and_pos(ui_evidence_present, "evidence_present"); + ui_evidence_present->set_image("present_disabled.png"); + + set_size_and_pos(ui_evidence_overlay, "evidence_overlay"); + ui_evidence_overlay->set_image("evidenceoverlay.png"); + + set_size_and_pos(ui_evidence_delete, "evidence_delete"); + ui_evidence_delete->set_image("deleteevidence.png"); + + set_size_and_pos(ui_evidence_image_name, "evidence_image_name"); + + set_size_and_pos(ui_evidence_image_button, "evidence_image_button"); + + set_size_and_pos(ui_evidence_x, "evidence_x"); + ui_evidence_x->set_image("evidencex.png"); + + set_size_and_pos(ui_evidence_description, "evidence_description"); + + ui_selector->set_image("char_selector.png"); + ui_selector->hide(); + + set_size_and_pos(ui_back_to_lobby, "back_to_lobby"); + ui_back_to_lobby->setText("Back to Lobby"); + + set_size_and_pos(ui_char_password, "char_password"); + + set_size_and_pos(ui_char_buttons, "char_buttons"); + + set_size_and_pos(ui_char_select_left, "char_select_left"); + ui_char_select_left->set_image("arrow_left.png"); + + set_size_and_pos(ui_char_select_right, "char_select_right"); + ui_char_select_right->set_image("arrow_right.png"); + + set_size_and_pos(ui_spectator, "spectator"); + + handle_music_anim(); + + set_size_and_pos(ui_set_notes, "set_notes_button"); + ui_set_notes->set_image("set_notes.png"); + ui_note_area->m_layout->setSpacing(10); + set_size_and_pos(ui_note_area, "note_area"); + set_size_and_pos(note_scroll_area, "note_area"); + note_scroll_area->setWidget(ui_note_area); + ui_note_area->set_image("note_area.png"); + ui_note_area->add_button->set_image("add_button.png"); + ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + ui_note_area->setLayout(ui_note_area->m_layout); + ui_note_area->show(); + note_scroll_area->hide(); + + list_note_files(); + + if(!contains_add_button) + { + ui_note_area->m_layout->addWidget(ui_note_area->add_button); + contains_add_button = true; + } + + timer_number = adapt_numbered_items(ui_timers, "timer_number", "timer"); + set_dropdowns(); + set_fonts(); +} + +void Courtroom::set_size_and_pos(QWidget *p_widget, QString p_identifier) +{ + QString filename = design_ini; + + + pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); + + 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::move_widget(QWidget *p_widget, QString p_identifier) +{ + QString filename = design_ini; + + pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); + + 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); + } +} + +template +int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_item_number, + QString item_name) +{ + // &item_vector must be a vector of size at least 1! + + // Redraw the new correct number of items. + int new_item_number = ao_app->read_theme_ini(config_item_number, cc_config_ini).toInt(); + int current_item_number = item_vector.size(); + // Note we use the fact that, if config_item_number is not there, read_theme_ini returns an + // empty string, which .toInt() would fail to convert and by documentation transform to 0. + + // Decide what to do if the new theme has a different amount of items than the old one + if (new_item_number < current_item_number) + { + // Hide old items if there are any. + for (int i=new_item_number; ihide(); + } + } + else if (current_item_number < new_item_number) + { + // Create new items + item_vector.resize(new_item_number); + for (int i=current_item_number; istackUnder(item_vector[i-1]); + // index i-1 exists as i >= current_item_number == item_vector.size() >= 1 + } + } + // Note that by design we do *not* destroy items (or similarly, the size of item_vector + // is non-decreasing. This is because we want to allow for items to, say, run in the + // background as invisible. + // With that said, we can now properly format our new items + for (int i=0; ishow(); + set_size_and_pos(item_vector[i], item_name+"_"+QString::number(i)); + // Note that show is deliberately placed before set_size_and_pos + // That is because we want to retain the functionality set_size_and_pos includes where + // hides a widget if it is unable to find a position for it, and does not change its + // visibility otherwise. + } + return new_item_number; +} + +void Courtroom::check_effects() +{ + QString char_path = ao_app->get_character_path(current_char); + QString theme_variant_path = ao_app->get_theme_variant_path(); + QString theme_path = ao_app->get_theme_path(); + for(int i = 0; i < ui_shouts.size(); ++i) + { + QStringList paths{ + char_path + effect_names.at(i) + ".gif", + theme_variant_path + effect_names.at(i) + ".gif", + theme_variant_path + effect_names.at(i) + ".apng", + theme_path + effect_names.at(i) + ".gif", + theme_path + effect_names.at(i) + ".apng" + }; + + // Assume the effect does not exist until a matching file is found + effects_enabled[i] = false; + for (QString path : paths) + { + if (file_exists(path)) + { + effects_enabled[i] = true; + break; + } + } + } +} + +void Courtroom::check_free_blocks() +{ + QString char_path = ao_app->get_character_path(current_char); + QString theme_variant_path = ao_app->get_theme_variant_path(); + QString theme_path = ao_app->get_theme_path(); + for(int i = 0; i < ui_free_blocks.size(); ++i) + { + QStringList paths{ + char_path + free_block_names.at(i) + ".gif", + theme_variant_path + free_block_names.at(i) + ".gif", + theme_variant_path + free_block_names.at(i) + ".apng", + theme_path + free_block_names.at(i) + ".gif", + theme_path + free_block_names.at(i) + ".apng" + }; + + // Assume the free block does not exist until a matching file is found + free_blocks_enabled[i] = false; + for (QString path : paths) + { + if (file_exists(path)) + { + free_blocks_enabled[i] = true; + break; + } + } + } +} + +void Courtroom::check_shouts() +{ + QString char_path = ao_app->get_character_path(current_char); + QString theme_variant_path = ao_app->get_theme_variant_path(); + QString theme_path = ao_app->get_theme_path(); + for(int i = 0; i < ui_shouts.size(); ++i) + { + QStringList paths{ + char_path + shout_names.at(i) + ".gif", + theme_variant_path + shout_names.at(i) + ".gif", + theme_variant_path + shout_names.at(i) + ".apng", + theme_path + shout_names.at(i) + ".gif", + theme_path + shout_names.at(i) + ".apng" + }; + + // Assume the shout does not exist until a matching file is found + shouts_enabled[i] = false; + for (QString path : paths) + { + if (file_exists(path)) + { + shouts_enabled[i] = true; + break; + } + } + } +} + +void Courtroom::check_wtce() +{ + QString char_path = ao_app->get_character_path(current_char); + QString theme_variant_path = ao_app->get_theme_variant_path(); + QString theme_path = ao_app->get_theme_path(); + for(int i = 0; i < ui_wtce.size(); ++i) + { + QStringList paths{ + char_path + wtce_names.at(i) + ".gif", + theme_variant_path + wtce_names.at(i) + ".gif", + theme_variant_path + wtce_names.at(i) + ".apng", + theme_path + wtce_names.at(i) + ".gif", + theme_path + wtce_names.at(i) + ".apng" + }; + + // Assume the judge button does not exist until a matching file is found + wtce_enabled[i] = false; + for (QString path : paths) + { + if (file_exists(path)) + { + wtce_enabled[i] = true; + break; + } + } + } +} + +void Courtroom::load_effects() +{ + // Close any existing effects to prevent memory leaks + for (int i=0; iclose(); + delete ui_effects[i]; + } + + // And create new effects + int effect_number = ao_app->get_design_ini_value("effect_number", cc_config_ini); + effects_enabled.resize(effect_number); + ui_effects.resize(effect_number); + + for (int i=0; isetProperty("effect_id", i+1); + ui_effects[i]->stackUnder(ui_effect_up); + ui_effects[i]->stackUnder(ui_effect_down); + } + + // And connect their actions + for (auto & effect : ui_effects) + connect(effect, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked())); +} + +void Courtroom::load_free_blocks() +{ + // Close any existing free blocks to prevent memory leaks + for (int i=0; iclose(); + delete ui_free_blocks[i]; + } + + // And create new free block buttons + int free_block_number = ao_app->get_design_ini_value("free_block_number", cc_config_ini); + free_blocks_enabled.resize(free_block_number); + ui_free_blocks.resize(free_block_number); + + for (int i=0; isetProperty("free_block_id", i+1); + ui_free_blocks[i]->set_play_once(false); + ui_free_blocks[i]->stackUnder(ui_vp_player_char); + } +} + +void Courtroom::load_shouts() +{ + // Close any existing shouts to prevent memory leaks + for (int i=0; iclose(); + delete ui_shouts[i]; + } + + // And create new shouts + int shout_number = ao_app->get_design_ini_value("shout_number", cc_config_ini); + shouts_enabled.resize(shout_number); + ui_shouts.resize(shout_number); + + for (int i=0; isetProperty("shout_id", i+1); + ui_shouts[i]->stackUnder(ui_shout_up); + ui_shouts[i]->stackUnder(ui_shout_down); + } + + // And connect their actions + for (auto & shout : ui_shouts) + connect(shout, SIGNAL(clicked(bool)), this, SLOT(on_shout_clicked())); +} + +void Courtroom::load_wtce() +{ + // Close any existing wtce buttons to prevent memory leaks + for (int i=0; iclose(); + delete ui_wtce[i]; + } + + // And create new wtce buttons + int wtce_number = ao_app->get_design_ini_value("wtce_number", cc_config_ini); + wtce_enabled.resize(wtce_number); + ui_wtce.resize(wtce_number); + + for (int i=0; isetProperty("wtce_id", i+1); + ui_wtce[i]->stackUnder(ui_wtce_up); + ui_wtce[i]->stackUnder(ui_wtce_down); + } + + // And connect their actions + for (auto & wtce : ui_wtce) + connect(wtce, SIGNAL(clicked(bool)), this, SLOT(on_wtce_clicked())); +} + +void Courtroom::set_shouts() +{ + for(auto & shout : ui_shouts) shout->hide(); + if (ui_shouts.size() > 0) ui_shouts[m_shout_state]->show(); // check to prevent crashing +} + +void Courtroom::set_effects() +{ + for(auto & effect : ui_effects) effect->hide(); + if (ui_effects.size() > 0) ui_effects[m_effect_current]->show(); // check to prevent crashing +} + +void Courtroom::set_wtce() +{ + for(auto & wtce : ui_wtce) wtce->hide(); + + if(is_judge && ui_wtce.size() > 0) // check to prevent crashing + { + if( ao_app->read_theme_ini("enable_single_wtce", cc_config_ini ) == "true" ) + { + ui_wtce[m_wtce_current]->show(); + ui_wtce_up->show(); + ui_wtce_down->show(); + } + else + { + for(auto & wtce : ui_wtce) wtce->show(); + ui_wtce_up->hide(); + ui_wtce_down->hide(); + } + } +} + +void Courtroom::set_free_blocks() +{ + for (int i=0; iplay("free_block_" + free_block_names[i]); + } +} + +void Courtroom::set_dropdown(QWidget *widget, QString target_tag) +{ + QString f_file = "courtroom_stylesheets.css"; + QString style_sheet_string = ao_app->get_stylesheet(target_tag, f_file); + if (style_sheet_string != "") + widget->setStyleSheet(style_sheet_string); +} + +void Courtroom::set_dropdowns() +{ + set_dropdown(ui_text_color, "[TEXT COLOR]"); + set_dropdown(ui_pos_dropdown, "[POS DROPDOWN]"); + set_dropdown(ui_theme_list, "[THEME LIST]"); + set_dropdown(ui_emote_dropdown, "[EMOTE DROPDOWN]"); + set_dropdown(ui_mute_list, "[MUTE LIST]"); + set_dropdown(ui_ic_chat_message, "[IC LINE]"); + set_dropdown(ui_ooc_chat_message, "[OOC LINE]"); +} + + +void Courtroom::set_font(QWidget *widget, QString p_identifier) +{ + QString design_file = fonts_ini; + int f_weight = ao_app->get_font_size(p_identifier, design_file); + QString class_name = widget->metaObject()->className(); + + QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); + + widget->setFont(QFont(font_name, f_weight)); + + QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); + + int bold = ao_app->get_font_size(p_identifier + "_bold", design_file); // is the font bold or not? + + QString is_bold = ""; + if(bold == 1) is_bold = "bold"; + + QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + + "color: rgba(" + + QString::number(f_color.red()) + ", " + + QString::number(f_color.green()) + ", " + + QString::number(f_color.blue()) + ", 255);\n" + "font: " + is_bold + "; }"; + + widget->setStyleSheet(style_sheet_string); +} + +void Courtroom::set_fonts() +{ + set_font(ui_vp_showname, "showname"); + set_font(ui_vp_message, "message"); + set_font(ui_ic_chatlog, "ic_chatlog"); + set_font(ui_ms_chatlog, "ms_chatlog"); + set_font(ui_server_chatlog, "server_chatlog"); + set_font(ui_music_list, "music_list"); + set_font(ui_area_list, "area_list"); + set_font(ui_sfx_list, "sfx_list"); + set_font(ui_vp_music_name, "music_name"); + set_font(ui_vp_notepad, "notepad"); + for (int i=0; iaddItem(i_name); + } +} From 681916cbe6d77ce549113b8ebaaa675816355e01 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 27 May 2020 08:34:18 -0400 Subject: [PATCH 020/842] Remove chatbox/ic_chat_message parameters from theme design (backwards compatibility break!) --- courtroom.cpp | 55 +++++++------------------------------------ courtroom.h | 3 --- courtroom_widgets.cpp | 12 ++-------- 3 files changed, 10 insertions(+), 60 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 6fa53f299..ae0406a17 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -236,18 +236,12 @@ void Courtroom::set_scene() if (f_side == "def") { f_background = "defenseempty"; - if (is_ao2_bg) - f_desk_image = "defensedesk"; - else - f_desk_image = "bancodefensa"; + f_desk_image = "defensedesk"; } else if (f_side == "pro") { f_background = "prosecutorempty"; - if (is_ao2_bg) - f_desk_image = "prosecutiondesk"; - else - f_desk_image = "bancoacusacion"; + f_desk_image = "prosecutiondesk"; } else if (f_side == "jud") { @@ -266,23 +260,18 @@ void Courtroom::set_scene() } else { - if (is_ao2_bg) - f_desk_image = "stand"; - else - f_desk_image = "estrado"; + f_desk_image = "stand"; } - if (f_desk_mod == "0" || (f_desk_mod != "1" && - (f_side == "jud" || + 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 if (is_ao2_bg || (f_side == "jud" || - f_side == "hld" || - f_side == "hlp")) + else if ((f_side == "jud" || f_side == "hld" || f_side == "hlp")) { ui_vp_legacy_desk->hide(); ui_vp_desk->show(); @@ -349,38 +338,10 @@ void Courtroom::set_taken(int n_char, bool p_taken) void Courtroom::set_background(QString p_background) { testimony_in_progress = false; - current_background = p_background; - QString bg_path = get_background_path(); - QVector exts{".apng", ".gif", ".png"}; - - QString ini_path = ao_app->get_background_path() + "backgrounds.ini"; - - if (file_exists(ini_path)) - { - is_ao2_bg = true; - } - else - { - if (file_exists(bg_path + "defensedesk", exts) == "") - is_ao2_bg = false; - else if (file_exists(bg_path + "prosecutiondesk", exts) == "") - is_ao2_bg = false; - else if (file_exists(bg_path + "stand", exts) == "") - is_ao2_bg = false; - is_ao2_bg = true; ; - } - if (is_ao2_bg) - { - 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_ic_chat_message, "ic_chat_message"); - } + set_size_and_pos(ui_vp_chatbox, "ao2_chatbox"); + set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message"); } void Courtroom::handle_music_anim() diff --git a/courtroom.h b/courtroom.h index bd9e98b72..44c91a889 100644 --- a/courtroom.h +++ b/courtroom.h @@ -371,9 +371,6 @@ class Courtroom : public QMainWindow int evidence_rows = 3; int max_evidence_on_page = 18; - //is set to true if the bg folder contains defensedesk.png, prosecutiondesk.png and stand.png - bool is_ao2_bg = false; - //whether the ooc chat is server or master chat, true is server bool server_ooc = true; diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 0a5223344..5ea8eb58e 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -494,16 +494,8 @@ void Courtroom::set_widgets() set_size_and_pos(ui_sfx_list, "sfx_list"); - 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"); - } - 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_ic_chat_message, "ao2_ic_chat_message"); + set_size_and_pos(ui_vp_chatbox, "ao2_chatbox"); set_size_and_pos(ui_vp_music_area, "music_area"); ui_vp_music_area->show(); From 23113924dd5b08fa9df8344a9f8742cb6391f7b4 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 27 May 2020 17:42:42 -0400 Subject: [PATCH 021/842] Move code that names effects/fb/shouts/wtce to immediately after creation --- courtroom.cpp | 3 -- courtroom.h | 89 +++++++++++++++++++++++++++++++++++++ courtroom_widgets.cpp | 100 ++++++++++++++++++++++++++++-------------- 3 files changed, 156 insertions(+), 36 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index ae0406a17..55d5b1e5c 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -339,9 +339,6 @@ void Courtroom::set_background(QString p_background) { testimony_in_progress = false; current_background = p_background; - - set_size_and_pos(ui_vp_chatbox, "ao2_chatbox"); - set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message"); } void Courtroom::handle_music_anim() diff --git a/courtroom.h b/courtroom.h index 44c91a889..b5a4ce36c 100644 --- a/courtroom.h +++ b/courtroom.h @@ -596,6 +596,95 @@ class Courtroom : public QMainWindow AOButton *ui_spectator; + QHash widget_names{ + {"viewport", ui_viewport}, + // ui_vp_background + // ui_vp_speedlines + // ui_vp_player_char + // ui_vp_desk + // ui_vp_legacy_desk + {"music_display_a", ui_vp_music_display_a}, + {"music_display_b", ui_vp_music_display_b}, + {"music_area", ui_vp_music_area}, + {"music_name", ui_vp_music_name}, + // music_anim + {"clock", ui_vp_clock}, + // ui_vp_evidence_display + {"ao2_chatbox", ui_vp_chatbox}, + {"showname", ui_vp_showname}, + {"message", ui_vp_message}, + {"showname_image", ui_vp_showname_image}, + // ui_vp_testimony + // ui_vp_effect + // ui_vp_wtce + // ui_vp_objection + {"ic_chatlog", ui_ic_chatlog}, + {"ms_chatlog", ui_ms_chatlog}, + {"server_chatlog", ui_server_chatlog}, + {"mute_list", ui_mute_list}, + {"area_list", ui_area_list}, + {"music_list", ui_music_list}, + {"sfx_list", ui_sfx_list}, + {"ao2_ic_chat_message", ui_ic_chat_message}, + // ui_muted + {"ooc_chat_message", ui_ooc_chat_message}, + {"ooc_chat_name", ui_ooc_chat_name}, + {"music_search", ui_music_search}, + {"sfx_search", ui_sfx_search}, + {"note_area", ui_note_area}, + // add_button + // m_layout + // note_scroll_area + {"set_notes_button", ui_set_notes}, + {"emotes", ui_emotes}, + {"emote_left", ui_emote_left}, + {"emote_right", ui_emote_right}, + {"emote_dropdown", ui_emote_dropdown}, + {"pos_dropdown", ui_pos_dropdown}, + {"defense_bar", ui_defense_bar}, + {"prosecution_bar", ui_prosecution_bar}, + {"music_label", ui_music_label}, + {"sfx_label", ui_sfx_label}, + {"blip_label", ui_blip_label}, + // Each ui_shouts[i] + {"shout_up", ui_shout_up}, + {"shout_down", ui_shout_down}, + // Each ui_effects[i] + {"effect_down", ui_effect_down}, + {"effect_up", ui_effect_up}, + // Each ui_wtce[i] + {"wtce_up", ui_wtce_up}, + {"wtce_down", ui_wtce_down}, + {"ooc_toggle", ui_ooc_toggle}, + {"change_character", ui_change_character}, + {"reload_theme", ui_reload_theme}, + {"call_mod", ui_call_mod}, + {"switch_area_music", ui_switch_area_music}, + {"theme_list", ui_theme_list}, + {"confirm_theme", ui_confirm_theme}, + {"note_button", ui_note_button}, + // Each ui_label_images[i] + {"pre", ui_pre}, + {"flip", ui_flip}, + {"guard", ui_guard}, + {"hidden", ui_hidden}, + {"defense_plus", ui_defense_plus}, + {"defense_minus", ui_defense_minus}, + {"prosecution_plus", ui_prosecution_plus}, + {"prosecution_minus", ui_prosecution_minus}, + {"text_color", ui_text_color}, + {"music_slider", ui_music_slider}, + {"sfx_slider", ui_sfx_slider}, + {"blip_slider", ui_blip_slider}, + {"evidence_button", ui_evidence_button}, + {"notepad_image", ui_vp_notepad_image}, + {"notepad", ui_vp_notepad}, + // Each ui_timers[i] + {"evidence_background", ui_evidence}, + + + }; + void create_widgets(); void connect_widgets(); diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 5ea8eb58e..6c661ab38 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -381,38 +381,6 @@ void Courtroom::set_widgets() this->resize(f_courtroom.width, f_courtroom.height); } - shout_names.clear(); - for(int i = 1; i <= ui_shouts.size(); ++i) - { - QString name = ao_app->get_spbutton("[SHOUTS]", i); - if(!name.isEmpty()) - shout_names.append(name.trimmed()); - } - - effect_names.clear(); - for(int i = 1; i <= ui_effects.size(); ++i) - { - QStringList names = ao_app->get_effect(i); - if(!names.isEmpty()) - effect_names.append(names.at(0).trimmed()); - } - - wtce_names.clear(); - for(int i = 1; i <= ui_wtce.size(); ++i) - { - QString name = ao_app->get_spbutton("[WTCE]", i); - if(!name.isEmpty()) - wtce_names.append(name.trimmed()); - } - - free_block_names.clear(); - for(int i = 1; i <= ui_free_blocks.size(); ++i) - { - QString name = ao_app->get_spbutton("[FREE BLOCKS]", i); - if(!name.isEmpty()) - free_block_names.append(name.trimmed()); - } - ui_background->move(0, 0); ui_background->resize(m_courtroom_width, m_courtroom_height); ui_background->set_image("courtroombackground.png"); @@ -1065,6 +1033,12 @@ void Courtroom::load_effects() // Close any existing effects to prevent memory leaks for (int i=0; iobjectName(); + widget_names.remove(name); + // This index exists as ui_effects[i] can only exist if it was added by a previous + // call of load_effects(). However, this code later adds the name of all shouts. + // As this is the only place that changes the size of ui_effects and it originally + // starts empty, this code is correct.. ui_effects[i]->close(); delete ui_effects[i]; } @@ -1085,6 +1059,20 @@ void Courtroom::load_effects() // And connect their actions for (auto & effect : ui_effects) connect(effect, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked())); + + // And add names + effect_names.clear(); + for (int i=1; i<=ui_effects.size(); ++i) + { + QStringList names = ao_app->get_effect(i); + if (!names.isEmpty()) + { + QString name = names.at(0).trimmed(); + effect_names.append(name); + widget_names[name] = ui_effects[i-1]; + ui_effects[i-1]->setObjectName(name); + } + } } void Courtroom::load_free_blocks() @@ -1092,6 +1080,9 @@ void Courtroom::load_free_blocks() // Close any existing free blocks to prevent memory leaks for (int i=0; iobjectName(); + widget_names.remove(name); + // Same logic for why this index exists as in ui_effects() ui_free_blocks[i]->close(); delete ui_free_blocks[i]; } @@ -1108,6 +1099,19 @@ void Courtroom::load_free_blocks() ui_free_blocks[i]->set_play_once(false); ui_free_blocks[i]->stackUnder(ui_vp_player_char); } + + // And add names + free_block_names.clear(); + for (int i=1; i<=ui_free_blocks.size(); ++i) + { + QString name = ao_app->get_spbutton("[FREE BLOCKS]", i).trimmed(); + if (!name.isEmpty()) + { + free_block_names.append(name); + widget_names[name] = ui_free_blocks[i-1]; + ui_free_blocks[i-1]->setObjectName(name); + } + } } void Courtroom::load_shouts() @@ -1115,8 +1119,10 @@ void Courtroom::load_shouts() // Close any existing shouts to prevent memory leaks for (int i=0; iobjectName(); + widget_names.remove(name); ui_shouts[i]->close(); - delete ui_shouts[i]; + delete ui_shouts[i]; } // And create new shouts @@ -1135,6 +1141,19 @@ void Courtroom::load_shouts() // And connect their actions for (auto & shout : ui_shouts) connect(shout, SIGNAL(clicked(bool)), this, SLOT(on_shout_clicked())); + + // And add names + shout_names.clear(); + for (int i=1; i<=ui_shouts.size(); ++i) + { + QString name = ao_app->get_spbutton("[SHOUTS]", i).trimmed(); + if (!name.isEmpty()) + { + shout_names.append(name); + widget_names[name] = ui_shouts[i-1]; + ui_shouts[i-1]->setObjectName(name); + } + } } void Courtroom::load_wtce() @@ -1142,6 +1161,8 @@ void Courtroom::load_wtce() // Close any existing wtce buttons to prevent memory leaks for (int i=0; iobjectName(); + widget_names.remove(name); ui_wtce[i]->close(); delete ui_wtce[i]; } @@ -1162,6 +1183,19 @@ void Courtroom::load_wtce() // And connect their actions for (auto & wtce : ui_wtce) connect(wtce, SIGNAL(clicked(bool)), this, SLOT(on_wtce_clicked())); + + // And add names + wtce_names.clear(); + for (int i=1; i<=ui_wtce.size(); ++i) + { + QString name = ao_app->get_spbutton("[WTCE]", i).trimmed(); + if (!name.isEmpty()) + { + wtce_names.append(name); + widget_names[name] = ui_wtce[i-1]; + ui_wtce[i-1]->setObjectName(name); + } + } } void Courtroom::set_shouts() From 5362f1d1ab7cec97c047b1590aeb8ffa602ecfdb Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 28 May 2020 23:11:41 -0400 Subject: [PATCH 022/842] Add widget naming --- courtroom.cpp | 4 +- courtroom.h | 93 ++------------------------ courtroom_widgets.cpp | 145 ++++++++++++++++++++++++++++++++++++++++ text_file_functions.cpp | 26 +++---- 4 files changed, 164 insertions(+), 104 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 55d5b1e5c..96000f068 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -26,14 +26,16 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() //initializing sound device BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, 0, NULL); - BASS_PluginLoad("bassopus.dll", BASS_UNICODE); + BASS_PluginLoad("bassopus.dll", BASS_UNICODE); create_widgets(); connect_widgets(); + name_widgets(); set_widgets(); set_bullets(); set_char_select(); + setWindowState(Qt::WindowMaximized); // Remove later } void Courtroom::enter_courtroom(int p_cid) diff --git a/courtroom.h b/courtroom.h index b5a4ce36c..3538edff6 100644 --- a/courtroom.h +++ b/courtroom.h @@ -596,97 +596,12 @@ class Courtroom : public QMainWindow AOButton *ui_spectator; - QHash widget_names{ - {"viewport", ui_viewport}, - // ui_vp_background - // ui_vp_speedlines - // ui_vp_player_char - // ui_vp_desk - // ui_vp_legacy_desk - {"music_display_a", ui_vp_music_display_a}, - {"music_display_b", ui_vp_music_display_b}, - {"music_area", ui_vp_music_area}, - {"music_name", ui_vp_music_name}, - // music_anim - {"clock", ui_vp_clock}, - // ui_vp_evidence_display - {"ao2_chatbox", ui_vp_chatbox}, - {"showname", ui_vp_showname}, - {"message", ui_vp_message}, - {"showname_image", ui_vp_showname_image}, - // ui_vp_testimony - // ui_vp_effect - // ui_vp_wtce - // ui_vp_objection - {"ic_chatlog", ui_ic_chatlog}, - {"ms_chatlog", ui_ms_chatlog}, - {"server_chatlog", ui_server_chatlog}, - {"mute_list", ui_mute_list}, - {"area_list", ui_area_list}, - {"music_list", ui_music_list}, - {"sfx_list", ui_sfx_list}, - {"ao2_ic_chat_message", ui_ic_chat_message}, - // ui_muted - {"ooc_chat_message", ui_ooc_chat_message}, - {"ooc_chat_name", ui_ooc_chat_name}, - {"music_search", ui_music_search}, - {"sfx_search", ui_sfx_search}, - {"note_area", ui_note_area}, - // add_button - // m_layout - // note_scroll_area - {"set_notes_button", ui_set_notes}, - {"emotes", ui_emotes}, - {"emote_left", ui_emote_left}, - {"emote_right", ui_emote_right}, - {"emote_dropdown", ui_emote_dropdown}, - {"pos_dropdown", ui_pos_dropdown}, - {"defense_bar", ui_defense_bar}, - {"prosecution_bar", ui_prosecution_bar}, - {"music_label", ui_music_label}, - {"sfx_label", ui_sfx_label}, - {"blip_label", ui_blip_label}, - // Each ui_shouts[i] - {"shout_up", ui_shout_up}, - {"shout_down", ui_shout_down}, - // Each ui_effects[i] - {"effect_down", ui_effect_down}, - {"effect_up", ui_effect_up}, - // Each ui_wtce[i] - {"wtce_up", ui_wtce_up}, - {"wtce_down", ui_wtce_down}, - {"ooc_toggle", ui_ooc_toggle}, - {"change_character", ui_change_character}, - {"reload_theme", ui_reload_theme}, - {"call_mod", ui_call_mod}, - {"switch_area_music", ui_switch_area_music}, - {"theme_list", ui_theme_list}, - {"confirm_theme", ui_confirm_theme}, - {"note_button", ui_note_button}, - // Each ui_label_images[i] - {"pre", ui_pre}, - {"flip", ui_flip}, - {"guard", ui_guard}, - {"hidden", ui_hidden}, - {"defense_plus", ui_defense_plus}, - {"defense_minus", ui_defense_minus}, - {"prosecution_plus", ui_prosecution_plus}, - {"prosecution_minus", ui_prosecution_minus}, - {"text_color", ui_text_color}, - {"music_slider", ui_music_slider}, - {"sfx_slider", ui_sfx_slider}, - {"blip_slider", ui_blip_slider}, - {"evidence_button", ui_evidence_button}, - {"notepad_image", ui_vp_notepad_image}, - {"notepad", ui_vp_notepad}, - // Each ui_timers[i] - {"evidence_background", ui_evidence}, - - - }; + QHash widget_names; void create_widgets(); void connect_widgets(); + void name_widgets(); + void set_widget_depths(); void construct_char_select(); void set_char_select(); @@ -703,6 +618,8 @@ class Courtroom : public QMainWindow void save_note(); void save_textlog(QString p_text); + void set_depth(); + void set_bullets(); void set_char_rpc(); diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 6c661ab38..8234b5109 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -359,6 +359,151 @@ void Courtroom::connect_widgets() connect(ui_set_notes, SIGNAL(clicked(bool)), this, SLOT(on_set_notes_clicked())); } +void Courtroom::name_widgets() +{ + // Assign names to the default widgets + widget_names = { + {"viewport", ui_viewport}, + {"background", ui_vp_background}, //* + {"speedlines", ui_vp_speedlines}, //* + {"player_char", ui_vp_player_char}, //* + {"desk", ui_vp_desk}, //* + {"legacy_desk", ui_vp_legacy_desk}, //* + {"music_display_a", ui_vp_music_display_a}, + {"music_display_b", ui_vp_music_display_b}, + {"music_area", ui_vp_music_area}, + {"music_name", ui_vp_music_name}, + // music_anim + {"clock", ui_vp_clock}, + // ui_vp_evidence_display + {"ao2_chatbox", ui_vp_chatbox}, + {"showname", ui_vp_showname}, + {"message", ui_vp_message}, + {"showname_image", ui_vp_showname_image}, + // ui_vp_testimony + // ui_vp_effect + // ui_vp_wtce + // ui_vp_objection + {"ic_chatlog", ui_ic_chatlog}, + {"ms_chatlog", ui_ms_chatlog}, + {"server_chatlog", ui_server_chatlog}, + {"mute_list", ui_mute_list}, + {"area_list", ui_area_list}, + {"music_list", ui_music_list}, + {"sfx_list", ui_sfx_list}, + {"ao2_ic_chat_message", ui_ic_chat_message}, + // ui_muted + {"ooc_chat_message", ui_ooc_chat_message}, + {"ooc_chat_name", ui_ooc_chat_name}, + {"music_search", ui_music_search}, + {"sfx_search", ui_sfx_search}, + {"note_area", ui_note_area}, + // add_button + // m_layout + // note_scroll_area + {"set_notes_button", ui_set_notes}, + {"emotes", ui_emotes}, + {"emote_left", ui_emote_left}, + {"emote_right", ui_emote_right}, + {"emote_dropdown", ui_emote_dropdown}, + {"pos_dropdown", ui_pos_dropdown}, + {"defense_bar", ui_defense_bar}, + {"prosecution_bar", ui_prosecution_bar}, + {"music_label", ui_music_label}, + {"sfx_label", ui_sfx_label}, + {"blip_label", ui_blip_label}, + // Each ui_shouts[i] + {"shout_up", ui_shout_up}, + {"shout_down", ui_shout_down}, + // Each ui_effects[i] + {"effect_down", ui_effect_down}, + {"effect_up", ui_effect_up}, + // Each ui_wtce[i] + {"wtce_up", ui_wtce_up}, + {"wtce_down", ui_wtce_down}, + {"ooc_toggle", ui_ooc_toggle}, + {"change_character", ui_change_character}, + {"reload_theme", ui_reload_theme}, + {"call_mod", ui_call_mod}, + {"switch_area_music", ui_switch_area_music}, + {"theme_list", ui_theme_list}, + {"confirm_theme", ui_confirm_theme}, + {"note_button", ui_note_button}, + // Each ui_label_images[i] + {"pre", ui_pre}, + {"flip", ui_flip}, + {"guard", ui_guard}, + {"hidden", ui_hidden}, + {"defense_plus", ui_defense_plus}, + {"defense_minus", ui_defense_minus}, + {"prosecution_plus", ui_prosecution_plus}, + {"prosecution_minus", ui_prosecution_minus}, + {"text_color", ui_text_color}, + {"music_slider", ui_music_slider}, + {"sfx_slider", ui_sfx_slider}, + {"blip_slider", ui_blip_slider}, + {"evidence_button", ui_evidence_button}, + {"notepad_image", ui_vp_notepad_image}, + {"notepad", ui_vp_notepad}, + // Each ui_timers[i] + {"evidence_background", ui_evidence}, + {"char_select", ui_char_select_background}, + {"back_to_lobby", ui_back_to_lobby}, + {"char_password", ui_char_password}, + {"char_buttons", ui_char_buttons}, + {"char_select_left", ui_char_select_left}, + {"char_select_right", ui_char_select_right}, + {"spectator", ui_spectator}, + }; + + QHash::iterator i; + for (i = widget_names.begin(); i != widget_names.end(); ++i) + { + QString name = i.key(); + QWidget* widget = i.value(); + widget->setObjectName(name); + } +} + +/* + * bool Courtroom::set_widget_depths() +{ + QFile design_ini; + + design_ini.setFileName(p_design_path); + + if (!design_ini.open(QIODevice::ReadOnly)) + { + return ""; + } + QTextStream in(&design_ini); + + QString result = ""; + + while (!in.atEnd()) + { + QString f_line = in.readLine().trimmed(); + + if (!f_line.startsWith(p_identifier)) + continue; + + QStringList line_elements = f_line.split("="); + + if (line_elements.at(0).trimmed() != p_identifier) + continue; + + if (line_elements.size() < 2) + continue; + + result = line_elements.at(1).trimmed(); + break; + } + + design_ini.close(); + + return result; +}*/ + void Courtroom::set_widgets() { blip_rate = ao_app->read_blip_rate(); diff --git a/text_file_functions.cpp b/text_file_functions.cpp index a9219b007..efb685ebf 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -897,21 +897,18 @@ QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) { // Try to obtain a theme ini from either the current theme variant folder, // the current theme folder or the default theme folder - QString variant_design_ini_path = get_theme_variant_path() + p_file; - QString design_ini_path = get_theme_path() + p_file; - QString default_path = get_default_theme_path() + p_file; + QStringList paths{ + get_theme_variant_path() + p_file, + get_theme_path() + p_file, + get_default_theme_path() + p_file, + }; - QString f_result = read_design_ini(p_identifier, variant_design_ini_path); - if (!f_result.isEmpty()) - return f_result; - - f_result = read_design_ini(p_identifier, design_ini_path); - if (!f_result.isEmpty()) - return f_result; - - f_result = read_design_ini(p_identifier, default_path); - if (!f_result.isEmpty()) - return f_result; + for (QString path: paths) + { + QString f_result = read_design_ini(p_identifier, path); + if (!f_result.isEmpty()) + return f_result; + } return ""; } @@ -930,4 +927,3 @@ QString AOApplication::get_image_path(QString p_image) return theme_image_path; return default_image_path; } - From be6e0bcdc692662f10c40cc718b2ca311fb65bf9 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 29 May 2020 13:06:31 -0400 Subject: [PATCH 023/842] Add support for reading through layers file to set layers --- courtroom.cpp | 18 +---- courtroom_widgets.cpp | 174 +++++++++++++++++++++++++++++++++--------- 2 files changed, 142 insertions(+), 50 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 96000f068..c0ce48fed 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -30,11 +30,12 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() create_widgets(); connect_widgets(); - name_widgets(); set_widgets(); set_bullets(); set_char_select(); + + name_widgets(); setWindowState(Qt::WindowMaximized); // Remove later } @@ -181,6 +182,8 @@ void Courtroom::enter_courtroom(int p_cid) ui_ic_chat_message->setFocus(); set_bullets(); + name_widgets(); + set_widget_depths(); } void Courtroom::done_received() @@ -2467,19 +2470,6 @@ void Courtroom::on_set_notes_clicked() note_scroll_area->hide(); } -void Courtroom::set_bullets() -{ - QString somethingsomethingpath = ao_app->get_base_path() + "configs/" + "wow.ini"; - - QString thing = ""; - int i = 0; - do - { - thing = ao_app->read_design_ini(QString::number(++i), somethingsomethingpath); - //qDebug() << QString::number(i) << thing; - } while(thing != ""); -} - void Courtroom::resume_timer(int timer_id) { if (timer_id >= timer_number) diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 8234b5109..ef746e243 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -377,8 +377,8 @@ void Courtroom::name_widgets() {"clock", ui_vp_clock}, // ui_vp_evidence_display {"ao2_chatbox", ui_vp_chatbox}, - {"showname", ui_vp_showname}, - {"message", ui_vp_message}, + {"showname", ui_vp_showname}, + {"message", ui_vp_message}, {"showname_image", ui_vp_showname_image}, // ui_vp_testimony // ui_vp_effect @@ -397,10 +397,10 @@ void Courtroom::name_widgets() {"ooc_chat_name", ui_ooc_chat_name}, {"music_search", ui_music_search}, {"sfx_search", ui_sfx_search}, - {"note_area", ui_note_area}, + {"note_scroll_area", note_scroll_area}, + {"note_area", ui_note_area}, // add_button // m_layout - // note_scroll_area {"set_notes_button", ui_set_notes}, {"emotes", ui_emotes}, {"emote_left", ui_emote_left}, @@ -434,6 +434,7 @@ void Courtroom::name_widgets() {"flip", ui_flip}, {"guard", ui_guard}, {"hidden", ui_hidden}, + {"mute_button", ui_mute}, {"defense_plus", ui_defense_plus}, {"defense_minus", ui_defense_minus}, {"prosecution_plus", ui_prosecution_plus}, @@ -447,6 +448,7 @@ void Courtroom::name_widgets() {"notepad", ui_vp_notepad}, // Each ui_timers[i] {"evidence_background", ui_evidence}, + {"evidence_buttons", ui_evidence_buttons}, {"char_select", ui_char_select_background}, {"back_to_lobby", ui_back_to_lobby}, {"char_password", ui_char_password}, @@ -463,46 +465,131 @@ void Courtroom::name_widgets() QWidget* widget = i.value(); widget->setObjectName(name); } + // Why + for (int i=0; isetObjectName(name); + } + for (int i=0; isetObjectName(name); + } + for (int i=0; isetObjectName(name); + } + for (int i=0; isetObjectName(name); + } + for (int i=0; isetObjectName(name); + } } -/* - * bool Courtroom::set_widget_depths() +void Courtroom::set_widget_depths() { - QFile design_ini; + qDebug() << widget_names; + QStringList paths{ + ao_app->get_theme_variant_path() + "courtroom_layers.ini", + ao_app->get_theme_path() + "courtroom_layers.ini", + ao_app->get_default_theme_path() + "courtroom_layers.ini", + }; + + QWidget* current_parent = this; + QString current_widget_name; + QWidget* current_widget; + + for (QString path: paths) + { + QFile layer_ini; + layer_ini.setFileName(path); + if (!layer_ini.open(QIODevice::ReadOnly)) + continue; + QTextStream in(&layer_ini); - design_ini.setFileName(p_design_path); + while (!in.atEnd()) + { + QString f_line = in.readLine().trimmed(); + // Lines are either empty, indicate the start of a frame + // or list the items in a frame. We consider each case in order. + if (f_line == "") + continue; + if (f_line.startsWith("[")) + { + current_widget_name = f_line.remove(0, 1).chopped(1); + current_parent = widget_names[current_widget_name]; + qDebug() << "New parent " << current_parent; + } + else + { + qDebug() << f_line; + current_widget = widget_names[f_line]; + current_widget->setParent(current_parent); + current_widget->raise(); + qDebug() << "Added " << f_line << "parent " << current_parent; + } + } - if (!design_ini.open(QIODevice::ReadOnly)) - { - return ""; + layer_ini.close(); + return; } - QTextStream in(&design_ini); + return; - QString result = ""; + /* + QHash frames = { + {"", QStringList()} + }; + QString current_frame = ""; - while (!in.atEnd()) + for (QString path: paths) { - QString f_line = in.readLine().trimmed(); - - if (!f_line.startsWith(p_identifier)) - continue; - - QStringList line_elements = f_line.split("="); - - if (line_elements.at(0).trimmed() != p_identifier) + QFile layer_ini; + layer_ini.setFileName(path); + if (!layer_ini.open(QIODevice::ReadOnly)) + { continue; + } + QTextStream in(&layer_ini); + QString result = ""; - if (line_elements.size() < 2) - continue; + while (!in.atEnd()) + { + QString f_line = in.readLine().trimmed(); + // Lines are either empty, indicate the start of a frame + // or list the items in a frame. We consider each case in order. + if (f_line == "") + continue; + if (f_line.startsWith("[")) + { + current_frame = f_line.remove(0, 1).chopped(1); + frames[current_frame] = QStringList(); + } + else + { + frames[current_frame].append(f_line); + qDebug() << "Added " << f_line; + } + } - result = line_elements.at(1).trimmed(); - break; + layer_ini.close(); + qDebug() << frames; + return; } - - design_ini.close(); - - return result; -}*/ + return; + */ +} void Courtroom::set_widgets() { @@ -754,7 +841,7 @@ void Courtroom::set_widgets() for(int i = 0; i < free_block_names.size(); ++i) { - set_size_and_pos(ui_free_blocks[i], "free_block_" + free_block_names[i]); + set_size_and_pos(ui_free_blocks[i], free_block_names[i]); } set_free_blocks(); @@ -1214,8 +1301,6 @@ void Courtroom::load_effects() { QString name = names.at(0).trimmed(); effect_names.append(name); - widget_names[name] = ui_effects[i-1]; - ui_effects[i-1]->setObjectName(name); } } } @@ -1249,7 +1334,7 @@ void Courtroom::load_free_blocks() free_block_names.clear(); for (int i=1; i<=ui_free_blocks.size(); ++i) { - QString name = ao_app->get_spbutton("[FREE BLOCKS]", i).trimmed(); + QString name = "free_block_" + ao_app->get_spbutton("[FREE BLOCKS]", i).trimmed(); if (!name.isEmpty()) { free_block_names.append(name); @@ -1257,6 +1342,7 @@ void Courtroom::load_free_blocks() ui_free_blocks[i-1]->setObjectName(name); } } + qDebug() << "FREE BLOCKS HERE " << free_block_names; } void Courtroom::load_shouts() @@ -1294,11 +1380,13 @@ void Courtroom::load_shouts() QString name = ao_app->get_spbutton("[SHOUTS]", i).trimmed(); if (!name.isEmpty()) { + qDebug() << "SHOUT " << name << " " << ui_shouts[i-1]; shout_names.append(name); widget_names[name] = ui_shouts[i-1]; ui_shouts[i-1]->setObjectName(name); } } + qDebug() << widget_names; } void Courtroom::load_wtce() @@ -1381,10 +1469,24 @@ void Courtroom::set_free_blocks() for (int i=0; iplay("free_block_" + free_block_names[i]); + free_block->play(free_block_names[i]); } } + +void Courtroom::set_bullets() +{ + QString somethingsomethingpath = ao_app->get_base_path() + "configs/" + "wow.ini"; + + QString thing = ""; + int i = 0; + do + { + thing = ao_app->read_design_ini(QString::number(++i), somethingsomethingpath); + //qDebug() << QString::number(i) << thing; + } while(thing != ""); +} + void Courtroom::set_dropdown(QWidget *widget, QString target_tag) { QString f_file = "courtroom_stylesheets.css"; From f5802b066b9614c3f14dc491c6736f40173b9c2d Mon Sep 17 00:00:00 2001 From: Jerm125067 <35246892+Jerm125067@users.noreply.github.com> Date: Fri, 29 May 2020 17:58:49 -0400 Subject: [PATCH 024/842] Colors pt. 1 Edits colors and adds new ones. Part 1 is fixing the courtroom to have these colors part 2 is to add Data Types. --- courtroom.cpp | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index ba7aee270..b85e5a65a 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -249,6 +249,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_text_color->addItem("Blue"); if (ao_app->yellow_text_enabled) ui_text_color->addItem("Yellow"); + ui_text_color->addItem("Purple"); + ui_text_color->addItem("Pink"); ui_music_slider = new QSlider(Qt::Horizontal, this); ui_music_slider->setRange(0, 100); @@ -2453,19 +2455,19 @@ void Courtroom::chat_tick() switch (rainbow_counter) { case 0: - html_color = "#FF0000"; + html_color = "#BA1518"; break; case 1: - html_color = "#FF7F00"; + html_color = "#D55900"; break; case 2: - html_color = "#FFFF00"; + html_color = "#E7CE4E"; break; case 3: - html_color = "#00FF00"; + html_color = "#65C856"; break; default: - html_color = "#2d96ff"; + html_color = "#1596C8"; rainbow_counter = -1; } @@ -2689,29 +2691,37 @@ void Courtroom::set_text_color() { case GREEN: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setRgb(0, 255, 0); + m_base_string_color.setRgb(101, 200, 86); break; case RED: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setNamedColor("red"); + m_base_string_color.setRgb(186, 21, 24); break; case ORANGE: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setNamedColor("orange"); + m_base_string_color.setRgb(213, 89, 0); break; case BLUE: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setRgb(45, 150, 255); + m_base_string_color.setRgb(21, 136, 200); break; case YELLOW: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setNamedColor("yellow"); + m_base_string_color.setRgb(231, 206, 78); break; - default: + case PURPLE: + ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); + m_base_string_color.setRgb(247, 118, 253); + break; + case PINK: + ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); + m_base_string_color.setRgb(218, 124, 128); + break; + default: qDebug() << "W: undefined text color: " << m_chatmessage[TEXT_COLOR]; case WHITE: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setNamedColor("white"); + m_base_string_color.setRgb(213, 213, 213); } } From 76817eaac3164d06ba7074b8875223e05d700fe3 Mon Sep 17 00:00:00 2001 From: Jerm125067 <35246892+Jerm125067@users.noreply.github.com> Date: Fri, 29 May 2020 18:00:22 -0400 Subject: [PATCH 025/842] Colors pt. 2 Adds new colors to the data types --- datatypes.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datatypes.h b/datatypes.h index ec44accc0..13c05cb86 100644 --- a/datatypes.h +++ b/datatypes.h @@ -120,6 +120,8 @@ enum COLOR ORANGE, BLUE, YELLOW, + PURPLE, + PINK, RAINBOW }; From b5ca43936e7514ed54654c7ab2bac80393f6fa81 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 29 May 2020 22:46:06 -0400 Subject: [PATCH 026/842] Finish up free blocks and layering --- courtroom.cpp | 4 +-- courtroom.h | 7 +--- courtroom_widgets.cpp | 77 +++++++------------------------------------ path_functions.cpp | 17 ++-------- 4 files changed, 16 insertions(+), 89 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index c0ce48fed..35cccb87d 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -32,7 +32,6 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() connect_widgets(); set_widgets(); - set_bullets(); set_char_select(); name_widgets(); @@ -181,9 +180,8 @@ void Courtroom::enter_courtroom(int p_cid) ui_ic_chat_message->setEnabled(m_cid != -1); ui_ic_chat_message->setFocus(); - set_bullets(); name_widgets(); - set_widget_depths(); + set_widget_layers(); } void Courtroom::done_received() diff --git a/courtroom.h b/courtroom.h index 3538edff6..f972b2a2d 100644 --- a/courtroom.h +++ b/courtroom.h @@ -601,7 +601,7 @@ class Courtroom : public QMainWindow void create_widgets(); void connect_widgets(); void name_widgets(); - void set_widget_depths(); + void set_widget_layers(); void construct_char_select(); void set_char_select(); @@ -618,13 +618,8 @@ class Courtroom : public QMainWindow void save_note(); void save_textlog(QString p_text); - void set_depth(); - - void set_bullets(); - void set_char_rpc(); - public slots: void objection_done(); void preanim_done(); diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index ef746e243..1eb16b122 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -498,7 +498,7 @@ void Courtroom::name_widgets() } } -void Courtroom::set_widget_depths() +void Courtroom::set_widget_layers() { qDebug() << widget_names; QStringList paths{ @@ -522,73 +522,34 @@ void Courtroom::set_widget_depths() while (!in.atEnd()) { QString f_line = in.readLine().trimmed(); - // Lines are either empty, indicate the start of a frame + // Lines are either empty, indicate the end or the start of a frame // or list the items in a frame. We consider each case in order. if (f_line == "") continue; + // This particular check is not needed, but is added to keep compatibility + // with the other ini format. + if (f_line.startsWith("[\\")) + continue; if (f_line.startsWith("[")) { current_widget_name = f_line.remove(0, 1).chopped(1); current_parent = widget_names[current_widget_name]; - qDebug() << "New parent " << current_parent; - } - else - { - qDebug() << f_line; - current_widget = widget_names[f_line]; - current_widget->setParent(current_parent); - current_widget->raise(); - qDebug() << "Added " << f_line << "parent " << current_parent; - } - } - - layer_ini.close(); - return; - } - return; - - /* - QHash frames = { - {"", QStringList()} - }; - QString current_frame = ""; - - for (QString path: paths) - { - QFile layer_ini; - layer_ini.setFileName(path); - if (!layer_ini.open(QIODevice::ReadOnly)) - { - continue; - } - QTextStream in(&layer_ini); - QString result = ""; - - while (!in.atEnd()) - { - QString f_line = in.readLine().trimmed(); - // Lines are either empty, indicate the start of a frame - // or list the items in a frame. We consider each case in order. - if (f_line == "") continue; - if (f_line.startsWith("[")) - { - current_frame = f_line.remove(0, 1).chopped(1); - frames[current_frame] = QStringList(); } - else + // If the item does not exist, do nothing; also prevent crashes + if (!widget_names.contains(f_line)) { - frames[current_frame].append(f_line); - qDebug() << "Added " << f_line; + continue; } + current_widget = widget_names[f_line]; + current_widget->setParent(current_parent); + current_widget->raise(); } layer_ini.close(); - qDebug() << frames; return; } return; - */ } void Courtroom::set_widgets() @@ -1473,20 +1434,6 @@ void Courtroom::set_free_blocks() } } - -void Courtroom::set_bullets() -{ - QString somethingsomethingpath = ao_app->get_base_path() + "configs/" + "wow.ini"; - - QString thing = ""; - int i = 0; - do - { - thing = ao_app->read_design_ini(QString::number(++i), somethingsomethingpath); - //qDebug() << QString::number(i) << thing; - } while(thing != ""); -} - void Courtroom::set_dropdown(QWidget *widget, QString target_tag) { QString f_file = "courtroom_stylesheets.css"; diff --git a/path_functions.cpp b/path_functions.cpp index 0b2d25302..38f186771 100644 --- a/path_functions.cpp +++ b/path_functions.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "debug_functions.h" #ifdef BASE_OVERRIDE #include "base_override.h" @@ -28,22 +29,8 @@ QString AOApplication::get_base_path() base_path = QDir::currentPath() + "/base/"; #endif } + call_notice(QDir::currentPath() + "/../../../base/"); return base_path; - /* -#ifdef OMNI_DEBUG - return "/media/omnitroid/Data/winshare/AO/client/base/"; -#elif OMNI_DEBUG2 - return "/home/omnitroid/winshare/AO/client/base/"; -#elif defined(OMNI_WIN_DEBUG) - return "E:/AO/client/base/"; -#elif defined(OMNI_WIN_DEBUG2) - return "F:/winshare/AO/client/base/"; -#elif defined(ANDROID) - return "/storage/extSdCard/AO2/"; -#else - return QDir::currentPath() + "/base/"; -#endif -*/ } QString AOApplication::get_data_path() From b1e57febd18e7c51b80465bc1c6cee3092c21749 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 29 May 2020 22:48:34 -0400 Subject: [PATCH 027/842] Remove debug call notices --- path_functions.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/path_functions.cpp b/path_functions.cpp index 38f186771..ab35acab8 100644 --- a/path_functions.cpp +++ b/path_functions.cpp @@ -4,7 +4,6 @@ #include #include #include -#include "debug_functions.h" #ifdef BASE_OVERRIDE #include "base_override.h" @@ -29,7 +28,6 @@ QString AOApplication::get_base_path() base_path = QDir::currentPath() + "/base/"; #endif } - call_notice(QDir::currentPath() + "/../../../base/"); return base_path; } From dcf8a31c456748225d2416c70151124e36d28144 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 30 May 2020 12:11:21 -0400 Subject: [PATCH 028/842] Remove legacy desks+Fix broken desk layers --- aoscene.cpp | 32 ----------------------------- aoscene.h | 1 - courtroom.cpp | 47 ++++++++++++++++++------------------------- courtroom.h | 1 - courtroom_widgets.cpp | 9 --------- 5 files changed, 20 insertions(+), 70 deletions(-) diff --git a/aoscene.cpp b/aoscene.cpp index d86b849b4..6f25e0fe1 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -62,35 +62,3 @@ void AOScene::set_image(QString p_image) this->setPixmap(default_bg.scaled(w, h)); } } - -void AOScene::set_legacy_desk(QString p_image) -{ - //vanilla desks vary in both width and height. in order to make that work with viewport rescaling, - //some INTENSE math is needed. - - QString desk_path = ao_app->get_background_path() + p_image; - QString default_path = ao_app->get_default_background_path() + p_image; - - QPixmap f_desk; - - if (file_exists(desk_path)) - f_desk.load(desk_path); - else - f_desk.load(default_path); - - int vp_width = m_parent->width(); - int vp_height = m_parent->height(); - - //double y_modifier = 147 / 192; - //double w_modifier = vp_width / 256; - double h_modifier = vp_height / 192; - - //int final_y = y_modifier * vp_height; - //int final_w = w_modifier * f_desk.width(); - int final_h = h_modifier * f_desk.height(); - - //this->resize(final_w, final_h); - //this->setPixmap(f_desk.scaled(final_w, final_h)); - this->resize(vp_width, final_h); - this->setPixmap(f_desk.scaled(vp_width, final_h)); -} diff --git a/aoscene.h b/aoscene.h index 468b0778b..9610085b5 100644 --- a/aoscene.h +++ b/aoscene.h @@ -13,7 +13,6 @@ class AOScene : public QLabel explicit AOScene(QWidget *parent, AOApplication *p_ao_app); void set_image(QString p_image); - void set_legacy_desk(QString p_image); private: QWidget* m_parent = nullptr; diff --git a/courtroom.cpp b/courtroom.cpp index 04620c537..1a2233e31 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -229,13 +229,12 @@ void Courtroom::set_scene() if (f_desk_mod == "0") // keeping a bit of the functionality for now { ui_vp_desk->hide(); - ui_vp_legacy_desk->hide(); } } else { - if (f_side == "def") - { + if (f_side == "def") + { f_background = "defenseempty"; f_desk_image = "defensedesk"; } @@ -264,37 +263,32 @@ void Courtroom::set_scene() f_desk_image = "stand"; } + QString bg_path = get_background_path(); + QVector exts{".apng", ".gif", ".png"}; + + bool has_all_desks; + if (file_exists(bg_path + "defensedesk", exts) == "") + has_all_desks = false; + else if (file_exists(bg_path + "prosecutiondesk", exts) == "") + has_all_desks = false; + else if (file_exists(bg_path + "stand", exts) == "") + has_all_desks = false; + else + has_all_desks = true; + if (f_desk_mod == "0" || (f_desk_mod != "1" && (f_side == "jud" || - f_side == "hld" || - f_side == "hlp"))) - { + f_side == "hld" || + f_side == "hlp"))) + ui_vp_desk->hide(); + else if (!has_all_desks) ui_vp_desk->hide(); - ui_vp_legacy_desk->hide(); - } - else if ((f_side == "jud" || f_side == "hld" || f_side == "hlp")) - { - ui_vp_legacy_desk->hide(); - ui_vp_desk->show(); - } else - { - if (f_side == "wit") - { - ui_vp_desk->show(); - ui_vp_legacy_desk->hide(); - } - else - { - ui_vp_desk->hide(); - ui_vp_legacy_desk->show(); - } - } + ui_vp_desk->show(); } 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); } void Courtroom::set_char_rpc() @@ -1006,7 +1000,6 @@ void Courtroom::handle_chatmessage_3() { QString side = m_chatmessage[SIDE]; ui_vp_desk->hide(); - ui_vp_legacy_desk->hide(); if (side == "pro" || side == "hlp" || diff --git a/courtroom.h b/courtroom.h index f972b2a2d..46a9eb78a 100644 --- a/courtroom.h +++ b/courtroom.h @@ -393,7 +393,6 @@ class Courtroom : public QMainWindow AOMovie *ui_vp_speedlines; AOCharMovie *ui_vp_player_char; AOScene *ui_vp_desk; - AOScene *ui_vp_legacy_desk; AOEvidenceDisplay *ui_vp_evidence_display; AONoteArea *ui_note_area; diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 1eb16b122..f25814a67 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -65,7 +65,6 @@ void Courtroom::create_widgets() ui_vp_speedlines->set_play_once(false); ui_vp_player_char = new AOCharMovie(ui_viewport, ao_app); ui_vp_desk = new AOScene(ui_viewport, ao_app); - ui_vp_legacy_desk = new AOScene(ui_viewport, ao_app); ui_vp_music_display_a = new AOImage(this, ao_app); ui_vp_music_display_b = new AOImage(this, ao_app); @@ -368,7 +367,6 @@ void Courtroom::name_widgets() {"speedlines", ui_vp_speedlines}, //* {"player_char", ui_vp_player_char}, //* {"desk", ui_vp_desk}, //* - {"legacy_desk", ui_vp_legacy_desk}, //* {"music_display_a", ui_vp_music_display_a}, {"music_display_b", ui_vp_music_display_b}, {"music_area", ui_vp_music_area}, @@ -593,13 +591,6 @@ void Courtroom::set_widgets() ui_vp_desk->move(0, 0); ui_vp_desk->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(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->resize(ui_viewport->width(), ui_viewport->height()); From 0f197e8dbd054eefcfbcf7048aa4f38b6e290d3c Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 30 May 2020 17:08:54 -0400 Subject: [PATCH 029/842] Add pending widget names --- courtroom_widgets.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index f25814a67..3f0877d39 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -378,10 +378,10 @@ void Courtroom::name_widgets() {"showname", ui_vp_showname}, {"message", ui_vp_message}, {"showname_image", ui_vp_showname_image}, - // ui_vp_testimony - // ui_vp_effect - // ui_vp_wtce - // ui_vp_objection + {"vp_testimony", ui_vp_testimony}, + {"vp_effect", ui_vp_effect}, + {"vp_wtce", ui_vp_wtce}, + {"vp_objection", ui_vp_objection}, {"ic_chatlog", ui_ic_chatlog}, {"ms_chatlog", ui_ms_chatlog}, {"server_chatlog", ui_server_chatlog}, From 4daec0fc548febd85f811fc7afe5be26aaec4994 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 31 May 2020 11:49:45 -0400 Subject: [PATCH 030/842] Fix visible widgets that change what frame they belong to not remaining visible through transition --- courtroom_widgets.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 3f0877d39..e92c442e0 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -498,7 +498,6 @@ void Courtroom::name_widgets() void Courtroom::set_widget_layers() { - qDebug() << widget_names; QStringList paths{ ao_app->get_theme_variant_path() + "courtroom_layers.ini", ao_app->get_theme_path() + "courtroom_layers.ini", @@ -540,8 +539,11 @@ void Courtroom::set_widget_layers() continue; } current_widget = widget_names[f_line]; + bool was_visible = current_widget->isVisible(); current_widget->setParent(current_parent); current_widget->raise(); + if (was_visible) + current_widget->show(); // Show again in case the widget was hidden after changing parent } layer_ini.close(); From 821dae1cbaa37bca5df1a2cf8ee80ee82e037dec Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 31 May 2020 12:02:03 -0400 Subject: [PATCH 031/842] Maintain current widget visibility independent of setParent()'s behavior with visibility changes --- courtroom_widgets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index e92c442e0..5cc7f1f01 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -542,8 +542,8 @@ void Courtroom::set_widget_layers() bool was_visible = current_widget->isVisible(); current_widget->setParent(current_parent); current_widget->raise(); - if (was_visible) - current_widget->show(); // Show again in case the widget was hidden after changing parent + // Readjust visibility in case this changed after the widget changed parent + current_widget->setVisible(was_visible); } layer_ini.close(); From d73cbc611de9fed48116a0c2a296ba671d7fec52 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 31 May 2020 14:14:26 -0400 Subject: [PATCH 032/842] Actually fix the panel issue, I think. Why. --- courtroom_widgets.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 5cc7f1f01..43a4a2d0f 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -540,10 +540,13 @@ void Courtroom::set_widget_layers() } current_widget = widget_names[f_line]; bool was_visible = current_widget->isVisible(); + qDebug() << current_widget << " " << current_widget->isVisible(); current_widget->setParent(current_parent); current_widget->raise(); // Readjust visibility in case this changed after the widget changed parent - current_widget->setVisible(was_visible); + if (was_visible != current_widget->isVisible()) + current_widget->setVisible(was_visible); + qDebug() << current_widget << " " << current_widget->isVisible(); } layer_ini.close(); From f6fd1c38d1e21b128b141418d2e257482b6efd87 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 31 May 2020 14:16:08 -0400 Subject: [PATCH 033/842] Actually fix the panel issue, I think. Why. (Part 2) --- courtroom_widgets.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 43a4a2d0f..c178d9a6d 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -540,13 +540,14 @@ void Courtroom::set_widget_layers() } current_widget = widget_names[f_line]; bool was_visible = current_widget->isVisible(); - qDebug() << current_widget << " " << current_widget->isVisible(); current_widget->setParent(current_parent); current_widget->raise(); // Readjust visibility in case this changed after the widget changed parent + // I don't know why, I don't want to know why, I shouldn't + // have to wonder why, but for whatever reason these stupid + // panels aren't laying out correctly unless we do this terribleness if (was_visible != current_widget->isVisible()) current_widget->setVisible(was_visible); - qDebug() << current_widget << " " << current_widget->isVisible(); } layer_ini.close(); From a06f1c9920d066eb4f39b521991c5f2cf7d49f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Thu, 11 Jun 2020 00:47:27 +0200 Subject: [PATCH 034/842] IcLog rework to fix lag + extra changes (#4) The IC Log was reworked so that it reduces the amount of work that is performed to render messages. This massively helps improve performance, even to the point of cutting down a full second in accepting messages when the IC Log is large. A new `config.ini` parameter was also introduced: `chatlog_newline` (which defaults to False). If False, IC log messages will be rendered as `: `; if True, IC log messages will be rendered as ` ` (three lines). Finally, `scroll_type` in `config.ini` was changed to `chatlog_scrolldown`. `scroll_type = down` is equivalent to `chatlog_scrolldown = true` and `scroll_type = up` is equivalent to `chatlog_scrolldown = false`. Co-authored-by: Chrezm --- aoapplication.h | 5 + courtroom.cpp | 320 ++++++++++++++++++++-------------------- courtroom.h | 17 +-- datatypes.h | 20 ++- debug_functions.cpp | 7 +- main.cpp | 11 +- text_file_functions.cpp | 5 + 7 files changed, 199 insertions(+), 186 deletions(-) diff --git a/aoapplication.h b/aoapplication.h index 383d421ba..924ca7d17 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -121,6 +121,10 @@ class AOApplication : public QApplication //Returns the blip rate from config.ini int read_blip_rate(); + // returns whatever we want newlines or ':' to be appended in front of names + // in the ic chat log + bool read_chatlog_newline(); + //Returns true if blank blips is enabled in config.ini and false otherwise bool get_blank_blip(); @@ -136,6 +140,7 @@ class AOApplication : public QApplication //Returns the list of words in callwords.ini QStringList get_call_words(); + // TODO document what this does QStringList get_sfx_list(); //Appends the argument string to serverlist.txt diff --git a/courtroom.cpp b/courtroom.cpp index 1a2233e31..0695a74b0 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -26,11 +26,11 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() //initializing sound device BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, 0, NULL); - BASS_PluginLoad("bassopus.dll", BASS_UNICODE); + BASS_PluginLoad("bassopus.dll", BASS_UNICODE); create_widgets(); connect_widgets(); - + set_widgets(); set_char_select(); name_widgets(); @@ -89,14 +89,29 @@ void Courtroom::enter_courtroom(int p_cid) m_wtce_current = 0; reset_wtce_buttons(); - if(const int log_limit = ao_app->read_config("chatlog_limit").toInt()) - m_log_limit = log_limit; + // forward declaration for a possible update of the chatlog + bool chatlog_changed = false; + + int chatlog_limit = ao_app->read_config("chatlog_limit").toInt(); + // default chatlog_limit? + chatlog_limit = chatlog_limit <= 0 ? 200 : chatlog_limit; // TODO declare the default somewhere so it's not a magic number + if (chatlog_limit < m_chatlog_limit) // only update if we need to chop away records + chatlog_changed = true; + m_chatlog_limit = chatlog_limit; + + bool chatlog_scrolldown = ao_app->read_config("chatlog_scrolldown") == "true"; + if (m_chatlog_scrolldown != chatlog_scrolldown) + chatlog_changed = true; + m_chatlog_scrolldown = chatlog_scrolldown; - m_scroll_down = ao_app->read_config("scroll_type") == "down"; - if(m_previously_scroll_down != m_scroll_down) - m_scroll_type_changed = !m_scroll_type_changed; + bool chatlog_newline = ao_app->read_chatlog_newline(); + if (m_chatlog_newline != chatlog_newline) + chatlog_changed = true; + m_chatlog_newline = chatlog_newline; - m_previously_scroll_down = m_scroll_down; + // refresh the log if needed + if (chatlog_changed) + update_ic_log(chatlog_changed); set_evidence_page(); @@ -857,7 +872,8 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) if (is_system_speaking) append_system_text(m_chatmessage[MESSAGE]); - else append_ic_text(m_chatmessage[MESSAGE], f_showname); + else + append_ic_text(f_showname, m_chatmessage[MESSAGE], false); if(ao_app->read_config("enable_logging") == "true") save_textlog("[" + QTime::currentTime().toString() + "] " + f_showname + ": " + m_chatmessage[MESSAGE]); @@ -1125,181 +1141,157 @@ void Courtroom::handle_chatmessage_3() } -void Courtroom::append_ic_text(QString p_text, QString p_name) +void Courtroom::update_ic_log(bool p_reset_log) { - QTextCharFormat bold; - QTextCharFormat normal; - QString color = ao_app->read_theme_ini("chatlog_showname", fonts_ini); - bold.setFontWeight(QFont::Bold); - normal.setFontWeight(QFont::Normal); - const QTextCursor old_cursor = ui_ic_chatlog->textCursor(); - const int old_scrollbar_value = ui_ic_chatlog->verticalScrollBar()->value(); - const bool is_scrolled_up = old_scrollbar_value == ui_ic_chatlog->verticalScrollBar()->maximum(); - const QTextCursor::MoveOperation f_operation = m_scroll_down ? QTextCursor::End : QTextCursor::Start; - - //rewrite everything that was written before - if(m_scroll_type_changed) - { - ui_ic_chatlog->clear(); + { // resize if needed + int len = m_ic_records.length(); - for (record_type_ptr record : m_ic_records) - { - ui_ic_chatlog->moveCursor(f_operation); - if (record->system == false) - { - ui_ic_chatlog->insertHtml("color + "\">" + record->name + ""); - ui_ic_chatlog->textCursor().insertText("\n"); - ui_ic_chatlog->textCursor().insertText(record->line + "\n\n", normal); - } - else - { - ui_ic_chatlog->insertHtml("color + "\">" + record->line + ""); - ui_ic_chatlog->textCursor().insertText("\n\n"); - } + if (len > m_chatlog_limit) + m_ic_records = m_ic_records.mid(len - m_chatlog_limit); } - m_scroll_type_changed = false; - } - // new record - m_ic_records.append(std::make_shared(p_name, p_text, color, false)); - - int len = m_ic_records.length(); - - if (len > m_log_limit) // magic numbers, woo! - { - m_ic_records = m_ic_records.mid(len - m_log_limit); - } - - - if(m_scroll_down) - { - ui_ic_chatlog->moveCursor(QTextCursor::End); - ui_ic_chatlog->insertHtml("" + p_name + ""); - ui_ic_chatlog->textCursor().insertText("\n" + p_text + "\n\n", normal); - } - else - { - ui_ic_chatlog->clear(); + /* + * first, we figure out whatever we append the last message or if we reset + * the entire log + * */ + record_type_array records_to_add; + // populate + if (p_reset_log) + { + // we need the entire recordings + records_to_add = m_ic_records; - for (record_type_ptr record : m_ic_records) + // clear log + ui_ic_chatlog->clear(); + } + else { - ui_ic_chatlog->moveCursor(QTextCursor::Start); - if (record->system == false) - { - ui_ic_chatlog->insertHtml("color + "\">" + record->name + ""); - ui_ic_chatlog->textCursor().insertText("\n"); - ui_ic_chatlog->textCursor().insertText(record->line + "\n\n", normal); - } - else - { - ui_ic_chatlog->insertHtml("color + "\">" + record->line + ""); - ui_ic_chatlog->textCursor().insertText("\n\n"); - } + records_to_add.append(m_ic_records.last()); } - } - - if (old_cursor.hasSelection() || !is_scrolled_up) - { - // The user has selected text or scrolled away from the top: maintain position. - ui_ic_chatlog->setTextCursor(old_cursor); - ui_ic_chatlog->verticalScrollBar()->setValue(old_scrollbar_value); - } - else - { - // The user hasn't selected any text and the scrollbar is at the top: scroll to the top. - ui_ic_chatlog->moveCursor(f_operation); - ui_ic_chatlog->verticalScrollBar()->setValue(ui_ic_chatlog->verticalScrollBar()->maximum()); - } -} - -void Courtroom::append_system_text(QString p_text) -{ - if (chatmessage_is_empty) return; - QTextCharFormat bold; - QTextCharFormat normal; - QString color = ao_app->read_theme_ini("system_msg", fonts_ini); - bold.setFontWeight(QFont::Bold); - normal.setFontWeight(QFont::Normal); - const QTextCursor old_cursor = ui_ic_chatlog->textCursor(); - const int old_scrollbar_value = ui_ic_chatlog->verticalScrollBar()->value(); - const bool is_scrolled_up = old_scrollbar_value == ui_ic_chatlog->verticalScrollBar()->maximum(); - const QTextCursor::MoveOperation f_operation = m_scroll_down ? QTextCursor::End : QTextCursor::Start; + // prepare the formats we need + QTextCharFormat name_format = ui_ic_chatlog->currentCharFormat(); + name_format.setFontWeight(QFont::Bold); + name_format.setForeground(ao_app->get_color("chatlog_showname_color", fonts_ini)); - //rewrite everything that was written before - if(m_scroll_type_changed) - { - ui_ic_chatlog->clear(); + QTextCharFormat line_format = ui_ic_chatlog->currentCharFormat(); + line_format.setFontWeight(QFont::Normal); + line_format.setForeground(ao_app->get_color("chatlog_message_color", fonts_ini)); - for (record_type_ptr record : m_ic_records) - { - ui_ic_chatlog->moveCursor(f_operation); - if (record->system == false) - { - ui_ic_chatlog->insertHtml("color + "\">" + record->name + ""); - ui_ic_chatlog->textCursor().insertText("\n" + record->line + "\n\n", normal); + QTextCharFormat system_format = ui_ic_chatlog->currentCharFormat(); + system_format.setFontWeight(QFont::Normal); + system_format.setForeground(ao_app->get_color("system_msg", fonts_ini)); - } - else - { - ui_ic_chatlog->insertHtml("color + "\">" + record->line + ""); - ui_ic_chatlog->textCursor().insertText("\n\n"); - } - } - m_scroll_type_changed = false; - } + // need vscroll bar for cache + QScrollBar *vscrollbar = ui_ic_chatlog->verticalScrollBar(); - // new record - m_ic_records.append(std::make_shared("", p_text, color, true)); + // cache previous values + const QTextCursor prev_cursor = ui_ic_chatlog->textCursor(); + const int scroll_pos = vscrollbar->value(); + const bool is_scrolled = m_chatlog_scrolldown ? scroll_pos == vscrollbar->maximum() : scroll_pos == vscrollbar->minimum(); - int len = m_ic_records.length(); + // recover cursor + QTextCursor cursor = ui_ic_chatlog->textCursor(); + // figure out if we need to move up or down + const QTextCursor::MoveOperation move_type = m_chatlog_scrolldown ? QTextCursor::End : QTextCursor::Start; - if (len > m_log_limit) // magic numbers, woo! - { - m_ic_records = m_ic_records.mid(len - m_log_limit); - } + for (record_type_ptr record : records_to_add) + { + // move cursor + cursor.movePosition(move_type); - if(m_scroll_down) - { - ui_ic_chatlog->moveCursor(QTextCursor::End); - ui_ic_chatlog->insertHtml("" + p_text + ""); - ui_ic_chatlog->textCursor().insertText("\n\n"); - } - else - { - ui_ic_chatlog->clear(); + if (record->system) + { + cursor.insertText(record->line + QChar::LineFeed, system_format); + } + else + { + cursor.insertText(record->name + (m_chatlog_newline ? QString(QChar::LineFeed) : ": "), name_format); + cursor.insertText(record->line + QChar::LineFeed + (m_chatlog_newline ? QChar::LineFeed : QChar()), line_format); + } + } + // figure out the number of blocks we need overall + // this is always going to amount to at least the current length of records + int block_count = m_ic_records.length() + 1; // there's always one extra block + // to do that, we need to go through the records for (record_type_ptr record : m_ic_records) + if (!record->system) + if (m_chatlog_newline) + block_count += 2; // if newline is actived, it always inserts two extra newlines; therefor two paragraphs + + // there's always one extra block count, so deduce one from block_count + int blocks_to_delete = ui_ic_chatlog->document()->blockCount() - block_count; + + // the orientation at which we need to delete from + const QTextCursor::MoveOperation start_location = m_chatlog_scrolldown ? QTextCursor::Start : QTextCursor::End; + const QTextCursor::MoveOperation block_orientation = m_chatlog_scrolldown ? QTextCursor::NextBlock : QTextCursor::PreviousBlock; + + /* Blocks appear like this + * textQChar(0x2029) + * additionaltextQChar(0x2029) + * moretextQChar(0x2029) + * where QChar(0x2029) is the paragraph break block. + * Do note that the above example has FOUR blocks: text, additionaltext, moretext, and + * an empty block. That is because QTextCursor separates blocks by paragraph break block + * (which is why the above code has a -1) and does not consider this break character as + * part of the block (which is why we move Left in the loop, to 'be in the block'). + * Finally, BlockUnderCursor does NOT select the break character, so we deleteChar after + * removing the selection to remove the straggling newline. + * */ + + // move our cursor at the start + cursor.movePosition(start_location); + + // move the cursor around, depending on the orientation we need + for (int i = 0; i < blocks_to_delete; ++i) + cursor.movePosition(block_orientation, QTextCursor::KeepAnchor); + + // now that everything is selected, delete it + cursor.removeSelectedText(); + + /* + * However, if we do this, we also remove the last newline of the last block that remains, + * which will make it difficult to append new blocks to it/figure out the amount of blocks + * if we have a scroll up log, so we add it again if we removed any break characters at all + * */ + if (!m_chatlog_scrolldown && blocks_to_delete > 0) + cursor.insertBlock(); + + /* + * Unfortunately, the simplest alternative, that is, move cursor to the last block, remove + * the block under it and delete the last char does not work, as this also removes the last + * character of the block that remains. That's why we have to do this whole complicated + * process. + * */ + if (prev_cursor.hasSelection() || is_scrolled) { - ui_ic_chatlog->moveCursor(QTextCursor::Start); - if (record->system == false) - { - ui_ic_chatlog->insertHtml("color + "\">" + record->name + ""); - ui_ic_chatlog->textCursor().insertText("\n" + record->line + "\n\n", normal); - } - else - { - qDebug() << "color + ">" + record->line + ""; - ui_ic_chatlog->insertHtml("color + "\">" + record->line + ""); - ui_ic_chatlog->textCursor().insertText("\n\n"); - } + // restore previous selection and vscrollbar + ui_ic_chatlog->setTextCursor(prev_cursor); + vscrollbar->setValue(scroll_pos); } - } + // scroll up/down depending on context + else + { + ui_ic_chatlog->moveCursor(move_type); + vscrollbar->setValue(m_chatlog_scrolldown ? vscrollbar->maximum() : vscrollbar->minimum()); + } +} - ui_ic_chatlog->setTextColor("#000000"); +void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system) +{ + // record new entry + m_ic_records.append(std::make_shared(p_name, p_line, "", p_system)); - if (old_cursor.hasSelection() || !is_scrolled_up) - { - // The user has selected text or scrolled away from the top: maintain position. - ui_ic_chatlog->setTextCursor(old_cursor); - ui_ic_chatlog->verticalScrollBar()->setValue(old_scrollbar_value); - } - else - { - // The user hasn't selected any text and the scrollbar is at the top: scroll to the top. - ui_ic_chatlog->moveCursor(f_operation); - ui_ic_chatlog->verticalScrollBar()->setValue(ui_ic_chatlog->verticalScrollBar()->maximum()); - } + // update + update_ic_log(false); +} + +void Courtroom::append_system_text(QString p_line) +{ + if (chatmessage_is_empty) + return; + append_ic_text("", p_line, true); } void Courtroom::play_preanim() @@ -1718,7 +1710,7 @@ void Courtroom::handle_song(QStringList *p_contents) } else { - append_ic_text("has played a song: " + f_song, str_char); + append_ic_text(str_char, "has played a song: " + f_song, false); m_music_player->play(f_song); } } diff --git a/courtroom.h b/courtroom.h index 46a9eb78a..74ae81620 100644 --- a/courtroom.h +++ b/courtroom.h @@ -170,9 +170,9 @@ class Courtroom : public QMainWindow //adds text to the IC chatlog. p_name first as bold then p_text then a newlin //this function keeps the chatlog scrolled to the top unless there's text selected // or the user isn't already scrolled to the top - void append_ic_text(QString p_text, QString p_name = ""); - - void append_system_text(QString p_text); + void update_ic_log(bool p_reset_log); + void append_ic_text(QString p_name, QString p_line, bool p_system); + void append_system_text(QString p_line); //prints who played the song to IC chat and plays said song(if found on local filesystem) //takes in a list where the first element is the song name and the second is the char id of who played it @@ -356,12 +356,11 @@ class Courtroom : public QMainWindow int emote_rows = 2; int max_emotes_on_page = 10; - int m_log_limit = 200; - bool m_scroll_down = false; - bool m_previously_scroll_down = false; - bool m_scroll_type_changed = false; + int m_chatlog_limit = 200; + bool m_chatlog_newline = false; + bool m_chatlog_scrolldown = false; -// int note_amount = 0; +// inmchatlog_changed; QVector local_evidence_list; @@ -428,7 +427,7 @@ class Courtroom : public QMainWindow QVector ui_timers; QTextEdit* ui_ic_chatlog = nullptr; - QVector m_ic_records; + record_type_array m_ic_records; AOTextArea *ui_ms_chatlog; AOTextArea *ui_server_chatlog; diff --git a/datatypes.h b/datatypes.h index 13c05cb86..ea552c3aa 100644 --- a/datatypes.h +++ b/datatypes.h @@ -6,19 +6,23 @@ struct record_type { - record_type() = default; - record_type(QString p_name, QString p_line, QString p_color, bool p_system) - : name(p_name), line(p_line), color(p_color), system(p_system) - {} + QString name; + QString line; + QString color; + bool system = false; - QString name; - QString line; - QString color; - bool system; + record_type() = default; + record_type(QString p_name, QString p_line, QString p_color, bool p_is_system) + : name(p_name), line(p_line), system(p_is_system) + { + Q_UNUSED(p_color); + } }; typedef std::shared_ptr record_type_ptr; +typedef QVector record_type_array; + struct server_type { QString name; diff --git a/debug_functions.cpp b/debug_functions.cpp index 0ff1ccd75..f1dc345c4 100644 --- a/debug_functions.cpp +++ b/debug_functions.cpp @@ -1,13 +1,15 @@ -#include - #include "debug_functions.h" +#include +#include + void call_error(QString p_message) { QMessageBox *f_box = new QMessageBox; f_box->setText("Error: " + p_message); f_box->setWindowTitle("Error"); + qDebug() << f_box->text(); //msgBox->setWindowModality(Qt::NonModal); f_box->exec(); @@ -20,6 +22,7 @@ void call_notice(QString p_message) f_box->setText(p_message); f_box->setWindowTitle("Notice"); + qDebug() << f_box->text(); //msgBox->setWindowModality(Qt::NonModal); f_box->exec(); diff --git a/main.cpp b/main.cpp index 46a870339..ca44af6e5 100644 --- a/main.cpp +++ b/main.cpp @@ -4,6 +4,8 @@ #include "networkmanager.h" #include "lobby.h" #include "courtroom.h" +#include "debug_functions.h" + #include #include #include @@ -17,15 +19,18 @@ int main(int argc, char *argv[]) AOApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif + AOApplication main_app(argc, argv); + QPluginLoader apng("imageformats/qapng.dll"); if (!apng.load()) { - qDebug() << apng.errorString(); +#ifdef QT_NO_DEBUG + call_error(QString("APNG plugin has encountered an error: %s").arg(apng.errorString())); +#endif } - AOApplication main_app(argc, argv); main_app.construct_lobby(); -#ifndef INDEV +#ifdef QT_NO_DEBUG main_app.net_manager->connect_to_master(); #endif main_app.w_lobby->show(); diff --git a/text_file_functions.cpp b/text_file_functions.cpp index efb685ebf..04d5cd152 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -63,6 +63,11 @@ int AOApplication::read_blip_rate() return result.toInt(); } +bool AOApplication::read_chatlog_newline() +{ + return read_config("chatlog_newline") == "true"; +} + int AOApplication::get_default_music() { QString f_result = read_config("default_music"); From aed179707c3a420de7bf3a41951896cd337ef935 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Thu, 11 Jun 2020 20:22:38 -0400 Subject: [PATCH 035/842] Fix memory leaks and other miscellaneous (#7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixing AOMovie memory leak * set_widget_names A few improvements. * Updated layering and blocks Fixed a possible bug where blocks would cause a crash when setting them again after reloading the theme * Properly clean up widgets. * Remove leftover destructor code (#6) Remove leftover cdestructors and other miscellaneous things Co-authored-by: Leifa♥ <26681464+TrickyLeifa@users.noreply.github.com> * Remove deprecated widget code Co-authored-by: Leifa♥ Co-authored-by: Leifa♥ <26681464+TrickyLeifa@users.noreply.github.com> --- aobutton.cpp | 5 - aobutton.h | 5 +- aocharbutton.h | 1 - aoemotebutton.h | 2 - aoevidencebutton.cpp | 1 - aoimage.cpp | 5 - aoimage.h | 4 +- aomovie.cpp | 5 + aomovie.h | 1 + aonotearea.cpp | 4 - aonotearea.hpp | 2 - aonotepicker.cpp | 3 - aonotepicker.hpp | 1 - aopacket.cpp | 5 - aopacket.h | 1 - courtroom.cpp | 14 +- courtroom.h | 19 +- courtroom_widgets.cpp | 457 ++++++++++++++++++++++-------------------- 18 files changed, 268 insertions(+), 267 deletions(-) diff --git a/aobutton.cpp b/aobutton.cpp index b2c07ad9a..61dddf70c 100644 --- a/aobutton.cpp +++ b/aobutton.cpp @@ -10,11 +10,6 @@ AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) : QPushButton(paren ao_app = p_ao_app; } -AOButton::~AOButton() -{ - -} - void AOButton::set_image(QString p_image) { QString f_image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); diff --git a/aobutton.h b/aobutton.h index c96938310..894e23447 100644 --- a/aobutton.h +++ b/aobutton.h @@ -11,12 +11,9 @@ class AOButton : public QPushButton public: AOButton(QWidget *parent, AOApplication *p_ao_app); - ~AOButton(); - - AOApplication *ao_app = nullptr; - void set_image(QString p_image); + AOApplication *ao_app = nullptr; QString image_path = ""; }; diff --git a/aocharbutton.h b/aocharbutton.h index a7071cb7d..9eb9a16d7 100644 --- a/aocharbutton.h +++ b/aocharbutton.h @@ -14,7 +14,6 @@ class AOCharButton : public QPushButton public: AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos); - AOApplication *ao_app = nullptr; void reset(); diff --git a/aoemotebutton.h b/aoemotebutton.h index edc528c08..5ea435f48 100644 --- a/aoemotebutton.h +++ b/aoemotebutton.h @@ -12,8 +12,6 @@ class AOEmoteButton : public QPushButton public: AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y); - //void set_on(QString p_char, int p_emote); - //void set_off(QString p_char, int p_emote); void set_image(QString p_char, int p_emote, QString suffix); void set_id(int p_id) {m_id = p_id;} diff --git a/aoevidencebutton.cpp b/aoevidencebutton.cpp index f3bc6b407..5767395f8 100644 --- a/aoevidencebutton.cpp +++ b/aoevidencebutton.cpp @@ -28,7 +28,6 @@ AOEvidenceButton::AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, i connect(this, SIGNAL(clicked()), this, SLOT(on_clicked())); } - void AOEvidenceButton::reset() { this->hide(); diff --git a/aoimage.cpp b/aoimage.cpp index c441a5a5e..5c741db99 100644 --- a/aoimage.cpp +++ b/aoimage.cpp @@ -9,11 +9,6 @@ AOImage::AOImage(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) ao_app = p_ao_app; } -AOImage::~AOImage() -{ - -} - void AOImage::set_image(QString p_image) { QString f_path = ao_app->get_image_path(p_image); diff --git a/aoimage.h b/aoimage.h index d423d55d3..918fea495 100644 --- a/aoimage.h +++ b/aoimage.h @@ -11,15 +11,13 @@ class AOImage : public QLabel { public: AOImage(QWidget *parent, AOApplication *p_ao_app); - ~AOImage(); - - AOApplication *ao_app = nullptr; void set_image(QString p_image); void set_image_variant(QString p_image, QString p_variant); void set_image_from_path(QString p_path); void set_size_and_pos(QString identifier); + AOApplication *ao_app = nullptr; QString image_path = ""; }; diff --git a/aomovie.cpp b/aomovie.cpp index 196a0a37e..2c1fecc84 100644 --- a/aomovie.cpp +++ b/aomovie.cpp @@ -15,6 +15,11 @@ AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); } +AOMovie::~AOMovie() +{ + delete m_movie; +} + void AOMovie::set_play_once(bool p_play_once) { play_once = p_play_once; diff --git a/aomovie.h b/aomovie.h index e116b6837..c890783ce 100644 --- a/aomovie.h +++ b/aomovie.h @@ -13,6 +13,7 @@ class AOMovie : public QLabel public: AOMovie(QWidget *p_parent, AOApplication *p_ao_app); + ~AOMovie(); void set_play_once(bool p_play_once); void play(QString p_file, QString p_char = "", QString p_custom_theme = ""); diff --git a/aonotearea.cpp b/aonotearea.cpp index 884a26a69..4480efe6e 100644 --- a/aonotearea.cpp +++ b/aonotearea.cpp @@ -10,10 +10,6 @@ AONoteArea::AONoteArea(QWidget *p_parent, AOApplication *p_ao_app) : AOImage(p_p ao_app = p_ao_app; } -AONoteArea::~AONoteArea() -{ -} - void Courtroom::on_add_button_clicked() { if(ui_note_area->m_layout->count() > 6) diff --git a/aonotearea.hpp b/aonotearea.hpp index 40a72c4e8..4b1120e76 100644 --- a/aonotearea.hpp +++ b/aonotearea.hpp @@ -18,11 +18,9 @@ class AONoteArea : public AOImage public: AONoteArea(QWidget *p_parent, AOApplication *p_ao_app); - ~AONoteArea(); AOButton *add_button; QVBoxLayout *m_layout; - AOButton *f_button; private: AOApplication *ao_app; diff --git a/aonotepicker.cpp b/aonotepicker.cpp index ba3585afe..5e0103072 100644 --- a/aonotepicker.cpp +++ b/aonotepicker.cpp @@ -10,9 +10,6 @@ AONotePicker::AONotePicker(QWidget *p_parent, AOApplication *p_ao_app) : QLabel( ao_app = p_ao_app; } -AONotePicker::~AONotePicker() -{} - void Courtroom::on_file_selected() { for(int i=0; i < ui_note_area->m_layout->count() -1; ++i) diff --git a/aonotepicker.hpp b/aonotepicker.hpp index cfc8881b4..c9f0592c6 100644 --- a/aonotepicker.hpp +++ b/aonotepicker.hpp @@ -12,7 +12,6 @@ class AONotePicker : public QLabel public: AONotePicker(QWidget *p_parent, AOApplication *p_ao_app); - ~AONotePicker(); QLineEdit *m_line; AOButton *m_button; diff --git a/aopacket.cpp b/aopacket.cpp index fa8f5be11..73036f58b 100644 --- a/aopacket.cpp +++ b/aopacket.cpp @@ -22,11 +22,6 @@ AOPacket::AOPacket(QString p_header, QStringList &p_contents) m_contents = p_contents; } -AOPacket::~AOPacket() -{ - -} - QString AOPacket::to_string() { QString f_string = m_header; diff --git a/aopacket.h b/aopacket.h index 40dd3ec38..6e328ac86 100644 --- a/aopacket.h +++ b/aopacket.h @@ -9,7 +9,6 @@ class AOPacket public: AOPacket(QString p_packet_string); AOPacket(QString header, QStringList &p_contents); - ~AOPacket(); QString get_header() {return m_header;} QStringList &get_contents() {return m_contents;} diff --git a/courtroom.cpp b/courtroom.cpp index 0695a74b0..b3dcffc89 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -33,7 +33,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() set_widgets(); set_char_select(); - name_widgets(); + set_widget_names(); } void Courtroom::enter_courtroom(int p_cid) @@ -193,7 +193,7 @@ void Courtroom::enter_courtroom(int p_cid) ui_ic_chat_message->setEnabled(m_cid != -1); ui_ic_chat_message->setFocus(); - name_widgets(); + set_widget_names(); set_widget_layers(); } @@ -1143,12 +1143,10 @@ void Courtroom::handle_chatmessage_3() void Courtroom::update_ic_log(bool p_reset_log) { - { // resize if needed - int len = m_ic_records.length(); - - if (len > m_chatlog_limit) - m_ic_records = m_ic_records.mid(len - m_chatlog_limit); - } + // resize if needed + int len = m_ic_records.length(); + if (len > m_chatlog_limit) + m_ic_records = m_ic_records.mid(len - m_chatlog_limit); /* * first, we figure out whatever we append the last message or if we reset diff --git a/courtroom.h b/courtroom.h index 74ae81620..435d1e9d1 100644 --- a/courtroom.h +++ b/courtroom.h @@ -598,7 +598,12 @@ class Courtroom : public QMainWindow void create_widgets(); void connect_widgets(); - void name_widgets(); + void set_widget_names(); + void reset_widget_names(); + void insert_widget_name(QString p_widget_name, QWidget *p_widget); + void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); + template + void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); void set_widget_layers(); void construct_char_select(); @@ -683,6 +688,7 @@ private slots: void on_set_file_button_clicked(); void on_file_selected(); + void delete_widget(QWidget *p_widget); void load_shouts(); void load_effects(); void load_wtce(); @@ -759,4 +765,15 @@ private slots: void ping_server(); }; +template +void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) +{ + QVector widgets; + + for (QWidget *widget : p_widgets) + widgets.append(widget); + + insert_widget_names(p_widget_names, widgets); +} + #endif // COURTROOM_H diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index c178d9a6d..792c274e9 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -358,202 +358,225 @@ void Courtroom::connect_widgets() connect(ui_set_notes, SIGNAL(clicked(bool)), this, SLOT(on_set_notes_clicked())); } -void Courtroom::name_widgets() +void Courtroom::reset_widget_names() { - // Assign names to the default widgets - widget_names = { - {"viewport", ui_viewport}, - {"background", ui_vp_background}, //* - {"speedlines", ui_vp_speedlines}, //* - {"player_char", ui_vp_player_char}, //* - {"desk", ui_vp_desk}, //* - {"music_display_a", ui_vp_music_display_a}, - {"music_display_b", ui_vp_music_display_b}, - {"music_area", ui_vp_music_area}, - {"music_name", ui_vp_music_name}, - // music_anim - {"clock", ui_vp_clock}, - // ui_vp_evidence_display - {"ao2_chatbox", ui_vp_chatbox}, - {"showname", ui_vp_showname}, - {"message", ui_vp_message}, - {"showname_image", ui_vp_showname_image}, - {"vp_testimony", ui_vp_testimony}, - {"vp_effect", ui_vp_effect}, - {"vp_wtce", ui_vp_wtce}, - {"vp_objection", ui_vp_objection}, - {"ic_chatlog", ui_ic_chatlog}, - {"ms_chatlog", ui_ms_chatlog}, - {"server_chatlog", ui_server_chatlog}, - {"mute_list", ui_mute_list}, - {"area_list", ui_area_list}, - {"music_list", ui_music_list}, - {"sfx_list", ui_sfx_list}, - {"ao2_ic_chat_message", ui_ic_chat_message}, - // ui_muted - {"ooc_chat_message", ui_ooc_chat_message}, - {"ooc_chat_name", ui_ooc_chat_name}, - {"music_search", ui_music_search}, - {"sfx_search", ui_sfx_search}, - {"note_scroll_area", note_scroll_area}, - {"note_area", ui_note_area}, - // add_button - // m_layout - {"set_notes_button", ui_set_notes}, - {"emotes", ui_emotes}, - {"emote_left", ui_emote_left}, - {"emote_right", ui_emote_right}, - {"emote_dropdown", ui_emote_dropdown}, - {"pos_dropdown", ui_pos_dropdown}, - {"defense_bar", ui_defense_bar}, - {"prosecution_bar", ui_prosecution_bar}, - {"music_label", ui_music_label}, - {"sfx_label", ui_sfx_label}, - {"blip_label", ui_blip_label}, - // Each ui_shouts[i] - {"shout_up", ui_shout_up}, - {"shout_down", ui_shout_down}, - // Each ui_effects[i] - {"effect_down", ui_effect_down}, - {"effect_up", ui_effect_up}, - // Each ui_wtce[i] - {"wtce_up", ui_wtce_up}, - {"wtce_down", ui_wtce_down}, - {"ooc_toggle", ui_ooc_toggle}, - {"change_character", ui_change_character}, - {"reload_theme", ui_reload_theme}, - {"call_mod", ui_call_mod}, - {"switch_area_music", ui_switch_area_music}, - {"theme_list", ui_theme_list}, - {"confirm_theme", ui_confirm_theme}, - {"note_button", ui_note_button}, - // Each ui_label_images[i] - {"pre", ui_pre}, - {"flip", ui_flip}, - {"guard", ui_guard}, - {"hidden", ui_hidden}, - {"mute_button", ui_mute}, - {"defense_plus", ui_defense_plus}, - {"defense_minus", ui_defense_minus}, - {"prosecution_plus", ui_prosecution_plus}, - {"prosecution_minus", ui_prosecution_minus}, - {"text_color", ui_text_color}, - {"music_slider", ui_music_slider}, - {"sfx_slider", ui_sfx_slider}, - {"blip_slider", ui_blip_slider}, - {"evidence_button", ui_evidence_button}, - {"notepad_image", ui_vp_notepad_image}, - {"notepad", ui_vp_notepad}, - // Each ui_timers[i] - {"evidence_background", ui_evidence}, - {"evidence_buttons", ui_evidence_buttons}, - {"char_select", ui_char_select_background}, - {"back_to_lobby", ui_back_to_lobby}, - {"char_password", ui_char_password}, - {"char_buttons", ui_char_buttons}, - {"char_select_left", ui_char_select_left}, - {"char_select_right", ui_char_select_right}, - {"spectator", ui_spectator}, + // Assign names to the default widgets + widget_names = { + {"courtroom", this}, + {"viewport", ui_viewport}, + {"background", ui_vp_background}, //* + {"speedlines", ui_vp_speedlines}, //* + {"player_char", ui_vp_player_char}, //* + {"desk", ui_vp_desk}, //* + {"music_display_a", ui_vp_music_display_a}, + {"music_display_b", ui_vp_music_display_b}, + {"music_area", ui_vp_music_area}, + {"music_name", ui_vp_music_name}, + // music_anim + {"clock", ui_vp_clock}, + // ui_vp_evidence_display + {"ao2_chatbox", ui_vp_chatbox}, + {"showname", ui_vp_showname}, + {"message", ui_vp_message}, + {"showname_image", ui_vp_showname_image}, + {"vp_testimony", ui_vp_testimony}, + {"vp_effect", ui_vp_effect}, + {"vp_wtce", ui_vp_wtce}, + {"vp_objection", ui_vp_objection}, + {"ic_chatlog", ui_ic_chatlog}, + {"ms_chatlog", ui_ms_chatlog}, + {"server_chatlog", ui_server_chatlog}, + {"mute_list", ui_mute_list}, + {"area_list", ui_area_list}, + {"music_list", ui_music_list}, + {"sfx_list", ui_sfx_list}, + {"ao2_ic_chat_message", ui_ic_chat_message}, + // ui_muted + {"ooc_chat_message", ui_ooc_chat_message}, + {"ooc_chat_name", ui_ooc_chat_name}, + {"music_search", ui_music_search}, + {"sfx_search", ui_sfx_search}, + {"note_scroll_area", note_scroll_area}, + {"note_area", ui_note_area}, + // add_button + // m_layout + {"set_notes_button", ui_set_notes}, + {"emotes", ui_emotes}, + {"emote_left", ui_emote_left}, + {"emote_right", ui_emote_right}, + {"emote_dropdown", ui_emote_dropdown}, + {"pos_dropdown", ui_pos_dropdown}, + {"defense_bar", ui_defense_bar}, + {"prosecution_bar", ui_prosecution_bar}, + {"music_label", ui_music_label}, + {"sfx_label", ui_sfx_label}, + {"blip_label", ui_blip_label}, + // Each ui_shouts[i] + {"shout_up", ui_shout_up}, + {"shout_down", ui_shout_down}, + // Each ui_effects[i] + {"effect_down", ui_effect_down}, + {"effect_up", ui_effect_up}, + // Each ui_wtce[i] + {"wtce_up", ui_wtce_up}, + {"wtce_down", ui_wtce_down}, + {"ooc_toggle", ui_ooc_toggle}, + {"change_character", ui_change_character}, + {"reload_theme", ui_reload_theme}, + {"call_mod", ui_call_mod}, + {"switch_area_music", ui_switch_area_music}, + {"theme_list", ui_theme_list}, + {"confirm_theme", ui_confirm_theme}, + {"note_button", ui_note_button}, + // Each ui_label_images[i] + {"pre", ui_pre}, + {"flip", ui_flip}, + {"guard", ui_guard}, + {"hidden", ui_hidden}, + {"mute_button", ui_mute}, + {"defense_plus", ui_defense_plus}, + {"defense_minus", ui_defense_minus}, + {"prosecution_plus", ui_prosecution_plus}, + {"prosecution_minus", ui_prosecution_minus}, + {"text_color", ui_text_color}, + {"music_slider", ui_music_slider}, + {"sfx_slider", ui_sfx_slider}, + {"blip_slider", ui_blip_slider}, + {"evidence_button", ui_evidence_button}, + {"notepad_image", ui_vp_notepad_image}, + {"notepad", ui_vp_notepad}, + // Each ui_timers[i] + {"evidence_background", ui_evidence}, + {"evidence_buttons", ui_evidence_buttons}, + {"char_select", ui_char_select_background}, + {"back_to_lobby", ui_back_to_lobby}, + {"char_password", ui_char_password}, + {"char_buttons", ui_char_buttons}, + {"char_select_left", ui_char_select_left}, + {"char_select_right", ui_char_select_right}, + {"spectator", ui_spectator}, }; +} - QHash::iterator i; - for (i = widget_names.begin(); i != widget_names.end(); ++i) - { - QString name = i.key(); - QWidget* widget = i.value(); - widget->setObjectName(name); - } - // Why - for (int i=0; isetObjectName(name); - } - for (int i=0; isetObjectName(name); - } - for (int i=0; isetObjectName(name); - } - for (int i=0; isetObjectName(name); - } - for (int i=0; isetObjectName(name); - } +void Courtroom::insert_widget_name(QString p_widget_name, QWidget *p_widget) +{ + // insert entry + widget_names[p_widget_name] = p_widget; + // set name + p_widget->setObjectName(p_widget_name); } -void Courtroom::set_widget_layers() + +void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) { - QStringList paths{ - ao_app->get_theme_variant_path() + "courtroom_layers.ini", - ao_app->get_theme_path() + "courtroom_layers.ini", - ao_app->get_default_theme_path() + "courtroom_layers.ini", - }; + for (int i = 0; i < p_widgets.length(); ++i) + insert_widget_name(p_widget_names[i], p_widgets[i]); +} - QWidget* current_parent = this; - QString current_widget_name; - QWidget* current_widget; +void Courtroom::set_widget_names() +{ + // Assign names to the default widgets + reset_widget_names(); + + // set existing widget names + for (QString widget_name : widget_names.keys()) + widget_names[widget_name]->setObjectName(widget_name); + + // setup table of widgets and names + insert_widget_names(effect_names, ui_effects); + insert_widget_names(free_block_names, ui_free_blocks); + insert_widget_names(shout_names, ui_shouts); + insert_widget_names(wtce_names, ui_wtce); + + // timers are special children + QVector timer_names; + for (int i = 0; i < ui_timers.length(); ++i) + timer_names.append("timer_" + QString::number(i)); + insert_widget_names(timer_names, ui_timers); +} - for (QString path: paths) - { - QFile layer_ini; - layer_ini.setFileName(path); - if (!layer_ini.open(QIODevice::ReadOnly)) - continue; - QTextStream in(&layer_ini); +void Courtroom::set_widget_layers() +{ + QStringList paths{ + ao_app->get_theme_variant_path() + "courtroom_layers.ini", + ao_app->get_theme_path() + "courtroom_layers.ini", + ao_app->get_default_theme_path() + "courtroom_layers.ini", + }; - while (!in.atEnd()) + // needed to avoid cyclic parenting + QStringList recorded_widgets; + + // read the entire thing + for (QString path : paths) { - QString f_line = in.readLine().trimmed(); - // Lines are either empty, indicate the end or the start of a frame - // or list the items in a frame. We consider each case in order. - if (f_line == "") - continue; - // This particular check is not needed, but is added to keep compatibility - // with the other ini format. - if (f_line.startsWith("[\\")) - continue; - if (f_line.startsWith("[")) - { - current_widget_name = f_line.remove(0, 1).chopped(1); - current_parent = widget_names[current_widget_name]; - continue; - } - // If the item does not exist, do nothing; also prevent crashes - if (!widget_names.contains(f_line)) - { - continue; - } - current_widget = widget_names[f_line]; - bool was_visible = current_widget->isVisible(); - current_widget->setParent(current_parent); - current_widget->raise(); - // Readjust visibility in case this changed after the widget changed parent - // I don't know why, I don't want to know why, I shouldn't - // have to wonder why, but for whatever reason these stupid - // panels aren't laying out correctly unless we do this terribleness - if (was_visible != current_widget->isVisible()) - current_widget->setVisible(was_visible); + QFile layer_ini(path); + + if (layer_ini.open(QFile::ReadOnly)) + { + QTextStream in(&layer_ini); + + // current parent's name + QString parent_name = "courtroom"; + // the courtroom is ALWAYS going to be recorded + recorded_widgets.append(parent_name); + + while (!in.atEnd()) + { + QString line = in.readLine().trimmed(); + + // skip if line is empty + if (line.isEmpty()) + continue; + + // revert to default parent if we encounter an end scope + if (line.startsWith("[\\")) + { + parent_name = "courtroom"; + } + // is this a parent? + else if (line.startsWith("[")) + { + // update the current parent + parent_name = line.remove(0, 1).chopped(1).trimmed(); + } + // if this is not a parent, it's a child + else + { + // if the child is already known, skip + if (recorded_widgets.contains(line)) + continue; + // make the child known + recorded_widgets.append(line); + + // attach the children to the parents' + QWidget *child = widget_names[line]; + // if child is null, then it does not exist + if (!child) + continue; + + QWidget *parent = widget_names[parent_name]; + // if parent is null, attach main parent + if (!parent) + parent = widget_names["courtroom"]; + + // set child to parent + bool was_visible = child->isVisible(); + child->setParent(parent); + child->raise(); + + // Readjust visibility in case this changed after the widget changed parent + // I don't know why, I don't want to know why, I shouldn't + // have to wonder why, but for whatever reason these stupid + // panels aren't laying out correctly unless we do this terribleness + if (child->isVisible() != was_visible) + child->setVisible(was_visible); + } + } + + // break the loop, we have found a proper file + break; + } } - - layer_ini.close(); - return; - } - return; } void Courtroom::set_widgets() @@ -1218,20 +1241,31 @@ void Courtroom::check_wtce() } } +void Courtroom::delete_widget(QWidget *p_widget) +{ + // remove the widget from recorded names + widget_names.remove(p_widget->objectName()); + + // transfer the children to our grandparent since our parent is + // about to commit suicide + QWidget *grand_parent = p_widget->parentWidget(); + // if we don't have a grand parent, attach ourselves to courtroom + if (!grand_parent) + grand_parent = this; + + // set new parent + for (QWidget *child : p_widget->findChildren(QString(), Qt::FindDirectChildrenOnly)) + child->setParent(grand_parent); + + // delete widget + delete p_widget; +} + void Courtroom::load_effects() { // Close any existing effects to prevent memory leaks - for (int i=0; iobjectName(); - widget_names.remove(name); - // This index exists as ui_effects[i] can only exist if it was added by a previous - // call of load_effects(). However, this code later adds the name of all shouts. - // As this is the only place that changes the size of ui_effects and it originally - // starts empty, this code is correct.. - ui_effects[i]->close(); - delete ui_effects[i]; - } + for (QWidget *widget : ui_effects) + delete_widget(widget); // And create new effects int effect_number = ao_app->get_design_ini_value("effect_number", cc_config_ini); @@ -1265,15 +1299,8 @@ void Courtroom::load_effects() void Courtroom::load_free_blocks() { - // Close any existing free blocks to prevent memory leaks - for (int i=0; iobjectName(); - widget_names.remove(name); - // Same logic for why this index exists as in ui_effects() - ui_free_blocks[i]->close(); - delete ui_free_blocks[i]; - } + for (QWidget *widget : ui_free_blocks) + delete_widget(widget); // And create new free block buttons int free_block_number = ao_app->get_design_ini_value("free_block_number", cc_config_ini); @@ -1305,14 +1332,8 @@ void Courtroom::load_free_blocks() void Courtroom::load_shouts() { - // Close any existing shouts to prevent memory leaks - for (int i=0; iobjectName(); - widget_names.remove(name); - ui_shouts[i]->close(); - delete ui_shouts[i]; - } + for (QWidget *widget : ui_shouts) + delete_widget(widget); // And create new shouts int shout_number = ao_app->get_design_ini_value("shout_number", cc_config_ini); @@ -1349,14 +1370,8 @@ void Courtroom::load_shouts() void Courtroom::load_wtce() { - // Close any existing wtce buttons to prevent memory leaks - for (int i=0; iobjectName(); - widget_names.remove(name); - ui_wtce[i]->close(); - delete ui_wtce[i]; - } + for (QWidget *widget : ui_wtce) + delete_widget(widget); // And create new wtce buttons int wtce_number = ao_app->get_design_ini_value("wtce_number", cc_config_ini); From 614c9368b2f9b66dbcc206ec99e301de23bb2989 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 9 Jul 2020 21:34:05 -0400 Subject: [PATCH 036/842] Fix debug OOC commands+Fix crash if there were more shouts than effects --- courtroom.cpp | 10 +++++----- courtroom_widgets.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index b3dcffc89..5cfff34aa 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1816,11 +1816,11 @@ void Courtroom::on_ooc_return_pressed() ui_ooc_chat_message->clear(); return; } - else if (ooc_message.startsWith("/roll")) + else if (ooc_message.startsWith("/rollp")) { m_sfx_player->play(ao_app->get_sfx("dice")); } - else if (ooc_message.startsWith("/rollp")) + else if (ooc_message.startsWith("/roll")) { m_sfx_player->play(ao_app->get_sfx("dice")); } @@ -1840,7 +1840,7 @@ void Courtroom::on_ooc_return_pressed() handle_theme_variant(variant); } - else if (ooc_message.startsWith("/tr")) + else if (ooc_message.startsWith("/tr ")) { // Timer resume int space_location = ooc_message.indexOf(" "); @@ -1852,7 +1852,7 @@ void Courtroom::on_ooc_return_pressed() timer_id = ooc_message.mid(space_location+1).toInt(); resume_timer(timer_id); } - else if (ooc_message.startsWith("/ts")) + else if (ooc_message.startsWith("/ts ")) { // Timer set QStringList arguments = ooc_message.split(" "); @@ -1870,7 +1870,7 @@ void Courtroom::on_ooc_return_pressed() set_timer_timestep(timer_id, timestep_length); set_timer_firing(timer_id, firing_interval); } - else if (ooc_message.startsWith("/tp")) + else if (ooc_message.startsWith("/tp ")) { // Timer pause int space_location = ooc_message.indexOf(" "); diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 792c274e9..459be63b6 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -1134,7 +1134,7 @@ void Courtroom::check_effects() QString char_path = ao_app->get_character_path(current_char); QString theme_variant_path = ao_app->get_theme_variant_path(); QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < ui_shouts.size(); ++i) + for(int i = 0; i < ui_effects.size(); ++i) { QStringList paths{ char_path + effect_names.at(i) + ".gif", From f64ae87e58c32c62806246ef94ecbbf081dc2b95 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Sat, 11 Jul 2020 09:23:50 -0400 Subject: [PATCH 037/842] Chat warnings (#8) * WIP warning for empty ooc username * Add warning for empty OOC message+Rephrase empty OOC name message * Make inputbox reappear only if the user hit ok and tried to use an empty username --- courtroom.cpp | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 5cfff34aa..2a3cc03a7 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -19,6 +19,7 @@ #include #include #include +#include Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { @@ -1773,10 +1774,32 @@ void Courtroom::mod_called(QString p_ip) void Courtroom::on_ooc_return_pressed() { + QString ooc_name = ui_ooc_chat_name->text(); QString ooc_message = ui_ooc_chat_message->text(); - if (ooc_message == "" || ui_ooc_chat_name->text() == "") + if (ooc_message.isEmpty()) + { + append_server_chatmessage( + "CLIENT", "You cannot send an empty message."); return; + } + if (ooc_name.isEmpty()) + { + bool ok; + QString name; + do + { + ooc_name = QInputDialog::getText(this, + "Enter a name", + "You must have a name to talk in OOC chat. Enter a name: ", + QLineEdit::Normal, + "user", &ok); + } while (ok && ooc_name.isEmpty()); + if (!ok) + return; + + ui_ooc_chat_name->setText(ooc_name); + } if (ooc_message.startsWith("/pos")) { @@ -1883,7 +1906,7 @@ void Courtroom::on_ooc_return_pressed() pause_timer(timer_id); } QStringList packet_contents; - packet_contents.append(ui_ooc_chat_name->text()); + packet_contents.append(ooc_name); packet_contents.append(ooc_message); AOPacket *f_packet = new AOPacket("CT", packet_contents); From a3a0cccfc98b922a82d1a29f3468a86f5f724ad5 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 11 Jul 2020 09:33:16 -0400 Subject: [PATCH 038/842] Make IC scrollbar adjust only if chatbox shows latest message --- courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courtroom.cpp b/courtroom.cpp index 2a3cc03a7..d1255f019 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1263,7 +1263,7 @@ void Courtroom::update_ic_log(bool p_reset_log) * character of the block that remains. That's why we have to do this whole complicated * process. * */ - if (prev_cursor.hasSelection() || is_scrolled) + if (prev_cursor.hasSelection() || !is_scrolled) { // restore previous selection and vscrollbar ui_ic_chatlog->setTextCursor(prev_cursor); From 491a0ff3111ea653dc114c87d63ee86cf1973f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Mon, 20 Jul 2020 22:24:48 +0200 Subject: [PATCH 039/842] AOScene changes. Hopefully they work. --- aoscene.cpp | 70 ++++++++++++++++++++--------------------------------- aoscene.h | 13 +++++----- 2 files changed, 32 insertions(+), 51 deletions(-) diff --git a/aoscene.cpp b/aoscene.cpp index 6f25e0fe1..db7b5ab49 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -1,64 +1,46 @@ #include "aoscene.h" - +// src #include "courtroom.h" - #include "file_functions.h" - -// core +// qt #include - -// gui #include -AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) +AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) + : QLabel(parent) { - m_parent = parent; - m_movie = new QMovie(this); - ao_app = p_ao_app; + ao_app = p_ao_app; } void AOScene::set_image(QString p_image) { - QString animated_background_path = ao_app->get_background_path() + p_image; - QString background_path = ao_app->get_background_path() + p_image + ".png"; - QString default_path = ao_app->get_default_background_path() + p_image; + QString target_path = ao_app->get_default_background_path() + p_image; + + // background specific path + QString background_path = ao_app->get_background_path() + p_image; - for (auto& ext : QVector{".apng", ".gif"}) - { - QString full_path = animated_background_path + ext; - if (file_exists(full_path)) + for (auto &ext : QVector{".apng", ".gif", ".png"}) { - animated_background_path = full_path; - break; + QString full_background_path = background_path + ext; + + if (file_exists(full_background_path)) + { + target_path = full_background_path; + break; + } } - } - QPixmap animated_background(animated_background_path); - QPixmap background(background_path); - QPixmap default_bg(default_path); + // clear previous + this->clear(); - int w = this->width(); - int h = this->height(); + // delete current movie + delete m_movie; - // remove movie - this->clear(); - this->setMovie(nullptr); - // stop current movie - m_movie->stop(); - m_movie->setFileName(animated_background_path); - m_movie->setScaledSize(QSize(w, h)); + qDebug() << target_path; - if (m_movie->isValid()) - { - this->setMovie(m_movie); + // create new movie to run + m_movie = new QMovie(this); + m_movie->setFileName(target_path); + m_movie->setScaledSize(size()); m_movie->start(); - } - else if (file_exists(background_path)) - { - this->setPixmap(background.scaled(w, h)); - } - else - { - this->setPixmap(default_bg.scaled(w, h)); - } } diff --git a/aoscene.h b/aoscene.h index 9610085b5..a77fef3e7 100644 --- a/aoscene.h +++ b/aoscene.h @@ -8,17 +8,16 @@ class AOApplication; class AOScene : public QLabel { - Q_OBJECT + Q_OBJECT + public: - explicit AOScene(QWidget *parent, AOApplication *p_ao_app); + explicit AOScene(QWidget *parent, AOApplication *p_ao_app); - void set_image(QString p_image); + void set_image(QString p_image); private: - QWidget* m_parent = nullptr; - QMovie* m_movie = nullptr; - AOApplication* ao_app = nullptr; - + AOApplication *ao_app = nullptr; + QMovie *m_movie = nullptr; }; #endif // AOSCENE_H From ef501e345f45c22df3ecded0d958df9a1f25ca16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Mon, 20 Jul 2020 22:28:34 +0200 Subject: [PATCH 040/842] Update aoscene.cpp --- aoscene.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aoscene.cpp b/aoscene.cpp index db7b5ab49..50dc56a5f 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -36,10 +36,9 @@ void AOScene::set_image(QString p_image) // delete current movie delete m_movie; - qDebug() << target_path; - // create new movie to run m_movie = new QMovie(this); + setMovie(m_movie); m_movie->setFileName(target_path); m_movie->setScaledSize(size()); m_movie->start(); From d6cce36842a259bd5c0367db0ed19fd4d9f4997b Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 28 Jul 2020 23:38:14 -0400 Subject: [PATCH 041/842] Resizing of emotes and char_select (#9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Resizing of emotes and char_select - Fixed emotes and char_select not resizing properly after theme reloading - Added small QOL: current selected theme is kept in memory after reloading the theme * Char selector ghosting - Fixed char selector 'ghosting' * Fixed judge buttons & wtce Co-authored-by: Leifa♥ --- aocharbutton.cpp | 104 ++++++++---------- aocharbutton.h | 28 ++--- charselect.cpp | 247 ++++++++++++++++++++++++------------------ courtroom.cpp | 60 ++-------- courtroom.h | 17 ++- courtroom_widgets.cpp | 93 ++++++++++------ emotes.cpp | 90 ++++++++++----- 7 files changed, 349 insertions(+), 290 deletions(-) diff --git a/aocharbutton.cpp b/aocharbutton.cpp index 07eca3086..ba4454e3a 100644 --- a/aocharbutton.cpp +++ b/aocharbutton.cpp @@ -6,84 +6,72 @@ AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos) : QPushButton(parent) { - ao_app = p_ao_app; - - this->resize(60, 60); - this->move(x_pos, y_pos); - - ui_taken = new AOImage(this, ao_app); - ui_taken->resize(60, 60); - ui_taken->set_image("char_taken.png"); - ui_taken->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_taken->hide(); - - ui_passworded = new AOImage(this, ao_app); - ui_passworded->resize(60, 60); - ui_passworded->set_image("char_passworded"); - ui_passworded->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_passworded->hide(); - - ui_selector = new AOImage(parent, ao_app); - ui_selector->resize(62, 62); - ui_selector->move(x_pos - 1, y_pos - 1); - ui_selector->set_image("char_selector.png"); - ui_selector->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_selector->hide(); + ao_app = p_ao_app; + + this->resize(60, 60); + this->move(x_pos, y_pos); + + ui_taken = new AOImage(this, ao_app); + ui_taken->resize(60, 60); + ui_taken->set_image("char_taken.png"); + ui_taken->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_taken->hide(); + + ui_passworded = new AOImage(this, ao_app); + ui_passworded->resize(60, 60); + ui_passworded->set_image("char_passworded"); + ui_passworded->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_passworded->hide(); } void AOCharButton::reset() { - ui_taken->hide(); - ui_passworded->hide(); - ui_selector->hide(); + ui_taken->hide(); + ui_passworded->hide(); } void AOCharButton::set_taken() { - ui_taken->show(); + ui_taken->show(); } void AOCharButton::set_passworded() { - ui_passworded->show(); + ui_passworded->show(); } void AOCharButton::set_image(QString p_character) { - QString image_path = ao_app->get_character_path(p_character) + "char_icon.png"; - QString legacy_path = ao_app->get_demothings_path() + p_character.toLower() + "_char_icon.png"; - QString alt_path = ao_app->get_demothings_path() + p_character.toLower() + "_off.png"; - - this->setText(""); - - if (file_exists(image_path)) - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - else if (file_exists(legacy_path)) - { - this->setStyleSheet("border-image:url(\"" + legacy_path + "\")"); - //ninja optimization - QFile::copy(legacy_path, image_path); - } - else - { - this->setStyleSheet("border-image:url()"); - this->setText(p_character); - } + QString image_path = ao_app->get_character_path(p_character) + "char_icon.png"; + QString legacy_path = ao_app->get_demothings_path() + p_character.toLower() + "_char_icon.png"; + QString alt_path = ao_app->get_demothings_path() + p_character.toLower() + "_off.png"; + + this->setText(""); + + if (file_exists(image_path)) + this->setStyleSheet("border-image:url(\"" + image_path + "\")"); + else if (file_exists(legacy_path)) + { + this->setStyleSheet("border-image:url(\"" + legacy_path + "\")"); + //ninja optimization + QFile::copy(legacy_path, image_path); + } + else + { + this->setStyleSheet("border-image:url()"); + this->setText(p_character); + } } -void AOCharButton::enterEvent(QEvent * e) +void AOCharButton::enterEvent(QEvent *e) { - ui_selector->raise(); - ui_selector->show(); - - setFlat(false); - QPushButton::enterEvent(e); + setFlat(false); + QPushButton::enterEvent(e); + emit mouse_entered(this); } -void AOCharButton::leaveEvent(QEvent * e) +void AOCharButton::leaveEvent(QEvent *e) { - ui_selector->hide(); - QPushButton::leaveEvent(e); + QPushButton::leaveEvent(e); + emit mouse_left(); } - - diff --git a/aocharbutton.h b/aocharbutton.h index 9eb9a16d7..c30c79baa 100644 --- a/aocharbutton.h +++ b/aocharbutton.h @@ -2,34 +2,36 @@ #define AOCHARBUTTON_H #include "aoapplication.h" +#include "aoimage.h" #include #include #include -#include "aoimage.h" class AOCharButton : public QPushButton { - Q_OBJECT + Q_OBJECT public: - AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos); - AOApplication *ao_app = nullptr; + AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos); + AOApplication *ao_app = nullptr; - void reset(); - void set_taken(); - void set_passworded(); + void reset(); + void set_taken(); + void set_passworded(); + void set_image(QString p_character); - void set_image(QString p_character); +signals: + void mouse_entered(AOCharButton *p_caller); + void mouse_left(); private: - AOImage *ui_taken; - AOImage *ui_passworded; - AOImage *ui_selector; + AOImage *ui_taken = nullptr; + AOImage *ui_passworded = nullptr; protected: - void enterEvent(QEvent *e); - void leaveEvent(QEvent *e); + void enterEvent(QEvent *e); + void leaveEvent(QEvent *e); }; #endif // AOCHARBUTTON_H diff --git a/charselect.cpp b/charselect.cpp index 4e4bccbf3..b965ec5ac 100644 --- a/charselect.cpp +++ b/charselect.cpp @@ -1,162 +1,201 @@ #include "courtroom.h" - -#include "file_functions.h" #include "debug_functions.h" +#include "file_functions.h" #include "hardware_functions.h" #include void Courtroom::construct_char_select() { - ui_char_select_background = new AOImage(this, ao_app); + ui_char_select_background = new AOImage(this, ao_app); - ui_char_buttons = new QWidget(ui_char_select_background); + ui_char_buttons = new QWidget(ui_char_select_background); - ui_selector = new AOImage(ui_char_select_background, ao_app); - ui_selector->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_selector->resize(62, 62); + ui_char_button_selector = new AOImage(ui_char_buttons, ao_app); + ui_char_button_selector->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_char_button_selector->resize(62, 62); - ui_back_to_lobby = new AOButton(ui_char_select_background, ao_app); + ui_back_to_lobby = new AOButton(ui_char_select_background, ao_app); - ui_char_password = new QLineEdit(ui_char_select_background); + ui_char_password = new QLineEdit(ui_char_select_background); - ui_char_select_left = new AOButton(ui_char_select_background, ao_app); - ui_char_select_right = new AOButton(ui_char_select_background, ao_app); + ui_char_select_left = new AOButton(ui_char_select_background, ao_app); + ui_char_select_right = new AOButton(ui_char_select_background, ao_app); - ui_spectator = new AOButton(ui_char_select_background, ao_app); - ui_spectator->setText("Spectator"); + ui_spectator = new AOButton(ui_char_select_background, ao_app); + ui_spectator->setText("Spectator"); - QPoint f_spacing = ao_app->get_button_spacing("char_button_spacing", "courtroom_design.ini"); + connect(char_button_mapper, SIGNAL(mapped(int)), this, SLOT(char_clicked(int))); + connect(ui_back_to_lobby, SIGNAL(clicked()), this, SLOT(on_back_to_lobby_clicked())); - const int button_width = 60; - int x_spacing = f_spacing.x(); - int x_mod_count = 0; + connect(ui_char_select_left, SIGNAL(clicked()), this, SLOT(on_char_select_left_clicked())); + connect(ui_char_select_right, SIGNAL(clicked()), this, SLOT(on_char_select_right_clicked())); - const int button_height = 60; - int y_spacing = f_spacing.y(); - int y_mod_count = 0; + connect(ui_spectator, SIGNAL(clicked()), this, SLOT(on_spectator_clicked())); + + reconstruct_char_select(); +} - set_size_and_pos(ui_char_buttons, "char_buttons"); +void Courtroom::reconstruct_char_select() +{ + while (!ui_char_button_list.isEmpty()) + delete ui_char_button_list.takeLast(); - char_columns = ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + 1; - char_rows = ((ui_char_buttons->height() - button_height) / (y_spacing + button_height)) + 1; + QPoint f_spacing = ao_app->get_button_spacing("char_button_spacing", "courtroom_design.ini"); - max_chars_on_page = char_columns * char_rows; + const int button_width = 60; + int x_spacing = f_spacing.x(); + int x_mod_count = 0; - for (int n = 0 ; n < max_chars_on_page ; ++n) - { - int x_pos = (button_width + x_spacing) * x_mod_count; - int y_pos = (button_height + y_spacing) * y_mod_count; + const int button_height = 60; + int y_spacing = f_spacing.y(); + int y_mod_count = 0; - ui_char_button_list.append(new AOCharButton(ui_char_buttons, ao_app, x_pos, y_pos)); + set_size_and_pos(ui_char_buttons, "char_buttons"); - connect(ui_char_button_list.at(n), SIGNAL(clicked()), char_button_mapper, SLOT(map())) ; - char_button_mapper->setMapping (ui_char_button_list.at(n), n) ; + char_columns = ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + 1; + char_rows = ((ui_char_buttons->height() - button_height) / (y_spacing + button_height)) + 1; - ++x_mod_count; + max_chars_on_page = char_columns * char_rows; - if (x_mod_count == char_columns) + for (int n = 0; n < max_chars_on_page; ++n) { - ++y_mod_count; - x_mod_count = 0; + int x_pos = (button_width + x_spacing) * x_mod_count; + int y_pos = (button_height + y_spacing) * y_mod_count; + + AOCharButton *button = new AOCharButton(ui_char_buttons, ao_app, x_pos, y_pos); + ui_char_button_list.append(button); + + connect(button, SIGNAL(clicked()), char_button_mapper, SLOT(map())); + char_button_mapper->setMapping(button, n); + + // mouse events + connect(button, SIGNAL(mouse_entered(AOCharButton *)), this, SLOT(char_mouse_entered(AOCharButton *))); + connect(button, SIGNAL(mouse_left()), this, SLOT(char_mouse_left())); + + ++x_mod_count; + + if (x_mod_count == char_columns) + { + ++y_mod_count; + x_mod_count = 0; + } } - } - connect (char_button_mapper, SIGNAL(mapped(int)), this, SLOT(char_clicked(int))); - connect(ui_back_to_lobby, SIGNAL(clicked()), this, SLOT(on_back_to_lobby_clicked())); + reset_char_select(); +} - connect(ui_char_select_left, SIGNAL(clicked()), this, SLOT(on_char_select_left_clicked())); - connect(ui_char_select_right, SIGNAL(clicked()), this, SLOT(on_char_select_right_clicked())); +void Courtroom::reset_char_select() +{ + current_char_page = 0; - connect(ui_spectator, SIGNAL(clicked()), this, SLOT(on_spectator_clicked())); + set_char_select(); + set_char_select_page(); } void Courtroom::set_char_select() { - QString filename = "courtroom_design.ini"; + QString filename = "courtroom_design.ini"; - pos_size_type f_charselect = ao_app->get_element_dimensions("char_select", filename); + pos_size_type f_charselect = ao_app->get_element_dimensions("char_select", filename); - if (f_charselect.width < 0 || f_charselect.height < 0) - { - qDebug() << "W: did not find courtroom width or height in courtroom_design.ini!"; - this->resize(714, 668); - } - else - this->resize(f_charselect.width, f_charselect.height); + if (f_charselect.width < 0 || f_charselect.height < 0) + { + qDebug() << "W: did not find courtroom width or height in courtroom_design.ini!"; + this->resize(714, 668); + } + else + this->resize(f_charselect.width, f_charselect.height); - ui_char_select_background->resize(f_charselect.width, f_charselect.height); - ui_char_select_background->set_image("charselect_background.png"); + ui_char_select_background->resize(f_charselect.width, f_charselect.height); + ui_char_select_background->set_image("charselect_background.png"); } void Courtroom::set_char_select_page() { - ui_char_select_background->show(); + ui_char_select_background->show(); - ui_char_select_left->hide(); - ui_char_select_right->hide(); + ui_char_select_left->hide(); + ui_char_select_right->hide(); - for (AOCharButton *i_button : ui_char_button_list) - i_button->hide(); + for (AOCharButton *button : ui_char_button_list) + { + button->reset(); + button->hide(); + } - int total_pages = char_list.size() / max_chars_on_page; - int chars_on_page = 0; + int total_pages = char_list.size() / max_chars_on_page; + int chars_on_page = 0; - if (char_list.size() % max_chars_on_page != 0) - { - ++total_pages; - //i. e. not on the last page - if (total_pages > current_char_page + 1) - chars_on_page = max_chars_on_page; + if (char_list.size() % max_chars_on_page != 0) + { + ++total_pages; + //i. e. not on the last page + if (total_pages > current_char_page + 1) + chars_on_page = max_chars_on_page; + else + chars_on_page = char_list.size() % max_chars_on_page; + } else - chars_on_page = char_list.size() % max_chars_on_page; - - } - else - chars_on_page = max_chars_on_page; + chars_on_page = max_chars_on_page; - if (total_pages > current_char_page + 1) - ui_char_select_right->show(); + if (total_pages > current_char_page + 1) + ui_char_select_right->show(); - if (current_char_page > 0) - ui_char_select_left->show(); + if (current_char_page > 0) + ui_char_select_left->show(); - for (int n_button = 0 ; n_button < chars_on_page ; ++n_button) - { - int n_real_char = n_button + current_char_page * max_chars_on_page; - AOCharButton *f_button = ui_char_button_list.at(n_button); + // show all buttons for this page + for (int n_button = 0; n_button < chars_on_page; ++n_button) + { + if (char_list.length() <= n_button) + continue; - f_button->reset(); - f_button->set_image(char_list.at(n_real_char).name); - f_button->show(); + int n_real_char = n_button + current_char_page * max_chars_on_page; + AOCharButton *f_button = ui_char_button_list.at(n_button); - if (char_list.at(n_real_char).taken) - f_button->set_taken(); - } + f_button->set_image(char_list.at(n_real_char).name); + f_button->show(); + if (char_list.at(n_real_char).taken) + f_button->set_taken(); + } } void Courtroom::char_clicked(int n_char) { - int n_real_char = n_char + current_char_page * max_chars_on_page; - - QString char_ini_path = ao_app->get_character_path(char_list.at(n_real_char).name) + "char.ini"; - qDebug() << "char_ini_path" << char_ini_path; - - if (!file_exists(char_ini_path)) - { - qDebug() << "did not find " << char_ini_path; - call_notice("Could not find " + char_ini_path); - return; - } - - if (n_real_char == m_cid) - { - enter_courtroom(m_cid); - } - else - { - ao_app->send_server_packet(new AOPacket("CC#" + QString::number(ao_app->s_pv) + "#" + QString::number(n_real_char) + "#" + get_hdid() + "#%")); - } + int n_real_char = n_char + current_char_page * max_chars_on_page; + + QString char_ini_path = ao_app->get_character_path(char_list.at(n_real_char).name) + "char.ini"; + qDebug() << "char_ini_path" << char_ini_path; + + if (!file_exists(char_ini_path)) + { + qDebug() << "did not find " << char_ini_path; + call_notice("Could not find " + char_ini_path); + return; + } + + if (n_real_char == m_cid) + { + enter_courtroom(m_cid); + } + else + { + ao_app->send_server_packet(new AOPacket("CC#" + QString::number(ao_app->s_pv) + "#" + QString::number(n_real_char) + "#" + get_hdid() + "#%")); + } +} + +void Courtroom::char_mouse_entered(AOCharButton *p_caller) +{ + if (p_caller == nullptr) + return; + ui_char_button_selector->move(p_caller->x() - 1, p_caller->y() - 1); + ui_char_button_selector->raise(); + ui_char_button_selector->show(); } +void Courtroom::char_mouse_left() +{ + ui_char_button_selector->hide(); +} diff --git a/courtroom.cpp b/courtroom.cpp index d1255f019..5fc27f9d1 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -88,7 +88,7 @@ void Courtroom::enter_courtroom(int p_cid) m_shout_state = 0; m_wtce_current = 0; - reset_wtce_buttons(); + reset_judge_wtce_buttons(); // forward declaration for a possible update of the chatlog bool chatlog_changed = false; @@ -118,30 +118,8 @@ void Courtroom::enter_courtroom(int p_cid) QString side = ao_app->get_char_side(f_char); - if (side == "jud") - { - is_judge = true; - //set_wtce(); - //ui_wtce_down->show(); - //ui_wtce_up->show(); - - ui_defense_minus->show(); - ui_defense_plus->show(); - ui_prosecution_minus->show(); - ui_prosecution_plus->show(); - } - else - { - is_judge = false; - //set_wtce(); - //ui_wtce_down->hide(); - //ui_wtce_up->hide(); - - ui_defense_minus->hide(); - ui_defense_plus->hide(); - ui_prosecution_minus->hide(); - ui_prosecution_plus->hide(); - } + // enable judge mechanics + set_judge_enabled(side == "jud"); // Update widgets first, then check if everything is valid // This will also handle showing the correct shouts, effects and wtce buttons, and cycling @@ -575,6 +553,7 @@ void Courtroom::save_textlog(QString p_text) void Courtroom::list_themes() { + QString prev_theme_name = ui_theme_list->currentText(); QString themes = ao_app->get_base_path() + "themes/"; QDir dir(themes); ui_theme_list->clear(); @@ -585,6 +564,8 @@ void Courtroom::list_themes() if(s != "." && s != "..") ui_theme_list->addItem(s); } + + ui_theme_list->setCurrentText(prev_theme_name); } void Courtroom::append_ms_chatmessage(QString f_name, QString f_message) @@ -856,7 +837,7 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) reset_effect_buttons(); m_wtce_current = 0; - reset_wtce_buttons(); + reset_judge_wtce_buttons(); is_presenting_evidence = false; ui_evidence_present->set_image("present_disabled.png"); @@ -1803,26 +1784,7 @@ void Courtroom::on_ooc_return_pressed() if (ooc_message.startsWith("/pos")) { - if (ooc_message.startsWith("/pos jud")) - { - is_judge = true; - set_wtce(); - - ui_defense_minus->show(); - ui_defense_plus->show(); - ui_prosecution_minus->show(); - ui_prosecution_plus->show(); - } - else - { - is_judge = false; - set_wtce(); - - ui_defense_minus->hide(); - ui_defense_plus->hide(); - ui_prosecution_minus->hide(); - ui_prosecution_plus->hide(); - } + set_judge_enabled(ooc_message.startsWith("/pos jud")); } else if (ooc_message.startsWith("/login")) ui_guard->show(); @@ -1992,6 +1954,8 @@ void Courtroom::on_pos_dropdown_changed(int p_index) if (f_pos == "" || ui_ooc_chat_name->text() == "") return; + set_judge_enabled(f_pos == "jud"); + ao_app->send_server_packet(new AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); } @@ -2164,7 +2128,7 @@ void Courtroom::cycle_wtce(int p_index) int n = ui_wtce.size(); do { m_wtce_current = (m_wtce_current - p_index + n) % n; } while ( !wtce_enabled[m_wtce_current] ); - set_wtce(); + set_judge_wtce(); } void Courtroom::reset_effect_buttons() @@ -2287,7 +2251,7 @@ void Courtroom::on_cross_examination_clicked() ui_ic_chat_message->setFocus(); } -void Courtroom::reset_wtce_buttons() // kind of an unnecessary function, but I added it just in case +void Courtroom::reset_judge_wtce_buttons() // kind of an unnecessary function, but I added it just in case { for(int i = 0; i < wtce_names.size(); ++i) // effect names does not necessarily have the same size as ui effects { diff --git a/courtroom.h b/courtroom.h index 435d1e9d1..a8d363250 100644 --- a/courtroom.h +++ b/courtroom.h @@ -149,7 +149,8 @@ class Courtroom : public QMainWindow void set_shouts(); void set_effects(); - void set_wtce(); + void set_judge_enabled(bool p_enabled); + void set_judge_wtce(); void set_free_blocks(); //these are for OOC chat @@ -581,9 +582,8 @@ class Courtroom : public QMainWindow //abstract widget to hold char buttons QWidget *ui_char_buttons = nullptr; - + AOImage *ui_char_button_selector = nullptr; QVector ui_char_button_list; - AOImage *ui_selector; AOButton *ui_back_to_lobby; @@ -607,10 +607,14 @@ class Courtroom : public QMainWindow void set_widget_layers(); void construct_char_select(); + void reconstruct_char_select(); + void reset_char_select(); void set_char_select(); void set_char_select_page(); void construct_emotes(); + void reconstruct_emotes(); + void reset_emote_page(); void set_emote_page(); void set_emote_dropdown(); @@ -724,7 +728,7 @@ private slots: void on_witness_testimony_clicked(); void on_cross_examination_clicked(); - void reset_wtce_buttons(); + void reset_judge_wtce_buttons(); void on_wtce_clicked(); void on_change_character_clicked(); @@ -757,11 +761,12 @@ private slots: void on_char_select_left_clicked(); void on_char_select_right_clicked(); + void char_clicked(int n_char); + void char_mouse_entered(AOCharButton *p_caller); + void char_mouse_left(); void on_spectator_clicked(); - void char_clicked(int n_char); - void ping_server(); }; diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 459be63b6..48867e80c 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -148,18 +148,6 @@ void Courtroom::create_widgets() construct_emotes(); - ui_emote_left = new AOButton(this, ao_app); - ui_emote_right = new AOButton(this, ao_app); - - ui_emote_dropdown = new QComboBox(this); - ui_pos_dropdown = new QComboBox(this); - ui_pos_dropdown->addItem("wit"); - ui_pos_dropdown->addItem("def"); - ui_pos_dropdown->addItem("pro"); - ui_pos_dropdown->addItem("jud"); - ui_pos_dropdown->addItem("hld"); - ui_pos_dropdown->addItem("hlp"); - ui_defense_bar = new AOImage(this, ao_app); ui_prosecution_bar = new AOImage(this, ao_app); @@ -712,7 +700,12 @@ void Courtroom::set_widgets() set_size_and_pos(ui_sfx_search, "sfx_search"); + // char select + reconstruct_char_select(); + + // emotes set_size_and_pos(ui_emotes, "emotes"); + reconstruct_emotes(); set_size_and_pos(ui_emote_left, "emote_left"); ui_emote_left->set_image("arrow_left.png"); @@ -801,11 +794,6 @@ void Courtroom::set_widgets() ui_effect_down->show(); } - for(int i = 0; i < wtce_names.size(); ++i) - { - set_size_and_pos(ui_wtce[i], wtce_names[i]); - } - set_size_and_pos(ui_wtce_up, "wtce_up"); ui_wtce_up->set_image("wtceup.png"); ui_wtce_up->hide(); @@ -813,12 +801,21 @@ void Courtroom::set_widgets() ui_wtce_down->set_image("wtcedown.png"); ui_wtce_down->hide(); + for(int i = 0; i < wtce_names.size(); ++i) + { + set_size_and_pos(ui_wtce[i], wtce_names[i]); + } + if (ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true" ) // courtroom_config.ini necessary { - for(auto & wtce : ui_wtce) move_widget(wtce, "wtce"); + for(auto & wtce : ui_wtce) + move_widget(wtce, "wtce"); qDebug() << "AA: single wtce"; - set_wtce(); } + set_judge_wtce(); + + // this will reset the image + reset_judge_wtce_buttons(); for(int i = 0; i < free_block_names.size(); ++i) { @@ -999,8 +996,8 @@ void Courtroom::set_widgets() set_size_and_pos(ui_evidence_description, "evidence_description"); - ui_selector->set_image("char_selector.png"); - ui_selector->hide(); + ui_char_button_selector->set_image("char_selector.png"); + ui_char_button_selector->hide(); set_size_and_pos(ui_back_to_lobby, "back_to_lobby"); ui_back_to_lobby->setText("Back to Lobby"); @@ -1412,29 +1409,55 @@ void Courtroom::set_shouts() void Courtroom::set_effects() { - for(auto & effect : ui_effects) effect->hide(); - if (ui_effects.size() > 0) ui_effects[m_effect_current]->show(); // check to prevent crashing + for(auto & effect : ui_effects) + effect->hide(); + + // check to prevent crashing + if (ui_effects.size() > 0) + ui_effects[m_effect_current]->show(); } -void Courtroom::set_wtce() +void Courtroom::set_judge_enabled(bool p_enabled) { - for(auto & wtce : ui_wtce) wtce->hide(); + is_judge = p_enabled; - if(is_judge && ui_wtce.size() > 0) // check to prevent crashing - { - if( ao_app->read_theme_ini("enable_single_wtce", cc_config_ini ) == "true" ) + // set judge button visibility + ui_defense_plus->setVisible(is_judge); + ui_defense_minus->setVisible(is_judge); + ui_prosecution_plus->setVisible(is_judge); + ui_prosecution_minus->setVisible(is_judge); + + set_judge_wtce(); +} + +void Courtroom::set_judge_wtce() +{ + // hide all wtce before enabling visibility + for(auto & wtce : ui_wtce) + wtce->hide(); + + // check if we use a single wtce or multiple + const bool is_single_wtce = ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true"; + + // update visibility for next/previous + ui_wtce_up->setVisible(is_judge && is_single_wtce); + ui_wtce_down->setVisible(is_judge && is_single_wtce); + + // prevent going ahead if we have no wtce + if (!is_judge || ui_wtce.length() == 0) + return; + + + // set visibility based off parameter + if (is_single_wtce == true) { - ui_wtce[m_wtce_current]->show(); - ui_wtce_up->show(); - ui_wtce_down->show(); + ui_wtce[m_wtce_current]->show(); } else { - for(auto & wtce : ui_wtce) wtce->show(); - ui_wtce_up->hide(); - ui_wtce_down->hide(); + for (AOButton *i_wtce : ui_wtce) + i_wtce->show(); } - } } void Courtroom::set_free_blocks() diff --git a/emotes.cpp b/emotes.cpp index 12f41a3b2..90fc1006c 100644 --- a/emotes.cpp +++ b/emotes.cpp @@ -6,46 +6,84 @@ void Courtroom::construct_emotes() { - ui_emotes = new QWidget(this); + ui_emotes = new QWidget(this); - set_size_and_pos(ui_emotes, "emotes"); + ui_emote_left = new AOButton(this, ao_app); + ui_emote_right = new AOButton(this, ao_app); - QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", "courtroom_design.ini"); + ui_emote_dropdown = new QComboBox(this); + ui_pos_dropdown = new QComboBox(this); + ui_pos_dropdown->addItem("wit"); + ui_pos_dropdown->addItem("def"); + ui_pos_dropdown->addItem("pro"); + ui_pos_dropdown->addItem("jud"); + ui_pos_dropdown->addItem("hld"); + ui_pos_dropdown->addItem("hlp"); - const int button_width = 40; - int x_spacing = f_spacing.x(); - int x_mod_count = 0; + reconstruct_emotes(); +} - const int button_height = 40; - int y_spacing = f_spacing.y(); - int y_mod_count = 0; +void Courtroom::reconstruct_emotes() +{ + // delete previous buttons + while (!ui_emote_list.isEmpty()) + delete ui_emote_list.takeLast(); - emote_columns = ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; - emote_rows = ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; + // resize and move + set_size_and_pos(ui_emotes, "emotes"); - max_emotes_on_page = emote_columns * emote_rows; + QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", "courtroom_design.ini"); - for (int n = 0 ; n < max_emotes_on_page ; ++n) - { - int x_pos = (button_width + x_spacing) * x_mod_count; - int y_pos = (button_height + y_spacing) * y_mod_count; + const int button_width = 40; + int x_spacing = f_spacing.x(); + int x_mod_count = 0; - AOEmoteButton *f_emote = new AOEmoteButton(ui_emotes, ao_app, x_pos, y_pos); + const int button_height = 40; + int y_spacing = f_spacing.y(); + int y_mod_count = 0; - ui_emote_list.append(f_emote); + emote_columns = ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; + emote_rows = ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; - f_emote->set_id(n); + max_emotes_on_page = emote_columns * emote_rows; - connect(f_emote, SIGNAL(emote_clicked(int)), this, SLOT(on_emote_clicked(int))); + for (int n = 0 ; n < max_emotes_on_page ; ++n) + { + int x_pos = (button_width + x_spacing) * x_mod_count; + int y_pos = (button_height + y_spacing) * y_mod_count; - ++x_mod_count; + AOEmoteButton *f_emote = new AOEmoteButton(ui_emotes, ao_app, x_pos, y_pos); - if (x_mod_count == emote_columns) - { - ++y_mod_count; - x_mod_count = 0; + ui_emote_list.append(f_emote); + + f_emote->set_id(n); + + connect(f_emote, SIGNAL(emote_clicked(int)), this, SLOT(on_emote_clicked(int))); + + ++x_mod_count; + + if (x_mod_count == emote_columns) + { + ++y_mod_count; + x_mod_count = 0; + } } - } + + reset_emote_page(); +} + +void Courtroom::reset_emote_page() +{ + current_emote_page = 0; + current_emote = 0; + + if (m_cid == -1) + ui_emotes->hide(); + else + ui_emotes->show(); + + set_emote_page(); + set_emote_dropdown(); } void Courtroom::set_emote_page() From c8e24d6226e4eedef5ff3fffcc1b56fdcaa92ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 5 Aug 2020 22:14:01 +0200 Subject: [PATCH 042/842] Added AOPixmap to deal with Qt's debug 'null' messages (#10) --- Attorney_Online_remake.pro | 6 +- aocharmovie.cpp | 251 ++++++++++++++++++------------------- aocharmovie.h | 2 + aoevidencedisplay.cpp | 4 +- aoevidencedisplay.h | 1 + aoimage.cpp | 9 +- aoimage.h | 1 + aolabel.cpp | 2 +- aolabel.hpp => aolabel.h | 0 aopixmap.cpp | 22 ++++ aopixmap.h | 15 +++ aotimer.h | 2 +- courtroom.h | 2 +- 13 files changed, 178 insertions(+), 139 deletions(-) rename aolabel.hpp => aolabel.h (100%) create mode 100644 aopixmap.cpp create mode 100644 aopixmap.h diff --git a/Attorney_Online_remake.pro b/Attorney_Online_remake.pro index e7dd507a0..c0dfd5f30 100644 --- a/Attorney_Online_remake.pro +++ b/Attorney_Online_remake.pro @@ -16,6 +16,7 @@ TEMPLATE = app VERSION = 2.4.8.0 SOURCES += main.cpp\ + aopixmap.cpp \ aotimer.cpp \ lobby.cpp \ text_file_functions.cpp \ @@ -62,6 +63,8 @@ SOURCES += main.cpp\ HEADERS += lobby.h \ aoimage.h \ + aolabel.h \ + aopixmap.h \ aotimer.h \ file_functions.h \ aobutton.h \ @@ -97,8 +100,7 @@ HEADERS += lobby.h \ aoabstractplayer.hpp \ aoshoutplayer.hpp \ aonotearea.hpp \ - aonotepicker.hpp \ - aolabel.hpp + aonotepicker.hpp # 1. You need to get BASS and put the x86 bass DLL/headers in the project root folder # AND the compilation output folder. If you want a static link, you'll probably diff --git a/aocharmovie.cpp b/aocharmovie.cpp index 9f1be7e2f..6879aa5ae 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -1,188 +1,185 @@ #include "aocharmovie.h" -#include "courtroom.h" -#include "misc_functions.h" -#include "file_functions.h" #include "aoapplication.h" +#include "courtroom.h" +#include "file_functions.h" +#include "misc_functions.h" #include #include AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) { - ao_app = p_ao_app; + ao_app = p_ao_app; - m_movie = new QMovie(this); + m_movie = new QMovie(this); - preanim_timer = new QTimer(this); - preanim_timer->setSingleShot(true); + preanim_timer = new QTimer(this); + preanim_timer->setSingleShot(true); - connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); - connect(preanim_timer, SIGNAL(timeout()), this, SLOT(timer_done())); + connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); + connect(preanim_timer, SIGNAL(timeout()), this, SLOT(timer_done())); } void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bool show) { - QString gif_path; - QStringList f_vec; - QStringList f_paths{ - ao_app->get_character_path(p_char) + emote_prefix + p_emote.toLower(), // .gif - ao_app->get_character_path(p_char) + p_emote.toLower(), // .png - ao_app->get_theme_variant_path() + "placeholder", // .gif - ao_app->get_theme_path() + "placeholder", // .gif - ao_app->get_default_theme_path() + "placeholder" // .gif - }; - - for(auto &f_file : f_paths) - { - bool found = false; - for (auto &ext : decltype(f_vec){".apng", ".gif", ".png"}) + QString gif_path; + QStringList f_vec; + QStringList f_paths{ + ao_app->get_character_path(p_char) + emote_prefix + p_emote.toLower(), // .gif + ao_app->get_character_path(p_char) + p_emote.toLower(), // .png + ao_app->get_theme_variant_path() + "placeholder", // .gif + ao_app->get_theme_path() + "placeholder", // .gif + ao_app->get_default_theme_path() + "placeholder" // .gif + }; + + for (auto &f_file : f_paths) { - QString fullPath = f_file + ext; - found = file_exists(fullPath); - if (found) - { - gif_path = fullPath; - break; - } + bool found = false; + for (auto &ext : decltype(f_vec){".apng", ".gif", ".png"}) + { + QString fullPath = f_file + ext; + found = file_exists(fullPath); + if (found) + { + gif_path = fullPath; + break; + } + } + + if (found) + break; } - if (found) - break; - } - - m_movie->stop(); - m_movie->setFileName(gif_path); + m_movie->stop(); + m_movie->setFileName(gif_path); - QImageReader *reader = new QImageReader(gif_path); + QImageReader *reader = new QImageReader(gif_path); - movie_frames.clear(); - QImage f_image = reader->read(); - while (!f_image.isNull()) - { - if (m_flipped) - movie_frames.append(f_image.mirrored(true, false)); - else - movie_frames.append(f_image); - f_image = reader->read(); - } + movie_frames.clear(); + QImage f_image = reader->read(); + while (!f_image.isNull()) + { + if (m_flipped) + movie_frames.append(f_image.mirrored(true, false)); + else + movie_frames.append(f_image); + f_image = reader->read(); + } - delete reader; + delete reader; - this->show(); - if(!show) this->hide(); + this->show(); + if (!show) this->hide(); - m_movie->start(); + m_movie->start(); } void AOCharMovie::play_pre(QString p_char, QString p_emote, int duration, bool show) { - QString gif_path = ao_app->get_character_path(p_char) + p_emote.toLower(); - - m_movie->stop(); - this->clear(); - m_movie->setFileName(gif_path); - m_movie->jumpToFrame(0); - - int full_duration = duration * time_mod; - int real_duration = 0; - - play_once = false; - - for (int n_frame = 0 ; n_frame < m_movie->frameCount() ; ++n_frame) - { - real_duration += m_movie->nextFrameDelay(); - m_movie->jumpToFrame(n_frame + 1); - } - qDebug() << "full_duration: " << full_duration; - qDebug() << "real_duration: " << real_duration; - - double percentage_modifier = 100.0; - - if (real_duration != 0 && duration != 0) - { - double modifier = full_duration / static_cast(real_duration); - percentage_modifier = 100 / modifier; - - if (percentage_modifier > 100.0) - percentage_modifier = 100.0; - } - qDebug() << "% mod: " << percentage_modifier; - - if (full_duration == 0 || full_duration >= real_duration) - { - play_once = true; - } - else - { + QString gif_path = ao_app->get_character_path(p_char) + p_emote.toLower(); + + m_movie->stop(); + this->clear(); + m_movie->setFileName(gif_path); + m_movie->jumpToFrame(0); + + int full_duration = duration * time_mod; + int real_duration = 0; + play_once = false; - preanim_timer->start(full_duration); - } + for (int n_frame = 0; n_frame < m_movie->frameCount(); ++n_frame) + { + real_duration += m_movie->nextFrameDelay(); + m_movie->jumpToFrame(n_frame + 1); + } + qDebug() << "full_duration: " << full_duration; + qDebug() << "real_duration: " << real_duration; + + double percentage_modifier = 100.0; + + if (real_duration != 0 && duration != 0) + { + double modifier = full_duration / static_cast(real_duration); + percentage_modifier = 100 / modifier; + + if (percentage_modifier > 100.0) + percentage_modifier = 100.0; + } + qDebug() << "% mod: " << percentage_modifier; + + if (full_duration == 0 || full_duration >= real_duration) + { + play_once = true; + } + else + { + play_once = false; + preanim_timer->start(full_duration); + } - m_movie->setSpeed(static_cast(percentage_modifier)); - play(p_char, p_emote, "", show); + m_movie->setSpeed(static_cast(percentage_modifier)); + play(p_char, p_emote, "", show); } void AOCharMovie::play_talking(QString p_char, QString p_emote, bool show) { - QString gif_path = ao_app->get_character_path(p_char) + "(b)" + p_emote.toLower(); + QString gif_path = ao_app->get_character_path(p_char) + "(b)" + p_emote.toLower(); - m_movie->stop(); - this->clear(); - m_movie->setFileName(gif_path); + m_movie->stop(); + this->clear(); + m_movie->setFileName(gif_path); - play_once = false; - m_movie->setSpeed(100); - play(p_char, p_emote, "(b)", show); + play_once = false; + m_movie->setSpeed(100); + play(p_char, p_emote, "(b)", show); } void AOCharMovie::play_idle(QString p_char, QString p_emote, bool show) { - QString gif_path = ao_app->get_character_path(p_char) + "(a)" + p_emote.toLower(); + QString gif_path = ao_app->get_character_path(p_char) + "(a)" + p_emote.toLower(); - m_movie->stop(); - this->clear(); - m_movie->setFileName(gif_path); + m_movie->stop(); + this->clear(); + m_movie->setFileName(gif_path); - play_once = false; - m_movie->setSpeed(100); - play(p_char, p_emote, "(a)", show); + play_once = false; + m_movie->setSpeed(100); + play(p_char, p_emote, "(a)", show); } 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 - m_movie->stop(); - preanim_timer->stop(); - this->hide(); + //for all intents and purposes, stopping is the same as hiding. at no point do we want a frozen gif to display + m_movie->stop(); + preanim_timer->stop(); + this->hide(); } void AOCharMovie::combo_resize(int w, int h) { - QSize f_size(w, h); - this->resize(f_size); - m_movie->setScaledSize(f_size); + QSize f_size(w, h); + this->resize(f_size); + m_movie->setScaledSize(f_size); } void AOCharMovie::frame_change(int n_frame) { - if (movie_frames.size() > n_frame) - { - QPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(n_frame)); - - this->setPixmap(f_pixmap.scaled(this->width(), this->height())); - } + if (movie_frames.size() > n_frame) + { + AOPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(n_frame)); + this->setPixmap(f_pixmap->scaled(this->width(), this->height())); + } - if (m_movie->frameCount() - 1 == n_frame && play_once) - { - preanim_timer->start(m_movie->nextFrameDelay()); - m_movie->stop(); - } + if (m_movie->frameCount() - 1 == n_frame && play_once) + { + preanim_timer->start(m_movie->nextFrameDelay()); + m_movie->stop(); + } } void AOCharMovie::timer_done() { - - done(); + done(); } diff --git a/aocharmovie.h b/aocharmovie.h index 7ceb293ea..6132dde98 100644 --- a/aocharmovie.h +++ b/aocharmovie.h @@ -5,6 +5,8 @@ #include #include +#include "aopixmap.h" + class AOApplication; class AOCharMovie : public QLabel diff --git a/aoevidencedisplay.cpp b/aoevidencedisplay.cpp index 66593f251..0b1234f74 100644 --- a/aoevidencedisplay.cpp +++ b/aoevidencedisplay.cpp @@ -25,7 +25,7 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid QString f_evidence_path = ao_app->get_evidence_path() + p_evidence_image; - QPixmap f_pixmap(f_evidence_path); + AOPixmap f_pixmap(f_evidence_path); QString final_gif_path; QString gif_name; @@ -46,7 +46,7 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid evidence_icon->move(icon_dimensions.x, icon_dimensions.y); evidence_icon->resize(icon_dimensions.width, icon_dimensions.height); - evidence_icon->setPixmap(f_pixmap.scaled(evidence_icon->width(), evidence_icon->height(), Qt::IgnoreAspectRatio)); + evidence_icon->setPixmap(f_pixmap->scaled(evidence_icon->width(), evidence_icon->height(), Qt::IgnoreAspectRatio)); QString f_path = ao_app->get_image_path(gif_name); evidence_movie->setFileName(f_path); diff --git a/aoevidencedisplay.h b/aoevidencedisplay.h index b90cf0678..9babffa96 100644 --- a/aoevidencedisplay.h +++ b/aoevidencedisplay.h @@ -6,6 +6,7 @@ #include "aoapplication.h" #include "aosfxplayer.h" +#include "aopixmap.h" class AOEvidenceDisplay : public QLabel { diff --git a/aoimage.cpp b/aoimage.cpp index 5c741db99..00a082d65 100644 --- a/aoimage.cpp +++ b/aoimage.cpp @@ -12,9 +12,8 @@ AOImage::AOImage(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) void AOImage::set_image(QString p_image) { QString f_path = ao_app->get_image_path(p_image); - QPixmap f_pixmap(f_path); - - this->setPixmap(f_pixmap.scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); + AOPixmap f_pixmap(f_path); + this->setPixmap(f_pixmap->scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); // Store final path if the path exists if (file_exists(f_path)) @@ -34,8 +33,8 @@ void AOImage::set_image_from_path(QString p_path) else final_path = default_path; - QPixmap f_pixmap(final_path); - this->setPixmap(f_pixmap.scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); + AOPixmap f_pixmap(final_path); + this->setPixmap(f_pixmap->scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); // Store final path if the path exists if (file_exists(final_path)) diff --git a/aoimage.h b/aoimage.h index 918fea495..a60c371e0 100644 --- a/aoimage.h +++ b/aoimage.h @@ -4,6 +4,7 @@ #define AOIMAGE_H #include "aoapplication.h" +#include "aopixmap.h" #include diff --git a/aolabel.cpp b/aolabel.cpp index 36b35c736..e6318fbea 100644 --- a/aolabel.cpp +++ b/aolabel.cpp @@ -1,4 +1,4 @@ -#include "aolabel.hpp" +#include "aolabel.h" #include "file_functions.h" diff --git a/aolabel.hpp b/aolabel.h similarity index 100% rename from aolabel.hpp rename to aolabel.h diff --git a/aopixmap.cpp b/aopixmap.cpp new file mode 100644 index 000000000..81c3375e4 --- /dev/null +++ b/aopixmap.cpp @@ -0,0 +1,22 @@ +#include "aopixmap.h" + +AOPixmap::AOPixmap(QPixmap p_pixmap) : m_pixmap(p_pixmap) +{ + +} + +AOPixmap::AOPixmap(QString p_file_path) : m_pixmap(p_file_path) +{ + +} + +QPixmap *AOPixmap::operator ->() +{ + if (m_pixmap.isNull()) + { + m_pixmap = QPixmap(1, 1); + m_pixmap.fill(Qt::transparent); + } + + return &m_pixmap; +} diff --git a/aopixmap.h b/aopixmap.h new file mode 100644 index 000000000..b4b5515b2 --- /dev/null +++ b/aopixmap.h @@ -0,0 +1,15 @@ +#pragma once +// qt +#include + +class AOPixmap +{ +public: + AOPixmap(QPixmap p_pixmap); + AOPixmap(QString p_file_path); + + QPixmap *operator ->(); + +private: + QPixmap m_pixmap; +}; diff --git a/aotimer.h b/aotimer.h index 8184552b8..5036739fc 100644 --- a/aotimer.h +++ b/aotimer.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include class ManualTimer { diff --git a/courtroom.h b/courtroom.h index a8d363250..154ee269a 100644 --- a/courtroom.h +++ b/courtroom.h @@ -23,7 +23,7 @@ #include "aoevidencedisplay.h" #include "aonotepad.h" #include "aonotearea.hpp" -#include "aolabel.hpp" +#include "aolabel.h" #include "aotimer.h" #include "datatypes.h" From 0821bcde49909f4a64b01df69041f803ebde61fb Mon Sep 17 00:00:00 2001 From: Ibrahim Jomaa Date: Fri, 7 Aug 2020 21:55:43 -0400 Subject: [PATCH 043/842] Revert "Merge branch 'chrezm-mirror' into master" This reverts commit 94c3bb4798647ee4504dd1376b9644ac080518d9, reversing changes made to c8e24d6226e4eedef5ff3fffcc1b56fdcaa92ef6. --- aocharmovie.cpp | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/aocharmovie.cpp b/aocharmovie.cpp index 6a60f75ac..6879aa5ae 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -166,27 +166,17 @@ void AOCharMovie::combo_resize(int w, int h) void AOCharMovie::frame_change(int n_frame) { - if (movie_frames.size() > n_frame) - { - QPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(n_frame)); - auto aspect_ratio = Qt::KeepAspectRatio; - - if (f_pixmap.size().width() > f_pixmap.size().height()) - aspect_ratio = Qt::KeepAspectRatioByExpanding; - - if (f_pixmap.size().width() > this->size().width() || f_pixmap.size().height() > this->size().height()) - this->setPixmap(f_pixmap.scaled(this->width(), this->height(), aspect_ratio, Qt::SmoothTransformation)); - else - this->setPixmap(f_pixmap.scaled(this->width(), this->height(), aspect_ratio, Qt::FastTransformation)); - - QLabel::move(x + (this->width() - this->pixmap()->width())/2, y); - } + if (movie_frames.size() > n_frame) + { + AOPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(n_frame)); + this->setPixmap(f_pixmap->scaled(this->width(), this->height())); + } - if (m_movie->frameCount() - 1 == n_frame && play_once) - { - preanim_timer->start(m_movie->nextFrameDelay()); - m_movie->stop(); - } + if (m_movie->frameCount() - 1 == n_frame && play_once) + { + preanim_timer->start(m_movie->nextFrameDelay()); + m_movie->stop(); + } } void AOCharMovie::timer_done() From b1d6499f57c030870c6f40677b2b0a00fe517cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sat, 8 Aug 2020 04:38:38 +0200 Subject: [PATCH 044/842] Clear messages based on chrId rather than previous message (#11) * Added AOPixmap to deal with Qt's debug 'null' messages * clear msg based on chrId - Fixed `first_person` in `config.ini` - Made message clearing based on chrId instead of previous message - The client now show duplicate messages if received * Update courtroom.cpp --- aoapplication.h | 3 ++ courtroom.cpp | 112 +++++++++++++++++++++++----------------- courtroom.h | 9 ++-- text_file_functions.cpp | 5 ++ 4 files changed, 78 insertions(+), 51 deletions(-) diff --git a/aoapplication.h b/aoapplication.h index 924ca7d17..dc51285bf 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -118,6 +118,9 @@ class AOApplication : public QApplication //Reads the theme from config.ini and loads it into the current_theme variable QString read_theme(); + //Returns a bool rate from config.ini + bool read_config_bool(QString p_name); + //Returns the blip rate from config.ini int read_blip_rate(); diff --git a/courtroom.cpp b/courtroom.cpp index 5fc27f9d1..4450d30b0 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -795,6 +795,30 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) if (mute_map.value(m_chatmessage[CHAR_ID].toInt())) return; + chatmessage_is_empty = m_chatmessage[MESSAGE] == " " || m_chatmessage[MESSAGE] == ""; + m_msg_is_first_person = false; + + // reset our ui state + if (m_cid == f_char_id && !is_system_speaking) + { + ui_ic_chat_message->clear(); + ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); + + m_objection_state = 0; + reset_shout_buttons(); + + m_effect_state = 0; + reset_effect_buttons(); + + m_wtce_current = 0; + reset_judge_wtce_buttons(); + + is_presenting_evidence = false; + ui_evidence_present->set_image("present_disabled.png"); + + m_msg_is_first_person = ao_app->read_config_bool("first_person"); + } + QString f_showname; qDebug() << "handle_chatmessage"; qDebug() << m_chatmessage[SHOWNAME] << ao_app->get_showname(char_list.at(f_char_id).name); @@ -810,8 +834,11 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) QString f_message = f_showname + ": " + m_chatmessage[MESSAGE] + "\n"; + /* if (f_message == previous_ic_message && is_system_speaking == false) return; + previous_ic_message = f_message; + */ text_state = 0; anim_state = 0; @@ -822,36 +849,6 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) // reset effect ui_vp_effect->stop(); - chatmessage_is_empty = m_chatmessage[MESSAGE] == " " || m_chatmessage[MESSAGE] == ""; - showed = true; - - if (m_chatmessage[MESSAGE] == ui_ic_chat_message->text()) - { - ui_ic_chat_message->clear(); - ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); - - m_objection_state = 0; - reset_shout_buttons(); - - m_effect_state = 0; - reset_effect_buttons(); - - m_wtce_current = 0; - reset_judge_wtce_buttons(); - - is_presenting_evidence = false; - ui_evidence_present->set_image("present_disabled.png"); - - // if(ao_app->read_config("first_person") == "true"){ - // ui_vp_player_char->hide(); - // qDebug() << "wow!"; - // } - // else ui_vp_player_char->show(); - - if(ao_app->read_config("first_person") == "true") showed = false; - else showed = true; - } - if (is_system_speaking) append_system_text(m_chatmessage[MESSAGE]); else @@ -860,8 +857,6 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) if(ao_app->read_config("enable_logging") == "true") save_textlog("[" + QTime::currentTime().toString() + "] " + f_showname + ": " + m_chatmessage[MESSAGE]); - previous_ic_message = f_message; - int 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); @@ -949,7 +944,11 @@ void Courtroom::handle_chatmessage_2() // handles IC ui_vp_chatbox->set_image_from_path(chatbox_path); } - set_scene(); + if (!m_msg_is_first_person) + { + set_scene(); + } + set_text_color(); int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); @@ -1047,13 +1046,19 @@ void Courtroom::handle_chatmessage_3() switch (f_anim_state) { case 2: - ui_vp_player_char->play_talking(f_char, f_emote, showed); + if (!m_msg_is_first_person) + { + ui_vp_player_char->play_talking(f_char, f_emote, true); + } anim_state = 2; break; default: qDebug() << "W: invalid anim_state: " << f_anim_state; case 3: - ui_vp_player_char->play_idle(f_char, f_emote, showed); + if (!m_msg_is_first_person) + { + ui_vp_player_char->play_idle(f_char, f_emote, true); + } anim_state = 3; } @@ -1293,21 +1298,29 @@ void Courtroom::play_preanim() sfx_delay_timer->start(sfx_delay); - if (!file_exists(ao_app->get_character_path(f_char) + f_preanim.toLower() + ".gif") || - preanim_duration < 0) + if (!m_msg_is_first_person) { - anim_state = 1; - preanim_done(); - qDebug() << "could not find " + ao_app->get_character_path(f_char) + f_preanim.toLower() + ".gif"; - return; - } + QString f_anim_path = ao_app->get_character_path(f_char) + f_preanim.toLower() + ".gif"; + if (file_exists(f_anim_path) && preanim_duration >= 0) + { + anim_state = 1; + ui_vp_player_char->play_pre(f_char, f_preanim, preanim_duration, true); - ui_vp_player_char->play_pre(f_char, f_preanim, preanim_duration, showed); + if (text_delay >= 0) + text_delay_timer->start(text_delay); - anim_state = 1; - if (text_delay >= 0) - text_delay_timer->start(text_delay); + // finished + return; + } + else + { + qDebug() << "could not find " + f_anim_path; + } + } + // no animation, continue + anim_state = 1; + preanim_done(); } void Courtroom::preanim_done() @@ -1400,7 +1413,12 @@ void Courtroom::chat_tick() text_state = 2; chat_tick_timer->stop(); anim_state = 3; - ui_vp_player_char->play_idle(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE], showed); + + if (!m_msg_is_first_person) + { + ui_vp_player_char->play_idle(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE], true); + } + m_string_color = ""; m_color_stack.clear(); } diff --git a/courtroom.h b/courtroom.h index 154ee269a..cfab63f14 100644 --- a/courtroom.h +++ b/courtroom.h @@ -316,16 +316,17 @@ class Courtroom : public QMainWindow bool is_muted = false; - //state of animation, 0 = objecting, 1 = preanim, 2 = talking, 3 = idle + // state of animation, 0 = objecting, 1 = preanim, 2 = talking, 3 = idle int anim_state = 3; - //state of text ticking, 0 = not yet ticking, 1 = ticking in progress, 2 = ticking done + // state of text ticking, 0 = not yet ticking, 1 = ticking in progress, 2 = ticking done int text_state = 2; - //character id, which index of the char_list the player is + // character id, which index of the char_list the player is int m_cid = -1; - bool showed = true; + // if enabled, disable showing our own sprites when we talk in ic + bool m_msg_is_first_person = false; //cid and this may differ in cases of ini-editing QString current_char = ""; diff --git a/text_file_functions.cpp b/text_file_functions.cpp index 04d5cd152..db37e3408 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -52,6 +52,11 @@ QString AOApplication::read_theme() return result; } +bool AOApplication::read_config_bool(QString p_name) +{ + return read_config(p_name) == "true"; +} + int AOApplication::read_blip_rate() { QString result = read_config("blip_rate"); From 37e27de11473736b779388b4f6050c197025f9c1 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Sat, 8 Aug 2020 00:10:21 -0400 Subject: [PATCH 045/842] Smooth Transformation for images larger than host pixmap. (#13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added AOPixmap to deal with Qt's debug 'null' messages * clear msg based on chrId - Fixed `first_person` in `config.ini` - Made message clearing based on chrId instead of previous message - The client now show duplicate messages if received * Update courtroom.cpp * Added scaleToSize method for AOPixmap **scaleToSize** Will resize and return a smoothly resized pixmap if the source is larger than the destination. Co-authored-by: Leifa♥ --- aocharmovie.cpp | 2 +- aoevidencedisplay.cpp | 2 +- aoimage.cpp | 4 ++-- aopixmap.cpp | 6 ++++++ aopixmap.h | 2 ++ 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/aocharmovie.cpp b/aocharmovie.cpp index 6879aa5ae..e410c39fd 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -169,7 +169,7 @@ void AOCharMovie::frame_change(int n_frame) if (movie_frames.size() > n_frame) { AOPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(n_frame)); - this->setPixmap(f_pixmap->scaled(this->width(), this->height())); + this->setPixmap(f_pixmap.scaleToSize(this->size())); } if (m_movie->frameCount() - 1 == n_frame && play_once) diff --git a/aoevidencedisplay.cpp b/aoevidencedisplay.cpp index 0b1234f74..fa7edb1f6 100644 --- a/aoevidencedisplay.cpp +++ b/aoevidencedisplay.cpp @@ -46,7 +46,7 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid evidence_icon->move(icon_dimensions.x, icon_dimensions.y); evidence_icon->resize(icon_dimensions.width, icon_dimensions.height); - evidence_icon->setPixmap(f_pixmap->scaled(evidence_icon->width(), evidence_icon->height(), Qt::IgnoreAspectRatio)); + evidence_icon->setPixmap(f_pixmap.scaleToSize(evidence_icon->size())); QString f_path = ao_app->get_image_path(gif_name); evidence_movie->setFileName(f_path); diff --git a/aoimage.cpp b/aoimage.cpp index 00a082d65..1f7cd5c92 100644 --- a/aoimage.cpp +++ b/aoimage.cpp @@ -13,7 +13,7 @@ void AOImage::set_image(QString p_image) { QString f_path = ao_app->get_image_path(p_image); AOPixmap f_pixmap(f_path); - this->setPixmap(f_pixmap->scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); + this->setPixmap(f_pixmap.scaleToSize(size())); // Store final path if the path exists if (file_exists(f_path)) @@ -34,7 +34,7 @@ void AOImage::set_image_from_path(QString p_path) final_path = default_path; AOPixmap f_pixmap(final_path); - this->setPixmap(f_pixmap->scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); + this->setPixmap(f_pixmap.scaleToSize(size())); // Store final path if the path exists if (file_exists(final_path)) diff --git a/aopixmap.cpp b/aopixmap.cpp index 81c3375e4..04b971477 100644 --- a/aopixmap.cpp +++ b/aopixmap.cpp @@ -10,6 +10,12 @@ AOPixmap::AOPixmap(QString p_file_path) : m_pixmap(p_file_path) } +QPixmap AOPixmap::scaleToSize(QSize p_size) +{ + bool f_is_pixmap_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); + return m_pixmap.scaled(p_size, Qt::IgnoreAspectRatio, f_is_pixmap_larger ? Qt::SmoothTransformation : Qt::FastTransformation); +} + QPixmap *AOPixmap::operator ->() { if (m_pixmap.isNull()) diff --git a/aopixmap.h b/aopixmap.h index b4b5515b2..278afdbc8 100644 --- a/aopixmap.h +++ b/aopixmap.h @@ -8,6 +8,8 @@ class AOPixmap AOPixmap(QPixmap p_pixmap); AOPixmap(QString p_file_path); + QPixmap scaleToSize(QSize p_size); + QPixmap *operator ->(); private: From 9f6a694ecc3823000bb0c85c82e9c6405f10a449 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Sat, 22 Aug 2020 17:13:06 -0400 Subject: [PATCH 046/842] Config Panel (#14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Config Panel The config panel allkows the user to modify certain values from the config.ini file and will automatically save them when the client closes. * Update aoconfig.cpp Typo <3 * Graceful disconnection when you leave a server * Update lobby - Changed lobby to prompt the user to connect to a server. - Added `Connected to ` to server description once connection has been established. * Update courtroom.cpp * Update d/c prompt Removed the prompt that happens when you ask to be brought back to lobby. It's a bit of a lazy fix, but it works * Fix compilation issues+configpanel -> config_panel * Update ao_config Made log and theme selection apply immediately * Update - Cpanel now always close if the courtroom close (whatever destroyed or simply closed) - Added `Save` to cpanel - Added webp support * Description now reads 'Connecting to' until connected+Player count hidden until successful connection (#15) * Description now reads 'Connecting to' until connected+Player count hidden until connection * Improve connecting to replacement * Updated cpanel - Added system slider - Sanitized slightly the audio system - Removed audio sliders & labels from Courtroom - Merged cycle and mod audio players into system player * Updated aocharmovie - Removed preanim duration shenanigans * Update AOCharMovie - Fixed (cheaply) a dumb bug regarding pre-animations hanging permanently * Update theme impl - Removed theme list, reload theme and confirm theme buttons from client area. - Added reload button to cpanel * Updated cpanel behaviour - Always hide the cpanel when the courtroom is closing/destroyed * Update cpanel - Added theme list refreshing after reloading theme * Update config_panel.ui * Update aoscene.cpp - Added a small fix preventing reloading of the same file * Update aoconfigpanel.cpp - Fixed theme list not clearing before refreshing Co-authored-by: Leifa♥ --- Attorney_Online_remake.pro | 32 +- aoabstractplayer.cpp | 2 +- aoabstractplayer.hpp => aoabstractplayer.h | 2 +- aoapplication.cpp | 134 ++- aoapplication.h | 401 +++---- aobasshandle.cpp | 2 +- aobasshandle.hpp => aobasshandle.h | 2 +- aocharmovie.cpp | 106 +- aocharmovie.h | 6 +- aoconfig.cpp | 379 +++++++ aoconfig.h | 75 ++ aoconfigpanel.cpp | 145 +++ aoconfigpanel.h | 65 ++ aoevidencedisplay.cpp | 4 +- aoevidencedisplay.h | 2 +- aoexception.cpp | 2 +- aoexception.hpp => aoexception.h | 0 aoguiloader.cpp | 30 + aoguiloader.h | 21 + aomovie.cpp | 2 +- aomusicplayer.h | 2 +- aonotearea.cpp | 4 +- aonotearea.hpp => aonotearea.h | 2 +- aonotepicker.cpp | 2 +- aonotepicker.hpp => aonotepicker.h | 0 aopixmap.cpp | 19 +- aopixmap.h | 7 +- aoscene.cpp | 6 +- aosfxplayer.h | 2 +- aoshoutplayer.cpp | 2 +- aoshoutplayer.hpp => aoshoutplayer.h | 2 +- aotimer.h | 2 +- audio_functions.cpp | 59 ++ courtroom.cpp | 307 ++---- courtroom.h | 1107 ++++++++++---------- courtroom_widgets.cpp | 141 +-- emotes.cpp | 2 +- lobby.cpp | 548 +++++----- lobby.h | 121 +-- main.cpp | 40 +- networkmanager.cpp | 10 +- networkmanager.h | 1 + packet_distribution.cpp | 8 - path_functions.cpp | 5 +- res.qrc | 6 + {resource => res}/fonts/Ace-Attorney.ttf | Bin res/ui/config_panel.ui | 457 ++++++++ resources.qrc | 5 - text_file_functions.cpp | 175 +--- 49 files changed, 2736 insertions(+), 1718 deletions(-) rename aoabstractplayer.hpp => aoabstractplayer.h (89%) rename aobasshandle.hpp => aobasshandle.h (92%) create mode 100644 aoconfig.cpp create mode 100644 aoconfig.h create mode 100644 aoconfigpanel.cpp create mode 100644 aoconfigpanel.h rename aoexception.hpp => aoexception.h (100%) create mode 100644 aoguiloader.cpp create mode 100644 aoguiloader.h rename aonotearea.hpp => aonotearea.h (88%) rename aonotepicker.hpp => aonotepicker.h (100%) rename aoshoutplayer.hpp => aoshoutplayer.h (85%) create mode 100644 audio_functions.cpp create mode 100644 res.qrc rename {resource => res}/fonts/Ace-Attorney.ttf (100%) create mode 100644 res/ui/config_panel.ui delete mode 100644 resources.qrc diff --git a/Attorney_Online_remake.pro b/Attorney_Online_remake.pro index c0dfd5f30..8431e7ff1 100644 --- a/Attorney_Online_remake.pro +++ b/Attorney_Online_remake.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core gui multimedia network +QT += core gui widgets uitools multimedia network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -16,9 +16,13 @@ TEMPLATE = app VERSION = 2.4.8.0 SOURCES += main.cpp\ - aopixmap.cpp \ + aoconfig.cpp \ + aoconfigpanel.cpp \ + aoguiloader.cpp \ + aopixmap.cpp \ aotimer.cpp \ - lobby.cpp \ + audio_functions.cpp \ + lobby.cpp \ text_file_functions.cpp \ path_functions.cpp \ aoimage.cpp \ @@ -62,9 +66,18 @@ SOURCES += main.cpp\ courtroom_widgets.cpp HEADERS += lobby.h \ + aoabstractplayer.h \ + aobasshandle.h \ + aoconfig.h \ + aoconfigpanel.h \ + aoexception.h \ + aoguiloader.h \ aoimage.h \ aolabel.h \ + aonotearea.h \ + aonotepicker.h \ aopixmap.h \ + aoshoutplayer.h \ aotimer.h \ file_functions.h \ aobutton.h \ @@ -94,13 +107,7 @@ HEADERS += lobby.h \ aoevidencedisplay.h \ discord_rich_presence.h \ discord-rpc.h \ - aonotepad.h \ - aobasshandle.hpp \ - aoexception.hpp \ - aoabstractplayer.hpp \ - aoshoutplayer.hpp \ - aonotearea.hpp \ - aonotepicker.hpp + aonotepad.h # 1. You need to get BASS and put the x86 bass DLL/headers in the project root folder # AND the compilation output folder. If you want a static link, you'll probably @@ -117,6 +124,9 @@ CONFIG += c++11 ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android RESOURCES += \ - resources.qrc + res.qrc DISTFILES += + +FORMS += \ + res/ui/config_panel.ui diff --git a/aoabstractplayer.cpp b/aoabstractplayer.cpp index a371a3625..59b5b0fe6 100644 --- a/aoabstractplayer.cpp +++ b/aoabstractplayer.cpp @@ -1,4 +1,4 @@ -#include "aoabstractplayer.hpp" +#include "aoabstractplayer.h" AOAbstractPlayer::AOAbstractPlayer(QObject *p_parent, AOApplication *p_ao_app) : QObject(p_parent), ao_app(p_ao_app) diff --git a/aoabstractplayer.hpp b/aoabstractplayer.h similarity index 89% rename from aoabstractplayer.hpp rename to aoabstractplayer.h index 0e7d9fc13..925fba738 100644 --- a/aoabstractplayer.hpp +++ b/aoabstractplayer.h @@ -5,7 +5,7 @@ #include #include "aoapplication.h" -#include "aobasshandle.hpp" +#include "aobasshandle.h" class AOAbstractPlayer : public QObject { diff --git a/aoapplication.cpp b/aoapplication.cpp index 707391c46..d18f0e6ae 100644 --- a/aoapplication.cpp +++ b/aoapplication.cpp @@ -5,16 +5,26 @@ #include "networkmanager.h" #include "debug_functions.h" +#include "aoconfig.h" +#include "aoconfigpanel.h" + #include #include #include AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) { - net_manager = new NetworkManager(this); - discord = new AttorneyOnline::Discord(); - QObject::connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), - SLOT(ms_connect_finished(bool, bool))); + discord = new AttorneyOnline::Discord(); + + net_manager = new NetworkManager(this); + connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), SLOT(ms_connect_finished(bool, bool))); + + config = new AOConfig(this); + connect(config, SIGNAL(theme_changed(QString)), this, SLOT(on_config_theme_changed())); + + config_panel = new AOConfigPanel; + connect(config_panel, SIGNAL(reload_theme()), this, SLOT(on_config_reload_theme_requested())); + config_panel->hide(); } AOApplication::~AOApplication() @@ -22,6 +32,7 @@ AOApplication::~AOApplication() destruct_lobby(); destruct_courtroom(); delete discord; + delete config_panel; } void AOApplication::construct_lobby() @@ -66,6 +77,8 @@ void AOApplication::construct_courtroom() } w_courtroom = new Courtroom(this); + connect(w_courtroom, SIGNAL(closing()), this, SLOT(on_courtroom_closing())); + connect(w_courtroom, SIGNAL(destroyed()), this, SLOT(on_courtroom_destroyed())); courtroom_constructed = true; QRect screenGeometry = QApplication::desktop()->screenGeometry(); @@ -76,37 +89,45 @@ void AOApplication::construct_courtroom() void AOApplication::destruct_courtroom() { - if (!courtroom_constructed) - { - qDebug() << "W: courtroom was attempted destructed when it did not exist"; - return; - } + // destruct courtroom + if (courtroom_constructed) + { + delete w_courtroom; + courtroom_constructed = false; + } + else + { + qDebug() << "W: courtroom was attempted destructed when it did not exist"; + } - delete w_courtroom; - courtroom_constructed = false; + // gracefully close our connection to the current server + net_manager->disconnect_from_server(); } QString AOApplication::get_version_string() { - return - QString::number(RELEASE) + "." + - QString::number(MAJOR_VERSION) + "." + - QString::number(MINOR_VERSION); + return QString::number(RELEASE) + "." + QString::number(MAJOR_VERSION) + "." + QString::number(MINOR_VERSION); } -void AOApplication::reload_theme() +void AOApplication::set_theme_variant(QString p_variant) { - current_theme = read_theme(); + m_theme_variant = p_variant; + emit reload_theme(); } -void AOApplication::set_theme_variant(QString theme_variant) +void AOApplication::on_config_theme_changed() { - this->theme_variant = theme_variant; + emit reload_theme(); +} + +void AOApplication::on_config_reload_theme_requested() +{ + emit reload_theme(); } void AOApplication::set_favorite_list() { - favorite_list = read_serverlist_txt(); + favorite_list = read_serverlist_txt(); } QString AOApplication::get_current_char() @@ -117,10 +138,64 @@ QString AOApplication::get_current_char() return ""; } +void AOApplication::toggle_config_panel() +{ + config_panel->setVisible(!config_panel->isVisible()); + if (config_panel->isVisible()) + { + QRect screenGeometry = QApplication::desktop()->screenGeometry(); + int x = (screenGeometry.width()-config_panel->width()) / 2; + int y = (screenGeometry.height()-config_panel->height()) / 2; + config_panel->setFocus(); + config_panel->raise(); + config_panel->move(x, y); + } +} + +bool AOApplication::get_always_pre_enabled() +{ + return config->get_bool("always_pre", true); +} + +bool AOApplication::get_first_person_enabled() +{ + return config->get_bool("first_person", false); +} + +bool AOApplication::get_chatlog_scrolldown() +{ + return config->log_goes_downward_enabled(); +} + +int AOApplication::get_chatlog_max_lines() +{ + return config->log_max_lines(); +} + +int AOApplication::get_chat_tick_interval() +{ + return config->get_number("chat_tick_interval", 60); +} + +bool AOApplication::get_chatlog_newline() +{ + return config->log_uses_newline_enabled(); +} + +bool AOApplication::get_enable_logging_enabled() +{ + return config->log_is_recording_enabled(); +} + +bool AOApplication::get_music_change_log_enabled() +{ + return config->get_bool("music_change_log", true); +} + void AOApplication::add_favorite_server(int p_server) { - if (p_server < 0 || p_server >= server_list.size()) - return; + if (p_server < 0 || p_server >= server_list.size()) + return; server_type fav_server = server_list.at(p_server); @@ -133,12 +208,11 @@ void AOApplication::add_favorite_server(int p_server) void AOApplication::server_disconnected() { - if (courtroom_constructed) - { + if (!courtroom_constructed) + return; call_notice("Disconnected from server."); construct_lobby(); destruct_courtroom(); - } } void AOApplication::loading_cancelled() @@ -176,3 +250,13 @@ void AOApplication::ms_connect_finished(bool connected, bool will_retry) } } } + +void AOApplication::on_courtroom_closing() +{ + config_panel->hide(); +} + +void AOApplication::on_courtroom_destroyed() +{ + config_panel->hide(); +} diff --git a/aoapplication.h b/aoapplication.h index dc51285bf..a39a208d2 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -6,289 +6,314 @@ #include "discord_rich_presence.h" #include -#include #include +#include class NetworkManager; class Lobby; class Courtroom; +class AOConfig; +class AOConfigPanel; class AOApplication : public QApplication { - Q_OBJECT + Q_OBJECT public: - AOApplication(int &argc, char **argv); - ~AOApplication(); + AOApplication(int &argc, char **argv); + ~AOApplication(); + + NetworkManager *net_manager = nullptr; + Lobby *w_lobby = nullptr; + Courtroom *w_courtroom = nullptr; + AttorneyOnline::Discord *discord = nullptr; + AOConfig *config = nullptr; + AOConfigPanel *config_panel = nullptr; + + bool lobby_constructed = false; + bool courtroom_constructed = false; + + void construct_lobby(); + void destruct_lobby(); + + void construct_courtroom(); + void destruct_courtroom(); + + void ms_packet_received(AOPacket *p_packet); + void server_packet_received(AOPacket *p_packet); + + void send_ms_packet(AOPacket *p_packet); + void send_server_packet(AOPacket *p_packet, bool encoded = true); + + /////////////////server metadata////////////////// + + unsigned int s_decryptor = 5; + bool encryption_needed = true; - NetworkManager *net_manager; - Lobby *w_lobby; - Courtroom *w_courtroom; - AttorneyOnline::Discord *discord; + bool yellow_text_enabled = false; + bool prezoom_enabled = false; + bool flipping_enabled = false; + bool custom_objection_enabled = false; + bool improved_loading_enabled = false; + bool desk_mod_enabled = false; + bool evidence_enabled = false; - bool lobby_constructed = false; - bool courtroom_constructed = false; + ///////////////loading info/////////////////// - void construct_lobby(); - void destruct_lobby(); + //player number, it's hardly used but might be needed for some old servers + int s_pv = 0; - void construct_courtroom(); - void destruct_courtroom(); + QString server_software = ""; - void ms_packet_received(AOPacket *p_packet); - void server_packet_received(AOPacket *p_packet); + int char_list_size = 0; + int loaded_chars = 0; + int evidence_list_size = 0; + int loaded_evidence = 0; + int music_list_size = 0; + int loaded_music = 0; - void send_ms_packet(AOPacket *p_packet); - void send_server_packet(AOPacket *p_packet, bool encoded = true); + bool courtroom_loaded = false; - /////////////////server metadata////////////////// + //////////////////versioning/////////////// - unsigned int s_decryptor = 5; - bool encryption_needed = true; + int get_release() { return RELEASE; } + int get_major_version() { return MAJOR_VERSION; } + int get_minor_version() { return MINOR_VERSION; } + QString get_version_string(); - bool yellow_text_enabled = false; - bool prezoom_enabled = false; - bool flipping_enabled = false; - bool custom_objection_enabled = false; - bool improved_loading_enabled = false; - bool desk_mod_enabled = false; - bool evidence_enabled = false; + /////////////////////////////////////////// - ///////////////loading info/////////////////// + void set_favorite_list(); + QVector &get_favorite_list() { return favorite_list; } + void add_favorite_server(int p_server); - //player number, it's hardly used but might be needed for some old servers - int s_pv = 0; + void set_server_list(); + QVector &get_server_list() { return server_list; } - QString server_software = ""; + //reads the theme from config.ini and sets it accordingly + void set_theme_name(QString p_name); - int char_list_size = 0; - int loaded_chars = 0; - int evidence_list_size = 0; - int loaded_evidence = 0; - int music_list_size = 0; - int loaded_music = 0; + //Returns the character the player has currently selected + QString get_current_char(); - bool courtroom_loaded = false; + //implementation in path_functions.cpp + QString get_base_path(); + QString get_data_path(); + QString get_theme_path(); + QString get_theme_variant_path(); + QString get_default_theme_path(); + QString get_character_path(QString p_character); + QString get_demothings_path(); + QString get_sounds_path(); + QString get_music_path(QString p_song); + QString get_background_path(); + QString get_default_background_path(); + QString get_evidence_path(); - //////////////////versioning/////////////// + ////// Functions for accessing the config panel ////// - int get_release() {return RELEASE;} - int get_major_version() {return MAJOR_VERSION;} - int get_minor_version() {return MINOR_VERSION;} - QString get_version_string(); + void toggle_config_panel(); - /////////////////////////////////////////// + ////// Functions for reading and writing files ////// + // Implementations file_functions.cpp - void set_favorite_list(); - QVector& get_favorite_list() {return favorite_list;} - void add_favorite_server(int p_server); + //Returns text from note file + QString read_note(QString filename); - void set_server_list(); - QVector& get_server_list() {return server_list;} + //Reads the theme from config.ini and loads it into the current_theme variable + QString get_theme(); - //reads the theme from config.ini and sets it accordingly - void reload_theme(); + //Returns the blip rate from config.ini + int read_blip_rate(); - //Returns the character the player has currently selected - QString get_current_char(); + // returns whatever we want newlines or ':' to be appended in front of names + // in the ic chat log + bool read_chatlog_newline(); - //implementation in path_functions.cpp - QString get_base_path(); - QString get_data_path(); - QString get_theme_path(); - QString get_theme_variant_path(); - QString get_default_theme_path(); - QString get_character_path(QString p_character); - QString get_demothings_path(); - QString get_sounds_path(); - QString get_music_path(QString p_song); - QString get_background_path(); - QString get_default_background_path(); - QString get_evidence_path(); + // returns the user name + QString get_username(); - ////// Functions for reading and writing files ////// - // Implementations file_functions.cpp + // returns a list of call words + QStringList get_call_words(); - //Returns the config value for the passed searchline from a properly formatted config ini file - QString read_config(QString searchline); + // returns whatever preanimations should always play or not + bool get_always_pre_enabled(); - //Returns text from note file - QString read_note(QString filename); + // returns whatever the client should simulate first person dialog + bool get_first_person_enabled(); - //Reads the theme from config.ini and loads it into the current_theme variable - QString read_theme(); + // returns if chatlog goes downward + bool get_chatlog_scrolldown(); - //Returns a bool rate from config.ini - bool read_config_bool(QString p_name); + int get_chatlog_max_lines(); - //Returns the blip rate from config.ini - int read_blip_rate(); + int get_chat_tick_interval(); - // returns whatever we want newlines or ':' to be appended in front of names - // in the ic chat log - bool read_chatlog_newline(); + bool get_chatlog_newline(); - //Returns true if blank blips is enabled in config.ini and false otherwise - bool get_blank_blip(); + bool get_enable_logging_enabled(); - //Returns the value of default_music in config.ini - int get_default_music(); + //Returns the value of default_sfx in config.ini + int get_default_sfx(); - //Returns the value of default_sfx in config.ini - int get_default_sfx(); + //Returns the value of default_music in config.ini + int get_default_music(); - //Returns the value of default_blip in config.ini - int get_default_blip(); + // returns whatever music is logged within the chatlog + bool get_music_change_log_enabled(); - //Returns the list of words in callwords.ini - QStringList get_call_words(); + //Returns the value of default_blip in config.ini + int get_default_blip(); - // TODO document what this does - QStringList get_sfx_list(); + //Returns true if blank blips is enabled in config.ini and false otherwise + bool get_blank_blip(); - //Appends the argument string to serverlist.txt - void write_to_serverlist_txt(QString p_line); + // TODO document what this does + QStringList get_sfx_list(); - //Writes to note file - void write_note(QString p_text, QString filename); + //Appends the argument string to serverlist.txt + void write_to_serverlist_txt(QString p_line); - //appends to note file - void append_note(QString p_line, QString filename); + //Writes to note file + void write_note(QString p_text, QString filename); - //Overwrites config.ini with new theme - void write_theme(QString theme); + //appends to note file + void append_note(QString p_line, QString filename); - //Set the theme variant - void set_theme_variant(QString theme_variant); + //Overwrites config.ini with new theme + void write_theme(QString theme); - //Returns the contents of serverlist.txt - QVector read_serverlist_txt(); + //Set the theme variant + void set_theme_variant(QString m_theme_variant); - //Returns the value of p_identifier in the design.ini file in p_design_path - QString read_design_ini(QString p_identifier, QString p_design_path); + //Returns the contents of serverlist.txt + QVector read_serverlist_txt(); - //Returns the value of p_identifier from p_file in either a theme variant subfolder, a theme folder, or default theme folder - QString read_theme_ini(QString p_identifier, QString p_file); + //Returns the value of p_identifier in the design.ini file in p_design_path + QString read_design_ini(QString p_identifier, QString p_design_path); - //Helper function for returning an int in a file inside of the theme folder - int get_design_ini_value(QString p_identifier, QString p_design_file); + //Returns the value of p_identifier from p_file in either a theme variant subfolder, a theme folder, or default theme folder + QString read_theme_ini(QString p_identifier, QString p_file); - //Returns the coordinates of widget with p_identifier from p_file - QPoint get_button_spacing(QString p_identifier, QString p_file); + //Helper function for returning an int in a file inside of the theme folder + int get_design_ini_value(QString p_identifier, QString p_design_file); - //Returns the dimensions of widget with specified identifier from p_file - pos_size_type get_element_dimensions(QString p_identifier, QString p_file); + //Returns the coordinates of widget with p_identifier from p_file + QPoint get_button_spacing(QString p_identifier, QString p_file); - //Returns the value of font_size with p_identifier from p_file - int get_font_size(QString p_identifier, QString p_file); + //Returns the dimensions of widget with specified identifier from p_file + pos_size_type get_element_dimensions(QString p_identifier, QString p_file); - //Returns the name of the font with p_identifier from p_file - QString get_font_name(QString p_identifier, QString p_file); + //Returns the value of font_size with p_identifier from p_file + int get_font_size(QString p_identifier, QString p_file); - //Returns the color with p_identifier from p_file - QColor get_color(QString p_identifier, QString p_file); + //Returns the name of the font with p_identifier from p_file + QString get_font_name(QString p_identifier, QString p_file); - //Returns the sfx with p_identifier from sounds.ini in the current theme path - QString get_sfx(QString p_identifier); + //Returns the color with p_identifier from p_file + QColor get_color(QString p_identifier, QString p_file); - //Returns the value of p_search_line within target_tag and terminator_tag - QString read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag); + //Returns the sfx with p_identifier from sounds.ini in the current theme path + QString get_sfx(QString p_identifier); - //Returns the text between target_tag and terminator_tag in p_file - QString get_stylesheet(QString target_tag, QString p_file); + //Returns the value of p_search_line within target_tag and terminator_tag + QString read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag); - //Returns string list (characters, color) from p_file - QVector get_highlight_color(); + //Returns the text between target_tag and terminator_tag in p_file + QString get_stylesheet(QString target_tag, QString p_file); - //Returns special button on cc_config according to index - QString get_spbutton(QString p_tag, int index); + //Returns string list (characters, color) from p_file + QVector get_highlight_color(); - //Returns effect on cc_config according to index - QStringList get_effect(int index); + //Returns special button on cc_config according to index + QString get_spbutton(QString p_tag, int index); - //Returns wtce on cc_config according to index - QStringList get_wtce(int index); + //Returns effect on cc_config according to index + QStringList get_effect(int index); - //Returns the side of the p_char character from that characters ini file - QString get_char_side(QString p_char); + //Returns wtce on cc_config according to index + QStringList get_wtce(int index); - //Returns the showname from the ini of p_char - QString get_showname(QString p_char); + //Returns the side of the p_char character from that characters ini file + QString get_char_side(QString p_char); - //Returns showname from showname.ini - QString read_showname(QString p_char); + //Returns the showname from the ini of p_char + QString get_showname(QString p_char); - //Returns the value of chat from the specific p_char's ini file - QString get_chat(QString p_char); + //Returns showname from showname.ini + QString read_showname(QString p_char); - //Returns the value of shouts from the specified p_char's ini file - QString get_char_shouts(QString p_char); + //Returns the value of chat from the specific p_char's ini file + QString get_chat(QString p_char); - //Returns the preanim duration of p_char's p_emote - int get_preanim_duration(QString p_char, QString p_emote); + //Returns the value of shouts from the specified p_char's ini file + QString get_char_shouts(QString p_char); - //Same as above, but only returns if it has a % in front(refer to Preanims section in the manual) - int get_ao2_preanim_duration(QString p_char, QString p_emote); + //Not in use + int get_text_delay(QString p_char, QString p_emote); - //Not in use - int get_text_delay(QString p_char, QString p_emote); + //Returns the name of p_char + QString get_char_name(QString p_char); - //Returns the name of p_char - QString get_char_name(QString p_char); + //Returns the total amount of emotes of p_char + int get_emote_number(QString p_char); - //Returns the total amount of emotes of p_char - int get_emote_number(QString p_char); + //Returns the emote comment of p_char's p_emote + QString get_emote_comment(QString p_char, int p_emote); - //Returns the emote comment of p_char's p_emote - QString get_emote_comment(QString p_char, int p_emote); + //Returns the base name of p_char's p_emote + QString get_emote(QString p_char, int p_emote); - //Returns the base name of p_char's p_emote - QString get_emote(QString p_char, int p_emote); + //Returns the preanimation name of p_char's p_emote + QString get_pre_emote(QString p_char, int p_emote); - //Returns the preanimation name of p_char's p_emote - QString get_pre_emote(QString p_char, int p_emote); + //Returns x,y offset for effect p_effect + QStringList get_effect_offset(QString p_char, int p_effect); - //Returns x,y offset for effect p_effect - QStringList get_effect_offset(QString p_char, int p_effect); + //Returns overlay at p_effect in char_path/overlay + QStringList get_overlay(QString p_char, int p_effect); - //Returns overlay at p_effect in char_path/overlay - QStringList get_overlay(QString p_char, int p_effect); + //Returns the sfx of p_char's p_emote + QString get_sfx_name(QString p_char, int p_emote); - //Returns the sfx of p_char's p_emote - QString get_sfx_name(QString p_char, int p_emote); + //Not in use + int get_sfx_delay(QString p_char, int p_emote); - //Not in use - int get_sfx_delay(QString p_char, int p_emote); + //Returns the modifier for p_char's p_emote + int get_emote_mod(QString p_char, int p_emote); - //Returns the modifier for p_char's p_emote - int get_emote_mod(QString p_char, int p_emote); + //Returns the desk modifier for p_char's p_emote + int get_desk_mod(QString p_char, int p_emote); - //Returns the desk modifier for p_char's p_emote - int get_desk_mod(QString p_char, int p_emote); + //Returns p_char's gender + QString get_gender(QString p_char); - //Returns p_char's gender - QString get_gender(QString p_char); + //Get the location of p_image, which is either in a theme variant subfolder, a theme folder, or default theme folder + QString get_image_path(QString p_image); - //Get the location of p_image, which is either in a theme variant subfolder, a theme folder, or default theme folder - QString get_image_path(QString p_image); +signals: + void reload_theme(); private: - const int RELEASE = 2; - const int MAJOR_VERSION = 4; - const int MINOR_VERSION = 8; + const int RELEASE = 2; + const int MAJOR_VERSION = 4; + const int MINOR_VERSION = 8; - QString current_theme = "default"; - QString theme_variant = ""; + QString m_theme_variant = ""; - QVector server_list; - QVector favorite_list; + QVector server_list; + QVector favorite_list; private slots: - void ms_connect_finished(bool connected, bool will_retry); + void ms_connect_finished(bool connected, bool will_retry); + void on_courtroom_closing(); + void on_courtroom_destroyed(); + void on_config_theme_changed(); + void on_config_reload_theme_requested(); public slots: - void server_disconnected(); - void loading_cancelled(); + void server_disconnected(); + void loading_cancelled(); }; #endif // AOAPPLICATION_H diff --git a/aobasshandle.cpp b/aobasshandle.cpp index 468d3c524..996d01679 100644 --- a/aobasshandle.cpp +++ b/aobasshandle.cpp @@ -1,4 +1,4 @@ -#include "aobasshandle.hpp" +#include "aobasshandle.h" AOBassHandle::AOBassHandle(QObject *p_parent) : QObject(p_parent) diff --git a/aobasshandle.hpp b/aobasshandle.h similarity index 92% rename from aobasshandle.hpp rename to aobasshandle.h index 3ff50cd26..d461c3f23 100644 --- a/aobasshandle.hpp +++ b/aobasshandle.h @@ -6,7 +6,7 @@ #include -#include "aoexception.hpp" +#include "aoexception.h" /** * @brief The AOBassHandle is a small class to play sounds. diff --git a/aocharmovie.cpp b/aocharmovie.cpp index e410c39fd..a556b4225 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -14,11 +14,11 @@ AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_ m_movie = new QMovie(this); - preanim_timer = new QTimer(this); - preanim_timer->setSingleShot(true); + m_frame_timer = new QTimer(this); + m_frame_timer->setSingleShot(true); - connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); - connect(preanim_timer, SIGNAL(timeout()), this, SLOT(timer_done())); + connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(on_frame_changed(int))); + connect(m_frame_timer, SIGNAL(timeout()), this, SLOT(timer_done())); } void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bool show) @@ -36,7 +36,7 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bo for (auto &f_file : f_paths) { bool found = false; - for (auto &ext : decltype(f_vec){".apng", ".gif", ".png"}) + for (auto &ext : decltype(f_vec){".webp", ".apng", ".gif", ".png"}) { QString fullPath = f_file + ext; found = file_exists(fullPath); @@ -66,61 +66,45 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bo movie_frames.append(f_image); f_image = reader->read(); } - delete reader; this->show(); - if (!show) this->hide(); + if (!show) + this->hide(); m_movie->start(); } -void AOCharMovie::play_pre(QString p_char, QString p_emote, int duration, bool show) +bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) { - QString gif_path = ao_app->get_character_path(p_char) + p_emote.toLower(); - - m_movie->stop(); - this->clear(); - m_movie->setFileName(gif_path); - m_movie->jumpToFrame(0); + QString f_file_path = ao_app->get_character_path(p_char) + p_emote.toLower(); + bool f_file_exist = false; - int full_duration = duration * time_mod; - int real_duration = 0; - - play_once = false; - - for (int n_frame = 0; n_frame < m_movie->frameCount(); ++n_frame) - { - real_duration += m_movie->nextFrameDelay(); - m_movie->jumpToFrame(n_frame + 1); - } - qDebug() << "full_duration: " << full_duration; - qDebug() << "real_duration: " << real_duration; - - double percentage_modifier = 100.0; - - if (real_duration != 0 && duration != 0) - { - double modifier = full_duration / static_cast(real_duration); - percentage_modifier = 100 / modifier; - - if (percentage_modifier > 100.0) - percentage_modifier = 100.0; + { // figure out what extension the animation is using + QString f_source_path = ao_app->get_character_path(p_char) + p_emote.toLower(); + for (QString &i_ext : QStringList{".webp", ".apng", ".gif", ".png"}) + { + QString f_target_path = f_source_path + i_ext; + if (file_exists(f_target_path)) + { + f_file_path = f_target_path; + f_file_exist = true; + break; + } + } } - qDebug() << "% mod: " << percentage_modifier; - if (full_duration == 0 || full_duration >= real_duration) + // play if it exist + if (f_file_exist) { + m_movie->stop(); + this->clear(); play_once = true; - } - else - { - play_once = false; - preanim_timer->start(full_duration); + m_movie->setFileName(f_file_path); + play(p_char, p_emote, "", show); } - m_movie->setSpeed(static_cast(percentage_modifier)); - play(p_char, p_emote, "", show); + return f_file_exist; } void AOCharMovie::play_talking(QString p_char, QString p_emote, bool show) @@ -129,10 +113,8 @@ void AOCharMovie::play_talking(QString p_char, QString p_emote, bool show) m_movie->stop(); this->clear(); - m_movie->setFileName(gif_path); - play_once = false; - m_movie->setSpeed(100); + m_movie->setFileName(gif_path); play(p_char, p_emote, "(b)", show); } @@ -140,12 +122,10 @@ void AOCharMovie::play_idle(QString p_char, QString p_emote, bool show) { QString gif_path = ao_app->get_character_path(p_char) + "(a)" + p_emote.toLower(); - m_movie->stop(); this->clear(); - m_movie->setFileName(gif_path); - + m_movie->stop(); play_once = false; - m_movie->setSpeed(100); + m_movie->setFileName(gif_path); play(p_char, p_emote, "(a)", show); } @@ -153,7 +133,7 @@ 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 m_movie->stop(); - preanim_timer->stop(); + m_frame_timer->stop(); this->hide(); } @@ -164,18 +144,26 @@ void AOCharMovie::combo_resize(int w, int h) m_movie->setScaledSize(f_size); } -void AOCharMovie::frame_change(int n_frame) +void AOCharMovie::on_frame_changed(int p_frame_num) { - if (movie_frames.size() > n_frame) + if (movie_frames.size() > p_frame_num) { - AOPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(n_frame)); + AOPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(p_frame_num)); this->setPixmap(f_pixmap.scaleToSize(this->size())); } - if (m_movie->frameCount() - 1 == n_frame && play_once) + // pre-anim only + if (play_once) { - preanim_timer->start(m_movie->nextFrameDelay()); - m_movie->stop(); + int f_frame_count = m_movie->frameCount(); + if (f_frame_count == 0 || p_frame_num == (f_frame_count - 1)) + { + int f_frame_delay = m_movie->nextFrameDelay(); + if (f_frame_delay < 0) + f_frame_delay = 0; + m_frame_timer->start(f_frame_delay); + m_movie->stop(); + } } } diff --git a/aocharmovie.h b/aocharmovie.h index 6132dde98..914ac1037 100644 --- a/aocharmovie.h +++ b/aocharmovie.h @@ -17,7 +17,7 @@ class AOCharMovie : public QLabel AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app); void play(QString p_char, QString p_emote, QString emote_prefix, bool show); - void play_pre(QString p_char, QString p_emote, int duration, bool show); + bool play_pre(QString p_char, QString p_emote, bool show); void play_talking(QString p_char, QString p_emote, bool show); void play_idle(QString p_char, QString p_emote, bool show); @@ -32,7 +32,7 @@ class AOCharMovie : public QLabel QMovie *m_movie; QVector movie_frames; - QTimer *preanim_timer; + QTimer *m_frame_timer; const int time_mod = 62; @@ -44,7 +44,7 @@ class AOCharMovie : public QLabel void done(); private slots: - void frame_change(int n_frame); + void on_frame_changed(int n_frame); void timer_done(); }; diff --git a/aoconfig.cpp b/aoconfig.cpp new file mode 100644 index 000000000..90e90bebb --- /dev/null +++ b/aoconfig.cpp @@ -0,0 +1,379 @@ +#include "aoconfig.h" +// qt +#include +#include +#include +#include + +/*! + We have to suffer through a lot of boilerplate code + but hey, when has ao2 ever cared? + Wait, am I using the term wrong? Nice. +*/ +class AOConfigPrivate : public QObject +{ + Q_OBJECT + + friend AOConfig; + + QSettings cfg; + // hate me more + QVector parents; + + // data + QString username; + QString callwords; + QString theme; + int log_max_lines; + bool log_goes_downward; + bool log_uses_newline; + bool log_is_recording; + int effects_volume; + int system_volume; + int music_volume; + int blips_volume; + int blip_rate; + int blank_blips; + +public: + AOConfigPrivate() : QObject(qApp), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat) + { + read_file(); + } + ~AOConfigPrivate() + { + save_file(); + } + + // setters +public slots: + void set_username(QString p_string) + { + if (username == p_string) + return; + username = p_string; + invoke_parents("username_changed", Q_ARG(QString, p_string)); + } + void set_callwords(QString p_string) + { + if (callwords == p_string) + return; + callwords = p_string; + invoke_parents("callwords_changed", Q_ARG(QString, p_string)); + } + void set_theme(QString p_string) + { + if (theme == p_string) + return; + theme = p_string; + invoke_parents("theme_changed", Q_ARG(QString, p_string)); + } + void set_log_max_lines(int p_number) + { + if (log_max_lines == p_number) + return; + log_max_lines = p_number; + invoke_parents("log_max_lines_changed", Q_ARG(int, p_number)); + } + void set_log_goes_downward(bool p_enabled) + { + if (log_goes_downward == p_enabled) + return; + log_goes_downward = p_enabled; + invoke_parents("log_goes_downward_changed", Q_ARG(bool, p_enabled)); + } + void set_log_uses_newline(bool p_enabled) + { + if (log_uses_newline == p_enabled) + return; + log_uses_newline = p_enabled; + invoke_parents("log_uses_newline_changed", Q_ARG(bool, p_enabled)); + } + void set_log_is_recording(bool p_enabled) + { + if (log_is_recording == p_enabled) + return; + log_is_recording = p_enabled; + invoke_parents("log_is_recording_changed", Q_ARG(bool, p_enabled)); + } + void set_effects_volume(int p_number) + { + if (effects_volume == p_number) + return; + effects_volume = p_number; + invoke_parents("effects_volume_changed", Q_ARG(int, p_number)); + } + void set_system_volume(int p_number) + { + if (system_volume == p_number) + return; + system_volume = p_number; + invoke_parents("system_volume_changed", Q_ARG(int, p_number)); + } + void set_music_volume(int p_number) + { + if (music_volume == p_number) + return; + music_volume = p_number; + invoke_parents("music_volume_changed", Q_ARG(int, p_number)); + } + void set_blips_volume(int p_number) + { + if (blips_volume == p_number) + return; + blips_volume = p_number; + invoke_parents("blips_volume_changed", Q_ARG(int, p_number)); + } + void set_blip_rate(int p_number) + { + if (blip_rate == p_number) + return; + blip_rate = p_number; + invoke_parents("blip_rate_changed", Q_ARG(int, p_number)); + } + void set_blank_blips(bool p_enabled) + { + if (blank_blips == p_enabled) + return; + blank_blips = p_enabled; + invoke_parents("blank_blips_changed", Q_ARG(bool, p_enabled)); + } + void read_file() + { + username = cfg.value("username").toString(); + callwords = cfg.value("callwords").toString(); + theme = cfg.value("theme", "default").toString(); + log_max_lines = cfg.value("chatlog_limit", 200).toInt(); + log_goes_downward = cfg.value("chatlog_scrolldown", true).toBool(); + log_uses_newline = cfg.value("chatlog_newline").toBool(); + log_is_recording = cfg.value("enable_logging").toBool(); + effects_volume = cfg.value("default_sfx", 50).toInt(); + system_volume = cfg.value("default_system", 50).toInt(); + music_volume = cfg.value("default_music", 50).toInt(); + blips_volume = cfg.value("default_blip", 50).toInt(); + blip_rate = cfg.value("blip_rate", 1000000000).toInt(); + blank_blips = cfg.value("blank_blips").toBool(); + } + void save_file() + { + cfg.setValue("username", username); + cfg.setValue("callwords", callwords); + cfg.setValue("theme", theme); + cfg.setValue("chatlog_limit", log_max_lines); + cfg.setValue("chatlog_scrolldown", log_goes_downward); + cfg.setValue("chatlog_newline", log_uses_newline); + cfg.setValue("enable_logging", log_is_recording); + cfg.setValue("default_sfx", effects_volume); + cfg.setValue("default_system", system_volume); + cfg.setValue("default_music", music_volume); + cfg.setValue("default_blip", blips_volume); + cfg.setValue("blip_rate", blip_rate); + cfg.setValue("blank_blips", blank_blips); + cfg.sync(); + } + +private: + void invoke_parents(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)) + { + for (QObject *i_parent : parents) + { + QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), p_arg1); + } + } +}; + +/*! + * private classes are cool + */ +static AOConfigPrivate *d = nullptr; + +AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) +{ + // init if not created yet + if (d == nullptr) + { + d = new AOConfigPrivate; + } + + // ao2 is the pinnacle of thread security + d->parents.append(this); +} + +AOConfig::~AOConfig() +{ + // totally safe! + d->parents.removeAll(this); +} + +QString AOConfig::get_string(QString p_name, QString p_default) +{ + return d->cfg.value(p_name, p_default).toString(); +} + +bool AOConfig::get_bool(QString p_name, bool p_default) +{ + return d->cfg.value(p_name, p_default).toBool(); +} + +int AOConfig::get_number(QString p_name, int p_default) +{ + return d->cfg.value(p_name, p_default).toInt(); +} + +QString AOConfig::username() +{ + return d->username; +} + +QString AOConfig::callwords() +{ + return d->callwords; +} + +QString AOConfig::theme() +{ + return d->theme; +} + +int AOConfig::log_max_lines() +{ + return d->log_max_lines; +} + +bool AOConfig::log_goes_downward_enabled() +{ + return d->log_goes_downward; +} + +bool AOConfig::log_uses_newline_enabled() +{ + return d->log_uses_newline; +} + +bool AOConfig::log_is_recording_enabled() +{ + return d->log_is_recording; +} + +int AOConfig::effects_volume() +{ + return d->effects_volume; +} + +int AOConfig::system_volume() +{ + return d->system_volume; +} + +int AOConfig::music_volume() +{ + return d->music_volume; +} + +int AOConfig::blips_volume() +{ + return d->blips_volume; +} + +int AOConfig::blip_rate() +{ + return d->blip_rate; +} + +bool AOConfig::blank_blips_enabled() +{ + return d->blank_blips; +} + +void AOConfig::set_username(QString p_string) +{ + d->set_username(p_string); +} + +void AOConfig::set_callwords(QString p_string) +{ + d->set_callwords(p_string); +} + +void AOConfig::set_theme(QString p_string) +{ + d->set_theme(p_string); +} + +void AOConfig::set_log_max_lines(int p_number) +{ + d->set_log_max_lines(p_number); +} + +void AOConfig::set_log_goes_downward(bool p_enabled) +{ + d->set_log_goes_downward(p_enabled); +} + +void AOConfig::set_log_goes_downward(int p_state) +{ + set_log_goes_downward(p_state == Qt::Checked); +} + +void AOConfig::set_log_uses_newline(bool p_enabled) +{ + d->set_log_uses_newline(p_enabled); +} + +void AOConfig::set_log_uses_newline(int p_state) +{ + set_log_uses_newline(p_state == Qt::Checked); +} + +void AOConfig::set_log_is_recording(bool p_enabled) +{ + d->set_log_is_recording(p_enabled); +} + +void AOConfig::set_log_is_recording(int p_state) +{ + set_log_is_recording(p_state == Qt::Checked); +} + +void AOConfig::set_effects_volume(int p_number) +{ + d->set_effects_volume(p_number); +} + +void AOConfig::set_system_volume(int p_number) +{ + d->set_system_volume(p_number); +} + +void AOConfig::set_music_volume(int p_number) +{ + d->set_music_volume(p_number); +} + +void AOConfig::set_blips_volume(int p_number) +{ + d->set_blips_volume(p_number); +} + +void AOConfig::set_blip_rate(int p_number) +{ + d->set_blip_rate(p_number); +} + +void AOConfig::set_blank_blips(bool p_enabled) +{ + d->set_blank_blips(p_enabled); +} + +void AOConfig::set_blank_blips(int p_state) +{ + set_blank_blips(p_state == Qt::Checked); +} + +void AOConfig::save_file() +{ + d->save_file(); +} + +// moc +#include "aoconfig.moc" diff --git a/aoconfig.h b/aoconfig.h new file mode 100644 index 000000000..8cda3d762 --- /dev/null +++ b/aoconfig.h @@ -0,0 +1,75 @@ +#ifndef AOCONFIG_H +#define AOCONFIG_H +// qt +#include + +class AOConfig : public QObject +{ + Q_OBJECT + +public: + AOConfig(QObject *p_parent = nullptr); + ~AOConfig(); + + // generic getters + QString get_string(QString p_name, QString p_default = nullptr); + bool get_bool(QString p_name, bool p_default = false); + int get_number(QString p_name, int p_default = 0); + + // getters + QString username(); + QString callwords(); + QString theme(); + int log_max_lines(); + bool log_goes_downward_enabled(); + bool log_uses_newline_enabled(); + bool log_is_recording_enabled(); + int effects_volume(); + int system_volume(); + int music_volume(); + int blips_volume(); + int blip_rate(); + bool blank_blips_enabled(); + + // io +public slots: + void save_file(); + + // setters +public slots: + void set_username(QString p_string); + void set_callwords(QString p_string); + void set_theme(QString p_string); + void set_log_max_lines(int p_number); + void set_log_goes_downward(bool p_enabled); + void set_log_goes_downward(int p_state); + void set_log_uses_newline(bool p_enabled); + void set_log_uses_newline(int p_state); + void set_log_is_recording(bool p_enabled); + void set_log_is_recording(int p_state); + void set_effects_volume(int p_number); + void set_system_volume(int p_number); + void set_music_volume(int p_number); + void set_blips_volume(int p_number); + void set_blip_rate(int p_number); + void set_blank_blips(bool p_enabled); + void set_blank_blips(int p_state); + + // signals +signals: + void username_changed(QString); + void callwords_changed(QString); + void theme_changed(QString); + void log_max_lines_changed(int); + void log_goes_downward_changed(bool); + void log_uses_newline_changed(bool); + void log_is_recording_changed(bool); + void effects_volume_changed(int); + void system_volume_changed(int); + void music_volume_changed(int); + void blips_volume_changed(int); + void blip_rate_changed(int); + void blank_blips_changed(bool); +}; + +#endif // AOCONFIG_H diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp new file mode 100644 index 000000000..abe547c35 --- /dev/null +++ b/aoconfigpanel.cpp @@ -0,0 +1,145 @@ +#include "aoconfigpanel.h" +// qt +#include +#include + +AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(new AOConfig(this)) +{ + setWindowTitle(tr("Config")); + setWindowFlag(Qt::WindowMinMaxButtonsHint, false); + + AOGuiLoader loader; + loader.load_from_file(":res/ui/config_panel.ui", this); + + // tab + setFocusProxy(AO_GUI_WIDGET(QTabWidget, "tab_widget")); + + // save + w_save = AO_GUI_WIDGET(QPushButton, "save"); + + // general + w_username = AO_GUI_WIDGET(QLineEdit, "username"); + w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); + w_theme = AO_GUI_WIDGET(QComboBox, "theme"); + w_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); + w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); + w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); + w_log_goes_downward = AO_GUI_WIDGET(QCheckBox, "log_downward"); + w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); + + // audio + w_effects = AO_GUI_WIDGET(QSlider, "effects"); + w_effects_value = AO_GUI_WIDGET(QLabel, "effects_value"); + w_system = AO_GUI_WIDGET(QSlider, "system"); + w_system_value = AO_GUI_WIDGET(QLabel, "system_value"); + w_music = AO_GUI_WIDGET(QSlider, "music"); + w_music_value = AO_GUI_WIDGET(QLabel, "music_value"); + w_blips = AO_GUI_WIDGET(QSlider, "blips"); + w_blips_value = AO_GUI_WIDGET(QLabel, "blips_value"); + w_blip_rate = AO_GUI_WIDGET(QSpinBox, "blip_rate"); + w_blank_blips = AO_GUI_WIDGET(QCheckBox, "blank_blips"); + + // themes + refresh_theme_list(); + + // input + connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); + connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); + connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); + connect(m_config, SIGNAL(log_goes_downward_changed(bool)), w_log_goes_downward, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(effects_volume_changed(int)), w_effects, SLOT(setValue(int))); + connect(m_config, SIGNAL(system_volume_changed(int)), w_system, SLOT(setValue(int))); + connect(m_config, SIGNAL(music_volume_changed(int)), w_music, SLOT(setValue(int))); + connect(m_config, SIGNAL(blips_volume_changed(int)), w_blips, SLOT(setValue(int))); + connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, SLOT(setValue(int))); + connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, SLOT(setChecked(bool))); + + // output + connect(w_save, SIGNAL(clicked()), m_config, SLOT(save_file())); + connect(w_username, SIGNAL(textEdited(QString)), m_config, SLOT(set_username(QString))); + connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); + connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); + connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); + connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); + connect(w_log_goes_downward, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_goes_downward(int))); + connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_uses_newline(int))); + connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_is_recording(int))); + connect(w_effects, SIGNAL(valueChanged(int)), m_config, SLOT(set_effects_volume(int))); + connect(w_effects, SIGNAL(valueChanged(int)), this, SLOT(on_effects_value_changed(int))); + connect(w_system, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_volume(int))); + connect(w_system, SIGNAL(valueChanged(int)), this, SLOT(on_system_value_changed(int))); + connect(w_music, SIGNAL(valueChanged(int)), m_config, SLOT(set_music_volume(int))); + connect(w_music, SIGNAL(valueChanged(int)), this, SLOT(on_music_value_changed(int))); + connect(w_blips, SIGNAL(valueChanged(int)), m_config, SLOT(set_blips_volume(int))); + connect(w_blips, SIGNAL(valueChanged(int)), this, SLOT(on_blips_value_changed(int))); + connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_rate(int))); + connect(w_blank_blips, SIGNAL(stateChanged(int)), m_config, SLOT(set_blank_blips(int))); + + // set values + w_username->setText(m_config->username()); + w_callwords->setText(m_config->callwords()); + w_theme->setCurrentText(m_config->theme()); + w_log_max_lines->setValue(m_config->log_max_lines()); + w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); + w_log_goes_downward->setChecked(m_config->log_goes_downward_enabled()); + w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); + w_effects->setValue(m_config->effects_volume()); + w_system->setValue(m_config->system_volume()); + w_music->setValue(m_config->music_volume()); + w_blips->setValue(m_config->blips_volume()); + w_blip_rate->setValue(m_config->blip_rate()); + w_blank_blips->setChecked(m_config->blank_blips_enabled()); +} + +void AOConfigPanel::refresh_theme_list() +{ + const QString p_prev_text = w_theme->currentText(); + + // block signals + w_theme->blockSignals(true); + w_theme->clear(); + + // themes + for (QString i_folder : QDir(QDir::currentPath() + "/base/themes").entryList()) + { + if (i_folder == "." || i_folder == "..") + continue; + w_theme->addItem(i_folder); + } + + // restore previous selection + w_theme->setCurrentText(p_prev_text); + + // unblock + w_theme->blockSignals(false); +} + +void AOConfigPanel::on_reload_theme_clicked() +{ + qDebug() << "reload theme clicked"; + refresh_theme_list(); + emit reload_theme(); +} + +void AOConfigPanel::on_effects_value_changed(int p_num) +{ + w_effects_value->setText(QString::number(p_num) + "%"); +} + +void AOConfigPanel::on_system_value_changed(int p_num) +{ + w_system_value->setText(QString::number(p_num) + "%"); +} + +void AOConfigPanel::on_music_value_changed(int p_num) +{ + w_music_value->setText(QString::number(p_num) + "%"); +} + +void AOConfigPanel::on_blips_value_changed(int p_num) +{ + w_blips_value->setText(QString::number(p_num) + "%"); +} diff --git a/aoconfigpanel.h b/aoconfigpanel.h new file mode 100644 index 000000000..389a56d5d --- /dev/null +++ b/aoconfigpanel.h @@ -0,0 +1,65 @@ +#ifndef AOCONFIGPANEL_H +#define AOCONFIGPANEL_H +// qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +// src +#include "aoguiloader.h" +#include "aoconfig.h" + +class AOConfigPanel : public QWidget +{ + Q_OBJECT + + AOConfig *m_config = nullptr; + + // general + QLineEdit *w_username = nullptr; + QLineEdit *w_callwords = nullptr; + QComboBox *w_theme = nullptr; + QPushButton *w_reload_theme = nullptr; + QSpinBox *w_log_max_lines = nullptr; + QCheckBox *w_log_uses_newline = nullptr; + QCheckBox *w_log_goes_downward = nullptr; + QCheckBox *w_log_is_recording = nullptr; + + // audio + QSlider *w_effects = nullptr; + QLabel *w_effects_value = nullptr; + QSlider *w_system = nullptr; + QLabel *w_system_value = nullptr; + QSlider *w_music = nullptr; + QLabel *w_music_value = nullptr; + QSlider *w_blips = nullptr; + QLabel *w_blips_value = nullptr; + QSpinBox *w_blip_rate = nullptr; + QCheckBox *w_blank_blips = nullptr; + + // save + QPushButton *w_save = nullptr; + +public: + AOConfigPanel(QWidget *p_parent = nullptr); + +signals: + void reload_theme(); + +private: + void refresh_theme_list(); + +private slots: + void on_reload_theme_clicked(); + void on_effects_value_changed(int p_num); + void on_system_value_changed(int p_num); + void on_music_value_changed(int p_num); + void on_blips_value_changed(int p_num); +}; + +#endif // AOCONFIGPANEL_H diff --git a/aoevidencedisplay.cpp b/aoevidencedisplay.cpp index fa7edb1f6..90c93a9c4 100644 --- a/aoevidencedisplay.cpp +++ b/aoevidencedisplay.cpp @@ -17,12 +17,10 @@ AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) connect(evidence_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); } -void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_side, int p_volume) +void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_side) { this->reset(); - sfx_player->set_volume(p_volume); - QString f_evidence_path = ao_app->get_evidence_path() + p_evidence_image; AOPixmap f_pixmap(f_evidence_path); diff --git a/aoevidencedisplay.h b/aoevidencedisplay.h index 9babffa96..199012100 100644 --- a/aoevidencedisplay.h +++ b/aoevidencedisplay.h @@ -15,7 +15,7 @@ class AOEvidenceDisplay : public QLabel public: AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app); - void show_evidence(QString p_evidence_image, bool is_left_side, int p_volume); + void show_evidence(QString p_evidence_image, bool is_left_side); QLabel* get_evidence_icon(); void reset(); diff --git a/aoexception.cpp b/aoexception.cpp index 93618dd9f..334f1275a 100644 --- a/aoexception.cpp +++ b/aoexception.cpp @@ -1,4 +1,4 @@ -#include "aoexception.hpp" +#include "aoexception.h" AOException::AOException(QString p_msg) : m_msg(p_msg) diff --git a/aoexception.hpp b/aoexception.h similarity index 100% rename from aoexception.hpp rename to aoexception.h diff --git a/aoguiloader.cpp b/aoguiloader.cpp new file mode 100644 index 000000000..e657a790e --- /dev/null +++ b/aoguiloader.cpp @@ -0,0 +1,30 @@ +#include "aoguiloader.h" +// qt +#include +#include + +AOGuiLoader::AOGuiLoader(QObject *p_parent) : QUiLoader(p_parent) +{ + // padding +} + +QWidget *AOGuiLoader::load_from_file(QString p_file_path, QWidget *p_parent) +{ + QWidget *r_widget = nullptr; + + QFile f_file(p_file_path); + if (f_file.open(QIODevice::ReadOnly)) + { + r_widget = load(&f_file, p_parent); + + // lazily replace the parent's layout with our own + if (p_parent != nullptr) + { + QVBoxLayout *f_parent_layout = new QVBoxLayout(p_parent); + f_parent_layout->addWidget(r_widget); + p_parent->setLayout(f_parent_layout); + } + } + + return r_widget; +} diff --git a/aoguiloader.h b/aoguiloader.h new file mode 100644 index 000000000..d2fc965a4 --- /dev/null +++ b/aoguiloader.h @@ -0,0 +1,21 @@ +#ifndef AOGUILOADER_H +#define AOGUILOADER_H +// qt +#include +#include + +// we could make a smarter system but let's keep it simple ;) +#define AO_GUI_WIDGET(p_type, p_name) \ + findChild(p_name) + +class AOGuiLoader : public QUiLoader +{ + Q_OBJECT + +public: + AOGuiLoader(QObject *p_parent = nullptr); + + QWidget *load_from_file(QString p_file_path, QWidget *p_parent = nullptr); +}; + +#endif diff --git a/aomovie.cpp b/aomovie.cpp index 2c1fecc84..af8d681c3 100644 --- a/aomovie.cpp +++ b/aomovie.cpp @@ -58,7 +58,7 @@ void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) for(auto &f_file : f_paths) { bool found = false; - for (auto &ext : decltype(f_vec){".apng", ".gif", ".png"}) + for (auto &ext : decltype(f_vec){".webp", ".apng", ".gif", ".png"}) { QString fullPath = f_file + ext; found = file_exists(fullPath); diff --git a/aomusicplayer.h b/aomusicplayer.h index ed2cc05e5..f8f48716a 100644 --- a/aomusicplayer.h +++ b/aomusicplayer.h @@ -1,7 +1,7 @@ #ifndef AOMUSICPLAYER_H #define AOMUSICPLAYER_H -#include "aoabstractplayer.hpp" +#include "aoabstractplayer.h" class AOMusicPlayer : public AOAbstractPlayer { diff --git a/aonotearea.cpp b/aonotearea.cpp index 4480efe6e..679aa3756 100644 --- a/aonotearea.cpp +++ b/aonotearea.cpp @@ -1,5 +1,5 @@ -#include "aonotepicker.hpp" -#include "aonotearea.hpp" +#include "aonotepicker.h" +#include "aonotearea.h" #include "courtroom.h" diff --git a/aonotearea.hpp b/aonotearea.h similarity index 88% rename from aonotearea.hpp rename to aonotearea.h index 4b1120e76..69a86ccbe 100644 --- a/aonotearea.hpp +++ b/aonotearea.h @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include "aoapplication.h" diff --git a/aonotepicker.cpp b/aonotepicker.cpp index 5e0103072..92dc067aa 100644 --- a/aonotepicker.cpp +++ b/aonotepicker.cpp @@ -1,4 +1,4 @@ -#include "aonotepicker.hpp" +#include "aonotepicker.h" #include "courtroom.h" diff --git a/aonotepicker.hpp b/aonotepicker.h similarity index 100% rename from aonotepicker.hpp rename to aonotepicker.h diff --git a/aopixmap.cpp b/aopixmap.cpp index 04b971477..4832bdb1d 100644 --- a/aopixmap.cpp +++ b/aopixmap.cpp @@ -2,10 +2,14 @@ AOPixmap::AOPixmap(QPixmap p_pixmap) : m_pixmap(p_pixmap) { - + if (m_pixmap.isNull()) + { + m_pixmap = QPixmap(1, 1); + m_pixmap.fill(Qt::transparent); + } } -AOPixmap::AOPixmap(QString p_file_path) : m_pixmap(p_file_path) +AOPixmap::AOPixmap(QString p_file_path) : AOPixmap(QPixmap(p_file_path)) { } @@ -15,14 +19,3 @@ QPixmap AOPixmap::scaleToSize(QSize p_size) bool f_is_pixmap_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); return m_pixmap.scaled(p_size, Qt::IgnoreAspectRatio, f_is_pixmap_larger ? Qt::SmoothTransformation : Qt::FastTransformation); } - -QPixmap *AOPixmap::operator ->() -{ - if (m_pixmap.isNull()) - { - m_pixmap = QPixmap(1, 1); - m_pixmap.fill(Qt::transparent); - } - - return &m_pixmap; -} diff --git a/aopixmap.h b/aopixmap.h index 278afdbc8..149eafe4d 100644 --- a/aopixmap.h +++ b/aopixmap.h @@ -1,4 +1,5 @@ -#pragma once +#ifndef AOPIXMAP_H +#define AOPIXMAP_H // qt #include @@ -10,8 +11,8 @@ class AOPixmap QPixmap scaleToSize(QSize p_size); - QPixmap *operator ->(); - private: QPixmap m_pixmap; }; + +#endif diff --git a/aoscene.cpp b/aoscene.cpp index 50dc56a5f..06ffe74d1 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -19,7 +19,7 @@ void AOScene::set_image(QString p_image) // background specific path QString background_path = ao_app->get_background_path() + p_image; - for (auto &ext : QVector{".apng", ".gif", ".png"}) + for (auto &ext : QVector{".webp", ".apng", ".gif", ".png"}) { QString full_background_path = background_path + ext; @@ -30,6 +30,10 @@ void AOScene::set_image(QString p_image) } } + // do not update the movie if we're using the same file + if (m_movie && m_movie->fileName() == target_path) + return; + // clear previous this->clear(); diff --git a/aosfxplayer.h b/aosfxplayer.h index 57ee3aeac..ceb358c92 100644 --- a/aosfxplayer.h +++ b/aosfxplayer.h @@ -1,7 +1,7 @@ #ifndef AOSFXPLAYER_H #define AOSFXPLAYER_H -#include "aoabstractplayer.hpp" +#include "aoabstractplayer.h" class AOSfxPlayer : public AOAbstractPlayer { diff --git a/aoshoutplayer.cpp b/aoshoutplayer.cpp index 16aecf062..70c6eeb5a 100644 --- a/aoshoutplayer.cpp +++ b/aoshoutplayer.cpp @@ -1,4 +1,4 @@ -#include "aoshoutplayer.hpp" +#include "aoshoutplayer.h" #include "file_functions.h" #include diff --git a/aoshoutplayer.hpp b/aoshoutplayer.h similarity index 85% rename from aoshoutplayer.hpp rename to aoshoutplayer.h index b5d7b4e22..de55a2786 100644 --- a/aoshoutplayer.hpp +++ b/aoshoutplayer.h @@ -1,7 +1,7 @@ #ifndef AOSHOUTPLAYER_HPP #define AOSHOUTPLAYER_HPP -#include "aoabstractplayer.hpp" +#include "aoabstractplayer.h" class AOShoutPlayer : public AOAbstractPlayer { diff --git a/aotimer.h b/aotimer.h index 5036739fc..698bddcc8 100644 --- a/aotimer.h +++ b/aotimer.h @@ -55,4 +55,4 @@ public slots: void set_fast_forward_mode(); }; -#endif +#endif // AOTIMER_H diff --git a/audio_functions.cpp b/audio_functions.cpp new file mode 100644 index 000000000..38e4afa61 --- /dev/null +++ b/audio_functions.cpp @@ -0,0 +1,59 @@ +#include "courtroom.h" + +bool Courtroom::is_audio_muted() +{ + return m_audio_mute; +} + +void Courtroom::set_audio_mute_enabled(bool p_enabled) +{ + if (m_audio_mute == p_enabled) + return; + m_audio_mute = p_enabled; + + // restore volume + set_effects_volume(ao_config->effects_volume()); + set_music_volume(ao_config->music_volume()); + set_blips_volume(ao_config->blips_volume()); +} + +void Courtroom::set_effects_volume(int p_volume) +{ + m_effects_player->set_volume(is_audio_muted() ? 0 : p_volume); + m_shouts_player->set_volume(is_audio_muted() ? 0 : p_volume); +} + +void Courtroom::set_system_volume(int p_volume) +{ + m_system_player->set_volume(p_volume); +} + +void Courtroom::set_music_volume(int p_volume) +{ + m_music_player->set_volume(is_audio_muted() ? 0 : p_volume); +} + +void Courtroom::set_blips_volume(int p_volume) +{ + m_blips_player->set_volume(is_audio_muted() ? 0 : p_volume); +} + +void Courtroom::on_config_effects_volume_changed(int p_volume) +{ + set_effects_volume(p_volume); +} + +void Courtroom::on_config_system_volume_changed(int p_volume) +{ + set_system_volume(p_volume); +} + +void Courtroom::on_config_music_volume_changed(int p_volume) +{ + set_music_volume(p_volume); +} + +void Courtroom::on_config_blips_volume_changed(int p_volume) +{ + set_blips_volume(p_volume); +} diff --git a/courtroom.cpp b/courtroom.cpp index 4450d30b0..55fd10ae8 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -24,6 +24,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; + ao_config = new AOConfig(this); //initializing sound device BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, 0, NULL); @@ -90,29 +91,8 @@ void Courtroom::enter_courtroom(int p_cid) m_wtce_current = 0; reset_judge_wtce_buttons(); - // forward declaration for a possible update of the chatlog - bool chatlog_changed = false; - - int chatlog_limit = ao_app->read_config("chatlog_limit").toInt(); - // default chatlog_limit? - chatlog_limit = chatlog_limit <= 0 ? 200 : chatlog_limit; // TODO declare the default somewhere so it's not a magic number - if (chatlog_limit < m_chatlog_limit) // only update if we need to chop away records - chatlog_changed = true; - m_chatlog_limit = chatlog_limit; - - bool chatlog_scrolldown = ao_app->read_config("chatlog_scrolldown") == "true"; - if (m_chatlog_scrolldown != chatlog_scrolldown) - chatlog_changed = true; - m_chatlog_scrolldown = chatlog_scrolldown; - - bool chatlog_newline = ao_app->read_chatlog_newline(); - if (m_chatlog_newline != chatlog_newline) - chatlog_changed = true; - m_chatlog_newline = chatlog_newline; - - // refresh the log if needed - if (chatlog_changed) - update_ic_log(chatlog_changed); + // setup chat + on_chat_config_changed(); set_evidence_page(); @@ -149,15 +129,11 @@ void Courtroom::enter_courtroom(int p_cid) list_music(); list_areas(); list_sfx(); - list_themes(); ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors - m_music_player->set_volume(ui_music_slider->value()); - m_sfx_player->set_volume(ui_sfx_slider->value()); - m_cycle_player->set_volume(ui_sfx_slider->value()); - m_shout_player->set_volume(ui_sfx_slider->value()); - m_blip_player->set_volume(ui_blip_slider->value()); + // unmute audio + set_audio_mute_enabled(false); testimony_in_progress = false; @@ -165,9 +141,7 @@ void Courtroom::enter_courtroom(int p_cid) ui_char_select_background->hide(); - bool ok; // ok will be set to false and this will return 0 if goes bad - chat_tick_interval = ao_app->read_config("chat_tick_interval").toInt(&ok, 10); - if(!ok) chat_tick_interval = 60; + chat_tick_interval = ao_app->get_chat_tick_interval(); ui_ic_chat_message->setEnabled(m_cid != -1); ui_ic_chat_message->setFocus(); @@ -180,10 +154,7 @@ void Courtroom::done_received() { m_cid = -1; - m_music_player->set_volume(0); - m_sfx_player->set_volume(0); - m_shout_player->set_volume(0); - m_blip_player->set_volume(0); + set_audio_mute_enabled(true); set_char_select_page(); @@ -258,7 +229,7 @@ void Courtroom::set_scene() } QString bg_path = get_background_path(); - QVector exts{".apng", ".gif", ".png"}; + QVector exts{".webp", ".apng", ".gif", ".png"}; bool has_all_desks; if (file_exists(bg_path + "defensedesk", exts) == "") @@ -364,7 +335,7 @@ void Courtroom::handle_clock(QString time) void Courtroom::handle_theme_variant(QString theme_variant) { ao_app->set_theme_variant(theme_variant); - on_reload_theme_clicked(); + on_app_reload_theme_requested(); } void Courtroom::list_music() { @@ -551,38 +522,16 @@ void Courtroom::save_textlog(QString p_text) ao_app->append_note(p_text, f_file); } -void Courtroom::list_themes() -{ - QString prev_theme_name = ui_theme_list->currentText(); - QString themes = ao_app->get_base_path() + "themes/"; - QDir dir(themes); - ui_theme_list->clear(); - - QStringList lis = dir.entryList(); - for(QString s : lis) - { - if(s != "." && s != "..") - ui_theme_list->addItem(s); - } - - ui_theme_list->setCurrentText(prev_theme_name); -} - -void Courtroom::append_ms_chatmessage(QString f_name, QString f_message) -{ - ui_ms_chatlog->append_chatmessage(f_name, f_message); -} - void Courtroom::append_server_chatmessage(QString p_name, QString p_message) { ui_server_chatlog->append_chatmessage(p_name, p_message); - if(ao_app->read_config("enable_logging") == "true") + if(ao_config->log_is_recording_enabled()) save_textlog("(OOC)[" + QTime::currentTime().toString() + "] " + p_name + ": " + p_message); } void Courtroom::on_chat_return_pressed() { - if (ui_ic_chat_message->text() == "" || is_muted) + if (ui_ic_chat_message->text() == "" || is_client_muted) return; if ((anim_state < 3 || text_state < 2) && @@ -732,40 +681,6 @@ void Courtroom::on_chat_return_pressed() ao_app->send_server_packet(new AOPacket("MS", packet_contents)); } -void Courtroom::handle_char_anim(AOCharMovie *charPlayer) -{ - int time = ao_app->read_config("opacity_time").toInt(); - int op = ao_app->read_config("char_opacity").toInt(); - - QGraphicsOpacityEffect *opacity = new QGraphicsOpacityEffect; - QPropertyAnimation *anim = new QPropertyAnimation(opacity, "opacity"); - - charPlayer->setGraphicsEffect(opacity); - - anim->setDuration(time); - anim->setStartValue(1.0); - anim->setEndValue(op); - anim->setEasingCurve(QEasingCurve::InCubic); - anim->start(); -} - -void Courtroom::handle_char_anim_2(AOCharMovie *charPlayer) -{ - int time = ao_app->read_config("opacity_time").toInt(); - int op = ao_app->read_config("char_opacity").toInt(); - - QGraphicsOpacityEffect *opacity = new QGraphicsOpacityEffect; - QPropertyAnimation *anim = new QPropertyAnimation(opacity, "opacity"); - - charPlayer->setGraphicsEffect(opacity); - - anim->setDuration(time); - anim->setStartValue(op); - anim->setEndValue(1.0); - anim->setEasingCurve(QEasingCurve::InCubic); - anim->start(); -} - void Courtroom::handle_chatmessage(QStringList *p_contents) { if (p_contents->size() < 15) @@ -816,7 +731,7 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) is_presenting_evidence = false; ui_evidence_present->set_image("present_disabled.png"); - m_msg_is_first_person = ao_app->read_config_bool("first_person"); + m_msg_is_first_person = ao_app->get_first_person_enabled(); } QString f_showname; @@ -854,7 +769,7 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) else append_ic_text(f_showname, m_chatmessage[MESSAGE], false); - if(ao_app->read_config("enable_logging") == "true") + if(ao_config->log_is_recording_enabled()) save_textlog("[" + QTime::currentTime().toString() + "] " + f_showname + ": " + m_chatmessage[MESSAGE]); int objection_mod = m_chatmessage[OBJECTION_MOD].toInt(); @@ -872,7 +787,7 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) if(objection_mod >= 1 && objection_mod <= ui_shouts.size() && ui_shouts.size() > 0) // check to prevent crashing { ui_vp_objection->play(shout_names.at(objection_mod-1), f_char, f_custom_theme); - m_shout_player->play(shout_names.at(objection_mod-1) + ".wav", f_char); + m_shouts_player->play(shout_names.at(objection_mod-1) + ".wav", f_char); } else qDebug() << "W: Shout identifier unknown" << objection_mod; @@ -958,7 +873,6 @@ void Courtroom::handle_chatmessage_2() // handles IC else ui_vp_player_char->set_flipped(false); - switch (emote_mod) { case 1: case 2: case 6: @@ -987,7 +901,7 @@ void Courtroom::handle_chatmessage_3() QString f_image = local_evidence_list.at(f_evi_id - 1).image; //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"); - ui_vp_evidence_display->show_evidence(f_image, is_left_side, ui_sfx_slider->value()); + ui_vp_evidence_display->show_evidence(f_image, is_left_side); } int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); @@ -1077,7 +991,7 @@ void Courtroom::handle_chatmessage_3() { if (overlay_sfx == "") overlay_sfx = ao_app->get_sfx("effect_flash"); - m_sfx_player->play(overlay_sfx); + m_effects_player->play(overlay_sfx); ui_vp_effect->set_play_once(true); if (overlay_name == "") overlay_name = "effect_flash"; @@ -1105,7 +1019,7 @@ void Courtroom::handle_chatmessage_3() if (overlay_sfx == "") overlay_sfx = ao_app->get_sfx(s_eff); // qDebug() << overlay_sfx << ao_app->get_sfx(s_eff); - m_sfx_player->play(overlay_sfx); + m_effects_player->play(overlay_sfx); ui_vp_effect->set_play_once(once); if (overlay_name == "") overlay_name = s_eff; @@ -1119,7 +1033,7 @@ void Courtroom::handle_chatmessage_3() { if (f_message.contains(word, Qt::CaseInsensitive)) { - m_mod_player->play(ao_app->get_sfx("word_call")); + m_system_player->play(ao_app->get_sfx("word_call")); ao_app->alert(this); break; @@ -1128,6 +1042,33 @@ void Courtroom::handle_chatmessage_3() } +void Courtroom::on_chat_config_changed() +{ + // forward declaration for a possible update of the chatlog + bool chatlog_changed = false; + + int chatlog_limit = ao_config->log_max_lines(); + // default chatlog_limit? + chatlog_limit = chatlog_limit <= 0 ? 200 : chatlog_limit; // TODO declare the default somewhere so it's not a magic number + if (chatlog_limit < m_chatlog_limit) // only update if we need to chop away records + chatlog_changed = true; + m_chatlog_limit = chatlog_limit; + + bool chatlog_scrolldown = ao_config->log_goes_downward_enabled(); + if (m_chatlog_scrolldown != chatlog_scrolldown) + chatlog_changed = true; + m_chatlog_scrolldown = chatlog_scrolldown; + + bool chatlog_newline = ao_config->log_uses_newline_enabled(); + if (m_chatlog_newline != chatlog_newline) + chatlog_changed = true; + m_chatlog_newline = chatlog_newline; + + // refresh the log if needed + if (chatlog_changed) + update_ic_log(chatlog_changed); +} + void Courtroom::update_ic_log(bool p_reset_log) { // resize if needed @@ -1285,27 +1226,19 @@ void Courtroom::play_preanim() 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 sfx_delay = m_chatmessage[SFX_DELAY].toInt() * 60; - int preanim_duration; - - if (ao2_duration < 0) - preanim_duration = ao_app->get_preanim_duration(f_char, f_preanim); - else - preanim_duration = ao2_duration; - sfx_delay_timer->start(sfx_delay); + // set state + anim_state = 1; + if (!m_msg_is_first_person) { - QString f_anim_path = ao_app->get_character_path(f_char) + f_preanim.toLower() + ".gif"; - if (file_exists(f_anim_path) && preanim_duration >= 0) + QString f_anim_path = ao_app->get_character_path(f_char) + f_preanim.toLower(); + if (ui_vp_player_char->play_pre(f_char, f_preanim, true)) { - anim_state = 1; - ui_vp_player_char->play_pre(f_char, f_preanim, preanim_duration, true); - if (text_delay >= 0) text_delay_timer->start(text_delay); @@ -1319,7 +1252,6 @@ void Courtroom::play_preanim() } // no animation, continue - anim_state = 1; preanim_done(); } @@ -1388,7 +1320,7 @@ void Courtroom::start_chat_ticking() QString f_gender = ao_app->get_gender(m_chatmessage[CHAR_NAME]); // m_blip_player->set_file(f_gender); - m_blip_player->set_blips("sfx-blip" + f_gender + ".wav"); + m_blips_player->set_blips("sfx-blip" + f_gender + ".wav"); //means text is currently ticking text_state = 1; @@ -1515,19 +1447,16 @@ void Courtroom::chat_tick() QScrollBar *scroll = ui_vp_message->verticalScrollBar(); scroll->setValue(scroll->maximum()); - if(blank_blip) - qDebug() << "blank_blip found true"; - - if ((f_message.at(tick_pos) != ' ' || blank_blip)) + if ((f_message.at(tick_pos) != ' ' || ao_config->blank_blips_enabled())) { - if (blip_pos % blip_rate == 0) + if (blip_pos % ao_app->read_blip_rate() == 0) { blip_pos = 0; // play blip // m_blip_player->play(); - m_blip_player->blip_tick(); + m_blips_player->blip_tick(); } ++blip_pos; @@ -1568,7 +1497,7 @@ void Courtroom::play_sfx() QString f_ext = file_exists(general_path + sfx_name, extensions); - m_sfx_player->play(sfx_name + f_ext); + m_effects_player->play(sfx_name + f_ext); } void Courtroom::set_text_color() @@ -1635,7 +1564,7 @@ void Courtroom::set_mute(bool p_muted, int p_cid) ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); ui_muted->set_image("muted.png"); - is_muted = p_muted; + is_client_muted = p_muted; ui_ic_chat_message->setEnabled(!p_muted); } @@ -1700,9 +1629,7 @@ void Courtroom::handle_song(QStringList *p_contents) if (!mute_map.value(n_char)) { - QString music_change_log = ao_app->read_config("music_change_log"); - - if (music_change_log == ("false")) + if (ao_app->get_music_change_log_enabled()) { m_music_player->play(f_song); } @@ -1732,7 +1659,7 @@ void Courtroom::handle_wtce(QString p_wtce) p_wtce.chop(1); // looking for the 'testimony' part if (p_wtce == "testimony") { - m_sfx_player->play(ao_app->get_sfx(wtce_names[index-1])); + m_effects_player->play(ao_app->get_sfx(wtce_names[index-1])); ui_vp_wtce->play(wtce_names[index-1]); if (index == 1) { @@ -1766,7 +1693,7 @@ void Courtroom::mod_called(QString p_ip) ui_server_chatlog->append(p_ip); if (ui_guard->isChecked()) { - m_mod_player->play(ao_app->get_sfx("mod_call")); + m_system_player->play(ao_app->get_sfx("mod_call")); ao_app->alert(this); } } @@ -1797,7 +1724,7 @@ void Courtroom::on_ooc_return_pressed() if (!ok) return; - ui_ooc_chat_name->setText(ooc_name); + ao_config->set_username(ooc_name); } if (ooc_message.startsWith("/pos")) @@ -1821,15 +1748,15 @@ void Courtroom::on_ooc_return_pressed() } else if (ooc_message.startsWith("/rollp")) { - m_sfx_player->play(ao_app->get_sfx("dice")); + m_effects_player->play(ao_app->get_sfx("dice")); } else if (ooc_message.startsWith("/roll")) { - m_sfx_player->play(ao_app->get_sfx("dice")); + m_effects_player->play(ao_app->get_sfx("dice")); } else if (ooc_message.startsWith("/coinflip")) { - m_sfx_player->play(ao_app->get_sfx("coinflip")); + m_effects_player->play(ao_app->get_sfx("coinflip")); } else if (ooc_message.startsWith("/variant")) { @@ -1891,36 +1818,13 @@ void Courtroom::on_ooc_return_pressed() AOPacket *f_packet = new AOPacket("CT", packet_contents); - if (server_ooc) - ao_app->send_server_packet(f_packet); - else - ao_app->send_ms_packet(f_packet); + ao_app->send_server_packet(f_packet); ui_ooc_chat_message->clear(); ui_ooc_chat_message->setFocus(); } -void Courtroom::on_ooc_toggle_clicked() -{ - if (server_ooc) - { - ui_ms_chatlog->show(); - ui_server_chatlog->hide(); - ui_ooc_toggle->setText("Master"); - - server_ooc = false; - } - else - { - ui_ms_chatlog->hide(); - ui_server_chatlog->show(); - ui_ooc_toggle->setText("Server"); - - server_ooc = true; - } -} - void Courtroom::on_music_search_edited(QString p_text) { //preventing compiler warnings @@ -2045,7 +1949,7 @@ void Courtroom::on_area_list_clicked() void Courtroom::on_music_list_double_clicked(QModelIndex p_model) { - if (is_muted) + if (is_client_muted) return; QString p_song = ui_music_list->item(p_model.row())->text(); @@ -2119,7 +2023,7 @@ void Courtroom::on_cycle_clicked() } if (ao_app->read_theme_ini("enable_cycle_ding", cc_config_ini) == "true") - m_cycle_player->play(ao_app->get_sfx("cycle")); + m_system_player->play(ao_app->get_sfx("cycle")); set_shouts(); ui_ic_chat_message->setFocus(); @@ -2229,29 +2133,9 @@ void Courtroom::on_text_color_changed(int p_color) ui_ic_chat_message->setFocus(); } -void Courtroom::on_music_slider_moved(int p_value) -{ - m_music_player->set_volume(p_value); - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_sfx_slider_moved(int p_value) -{ - m_sfx_player->set_volume(p_value); - m_shout_player->set_volume(p_value); - m_cycle_player->set_volume(p_value); - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_blip_slider_moved(int p_value) -{ - m_blip_player->set_volume(p_value); - ui_ic_chat_message->setFocus(); -} - void Courtroom::on_witness_testimony_clicked() { - if (is_muted) + if (is_client_muted) return; ao_app->send_server_packet(new AOPacket("RT#testimony1#%")); @@ -2261,7 +2145,7 @@ void Courtroom::on_witness_testimony_clicked() void Courtroom::on_cross_examination_clicked() { - if (is_muted) + if (is_client_muted) return; ao_app->send_server_packet(new AOPacket("RT#testimony2#%")); @@ -2280,7 +2164,7 @@ void Courtroom::reset_judge_wtce_buttons() // kind of an unnecessary function, b void Courtroom::on_wtce_clicked() { // qDebug() << "AA: wtce clicked!"; - if (is_muted) + if (is_client_muted) return; AOButton* f_sig = static_cast(sender()); @@ -2295,10 +2179,7 @@ void Courtroom::on_wtce_clicked() void Courtroom::on_change_character_clicked() { - m_music_player->set_volume(0); - m_sfx_player->set_volume(0); - m_sfx_player->set_volume(0); - m_blip_player->set_volume(0); + set_audio_mute_enabled(true); set_char_select(); @@ -2306,31 +2187,29 @@ void Courtroom::on_change_character_clicked() ui_spectator->hide(); } -void Courtroom::on_reload_theme_clicked() +void Courtroom::on_app_reload_theme_requested() { - ao_app->reload_theme(); - load_shouts(); - load_effects(); - load_wtce(); - load_free_blocks(); + load_shouts(); + load_effects(); + load_wtce(); + load_free_blocks(); - //to update status on the background - set_background(current_background); - enter_courtroom(m_cid); - anim_state = 4; - text_state = 3; + //to update status on the background + set_background(current_background); + enter_courtroom(m_cid); + anim_state = 4; + text_state = 3; } void Courtroom::on_back_to_lobby_clicked() { - ao_app->construct_lobby(); - ao_app->w_lobby->list_servers(); - ao_app->destruct_courtroom(); -} + // hide so we don't get the 'disconnected from server' prompt + hide(); -void Courtroom::on_confirm_theme_clicked() -{ - ao_app->write_theme(ui_theme_list->currentText()); + ao_app->construct_lobby(); + ao_app->w_lobby->list_servers(); + ao_app->w_lobby->set_choose_a_server(); + ao_app->destruct_courtroom(); } void Courtroom::on_char_select_left_clicked() @@ -2419,6 +2298,12 @@ void Courtroom::on_evidence_button_clicked() } } +void Courtroom::on_config_panel_clicked() +{ + ao_app->toggle_config_panel(); + ui_ic_chat_message->setFocus(); +} + void Courtroom::on_note_button_clicked() { if(!note_shown) @@ -2449,9 +2334,15 @@ void Courtroom::ping_server() ao_app->send_server_packet(new AOPacket("CH#" + QString::number(m_cid) + "#%")); } +void Courtroom::closeEvent(QCloseEvent *event) +{ + emit closing(); + QMainWindow::closeEvent(event); +} + void Courtroom::on_sfx_list_clicked() { - ui_ic_chat_message->setFocus(); + ui_ic_chat_message->setFocus(); } void Courtroom::on_set_notes_clicked() diff --git a/courtroom.h b/courtroom.h index cfab63f14..4c4219a19 100644 --- a/courtroom.h +++ b/courtroom.h @@ -1,774 +1,769 @@ #ifndef COURTROOM_H #define COURTROOM_H -#include "aoimage.h" +#include "aobasshandle.h" +#include "aoblipplayer.h" #include "aobutton.h" #include "aocharbutton.h" +#include "aocharmovie.h" +#include "aoconfig.h" +#include "aoconfigpanel.h" #include "aoemotebutton.h" -#include "aopacket.h" -#include "aoscene.h" +#include "aoevidencebutton.h" +#include "aoevidencedisplay.h" +#include "aoimage.h" +#include "aolabel.h" +#include "aolineedit.h" #include "aomovie.h" -#include "aocharmovie.h" - -#include "aobasshandle.hpp" -#include "aoblipplayer.h" #include "aomusicplayer.h" +#include "aonotearea.h" +#include "aonotepad.h" +#include "aopacket.h" +#include "aoscene.h" #include "aosfxplayer.h" -#include "aoshoutplayer.hpp" - -#include "aoevidencebutton.h" +#include "aoshoutplayer.h" #include "aotextarea.h" -#include "aolineedit.h" #include "aotextedit.h" -#include "aoevidencedisplay.h" -#include "aonotepad.h" -#include "aonotearea.hpp" -#include "aolabel.h" #include "aotimer.h" #include "datatypes.h" -#include -#include -#include -#include #include -#include -#include -#include #include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include #include +#include +#include +#include #include +#include +#include class AOApplication; class Courtroom : public QMainWindow { - Q_OBJECT + Q_OBJECT public: - explicit Courtroom(AOApplication *p_ao_app); - - void append_char(char_type p_char){char_list.append(p_char);} - void append_evidence(evi_type p_evi){evidence_list.append(p_evi);} - void append_music(QString f_music){music_list.append(f_music);} - void append_area(QString f_area){area_list.append(f_area);} - void clear_music(){music_list.clear();} - void clear_areas(){area_list.clear();} - - void fix_last_area() - { - if (area_list.size() > 0) + explicit Courtroom(AOApplication *p_ao_app); + + void append_char(char_type p_char) { char_list.append(p_char); } + void append_evidence(evi_type p_evi) { evidence_list.append(p_evi); } + void append_music(QString f_music) { music_list.append(f_music); } + void append_area(QString f_area) { area_list.append(f_area); } + void clear_music() { music_list.clear(); } + void clear_areas() { area_list.clear(); } + + void fix_last_area() { - QString malplaced = area_list.last(); - area_list.removeLast(); - append_music(malplaced); -// qDebug() << "what" << malplaced; + if (area_list.size() > 0) + { + QString malplaced = area_list.last(); + area_list.removeLast(); + append_music(malplaced); + // qDebug() << "what" << malplaced; + } } - } - - //sets position of widgets based on theme ini files - void set_widgets(); - //sets font size based on theme ini files - void set_font(QWidget *widget, QString p_identifier); - //helper function that calls above function on the relevant widgets - void set_fonts(); - //sets dropdown menu stylesheet - void set_dropdown(QWidget *widget, QString target_tag); + //sets position of widgets based on theme ini files + void set_widgets(); + //sets font size based on theme ini files + void set_font(QWidget *widget, QString p_identifier); + //helper function that calls above function on the relevant widgets + void set_fonts(); - //helper funciton that call above function on the relevant widgets - void set_dropdowns(); + //sets dropdown menu stylesheet + void set_dropdown(QWidget *widget, QString target_tag); - void set_window_title(QString p_title); + //helper funciton that call above function on the relevant widgets + void set_dropdowns(); - //reads theme inis and sets size and pos based on the identifier - void set_size_and_pos(QWidget *p_widget, QString p_identifier); + void set_window_title(QString p_title); - //sets status as taken on character with cid n_char and places proper shading on charselect - void set_taken(int n_char, bool p_taken); + //reads theme inis and sets size and pos based on the identifier + void set_size_and_pos(QWidget *p_widget, QString p_identifier); - //sets the current background to argument. also does some checks to see if it's a legacy bg - void set_background(QString p_background); + //sets status as taken on character with cid n_char and places proper shading on charselect + void set_taken(int n_char, bool p_taken); - //sets the evidence list member variable to argument - void set_evidence_list(QVector &p_evi_list); + //sets the current background to argument. also does some checks to see if it's a legacy bg + void set_background(QString p_background); - //called when a DONE#% from the server was received - void done_received(); + //sets the evidence list member variable to argument + void set_evidence_list(QVector &p_evi_list); - //sets the local mute list based on characters available on the server - void set_mute_list(); + //called when a DONE#% from the server was received + void done_received(); - //sets desk and bg based on pos in chatmessage - void set_scene(); + //sets the local mute list based on characters available on the server + void set_mute_list(); - //sets text color based on text color in chatmessage - void set_text_color(); + //sets desk and bg based on pos in chatmessage + void set_scene(); - //takes in serverD-formatted IP list as prints a converted version to server OOC - //admittedly poorly named - void set_ip_list(QString p_list); + //sets text color based on text color in chatmessage + void set_text_color(); - //disables chat if current cid matches second argument - //enables if p_muted is false - void set_mute(bool p_muted, int p_cid); + //takes in serverD-formatted IP list as prints a converted version to server OOC + //admittedly poorly named + void set_ip_list(QString p_list); - //send a message that the player is banned and quits the server - void set_ban(int p_cid); + //disables chat if current cid matches second argument + //enables if p_muted is false + void set_mute(bool p_muted, int p_cid); - //implementations in path_functions.cpp - QString get_background_path(); - QString get_default_background_path(); + //send a message that the player is banned and quits the server + void set_ban(int p_cid); - //cid = character id, returns the cid of the currently selected character - int get_cid() {return m_cid;} - QString get_current_char() {return current_char;} + //implementations in path_functions.cpp + QString get_background_path(); + QString get_default_background_path(); - //properly sets up some varibles: resets user state - void enter_courtroom(int p_cid); + //cid = character id, returns the cid of the currently selected character + int get_cid() { return m_cid; } + QString get_current_char() { return current_char; } - //helper function that populates ui_music_list with the contents of music_list - void list_music(); + //properly sets up some varibles: resets user state + void enter_courtroom(int p_cid); - void list_areas(); + //helper function that populates ui_music_list with the contents of music_list + void list_music(); - void list_sfx(); + void list_areas(); - void list_themes(); + void list_sfx(); - void list_note_files(); + void list_note_files(); - void set_note_files(); + void set_note_files(); - void move_widget(QWidget *p_widget, QString p_identifier); + void move_widget(QWidget *p_widget, QString p_identifier); - void set_shouts(); - void set_effects(); - void set_judge_enabled(bool p_enabled); - void set_judge_wtce(); - void set_free_blocks(); + void set_shouts(); + void set_effects(); + void set_judge_enabled(bool p_enabled); + void set_judge_wtce(); + void set_free_blocks(); - //these are for OOC chat - void append_ms_chatmessage(QString f_name, QString f_message); - void append_server_chatmessage(QString p_name, QString p_message); + //these are for OOC chat + void append_server_chatmessage(QString p_name, QString p_message); - //these functions handle chatmessages sequentially. - //The process itself is very convoluted and merits separate documentation - //But the general idea is objection animation->pre animation->talking->idle - void handle_chatmessage(QStringList *p_contents); - void handle_chatmessage_2(); - void handle_chatmessage_3(); + //these functions handle chatmessages sequentially. + //The process itself is very convoluted and merits separate documentation + //But the general idea is objection animation->pre animation->talking->idle + void handle_chatmessage(QStringList *p_contents); + void handle_chatmessage_2(); + void handle_chatmessage_3(); - //handles character portrait animation - void handle_char_anim(AOCharMovie *charPlayer); - void handle_char_anim_2(AOCharMovie *charPlayer); + //adds text to the IC chatlog. p_name first as bold then p_text then a newlin + //this function keeps the chatlog scrolled to the top unless there's text selected + // or the user isn't already scrolled to the top + void update_ic_log(bool p_reset_log); + void append_ic_text(QString p_name, QString p_line, bool p_system); + void append_system_text(QString p_line); - //adds text to the IC chatlog. p_name first as bold then p_text then a newlin - //this function keeps the chatlog scrolled to the top unless there's text selected - // or the user isn't already scrolled to the top - void update_ic_log(bool p_reset_log); - void append_ic_text(QString p_name, QString p_line, bool p_system); - void append_system_text(QString p_line); + //prints who played the song to IC chat and plays said song(if found on local filesystem) + //takes in a list where the first element is the song name and the second is the char id of who played it + void handle_song(QStringList *p_contents); - //prints who played the song to IC chat and plays said song(if found on local filesystem) - //takes in a list where the first element is the song name and the second is the char id of who played it - void handle_song(QStringList *p_contents); + //animates music text + void handle_music_anim(); - //animates music text - void handle_music_anim(); + //handle server-side clock animation and display + void handle_clock(QString time); - //handle server-side clock animation and display - void handle_clock(QString time); + //handle request to change theme variant + void handle_theme_variant(QString theme_variant); - //handle request to change theme variant - void handle_theme_variant(QString theme_variant); + void play_preanim(); - void play_preanim(); + QString parse_message(QString message); - QString parse_message(QString message); + //plays the witness testimony or cross examination animation based on argument + void handle_wtce(QString p_wtce); - //plays the witness testimony or cross examination animation based on argument - void handle_wtce(QString p_wtce); + //sets the hp bar of defense(p_bar 1) or pro(p_bar 2) + //state is an number between 0 and 10 inclusive + void set_hp_bar(int p_bar, int p_state); - //sets the hp bar of defense(p_bar 1) or pro(p_bar 2) - //state is an number between 0 and 10 inclusive - void set_hp_bar(int p_bar, int p_state); + void check_connection_received(); - void check_connection_received(); + //checks whether shout/effect/wtce/free block files are found + void check_shouts(); + void check_effects(); + void check_wtce(); + void check_free_blocks(); - //checks whether shout/effect/wtce/free block files are found - void check_shouts(); - void check_effects(); - void check_wtce(); - void check_free_blocks(); + void resume_timer(int timer_id); + void set_timer_time(int timer_id, int new_time); + void set_timer_timestep(int timer_id, int timestep_length); + void set_timer_firing(int timer_id, int firing_interval); + void pause_timer(int timer_id); - void resume_timer(int timer_id); - void set_timer_time(int timer_id, int new_time); - void set_timer_timestep(int timer_id, int timestep_length); - void set_timer_firing(int timer_id, int firing_interval); - void pause_timer(int timer_id); + template + int adapt_numbered_items(QVector &item_vector, QString config_item_number, QString item_name); - template - int adapt_numbered_items(QVector &item_vector, QString config_item_number, - QString item_name); +signals: + void closing(); private: - AOApplication *ao_app = nullptr; - - int m_courtroom_width = 714; - int m_courtroom_height = 668; - - int m_viewport_x = 0; - int m_viewport_y = 0; - - int m_viewport_width = 256; - int m_viewport_height = 192; - - QVector char_list; - QVector evidence_list; - QVector music_list; - QVector area_list; - QVector sfx_names; - QVector area_names; - QVector note_list; + AOApplication *ao_app = nullptr; + AOConfig *ao_config = nullptr; - QSignalMapper *char_button_mapper; + int m_courtroom_width = 714; + int m_courtroom_height = 668; - //triggers ping_server() every 60 seconds - QTimer *keepalive_timer; + int m_viewport_x = 0; + int m_viewport_y = 0; - //determines how fast messages tick onto screen - QTimer *chat_tick_timer; - int chat_tick_interval = 60; - //which tick position(character in chat message) we are at - int tick_pos = 0; - //used to determine how often blips sound - int blip_pos = 0; - int blip_rate = 1; - int rainbow_counter = 0; - bool rainbow_appended = false; - bool blank_blip = false; - bool note_shown = false; - bool contains_add_button = false; + int m_viewport_width = 256; + int m_viewport_height = 192; - ////////////// - QScrollArea *note_scroll_area; + QVector char_list; + QVector evidence_list; + QVector music_list; + QVector area_list; + QVector sfx_names; + QVector area_names; + QVector note_list; - //delay before chat messages starts ticking - QTimer *text_delay_timer; + QSignalMapper *char_button_mapper; - //delay before sfx plays - QTimer *sfx_delay_timer; + //triggers ping_server() every 60 seconds + QTimer *keepalive_timer; - //keeps track of how long realization is visible(it's just a white square and should be visible less than a second) - QTimer *realization_timer; + //determines how fast messages tick onto screen + QTimer *chat_tick_timer; + int chat_tick_interval = 60; + //which tick position(character in chat message) we are at + int tick_pos = 0; + //used to determine how often blips sound + int blip_pos = 0; + int rainbow_counter = 0; + bool rainbow_appended = false; + bool note_shown = false; + bool contains_add_button = false; - //times how long the blinking testimony should be shown(green one in the corner) - QTimer *testimony_show_timer; - //times how long the blinking testimony should be hidden - QTimer *testimony_hide_timer; + ////////////// + QScrollArea *note_scroll_area; - //Generate a File Name based on the time you launched the client - QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM yyyy hh.mm.ss.z'.txt'"); + //delay before chat messages starts ticking + QTimer *text_delay_timer; - //configuration files locations - QString rpc_ini = "configs/rpccharlist.ini"; - QString file_select_ini = "configs/filesabstract.ini"; - QString shownames_ini = "configs/shownames.ini"; + //delay before sfx plays + QTimer *sfx_delay_timer; - //theme files locations - QString design_ini = "courtroom_design.ini"; - QString fonts_ini = "courtroom_fonts.ini"; - QString cc_config_ini = "courtroom_config.ini"; - QString cc_sounds_ini = "courtroom_sounds.ini"; + //keeps track of how long realization is visible(it's just a white square and should be visible less than a second) + QTimer *realization_timer; - //every time point in char.inis times this equals the final time - const int time_mod = 40; + //times how long the blinking testimony should be shown(green one in the corner) + QTimer *testimony_show_timer; + //times how long the blinking testimony should be hidden + QTimer *testimony_hide_timer; - static const int chatmessage_size = 16; - QString m_chatmessage[chatmessage_size]; - bool chatmessage_is_empty = false; + //Generate a File Name based on the time you launched the client + QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM yyyy hh.mm.ss.z'.txt'"); - QString previous_ic_message = ""; + //configuration files locations + QString rpc_ini = "configs/rpccharlist.ini"; + QString file_select_ini = "configs/filesabstract.ini"; + QString shownames_ini = "configs/shownames.ini"; - QColor m_base_string_color; - QString m_string_color = ""; + //theme files locations + QString design_ini = "courtroom_design.ini"; + QString fonts_ini = "courtroom_fonts.ini"; + QString cc_config_ini = "courtroom_config.ini"; + QString cc_sounds_ini = "courtroom_sounds.ini"; - QStack m_color_stack; + //every time point in char.inis times this equals the final time + const int time_mod = 40; - bool testimony_in_progress = false; + static const int chatmessage_size = 16; + QString m_chatmessage[chatmessage_size]; + bool chatmessage_is_empty = false; - //in milliseconds - const int testimony_show_time = 1500; + QString previous_ic_message = ""; - //in milliseconds - const int testimony_hide_time = 500; + QColor m_base_string_color; + QString m_string_color = ""; - //char id, muted or not - QMap mute_map; + QStack m_color_stack; - //QVector muted_cids; + bool testimony_in_progress = false; - bool is_muted = false; + //in milliseconds + const int testimony_show_time = 1500; - // state of animation, 0 = objecting, 1 = preanim, 2 = talking, 3 = idle - int anim_state = 3; + //in milliseconds + const int testimony_hide_time = 500; - // state of text ticking, 0 = not yet ticking, 1 = ticking in progress, 2 = ticking done - int text_state = 2; + //char id, muted or not + QMap mute_map; - // character id, which index of the char_list the player is - int m_cid = -1; + //QVector muted_cids; - // if enabled, disable showing our own sprites when we talk in ic - bool m_msg_is_first_person = false; + bool is_client_muted = false; - //cid and this may differ in cases of ini-editing - QString current_char = ""; + // state of animation, 0 = objecting, 1 = preanim, 2 = talking, 3 = idle + int anim_state = 3; - QString current_file = ""; + // state of text ticking, 0 = not yet ticking, 1 = ticking in progress, 2 = ticking done + int text_state = 2; - int m_objection_state = 0; - int m_effect_state = 0; - int m_text_color = 0; - int m_shout_state = 0; - int m_effect_current = 0; - int m_wtce_current = 0; - bool is_presenting_evidence = false; - bool is_judge = false; - bool is_system_speaking = false; + // character id, which index of the char_list the player is + int m_cid = -1; - int defense_bar_state = 0; - int prosecution_bar_state = 0; + // if enabled, disable showing our own sprites when we talk in ic + bool m_msg_is_first_person = false; - int current_char_page = 0; - int char_columns = 10; - int char_rows = 9; - int max_chars_on_page = 90; + //cid and this may differ in cases of ini-editing + QString current_char = ""; - int current_emote_page = 0; - int current_emote = 0; - int prev_emote = 0; - int emote_columns = 5; - int emote_rows = 2; - int max_emotes_on_page = 10; + QString current_file = ""; - int m_chatlog_limit = 200; - bool m_chatlog_newline = false; - bool m_chatlog_scrolldown = false; + int m_objection_state = 0; + int m_effect_state = 0; + int m_text_color = 0; + int m_shout_state = 0; + int m_effect_current = 0; + int m_wtce_current = 0; + bool is_presenting_evidence = false; + bool is_judge = false; + bool is_system_speaking = false; -// inmchatlog_changed; + int defense_bar_state = 0; + int prosecution_bar_state = 0; - QVector local_evidence_list; + int current_char_page = 0; + int char_columns = 10; + int char_rows = 9; + int max_chars_on_page = 90; - int current_evidence_page = 0; - int current_evidence = 0; - int evidence_columns = 6; - int evidence_rows = 3; - int max_evidence_on_page = 18; + int current_emote_page = 0; + int current_emote = 0; + int prev_emote = 0; + int emote_columns = 5; + int emote_rows = 2; + int max_emotes_on_page = 10; - //whether the ooc chat is server or master chat, true is server - bool server_ooc = true; + int m_chatlog_limit = 200; + bool m_chatlog_newline = false; + bool m_chatlog_scrolldown = false; - int current_clock = -1; - int timer_number = 0; + // inmchatlog_changed; - QString current_background = "gs4"; + QVector local_evidence_list; - AOBlipPlayer* m_blip_player = nullptr; - AOSfxPlayer* m_mod_player = nullptr; - AOMusicPlayer* m_music_player = nullptr; - AOSfxPlayer* m_sfx_player = nullptr; - AOSfxPlayer* m_cycle_player = nullptr; - AOShoutPlayer* m_shout_player = nullptr; + int current_evidence_page = 0; + int current_evidence = 0; + int evidence_columns = 6; + int evidence_rows = 3; + int max_evidence_on_page = 18; - AOImage *ui_background; + int current_clock = -1; + int timer_number = 0; - QWidget *ui_viewport; - AOScene *ui_vp_background; - AOMovie *ui_vp_speedlines; - AOCharMovie *ui_vp_player_char; - AOScene *ui_vp_desk; - AOEvidenceDisplay *ui_vp_evidence_display; + QString current_background = "gs4"; - AONoteArea *ui_note_area; + AOImage *ui_background; -// AONotepad *ui_vp_notepad; + QWidget *ui_viewport; + AOScene *ui_vp_background; + AOMovie *ui_vp_speedlines; + AOCharMovie *ui_vp_player_char; + AOScene *ui_vp_desk; + AOEvidenceDisplay *ui_vp_evidence_display; - // list of characters that require a second application ID - // note that since it's hardcoded, it won't be of much use in other servers - QVector rpc_char_list; + AONoteArea *ui_note_area; - AOImage *ui_vp_notepad_image; - QTextEdit *ui_vp_notepad; + // AONotepad *ui_vp_notepad; + // list of characters that require a second application ID + // note that since it's hardcoded, it won't be of much use in other servers + QVector rpc_char_list; - AOImage* ui_vp_chatbox = nullptr; - QLabel* ui_vp_showname = nullptr; - QTextEdit* ui_vp_message = nullptr; - AOImage* ui_vp_testimony = nullptr; - AOMovie* ui_vp_effect = nullptr; - AOMovie* ui_vp_wtce = nullptr; - AOMovie* ui_vp_objection = nullptr; + AOImage *ui_vp_notepad_image; + QTextEdit *ui_vp_notepad; - AOImage* ui_vp_music_display_a = nullptr; - AOImage* ui_vp_music_display_b = nullptr; + AOImage *ui_vp_chatbox = nullptr; + QLabel *ui_vp_showname = nullptr; + QTextEdit *ui_vp_message = nullptr; + AOImage *ui_vp_testimony = nullptr; + AOMovie *ui_vp_effect = nullptr; + AOMovie *ui_vp_wtce = nullptr; + AOMovie *ui_vp_objection = nullptr; - AOImage* ui_vp_showname_image = nullptr; + AOImage *ui_vp_music_display_a = nullptr; + AOImage *ui_vp_music_display_b = nullptr; - QTextEdit* ui_vp_music_name = nullptr; - QPropertyAnimation* music_anim = nullptr; + AOImage *ui_vp_showname_image = nullptr; - QWidget *ui_vp_music_area; + QTextEdit *ui_vp_music_name = nullptr; + QPropertyAnimation *music_anim = nullptr; - AOMovie *ui_vp_clock; - QVector ui_timers; + QWidget *ui_vp_music_area; - QTextEdit* ui_ic_chatlog = nullptr; - record_type_array m_ic_records; + AOMovie *ui_vp_clock; + QVector ui_timers; - AOTextArea *ui_ms_chatlog; - AOTextArea *ui_server_chatlog; + QTextEdit *ui_ic_chatlog = nullptr; + record_type_array m_ic_records; - QListWidget *ui_mute_list; - QListWidget *ui_area_list; - QListWidget *ui_music_list; - QListWidget *ui_sfx_list; + AOTextArea *ui_server_chatlog; -// QListWidget *ui_sfx_list; + QListWidget *ui_mute_list; + QListWidget *ui_area_list; + QListWidget *ui_music_list; + QListWidget *ui_sfx_list; - QLineEdit *ui_ic_chat_message; + // QListWidget *ui_sfx_list; - QLineEdit *ui_ooc_chat_message; - QLineEdit *ui_ooc_chat_name; + QLineEdit *ui_ic_chat_message; - //QLineEdit *ui_area_password; - QLineEdit *ui_music_search; + QLineEdit *ui_ooc_chat_message; + QLineEdit *ui_ooc_chat_name; - QLineEdit *ui_sfx_search; + //QLineEdit *ui_area_password; + QLineEdit *ui_music_search; - QWidget *ui_emotes = nullptr; - QVector ui_emote_list; - AOButton *ui_emote_left; - AOButton *ui_emote_right; + QLineEdit *ui_sfx_search; - QComboBox *ui_emote_dropdown; - QComboBox *ui_pos_dropdown; + QWidget *ui_emotes = nullptr; + QVector ui_emote_list; + AOButton *ui_emote_left; + AOButton *ui_emote_right; - AOImage *ui_defense_bar; - AOImage *ui_prosecution_bar; + QComboBox *ui_emote_dropdown; + QComboBox *ui_pos_dropdown; - AOLabel *ui_music_label; - AOLabel *ui_sfx_label; - AOLabel *ui_blip_label; + AOImage *ui_defense_bar; + AOImage *ui_prosecution_bar; - //buttons to cycle through shouts - AOButton* ui_shout_up = nullptr; - AOButton* ui_shout_down = nullptr; - //buttons to cycle through effects - AOButton* ui_effect_up = nullptr; - AOButton* ui_effect_down = nullptr; - //buttons to cycle through wtce - AOButton* ui_wtce_up = nullptr; - AOButton* ui_wtce_down = nullptr; + //buttons to cycle through shouts + AOButton *ui_shout_up = nullptr; + AOButton *ui_shout_down = nullptr; + //buttons to cycle through effects + AOButton *ui_effect_up = nullptr; + AOButton *ui_effect_down = nullptr; + //buttons to cycle through wtce + AOButton *ui_wtce_up = nullptr; + AOButton *ui_wtce_down = nullptr; - //holds all the shout button objects - QVector ui_shouts; - //holds all the effect button objects - QVector ui_effects; - //holds all the shout buttons objects - QVector ui_wtce; - //holds all the free block objects - QVector ui_free_blocks; + //holds all the shout button objects + QVector ui_shouts; + //holds all the effect button objects + QVector ui_effects; + //holds all the shout buttons objects + QVector ui_wtce; + //holds all the free block objects + QVector ui_free_blocks; - //holds all the names for sound files for the shouts -// QVector shout_names = {"holdit", "objection", "takethat", "custom", "gotit", "crossswords", "counteralt"}; - QVector shout_names; + //holds all the names for sound files for the shouts + // QVector shout_names = {"holdit", "objection", "takethat", "custom", "gotit", "crossswords", "counteralt"}; + QVector shout_names; - //holds all the names for sound/anim files for the effects - //QVector effect_names = {"effect_flash", "effect_gloom", "effect_question", "effect_pow"}; - QVector effect_names; + //holds all the names for sound/anim files for the effects + //QVector effect_names = {"effect_flash", "effect_gloom", "effect_question", "effect_pow"}; + QVector effect_names; - //holds all the names for sound/anim files for the shouts - //QVector shout_names = {"witnesstestimony", "crossexamination", "investigation", "nonstop"}; - QVector wtce_names; + //holds all the names for sound/anim files for the shouts + //QVector shout_names = {"witnesstestimony", "crossexamination", "investigation", "nonstop"}; + QVector wtce_names; - //holds all the names for free blocks - QVector free_block_names; + //holds all the names for free blocks + QVector free_block_names; - //holds whether the animation file exists for a determined shout/effect - QVector shouts_enabled; - QVector effects_enabled; - QVector wtce_enabled; - QVector free_blocks_enabled; + //holds whether the animation file exists for a determined shout/effect + QVector shouts_enabled; + QVector effects_enabled; + QVector wtce_enabled; + QVector free_blocks_enabled; -// AOButton* ui_shout_hold_it = nullptr; // 1 -// AOButton* ui_shout_objection = nullptr; // 2 -// AOButton* ui_shout_take_that = nullptr; // 3 -// AOButton* ui_shout_custom = nullptr; // 4 -// AOButton* ui_shout_got_it = nullptr; // 5 -// AOButton* ui_shout_cross_swords = nullptr; // 6 -// AOButton* ui_shout_counter_alt = nullptr; // 7 + // AOButton* ui_shout_hold_it = nullptr; // 1 + // AOButton* ui_shout_objection = nullptr; // 2 + // AOButton* ui_shout_take_that = nullptr; // 3 + // AOButton* ui_shout_custom = nullptr; // 4 + // AOButton* ui_shout_got_it = nullptr; // 5 + // AOButton* ui_shout_cross_swords = nullptr; // 6 + // AOButton* ui_shout_counter_alt = nullptr; // 7 - AOButton *ui_ooc_toggle; + AOButton *ui_witness_testimony; + AOButton *ui_cross_examination; + AOButton *ui_investigation; + AOButton *ui_nonstop; - AOButton *ui_witness_testimony; - AOButton *ui_cross_examination; - AOButton *ui_investigation; - AOButton *ui_nonstop; + AOButton *ui_change_character; + AOButton *ui_call_mod; + AOButton *ui_switch_area_music; - AOButton *ui_change_character; - AOButton *ui_reload_theme; - AOButton *ui_call_mod; - AOButton *ui_switch_area_music; + AOButton *ui_config_panel; + AOButton *ui_set_notes; - QComboBox *ui_theme_list; + QCheckBox *ui_pre; + QCheckBox *ui_flip; + QCheckBox *ui_guard; + QCheckBox *ui_hidden; - AOButton *ui_confirm_theme; + QVector ui_checks; // 0 = pre, 1 = flip, 2 = guard, 3 = hidden + QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip + QVector ui_label_images; + QVector label_images = {"Pre", "Flip", "Guard", "Hidden", "Music", "SFX", "Blip"}; - AOButton *ui_set_notes; + AOButton *ui_effect_flash = nullptr; + AOButton *ui_effect_gloom = nullptr; - QCheckBox *ui_pre; - QCheckBox *ui_flip; - QCheckBox *ui_guard; - QCheckBox *ui_hidden; + AOButton *ui_mute = nullptr; - QVector ui_checks; // 0 = pre, 1 = flip, 2 = guard, 3 = hidden - QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip - QVector ui_label_images; - QVector label_images = {"Pre", "Flip", "Guard", "Hidden", "Music", "SFX", "Blip"}; + AOButton *ui_defense_plus; + AOButton *ui_defense_minus; - AOButton* ui_effect_flash = nullptr; - AOButton* ui_effect_gloom = nullptr; + AOButton *ui_prosecution_plus; + AOButton *ui_prosecution_minus; + + QComboBox *ui_text_color; + + AOImage *ui_muted; + + AOButton *ui_note_button; + + AOButton *ui_evidence_button; + AOImage *ui_evidence; + AOLineEdit *ui_evidence_name; + QWidget *ui_evidence_buttons; + QVector ui_evidence_list; + AOButton *ui_evidence_left; + AOButton *ui_evidence_right; + AOButton *ui_evidence_present; + AOImage *ui_evidence_overlay; + AOButton *ui_evidence_delete; + AOLineEdit *ui_evidence_image_name; + AOButton *ui_evidence_image_button; + AOButton *ui_evidence_x; + AOTextEdit *ui_evidence_description; + + AOImage *ui_char_select_background; + + //abstract widget to hold char buttons + QWidget *ui_char_buttons = nullptr; + AOImage *ui_char_button_selector = nullptr; + QVector ui_char_button_list; - AOButton* ui_mute = nullptr; + AOButton *ui_back_to_lobby; + + QLineEdit *ui_char_password; - AOButton *ui_defense_plus; - AOButton *ui_defense_minus; + AOButton *ui_char_select_left; + AOButton *ui_char_select_right; - AOButton *ui_prosecution_plus; - AOButton *ui_prosecution_minus; + AOButton *ui_spectator; - QComboBox *ui_text_color; + QHash widget_names; - QSlider *ui_music_slider; - QSlider *ui_sfx_slider; - QSlider *ui_blip_slider; - - AOImage *ui_muted; + void create_widgets(); + void connect_widgets(); + void set_widget_names(); + void reset_widget_names(); + void insert_widget_name(QString p_widget_name, QWidget *p_widget); + void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); + template + void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); + void set_widget_layers(); - AOButton *ui_note_button; - - AOButton *ui_evidence_button; - AOImage *ui_evidence; - AOLineEdit *ui_evidence_name; - QWidget *ui_evidence_buttons; - QVector ui_evidence_list; - AOButton *ui_evidence_left; - AOButton *ui_evidence_right; - AOButton *ui_evidence_present; - AOImage *ui_evidence_overlay; - AOButton *ui_evidence_delete; - AOLineEdit *ui_evidence_image_name; - AOButton *ui_evidence_image_button; - AOButton *ui_evidence_x; - AOTextEdit *ui_evidence_description; + void construct_char_select(); + void reconstruct_char_select(); + void reset_char_select(); + void set_char_select(); + void set_char_select_page(); - AOImage *ui_char_select_background; + void construct_emotes(); + void reconstruct_emotes(); + void reset_emote_page(); + void set_emote_page(); + void set_emote_dropdown(); - //abstract widget to hold char buttons - QWidget *ui_char_buttons = nullptr; - AOImage *ui_char_button_selector = nullptr; - QVector ui_char_button_list; + void construct_evidence(); + void set_evidence_page(); - AOButton *ui_back_to_lobby; + void load_note(); + void save_note(); + void save_textlog(QString p_text); - QLineEdit *ui_char_password; - - AOButton *ui_char_select_left; - AOButton *ui_char_select_right; - - AOButton *ui_spectator; - - QHash widget_names; - - void create_widgets(); - void connect_widgets(); - void set_widget_names(); - void reset_widget_names(); - void insert_widget_name(QString p_widget_name, QWidget *p_widget); - void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); - template - void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); - void set_widget_layers(); - - void construct_char_select(); - void reconstruct_char_select(); - void reset_char_select(); - void set_char_select(); - void set_char_select_page(); - - void construct_emotes(); - void reconstruct_emotes(); - void reset_emote_page(); - void set_emote_page(); - void set_emote_dropdown(); - - void construct_evidence(); - void set_evidence_page(); - - void load_note(); - void save_note(); - void save_textlog(QString p_text); - - void set_char_rpc(); + void set_char_rpc(); public slots: - void objection_done(); - void preanim_done(); + void objection_done(); + void preanim_done(); - void realization_done(); + void realization_done(); - void show_testimony(); - void hide_testimony(); + void show_testimony(); + void hide_testimony(); - void mod_called(QString p_ip); + void mod_called(QString p_ip); private slots: - void start_chat_ticking(); - void play_sfx(); + void start_chat_ticking(); + void play_sfx(); - void chat_tick(); + void chat_tick(); - void on_mute_list_clicked(QModelIndex p_index); + void on_mute_list_clicked(QModelIndex p_index); - void on_chat_return_pressed(); + void on_chat_return_pressed(); + void on_chat_config_changed(); - void on_ooc_return_pressed(); + void on_ooc_return_pressed(); - void on_music_search_edited(QString p_text); - void on_music_list_clicked(); - void on_area_list_clicked(); - void on_music_list_double_clicked(QModelIndex p_model); - void on_area_list_double_clicked(QModelIndex p_model); + void on_music_search_edited(QString p_text); + void on_music_list_clicked(); + void on_area_list_clicked(); + void on_music_list_double_clicked(QModelIndex p_model); + void on_area_list_double_clicked(QModelIndex p_model); - void on_sfx_search_edited(QString p_text); + void on_sfx_search_edited(QString p_text); - void select_emote(int p_id); + void select_emote(int p_id); - void on_emote_clicked(int p_id); + void on_emote_clicked(int p_id); - void on_emote_left_clicked(); - void on_emote_right_clicked(); + void on_emote_left_clicked(); + void on_emote_right_clicked(); - void on_emote_dropdown_changed(int p_index); - void on_pos_dropdown_changed(int p_index); + void on_emote_dropdown_changed(int p_index); + void on_pos_dropdown_changed(int p_index); - void on_evidence_name_edited(); - void on_evidence_image_name_edited(); - void on_evidence_image_button_clicked(); - void on_evidence_clicked(int p_id); - void on_evidence_double_clicked(int p_id); + void on_evidence_name_edited(); + void on_evidence_image_name_edited(); + void on_evidence_image_button_clicked(); + void on_evidence_clicked(int p_id); + void on_evidence_double_clicked(int p_id); - void on_evidence_hover(int p_id, bool p_state); + void on_evidence_hover(int p_id, bool p_state); - void on_evidence_left_clicked(); - void on_evidence_right_clicked(); - void on_evidence_present_clicked(); + void on_evidence_left_clicked(); + void on_evidence_right_clicked(); + void on_evidence_present_clicked(); - void on_cycle_clicked(); + void on_cycle_clicked(); - void cycle_shout(int p_index); - void cycle_effect(int p_index); - void cycle_wtce(int p_index); + void cycle_shout(int p_index); + void cycle_effect(int p_index); + void cycle_wtce(int p_index); - void on_add_button_clicked(); - void on_delete_button_clicked(); + void on_add_button_clicked(); + void on_delete_button_clicked(); - void on_set_file_button_clicked(); - void on_file_selected(); + void on_set_file_button_clicked(); + void on_file_selected(); - void delete_widget(QWidget *p_widget); - void load_shouts(); - void load_effects(); - void load_wtce(); - void load_free_blocks(); - /** + void delete_widget(QWidget *p_widget); + void load_shouts(); + void load_effects(); + void load_wtce(); + void load_free_blocks(); + /** * @brief reset the shout button's texture to default * DOES NOT MODIFY OBJECTION_STATE */ - void reset_shout_buttons(); + void reset_shout_buttons(); - /** + /** * @brief a general purpose function to toggle button selection */ - void on_shout_clicked(); + void on_shout_clicked(); + + void reset_effect_buttons(); + void on_effect_button_clicked(); + + void on_mute_clicked(); + + void on_defense_minus_clicked(); + void on_defense_plus_clicked(); + void on_prosecution_minus_clicked(); + void on_prosecution_plus_clicked(); - void reset_effect_buttons(); - void on_effect_button_clicked(); + void on_text_color_changed(int p_color); - void on_mute_clicked(); + void on_witness_testimony_clicked(); + void on_cross_examination_clicked(); + void reset_judge_wtce_buttons(); + void on_wtce_clicked(); - void on_defense_minus_clicked(); - void on_defense_plus_clicked(); - void on_prosecution_minus_clicked(); - void on_prosecution_plus_clicked(); + void on_change_character_clicked(); + void on_app_reload_theme_requested(); + void on_call_mod_clicked(); - void on_text_color_changed(int p_color); + void on_switch_area_music_clicked(); - void on_music_slider_moved(int p_value); - void on_sfx_slider_moved(int p_value); - void on_blip_slider_moved(int p_value); + void on_config_panel_clicked(); + void on_note_button_clicked(); - void on_ooc_toggle_clicked(); + void on_set_notes_clicked(); - void on_witness_testimony_clicked(); - void on_cross_examination_clicked(); - void reset_judge_wtce_buttons(); - void on_wtce_clicked(); + void on_note_text_changed(); - void on_change_character_clicked(); - void on_reload_theme_clicked(); - void on_call_mod_clicked(); + void on_pre_clicked(); + void on_flip_clicked(); + void on_guard_clicked(); - void on_switch_area_music_clicked(); + void on_hidden_clicked(); - void on_confirm_theme_clicked(); - void on_note_button_clicked(); + void on_sfx_list_clicked(); - void on_set_notes_clicked(); + void on_evidence_button_clicked(); - void on_note_text_changed(); + void on_evidence_delete_clicked(); + void on_evidence_x_clicked(); - void on_pre_clicked(); - void on_flip_clicked(); - void on_guard_clicked(); + void on_back_to_lobby_clicked(); - void on_hidden_clicked(); + void on_char_select_left_clicked(); + void on_char_select_right_clicked(); + void char_clicked(int n_char); + void char_mouse_entered(AOCharButton *p_caller); + void char_mouse_left(); - void on_sfx_list_clicked(); + void on_spectator_clicked(); - void on_evidence_button_clicked(); + void ping_server(); - void on_evidence_delete_clicked(); - void on_evidence_x_clicked(); +/*! + * ============================================================================= + * AUDIO SYSTEM + */ - void on_back_to_lobby_clicked(); +public: + bool is_audio_muted(); - void on_char_select_left_clicked(); - void on_char_select_right_clicked(); - void char_clicked(int n_char); - void char_mouse_entered(AOCharButton *p_caller); - void char_mouse_left(); +public slots: + void set_audio_mute_enabled(bool p_enabled); + void set_effects_volume(int p_volume); + void set_system_volume(int p_volume); + void set_music_volume(int p_volume); + void set_blips_volume(int p_volume); - void on_spectator_clicked(); +private: + bool m_audio_mute = false; + AOSfxPlayer *m_effects_player = nullptr; + AOShoutPlayer *m_shouts_player = nullptr; + AOSfxPlayer *m_system_player = nullptr; + AOMusicPlayer *m_music_player = nullptr; + AOBlipPlayer *m_blips_player = nullptr; - void ping_server(); +private slots: + void on_config_effects_volume_changed(int p_volume); + void on_config_system_volume_changed(int p_volume); + void on_config_music_volume_changed(int p_volume); + void on_config_blips_volume_changed(int p_volume); + + // QWidget interface +protected: + void closeEvent(QCloseEvent *event) override; }; template diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 48867e80c..312cdb9e1 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -44,18 +44,20 @@ void Courtroom::create_widgets() char_button_mapper = new QSignalMapper(this); - m_blip_player = new AOBlipPlayer(this, ao_app); - m_blip_player->set_volume(0); - m_sfx_player = new AOSfxPlayer(this, ao_app); - m_sfx_player->set_volume(0); - m_shout_player = new AOShoutPlayer(this, ao_app); - m_shout_player->set_volume(0); - m_mod_player = new AOSfxPlayer(this, ao_app); - m_mod_player->set_volume(50); - m_cycle_player = new AOSfxPlayer(this, ao_app); - m_cycle_player->set_volume(0); + m_effects_player = new AOSfxPlayer(this, ao_app); + m_effects_player->set_volume(ao_config->effects_volume()); + m_shouts_player = new AOShoutPlayer(this, ao_app); + m_shouts_player->set_volume(ao_config->effects_volume()); + connect(ao_config, SIGNAL(effects_volume_changed(int)), this, SLOT(on_config_effects_volume_changed(int))); + m_system_player = new AOSfxPlayer(this, ao_app); + m_system_player->set_volume(ao_config->system_volume()); + connect(ao_config, SIGNAL(system_volume_changed(int)), this, SLOT(on_config_system_volume_changed(int))); m_music_player = new AOMusicPlayer(this, ao_app); - m_music_player->set_volume(0); + m_music_player->set_volume(ao_config->music_volume()); + connect(ao_config, SIGNAL(music_volume_changed(int)), this, SLOT(on_config_music_volume_changed(int))); + m_blips_player = new AOBlipPlayer(this, ao_app); + m_blips_player->set_volume(ao_config->blips_volume()); + connect(ao_config, SIGNAL(blips_volume_changed(int)), this, SLOT(on_config_blips_volume_changed(int))); ui_background = new AOImage(this, ao_app); @@ -100,11 +102,6 @@ void Courtroom::create_widgets() ui_ic_chatlog = new QTextEdit(this); ui_ic_chatlog->setReadOnly(true); - ui_ms_chatlog = new AOTextArea(this); - ui_ms_chatlog->setReadOnly(true); - ui_ms_chatlog->setOpenExternalLinks(true); - ui_ms_chatlog->hide(); - ui_server_chatlog = new AOTextArea(this); ui_server_chatlog->setReadOnly(true); ui_server_chatlog->setOpenExternalLinks(true); @@ -126,6 +123,7 @@ void Courtroom::create_widgets() ui_ooc_chat_name = new QLineEdit(this); ui_ooc_chat_name->setFrame(false); ui_ooc_chat_name->setPlaceholderText("Name"); + ui_ooc_chat_name->setText(ao_config->username()); //ui_area_password = new QLineEdit(this); //ui_area_password->setFrame(false); @@ -151,10 +149,6 @@ void Courtroom::create_widgets() ui_defense_bar = new AOImage(this, ao_app); ui_prosecution_bar = new AOImage(this, ao_app); - ui_music_label = new AOLabel(this, ao_app); - ui_sfx_label = new AOLabel(this, ao_app); - ui_blip_label = new AOLabel(this, ao_app); - load_shouts(); // Reads from theme, deletes old shouts if needed and creates new ones ui_shout_up = new AOButton(this, ao_app); @@ -176,15 +170,11 @@ void Courtroom::create_widgets() ui_wtce_down = new AOButton(this, ao_app); ui_wtce_down->setProperty("cycle_id", 4); - ui_ooc_toggle = new AOButton(this, ao_app); - ui_change_character = new AOButton(this, ao_app); - ui_reload_theme = new AOButton(this, ao_app); ui_call_mod = new AOButton(this, ao_app); ui_switch_area_music = new AOButton(this, ao_app); - ui_theme_list = new QComboBox(this); - ui_confirm_theme = new AOButton(this, ao_app); + ui_config_panel = new AOButton(this, ao_app); ui_note_button = new AOButton(this, ao_app); ui_label_images.resize(7); @@ -209,9 +199,6 @@ void Courtroom::create_widgets() ui_checks.push_back(ui_flip); ui_checks.push_back(ui_guard); ui_checks.push_back(ui_hidden); - ui_labels.push_back(ui_music_label); - ui_labels.push_back(ui_sfx_label); - ui_labels.push_back(ui_blip_label); // ui_mute = new AOButton(this, ao_app); @@ -231,18 +218,6 @@ void Courtroom::create_widgets() if (ao_app->yellow_text_enabled) ui_text_color->addItem("Yellow"); - ui_music_slider = new QSlider(Qt::Horizontal, this); - ui_music_slider->setRange(0, 100); - ui_music_slider->setValue(ao_app->get_default_music()); - - ui_sfx_slider = new QSlider(Qt::Horizontal, this); - ui_sfx_slider->setRange(0, 100); - ui_sfx_slider->setValue(ao_app->get_default_sfx()); - - ui_blip_slider = new QSlider(Qt::Horizontal, this); - ui_blip_slider->setRange(0, 100); - ui_blip_slider->setValue(ao_app->get_default_blip()); - ui_evidence_button = new AOButton(this, ao_app); ui_vp_notepad_image = new AOImage(this, ao_app); @@ -263,6 +238,8 @@ void Courtroom::connect_widgets() { connect(keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); + connect(ao_app, SIGNAL(reload_theme()), this, SLOT(on_app_reload_theme_requested())); + connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); @@ -286,6 +263,8 @@ void Courtroom::connect_widgets() connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_chat_return_pressed())); + connect(ui_ooc_chat_name, SIGNAL(textEdited(QString)), ao_config, SLOT(set_username(QString))); + connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_music_list_clicked())); @@ -312,22 +291,21 @@ void Courtroom::connect_widgets() connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, SLOT(on_text_color_changed(int))); - connect(ui_music_slider, SIGNAL(valueChanged(int)), this, SLOT(on_music_slider_moved(int))); - connect(ui_sfx_slider, SIGNAL(valueChanged(int)), this, SLOT(on_sfx_slider_moved(int))); - connect(ui_blip_slider, SIGNAL(valueChanged(int)), this, SLOT(on_blip_slider_moved(int))); - - connect(ui_ooc_toggle, SIGNAL(clicked()), this, SLOT(on_ooc_toggle_clicked())); + connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_goes_downward_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_uses_newline_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited(QString))); connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); - connect(ui_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); connect(ui_switch_area_music, SIGNAL(clicked()), this, SLOT(on_switch_area_music_clicked())); - connect(ui_confirm_theme, SIGNAL(clicked()), this, SLOT(on_confirm_theme_clicked())); + connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(set_theme(QString))); + + connect(ui_config_panel, SIGNAL(clicked()), this, SLOT(on_config_panel_clicked())); connect(ui_note_button, SIGNAL(clicked()), this, SLOT(on_note_button_clicked())); connect(ui_vp_notepad, SIGNAL(textChanged()), this, SLOT(on_note_text_changed())); @@ -372,7 +350,6 @@ void Courtroom::reset_widget_names() {"vp_wtce", ui_vp_wtce}, {"vp_objection", ui_vp_objection}, {"ic_chatlog", ui_ic_chatlog}, - {"ms_chatlog", ui_ms_chatlog}, {"server_chatlog", ui_server_chatlog}, {"mute_list", ui_mute_list}, {"area_list", ui_area_list}, @@ -396,9 +373,6 @@ void Courtroom::reset_widget_names() {"pos_dropdown", ui_pos_dropdown}, {"defense_bar", ui_defense_bar}, {"prosecution_bar", ui_prosecution_bar}, - {"music_label", ui_music_label}, - {"sfx_label", ui_sfx_label}, - {"blip_label", ui_blip_label}, // Each ui_shouts[i] {"shout_up", ui_shout_up}, {"shout_down", ui_shout_down}, @@ -408,13 +382,10 @@ void Courtroom::reset_widget_names() // Each ui_wtce[i] {"wtce_up", ui_wtce_up}, {"wtce_down", ui_wtce_down}, - {"ooc_toggle", ui_ooc_toggle}, {"change_character", ui_change_character}, - {"reload_theme", ui_reload_theme}, {"call_mod", ui_call_mod}, {"switch_area_music", ui_switch_area_music}, - {"theme_list", ui_theme_list}, - {"confirm_theme", ui_confirm_theme}, + {"config_panel", ui_config_panel}, {"note_button", ui_note_button}, // Each ui_label_images[i] {"pre", ui_pre}, @@ -427,9 +398,6 @@ void Courtroom::reset_widget_names() {"prosecution_plus", ui_prosecution_plus}, {"prosecution_minus", ui_prosecution_minus}, {"text_color", ui_text_color}, - {"music_slider", ui_music_slider}, - {"sfx_slider", ui_sfx_slider}, - {"blip_slider", ui_blip_slider}, {"evidence_button", ui_evidence_button}, {"notepad_image", ui_vp_notepad_image}, {"notepad", ui_vp_notepad}, @@ -569,9 +537,6 @@ void Courtroom::set_widget_layers() void Courtroom::set_widgets() { - blip_rate = ao_app->read_blip_rate(); - blank_blip = ao_app->get_blank_blip(); - QString filename = design_ini; pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", filename); @@ -648,8 +613,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_ic_chatlog, "ic_chatlog"); - set_size_and_pos(ui_ms_chatlog, "ms_chatlog"); - set_size_and_pos(ui_server_chatlog, "server_chatlog"); set_size_and_pos(ui_mute_list, "mute_list"); @@ -723,13 +686,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_prosecution_bar, "prosecution_bar"); ui_prosecution_bar->set_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); - set_size_and_pos(ui_music_label, "music_label"); - ui_music_label->setText("Music"); - set_size_and_pos(ui_sfx_label, "sfx_label"); - ui_sfx_label->setText("Sfx"); - set_size_and_pos(ui_blip_label, "blip_label"); - ui_blip_label->setText("Blips"); - // set_size_and_pos(ui_shouts[0], "hold_it"); // ui_shouts[0]->show(); // set_size_and_pos(ui_shouts[1], "objection"); @@ -823,15 +779,11 @@ void Courtroom::set_widgets() } set_free_blocks(); - set_size_and_pos(ui_ooc_toggle, "ooc_toggle"); - ui_ooc_toggle->setText("Server"); - set_size_and_pos(ui_call_mod, "call_mod"); set_size_and_pos(ui_change_character, "change_character"); - set_size_and_pos(ui_reload_theme, "reload_theme"); set_size_and_pos(ui_call_mod, "call_mod"); - set_size_and_pos(ui_confirm_theme, "confirm_theme"); + set_size_and_pos(ui_config_panel, "config_panel"); set_size_and_pos(ui_note_button, "note_button"); set_size_and_pos(ui_switch_area_music, "switch_area_music"); @@ -840,17 +792,14 @@ void Courtroom::set_widgets() ui_change_character->setText("Change character"); ui_change_character->setStyleSheet(""); - ui_reload_theme->setText("Reload theme"); - ui_reload_theme->setStyleSheet(""); - ui_call_mod->setText("Call mod"); ui_call_mod->setStyleSheet(""); ui_switch_area_music->setText("A/M"); ui_switch_area_music->setStyleSheet(""); - ui_confirm_theme->setText("^"); - ui_confirm_theme->setStyleSheet(""); + ui_config_panel->setText("Config"); + ui_config_panel->setStyleSheet(""); ui_note_button->setText("><:"); ui_note_button->setStyleSheet(""); @@ -863,10 +812,6 @@ void Courtroom::set_widgets() if (!ui_change_character->image_path.isEmpty()) ui_change_character->setText(""); - ui_reload_theme->set_image("reloadtheme.png"); - if (!ui_reload_theme->image_path.isEmpty()) - ui_reload_theme->setText(""); - ui_call_mod->set_image("callmod.png"); if (!ui_call_mod->image_path.isEmpty()) ui_call_mod->setText(""); @@ -875,17 +820,15 @@ void Courtroom::set_widgets() if (!ui_switch_area_music->image_path.isEmpty()) ui_switch_area_music->setText(""); - ui_confirm_theme->set_image("confirmtheme.png"); - if (!ui_confirm_theme->image_path.isEmpty()) - ui_confirm_theme->setText(""); + ui_config_panel->set_image("config_panel.png"); + if (!ui_config_panel->image_path.isEmpty()) + ui_config_panel->setText(""); ui_note_button->set_image("notebutton.png"); if (!ui_note_button->image_path.isEmpty()) - ui_note_button->setText(""); + ui_note_button->setText(""); } - set_size_and_pos(ui_theme_list, "theme_list"); - set_size_and_pos(ui_pre, "pre"); ui_pre->setText("Pre"); @@ -958,10 +901,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_text_color, "text_color"); - set_size_and_pos(ui_music_slider, "music_slider"); - set_size_and_pos(ui_sfx_slider, "sfx_slider"); - set_size_and_pos(ui_blip_slider, "blip_slider"); - set_size_and_pos(ui_evidence_button, "evidence_button"); ui_evidence_button->set_image("evidencebutton.png"); @@ -1134,9 +1073,12 @@ void Courtroom::check_effects() for(int i = 0; i < ui_effects.size(); ++i) { QStringList paths{ + char_path + effect_names.at(i) + ".webp", char_path + effect_names.at(i) + ".gif", + theme_variant_path + effect_names.at(i) + ".webp", theme_variant_path + effect_names.at(i) + ".gif", theme_variant_path + effect_names.at(i) + ".apng", + theme_path + effect_names.at(i) + ".webp", theme_path + effect_names.at(i) + ".gif", theme_path + effect_names.at(i) + ".apng" }; @@ -1162,9 +1104,12 @@ void Courtroom::check_free_blocks() for(int i = 0; i < ui_free_blocks.size(); ++i) { QStringList paths{ + char_path + free_block_names.at(i) + ".webp", char_path + free_block_names.at(i) + ".gif", + theme_variant_path + free_block_names.at(i) + ".webp", theme_variant_path + free_block_names.at(i) + ".gif", theme_variant_path + free_block_names.at(i) + ".apng", + theme_path + free_block_names.at(i) + ".webp", theme_path + free_block_names.at(i) + ".gif", theme_path + free_block_names.at(i) + ".apng" }; @@ -1190,9 +1135,12 @@ void Courtroom::check_shouts() for(int i = 0; i < ui_shouts.size(); ++i) { QStringList paths{ + char_path + shout_names.at(i) + ".webp", char_path + shout_names.at(i) + ".gif", + theme_variant_path + shout_names.at(i) + ".webp", theme_variant_path + shout_names.at(i) + ".gif", theme_variant_path + shout_names.at(i) + ".apng", + theme_path + shout_names.at(i) + ".webp", theme_path + shout_names.at(i) + ".gif", theme_path + shout_names.at(i) + ".apng" }; @@ -1218,9 +1166,12 @@ void Courtroom::check_wtce() for(int i = 0; i < ui_wtce.size(); ++i) { QStringList paths{ + char_path + wtce_names.at(i) + ".webp", char_path + wtce_names.at(i) + ".gif", + theme_variant_path + wtce_names.at(i) + ".webp", theme_variant_path + wtce_names.at(i) + ".gif", theme_variant_path + wtce_names.at(i) + ".apng", + theme_path + wtce_names.at(i) + ".webp", theme_path + wtce_names.at(i) + ".gif", theme_path + wtce_names.at(i) + ".apng" }; @@ -1481,7 +1432,6 @@ void Courtroom::set_dropdowns() { set_dropdown(ui_text_color, "[TEXT COLOR]"); set_dropdown(ui_pos_dropdown, "[POS DROPDOWN]"); - set_dropdown(ui_theme_list, "[THEME LIST]"); set_dropdown(ui_emote_dropdown, "[EMOTE DROPDOWN]"); set_dropdown(ui_mute_list, "[MUTE LIST]"); set_dropdown(ui_ic_chat_message, "[IC LINE]"); @@ -1521,7 +1471,6 @@ void Courtroom::set_fonts() set_font(ui_vp_showname, "showname"); set_font(ui_vp_message, "message"); set_font(ui_ic_chatlog, "ic_chatlog"); - set_font(ui_ms_chatlog, "ms_chatlog"); set_font(ui_server_chatlog, "server_chatlog"); set_font(ui_music_list, "music_list"); set_font(ui_area_list, "area_list"); diff --git a/emotes.cpp b/emotes.cpp index 90fc1006c..cd3194990 100644 --- a/emotes.cpp +++ b/emotes.cpp @@ -172,7 +172,7 @@ void Courtroom::select_emote(int p_id) if (old_emote == current_emote) // toggle ui_pre->setChecked(!ui_pre->isChecked()); - else if (emote_mod == 1 || ao_app->read_config("always_pre") == "true") + else if (emote_mod == 1 || ao_app->get_always_pre_enabled()) ui_pre->setChecked(true); else ui_pre->setChecked(false); diff --git a/lobby.cpp b/lobby.cpp index bba8dddee..e5ebbb7b5 100644 --- a/lobby.cpp +++ b/lobby.cpp @@ -1,174 +1,171 @@ #include "lobby.h" -#include "debug_functions.h" #include "aoapplication.h" -#include "networkmanager.h" #include "aosfxplayer.h" +#include "debug_functions.h" +#include "networkmanager.h" #include #include Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() { - ao_app = p_ao_app; - - this->setWindowTitle("Danganronpa Online"); - - ui_background = new AOImage(this, ao_app); - ui_public_servers = new AOButton(this, ao_app); - ui_favorites = new AOButton(this, ao_app); - ui_refresh = new AOButton(this, ao_app); - ui_add_to_fav = new AOButton(this, ao_app); - ui_connect = new AOButton(this, ao_app); - ui_version = new QLabel(this); - ui_about = new AOButton(this, ao_app); - ui_server_list = new QListWidget(this); - ui_player_count = new QLabel(this); - ui_description = new AOTextArea(this); - ui_chatbox = new AOTextArea(this); - ui_chatbox->setOpenExternalLinks(true); - ui_chatname = new QLineEdit(this); - ui_chatname->setPlaceholderText("Name"); - ui_chatmessage = new QLineEdit(this); - ui_loading_background = new AOImage(this, ao_app); - ui_loading_text = new QTextEdit(ui_loading_background); - ui_progress_bar = new QProgressBar(ui_loading_background); - ui_progress_bar->setMinimum(0); - ui_progress_bar->setMaximum(100); - ui_progress_bar->setStyleSheet("QProgressBar{ color: white; }"); - ui_cancel = new AOButton(ui_loading_background, ao_app); - - connect(ui_public_servers, SIGNAL(clicked()), this, SLOT(on_public_servers_clicked())); - connect(ui_favorites, SIGNAL(clicked()), this, SLOT(on_favorites_clicked())); - connect(ui_refresh, SIGNAL(pressed()), this, SLOT(on_refresh_pressed())); - connect(ui_refresh, SIGNAL(released()), this, SLOT(on_refresh_released())); - connect(ui_add_to_fav, SIGNAL(pressed()), this, SLOT(on_add_to_fav_pressed())); - connect(ui_add_to_fav, SIGNAL(released()), this, SLOT(on_add_to_fav_released())); - connect(ui_connect, SIGNAL(pressed()), this, SLOT(on_connect_pressed())); - connect(ui_connect, SIGNAL(released()), this, SLOT(on_connect_released())); - connect(ui_about, SIGNAL(clicked()), this, SLOT(on_about_clicked())); - connect(ui_server_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_server_list_clicked(QModelIndex))); - connect(ui_chatmessage, SIGNAL(returnPressed()), this, SLOT(on_chatfield_return_pressed())); - connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); - - set_widgets(); + ao_app = p_ao_app; + + this->setWindowTitle("Danganronpa Online"); + + ui_background = new AOImage(this, ao_app); + ui_public_servers = new AOButton(this, ao_app); + ui_favorites = new AOButton(this, ao_app); + ui_refresh = new AOButton(this, ao_app); + ui_add_to_fav = new AOButton(this, ao_app); + ui_connect = new AOButton(this, ao_app); + ui_version = new QLabel(this); + ui_about = new AOButton(this, ao_app); + ui_server_list = new QListWidget(this); + ui_player_count = new QLabel(this); + ui_description = new AOTextArea(this); + ui_chatbox = new AOTextArea(this); + ui_chatbox->setOpenExternalLinks(true); + ui_chatname = new QLineEdit(this); + ui_chatname->setPlaceholderText("Name"); + ui_chatmessage = new QLineEdit(this); + ui_loading_background = new AOImage(this, ao_app); + ui_loading_text = new QTextEdit(ui_loading_background); + ui_progress_bar = new QProgressBar(ui_loading_background); + ui_progress_bar->setMinimum(0); + ui_progress_bar->setMaximum(100); + ui_progress_bar->setStyleSheet("QProgressBar{ color: white; }"); + ui_cancel = new AOButton(ui_loading_background, ao_app); + + connect(ui_public_servers, SIGNAL(clicked()), this, SLOT(on_public_servers_clicked())); + connect(ui_favorites, SIGNAL(clicked()), this, SLOT(on_favorites_clicked())); + connect(ui_refresh, SIGNAL(pressed()), this, SLOT(on_refresh_pressed())); + connect(ui_refresh, SIGNAL(released()), this, SLOT(on_refresh_released())); + connect(ui_add_to_fav, SIGNAL(pressed()), this, SLOT(on_add_to_fav_pressed())); + connect(ui_add_to_fav, SIGNAL(released()), this, SLOT(on_add_to_fav_released())); + connect(ui_connect, SIGNAL(pressed()), this, SLOT(on_connect_pressed())); + connect(ui_connect, SIGNAL(released()), this, SLOT(on_connect_released())); + connect(ui_about, SIGNAL(clicked()), this, SLOT(on_about_clicked())); + connect(ui_server_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_server_list_clicked(QModelIndex))); + connect(ui_chatmessage, SIGNAL(returnPressed()), this, SLOT(on_chatfield_return_pressed())); + connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); + + set_widgets(); } //sets images, position and size void Lobby::set_widgets() { - ao_app->reload_theme(); + QString filename = "lobby_design.ini"; - QString filename = "lobby_design.ini"; + pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", filename); - pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", filename); + if (f_lobby.width < 0 || f_lobby.height < 0) + { + qDebug() << "W: did not find lobby width or height in " << filename; - if (f_lobby.width < 0 || f_lobby.height < 0) - { - qDebug() << "W: did not find lobby width or height in " << filename; + // Most common symptom of bad config files and missing assets. + call_notice("It doesn't look like your client is set up correctly.\n" + "Did you download all resources correctly from tiny.cc/getao, " + "including the large 'base' folder?"); - // Most common symptom of bad config files and missing assets. - call_notice("It doesn't look like your client is set up correctly.\n" - "Did you download all resources correctly from tiny.cc/getao, " - "including the large 'base' folder?"); + this->resize(517, 666); + } + else + { + this->resize(f_lobby.width, f_lobby.height); + } - this->resize(517, 666); - } - else - { - this->resize(f_lobby.width, f_lobby.height); - } + set_size_and_pos(ui_background, "lobby"); + ui_background->set_image("lobbybackground.png"); - set_size_and_pos(ui_background, "lobby"); - ui_background->set_image("lobbybackground.png"); + set_size_and_pos(ui_public_servers, "public_servers"); + ui_public_servers->set_image("publicservers_selected.png"); - set_size_and_pos(ui_public_servers, "public_servers"); - ui_public_servers->set_image("publicservers_selected.png"); + set_size_and_pos(ui_favorites, "favorites"); + ui_favorites->set_image("favorites.png"); - set_size_and_pos(ui_favorites, "favorites"); - ui_favorites->set_image("favorites.png"); + set_size_and_pos(ui_refresh, "refresh"); + ui_refresh->set_image("refresh.png"); - set_size_and_pos(ui_refresh, "refresh"); - ui_refresh->set_image("refresh.png"); + set_size_and_pos(ui_add_to_fav, "add_to_fav"); + ui_add_to_fav->set_image("addtofav.png"); - set_size_and_pos(ui_add_to_fav, "add_to_fav"); - ui_add_to_fav->set_image("addtofav.png"); + set_size_and_pos(ui_connect, "connect"); + ui_connect->set_image("connect.png"); - set_size_and_pos(ui_connect, "connect"); - ui_connect->set_image("connect.png"); + set_size_and_pos(ui_version, "version"); + ui_version->setText("Version: " + ao_app->get_version_string()); - set_size_and_pos(ui_version, "version"); - ui_version->setText("Version: " + ao_app->get_version_string()); + set_size_and_pos(ui_about, "about"); + ui_about->set_image("about.png"); - set_size_and_pos(ui_about, "about"); - ui_about->set_image("about.png"); - - set_size_and_pos(ui_server_list, "server_list"); - ui_server_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + set_size_and_pos(ui_server_list, "server_list"); + ui_server_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "font: bold;"); - set_size_and_pos(ui_player_count, "player_count"); - ui_player_count->setText("Offline"); - ui_player_count->setStyleSheet("font: bold;" - "color: white;" - "qproperty-alignment: AlignCenter;"); - - set_size_and_pos(ui_description, "description"); - ui_description->setReadOnly(true); - ui_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: white;"); - - set_size_and_pos(ui_chatbox, "chatbox"); - ui_chatbox->setReadOnly(true); - ui_chatbox->setStyleSheet("QTextBrowser{background-color: rgba(0, 0, 0, 0);}"); - - set_size_and_pos(ui_chatname, "chatname"); - ui_chatname->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "selection-background-color: rgba(0, 0, 0, 0);"); - - set_size_and_pos(ui_chatmessage, "chatmessage"); - ui_chatmessage->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "selection-background-color: rgba(0, 0, 0, 0);"); - - ui_loading_background->resize(this->width(), this->height()); - ui_loading_background->set_image("loadingbackground.png"); - - - set_size_and_pos(ui_loading_text, "loading_label"); - ui_loading_text->setFont(QFont("Arial", 20, QFont::Bold)); - ui_loading_text->setReadOnly(true); - ui_loading_text->setAlignment(Qt::AlignCenter); - ui_loading_text->setFrameStyle(QFrame::NoFrame); - ui_loading_text->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: rgba(255, 128, 0, 255);"); - ui_loading_text->append("Loading"); - - set_size_and_pos(ui_progress_bar, "progress_bar"); - set_size_and_pos(ui_cancel, "cancel"); - ui_cancel->setText("Cancel"); - - ui_loading_background->hide(); - - set_fonts(); - set_stylesheets(); + set_size_and_pos(ui_player_count, "player_count"); + ui_player_count->setStyleSheet("font: bold;" + "color: white;" + "qproperty-alignment: AlignCenter;"); + + set_size_and_pos(ui_description, "description"); + ui_description->setReadOnly(true); + ui_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "color: white;"); + + set_size_and_pos(ui_chatbox, "chatbox"); + ui_chatbox->setReadOnly(true); + ui_chatbox->setStyleSheet("QTextBrowser{background-color: rgba(0, 0, 0, 0);}"); + + set_size_and_pos(ui_chatname, "chatname"); + ui_chatname->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "selection-background-color: rgba(0, 0, 0, 0);"); + + set_size_and_pos(ui_chatmessage, "chatmessage"); + ui_chatmessage->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "selection-background-color: rgba(0, 0, 0, 0);"); + + ui_loading_background->resize(this->width(), this->height()); + ui_loading_background->set_image("loadingbackground.png"); + + set_size_and_pos(ui_loading_text, "loading_label"); + ui_loading_text->setFont(QFont("Arial", 20, QFont::Bold)); + ui_loading_text->setReadOnly(true); + ui_loading_text->setAlignment(Qt::AlignCenter); + ui_loading_text->setFrameStyle(QFrame::NoFrame); + ui_loading_text->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "color: rgba(255, 128, 0, 255);"); + ui_loading_text->append("Loading"); + + set_size_and_pos(ui_progress_bar, "progress_bar"); + set_size_and_pos(ui_cancel, "cancel"); + ui_cancel->setText("Cancel"); + + ui_loading_background->hide(); + + set_fonts(); + set_stylesheets(); + set_choose_a_server(); } void Lobby::set_size_and_pos(QWidget *p_widget, QString p_identifier) { - QString filename = "lobby_design.ini"; - - pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - - 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); - } + QString filename = "lobby_design.ini"; + + pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); + + 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 Lobby::set_fonts() @@ -184,272 +181,271 @@ void Lobby::set_fonts() void Lobby::set_stylesheet(QWidget *widget, QString target_tag) { - QString f_file = "lobby_stylesheets.css"; - QString style_sheet_string = ao_app->get_stylesheet(target_tag, f_file); - if (style_sheet_string != "") - widget->setStyleSheet(style_sheet_string); + QString f_file = "lobby_stylesheets.css"; + QString style_sheet_string = ao_app->get_stylesheet(target_tag, f_file); + if (style_sheet_string != "") + widget->setStyleSheet(style_sheet_string); } void Lobby::set_stylesheets() { - set_stylesheet(ui_player_count, "[PLAYER COUNT]"); - set_stylesheet(ui_description, "[DESCRIPTION]"); - set_stylesheet(ui_chatbox, "[CHAT BOX]"); - set_stylesheet(ui_chatname, "[CHAT NAME]"); - set_stylesheet(ui_chatmessage, "[CHAT MESSAGE]"); - set_stylesheet(ui_loading_text, "[LOADING TEXT]"); - set_stylesheet(ui_server_list, "[SERVER LIST]"); + set_stylesheet(ui_player_count, "[PLAYER COUNT]"); + set_stylesheet(ui_description, "[DESCRIPTION]"); + set_stylesheet(ui_chatbox, "[CHAT BOX]"); + set_stylesheet(ui_chatname, "[CHAT NAME]"); + set_stylesheet(ui_chatmessage, "[CHAT MESSAGE]"); + set_stylesheet(ui_loading_text, "[LOADING TEXT]"); + set_stylesheet(ui_server_list, "[SERVER LIST]"); } void Lobby::set_font(QWidget *widget, QString p_identifier) { - QString design_file = "lobby_fonts.ini"; - int f_weight = ao_app->get_font_size(p_identifier, design_file); - QString class_name = widget->metaObject()->className(); - - QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); - - QFont font(font_name, f_weight); + QString design_file = "lobby_fonts.ini"; + int f_weight = ao_app->get_font_size(p_identifier, design_file); + QString class_name = widget->metaObject()->className(); - bool use = (bool)ao_app->get_font_size("use_custom_fonts", design_file); + QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); - if(use) - { - widget->setFont(font); + QFont font(font_name, f_weight); - QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); + bool use = (bool)ao_app->get_font_size("use_custom_fonts", design_file); - bool bold = (bool)ao_app->get_font_size(p_identifier + "_bold", design_file); // is the font bold or not? - bool center = (bool)ao_app->get_font_size(p_identifier + "_center", design_file); // should it be centered? + if (use) + { + widget->setFont(font); + QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); - QString is_bold = ""; - if(bold) is_bold = "bold"; + bool bold = (bool)ao_app->get_font_size(p_identifier + "_bold", design_file); // is the font bold or not? + bool center = (bool)ao_app->get_font_size(p_identifier + "_center", design_file); // should it be centered? - QString is_center = ""; - if(center) is_center = "qproperty-alignment: AlignCenter;"; + QString is_bold = ""; + if (bold) is_bold = "bold"; - QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + - "color: rgba(" + - QString::number(f_color.red()) + ", " + - QString::number(f_color.green()) + ", " + - QString::number(f_color.blue()) + ", 255);\n" + - is_center + "\n" + - "font: " + is_bold + "; }"; + QString is_center = ""; + if (center) is_center = "qproperty-alignment: AlignCenter;"; - widget->setStyleSheet(style_sheet_string); - } + QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + + "color: rgba(" + + QString::number(f_color.red()) + ", " + + QString::number(f_color.green()) + ", " + + QString::number(f_color.blue()) + ", 255);\n" + + is_center + "\n" + + "font: " + is_bold + "; }"; + widget->setStyleSheet(style_sheet_string); + } - return; + return; } void Lobby::set_loading_text(QString p_text) { - ui_loading_text->clear(); - ui_loading_text->setAlignment(Qt::AlignCenter); - ui_loading_text->append(p_text); + ui_loading_text->clear(); + ui_loading_text->setAlignment(Qt::AlignCenter); + ui_loading_text->append(p_text); } QString Lobby::get_chatlog() { - QString return_value = ui_chatbox->toPlainText(); + QString return_value = ui_chatbox->toPlainText(); - return return_value; + return return_value; } int Lobby::get_selected_server() { - return ui_server_list->currentRow(); + return ui_server_list->currentRow(); } void Lobby::set_loading_value(int p_value) { - ui_progress_bar->setValue(p_value); + ui_progress_bar->setValue(p_value); } void Lobby::on_public_servers_clicked() { - ui_public_servers->set_image("publicservers_selected.png"); - ui_favorites->set_image("favorites.png"); + ui_public_servers->set_image("publicservers_selected.png"); + ui_favorites->set_image("favorites.png"); - list_servers(); + list_servers(); - public_servers_selected = true; + public_servers_selected = true; } void Lobby::on_favorites_clicked() { - ui_favorites->set_image("favorites_selected.png"); - ui_public_servers->set_image("publicservers.png"); + ui_favorites->set_image("favorites_selected.png"); + ui_public_servers->set_image("publicservers.png"); - ao_app->set_favorite_list(); - //ao_app->favorite_list = read_serverlist_txt(); + ao_app->set_favorite_list(); + //ao_app->favorite_list = read_serverlist_txt(); - list_favorites(); + list_favorites(); - public_servers_selected = false; + public_servers_selected = false; } void Lobby::on_refresh_pressed() { - ui_refresh->set_image("refresh_pressed.png"); + ui_refresh->set_image("refresh_pressed.png"); } void Lobby::on_refresh_released() { - ui_refresh->set_image("refresh.png"); + ui_refresh->set_image("refresh.png"); - AOPacket *f_packet = new AOPacket("ALL#%"); + AOPacket *f_packet = new AOPacket("ALL#%"); - ao_app->send_ms_packet(f_packet); + ao_app->send_ms_packet(f_packet); } void Lobby::on_add_to_fav_pressed() { - ui_add_to_fav->set_image("addtofav_pressed.png"); + ui_add_to_fav->set_image("addtofav_pressed.png"); } void Lobby::on_add_to_fav_released() { - ui_add_to_fav->set_image("addtofav.png"); + ui_add_to_fav->set_image("addtofav.png"); - //you cant add favorites from favorites m8 - if (!public_servers_selected) - return; + //you cant add favorites from favorites m8 + if (!public_servers_selected) + return; - ao_app->add_favorite_server(ui_server_list->currentRow()); + ao_app->add_favorite_server(ui_server_list->currentRow()); } void Lobby::on_connect_pressed() { - ui_connect->set_image("connect_pressed.png"); + ui_connect->set_image("connect_pressed.png"); } void Lobby::on_connect_released() { - ui_connect->set_image("connect.png"); + ui_connect->set_image("connect.png"); - AOPacket *f_packet; + AOPacket *f_packet; - f_packet = new AOPacket("askchaa#%"); + f_packet = new AOPacket("askchaa#%"); - ao_app->send_server_packet(f_packet); + ao_app->send_server_packet(f_packet); } void Lobby::on_about_clicked() { - call_notice("Attorney Online 2 is built using Qt 5.7\n\n" - "Lead development:\n" - "OmniTroid\n\n" - "stonedDiscord\n" - "longbyte1\n" - "Supporting development:\n" - "Fiercy\n\n" - "UI design:\n" - "Ruekasu\n" - "Draxirch\n\n" - "Special thanks:\n" - "Unishred\n" - "Argoneus\n" - "Noevain\n" - "Cronnicossy"); + call_notice("Attorney Online 2 is built using Qt.\n\n" + "Lead development:\n" + "OmniTroid\n\n" + "stonedDiscord\n" + "longbyte1\n" + "Supporting development:\n" + "Fiercy\n\n" + "UI design:\n" + "Ruekasu\n" + "Draxirch\n\n" + "Special thanks:\n" + "Unishred\n" + "Argoneus\n" + "Noevain\n" + "Cronnicossy"); } void Lobby::on_server_list_clicked(QModelIndex p_model) { - server_type f_server; - int n_server = p_model.row(); + int n_server = p_model.row(); - if (n_server < 0) - return; + if (n_server < 0) + return; - if (public_servers_selected) - { - QVector f_server_list = ao_app->get_server_list(); + if (public_servers_selected) + { + QVector f_server_list = ao_app->get_server_list(); - if (n_server >= f_server_list.size()) - return; + if (n_server >= f_server_list.size()) + return; - f_server = f_server_list.at(p_model.row()); - } - else - { - if (n_server >= ao_app->get_favorite_list().size()) - return; + f_last_server = f_server_list.at(p_model.row()); + } + else + { + if (n_server >= ao_app->get_favorite_list().size()) + return; - f_server = ao_app->get_favorite_list().at(p_model.row()); - } + f_last_server = ao_app->get_favorite_list().at(p_model.row()); + } - ui_description->clear(); - ui_description->append(f_server.desc); + ui_player_count->setText(nullptr); + ui_description->moveCursor(QTextCursor::Start); + ui_description->setText("Connecting to " + f_last_server.name + "...\n\n"); + ui_description->append(f_last_server.desc); + ui_description->ensureCursorVisible(); - ui_description->moveCursor(QTextCursor::Start); - ui_description->ensureCursorVisible(); - - ui_player_count->setText("Offline"); - - ao_app->net_manager->connect_to_server(f_server); + ao_app->net_manager->connect_to_server(f_last_server); } void Lobby::on_chatfield_return_pressed() { - //no you can't send empty messages - if (ui_chatname->text() == "" || ui_chatmessage->text() == "") - return; + //no you can't send empty messages + if (ui_chatname->text() == "" || ui_chatmessage->text() == "") + return; + QString f_header = "CT"; + QStringList f_contents{ui_chatname->text(), ui_chatmessage->text()}; - QString f_header = "CT"; - QStringList f_contents{ui_chatname->text(), ui_chatmessage->text()}; + AOPacket *f_packet = new AOPacket(f_header, f_contents); - AOPacket *f_packet = new AOPacket(f_header, f_contents); + ao_app->send_ms_packet(f_packet); - ao_app->send_ms_packet(f_packet); - - ui_chatmessage->clear(); + ui_chatmessage->clear(); } void Lobby::list_servers() { - public_servers_selected = true; - ui_favorites->set_image("favorites.png"); - ui_public_servers->set_image("publicservers_selected.png"); + public_servers_selected = true; + ui_favorites->set_image("favorites.png"); + ui_public_servers->set_image("publicservers_selected.png"); - ui_server_list->clear(); + ui_server_list->clear(); - for (server_type i_server : ao_app->get_server_list()) - { - ui_server_list->addItem(i_server.name); - } + for (server_type i_server : ao_app->get_server_list()) + { + ui_server_list->addItem(i_server.name); + } } void Lobby::list_favorites() { - ui_server_list->clear(); + ui_server_list->clear(); - for (server_type i_server : ao_app->get_favorite_list()) - { - ui_server_list->addItem(i_server.name); - } + for (server_type i_server : ao_app->get_favorite_list()) + { + ui_server_list->addItem(i_server.name); + } } void Lobby::append_chatmessage(QString f_name, QString f_message) { - ui_chatbox->append_chatmessage(f_name, f_message); + ui_chatbox->append_chatmessage(f_name, f_message); } void Lobby::append_error(QString f_message) { - ui_chatbox->append_error(f_message); + ui_chatbox->append_error(f_message); } -void Lobby::set_player_count(int players_online, int max_players) +void Lobby::set_choose_a_server() { - QString f_string = "Online: " + QString::number(players_online) + "/" + QString::number(max_players); - ui_player_count->setText(f_string); + ui_player_count->setText(nullptr); + ui_description->setText(tr("Choose a server.")); } -Lobby::~Lobby() +void Lobby::set_player_count(int players_online, int max_players) { + QString f_string = "Online: " + QString::number(players_online) + "/" + QString::number(max_players); + ui_player_count->setText(f_string); + ui_description->setText("Connected to " + f_last_server.name + "\n\n"); + ui_description->append(f_last_server.desc); + ui_description->ensureCursorVisible(); } diff --git a/lobby.h b/lobby.h index e0eb0bfa0..1293b2dbc 100644 --- a/lobby.h +++ b/lobby.h @@ -1,16 +1,16 @@ #ifndef LOBBY_H #define LOBBY_H -#include "aoimage.h" #include "aobutton.h" +#include "aoimage.h" #include "aopacket.h" #include "aotextarea.h" -#include -#include #include -#include #include +#include +#include +#include #include #include @@ -18,78 +18,79 @@ class AOApplication; class Lobby : public QMainWindow { - Q_OBJECT + Q_OBJECT public: - Lobby(AOApplication *p_ao_app); - - void set_widgets(); - void list_servers(); - void list_favorites(); - void append_chatmessage(QString f_name, QString f_message); - void append_error(QString f_message); - void set_player_count(int players_online, int max_players); - void set_loading_text(QString p_text); - void set_stylesheet(QWidget *widget, QString target_tag); - void set_stylesheets(); - void set_fonts(); - void set_font(QWidget *widget, QString p_identifier); - void show_loading_overlay(){ui_loading_background->show();} - void hide_loading_overlay(){ui_loading_background->hide();} - QString get_chatlog(); - int get_selected_server(); - - void set_loading_value(int p_value); - - bool public_servers_selected = true; - - ~Lobby(); + Lobby(AOApplication *p_ao_app); + + void set_widgets(); + void list_servers(); + void list_favorites(); + void append_chatmessage(QString f_name, QString f_message); + void append_error(QString f_message); + void set_choose_a_server(); + void set_player_count(int players_online, int max_players); + void set_loading_text(QString p_text); + void set_stylesheet(QWidget *widget, QString target_tag); + void set_stylesheets(); + void set_fonts(); + void set_font(QWidget *widget, QString p_identifier); + void show_loading_overlay() { ui_loading_background->show(); } + void hide_loading_overlay() { ui_loading_background->hide(); } + QString get_chatlog(); + int get_selected_server(); + + void set_loading_value(int p_value); + + bool public_servers_selected = true; private: - AOApplication *ao_app = nullptr; + AOApplication *ao_app = nullptr; + + AOImage *ui_background; - AOImage *ui_background; + AOButton *ui_public_servers; + AOButton *ui_favorites; - AOButton *ui_public_servers; - AOButton *ui_favorites; + AOButton *ui_refresh; + AOButton *ui_add_to_fav; + AOButton *ui_connect; - AOButton *ui_refresh; - AOButton *ui_add_to_fav; - AOButton *ui_connect; + QLabel *ui_version; + AOButton *ui_about; - QLabel *ui_version; - AOButton *ui_about; + QListWidget *ui_server_list; - QListWidget *ui_server_list; + QLabel *ui_player_count; + AOTextArea *ui_description; - QLabel *ui_player_count; - AOTextArea *ui_description; + AOTextArea *ui_chatbox; - AOTextArea *ui_chatbox; + QLineEdit *ui_chatname; + QLineEdit *ui_chatmessage; - QLineEdit *ui_chatname; - QLineEdit *ui_chatmessage; + AOImage *ui_loading_background; + QTextEdit *ui_loading_text; + QProgressBar *ui_progress_bar; + AOButton *ui_cancel; - AOImage *ui_loading_background; - QTextEdit *ui_loading_text; - QProgressBar *ui_progress_bar; - AOButton *ui_cancel; + server_type f_last_server; - void set_size_and_pos(QWidget *p_widget, QString p_identifier); + void set_size_and_pos(QWidget *p_widget, QString p_identifier); private slots: - void on_public_servers_clicked(); - void on_favorites_clicked(); - - void on_refresh_pressed(); - void on_refresh_released(); - void on_add_to_fav_pressed(); - void on_add_to_fav_released(); - void on_connect_pressed(); - void on_connect_released(); - void on_about_clicked(); - void on_server_list_clicked(QModelIndex p_model); - void on_chatfield_return_pressed(); + void on_public_servers_clicked(); + void on_favorites_clicked(); + + void on_refresh_pressed(); + void on_refresh_released(); + void on_add_to_fav_pressed(); + void on_add_to_fav_released(); + void on_connect_pressed(); + void on_connect_released(); + void on_about_clicked(); + void on_server_list_clicked(QModelIndex p_model); + void on_chatfield_return_pressed(); }; #endif // LOBBY_H diff --git a/main.cpp b/main.cpp index ca44af6e5..347db528d 100644 --- a/main.cpp +++ b/main.cpp @@ -1,39 +1,39 @@ #include "aoapplication.h" - -#include "datatypes.h" -#include "networkmanager.h" -#include "lobby.h" #include "courtroom.h" +#include "datatypes.h" #include "debug_functions.h" +#include "lobby.h" +#include "networkmanager.h" -#include #include +#include +#include +#include #include int main(int argc, char *argv[]) { #if QT_VERSION > QT_VERSION_CHECK(5, 6, 0) - // High-DPI support is for Qt version >=5.6. - // However, many Linux distros still haven't brought their stable/LTS - // packages up to Qt 5.6, so this is conditional. - AOApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + // High-DPI support is for Qt version >=5.6. + // However, many Linux distros still haven't brought their stable/LTS + // packages up to Qt 5.6, so this is conditional. + AOApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif + AOApplication app(argc, argv); - AOApplication main_app(argc, argv); - - QPluginLoader apng("imageformats/qapng.dll"); - if (!apng.load()) - { + QPluginLoader apng("imageformats/qapng.dll"); + if (!apng.load()) + { #ifdef QT_NO_DEBUG - call_error(QString("APNG plugin has encountered an error: %s").arg(apng.errorString())); + call_error(QString("APNG plugin has encountered an error: %s").arg(apng.errorString())); #endif - } + } - main_app.construct_lobby(); + app.construct_lobby(); #ifdef QT_NO_DEBUG - main_app.net_manager->connect_to_master(); + app.net_manager->connect_to_master(); #endif - main_app.w_lobby->show(); + app.w_lobby->show(); - return main_app.exec(); + return app.exec(); } diff --git a/networkmanager.cpp b/networkmanager.cpp index 0c97cef2b..6aebb1e78 100644 --- a/networkmanager.cpp +++ b/networkmanager.cpp @@ -51,10 +51,14 @@ void NetworkManager::connect_to_master_nosrv() void NetworkManager::connect_to_server(server_type p_server) { - server_socket->close(); - server_socket->abort(); + disconnect_from_server(); + server_socket->connectToHost(p_server.ip, p_server.port); +} - server_socket->connectToHost(p_server.ip, p_server.port); +void NetworkManager::disconnect_from_server() +{ + server_socket->close(); + server_socket->abort(); } void NetworkManager::ship_ms_packet(QString p_packet) diff --git a/networkmanager.h b/networkmanager.h index 5003883a4..2bd4ba285 100644 --- a/networkmanager.h +++ b/networkmanager.h @@ -59,6 +59,7 @@ class NetworkManager : public QObject void connect_to_master(); void connect_to_master_nosrv(); void connect_to_server(server_type p_server); + void disconnect_from_server(); public slots: void ship_ms_packet(QString p_packet); diff --git a/packet_distribution.cpp b/packet_distribution.cpp index 0ce0f9436..dc0815066 100644 --- a/packet_distribution.cpp +++ b/packet_distribution.cpp @@ -69,10 +69,6 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) { w_lobby->append_chatmessage(f_name, f_message); } - if (courtroom_constructed && courtroom_loaded) - { - w_courtroom->append_ms_chatmessage(f_name, f_message); - } } else if (header == "AO2CHECK") { @@ -219,7 +215,6 @@ void AOApplication::server_packet_received(AOPacket *p_packet) loaded_evidence = 0; loaded_music = 0; - destruct_courtroom(); construct_courtroom(); courtroom_loaded = false; @@ -542,9 +537,6 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (!courtroom_constructed) goto end; - if (lobby_constructed) - w_courtroom->append_ms_chatmessage("", w_lobby->get_chatlog()); - w_courtroom->done_received(); courtroom_loaded = true; diff --git a/path_functions.cpp b/path_functions.cpp index ab35acab8..711e1e25d 100644 --- a/path_functions.cpp +++ b/path_functions.cpp @@ -38,13 +38,12 @@ QString AOApplication::get_data_path() QString AOApplication::get_theme_path() { - return get_base_path() + "themes/" + current_theme.toLower() + "/"; + return get_base_path() + "themes/" + get_theme().toLower() + "/"; } QString AOApplication::get_theme_variant_path() { - return get_base_path() + "themes/" + current_theme.toLower() + "/" - + theme_variant.toLower() + "/"; + return get_base_path() + "themes/" + get_theme().toLower() + "/" + m_theme_variant.toLower() + "/"; } QString AOApplication::get_default_theme_path() diff --git a/res.qrc b/res.qrc new file mode 100644 index 000000000..7a3f29c47 --- /dev/null +++ b/res.qrc @@ -0,0 +1,6 @@ + + + res/fonts/Ace-Attorney.ttf + res/ui/config_panel.ui + + diff --git a/resource/fonts/Ace-Attorney.ttf b/res/fonts/Ace-Attorney.ttf similarity index 100% rename from resource/fonts/Ace-Attorney.ttf rename to res/fonts/Ace-Attorney.ttf diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui new file mode 100644 index 000000000..777660c74 --- /dev/null +++ b/res/ui/config_panel.ui @@ -0,0 +1,457 @@ + + + Form + + + + 0 + 0 + 390 + 268 + + + + + 0 + 0 + + + + + 390 + 268 + + + + Form + + + + + + 0 + + + + General + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + Username: + + + + + + + + + + Callwords: + + + + + + + + + + Qt::Horizontal + + + + + + + Theme: + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + 0 + + + + Reload + + + + + + + + + + Qt::Horizontal + + + + + + + Log length: + + + + + + + line(s) + + + 0 + + + 10000 + + + 200 + + + + + + + Log uses newline: + + + + + + + + 0 + 0 + + + + + + + + Log is downward: + + + + + + + + 0 + 0 + + + + + + + + Log is recorded: + + + + + + + + 0 + 0 + + + + + + + + + Audio + + + + + + System: + + + + + + + + + + 0 + 0 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Music: + + + + + + + + + + 0 + 0 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Effects: + + + + + + + + + + 0 + 0 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Blips: + + + + + + + + + + 0 + 0 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Blip rate: + + + + + + + 1 + + + 1000000000 + + + 1000000000 + + + + + + + Blank blips: + + + + + + + + 0 + 0 + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Save + + + + + + + + + + diff --git a/resources.qrc b/resources.qrc deleted file mode 100644 index d0c2f22b4..000000000 --- a/resources.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - resource/fonts/Ace-Attorney.ttf - - diff --git a/text_file_functions.cpp b/text_file_functions.cpp index db37e3408..871b32ed9 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -2,176 +2,51 @@ #include "file_functions.h" +#include "aoconfig.h" + #include #include #include #include #include -QString AOApplication::read_config(QString searchline) -{ - QString return_value = ""; - - QFile config_file(get_base_path() + "config.ini"); - if (!config_file.open(QIODevice::ReadOnly)) - return return_value; - - QTextStream in(&config_file); - - while(!in.atEnd()) - { - QString f_line = in.readLine().trimmed(); - - if (!f_line.startsWith(searchline)) - continue; - - QStringList line_elements = f_line.split("="); - - if (line_elements.at(0).trimmed() != searchline) - continue; - - if (line_elements.size() < 2) - continue; - - return_value = line_elements.at(1).trimmed(); - break; - } - - config_file.close(); - - return return_value; -} - -QString AOApplication::read_theme() +QString AOApplication::get_theme() { - QString result = read_config("theme"); - - if (result == "") - return "default"; - else - return result; -} - -bool AOApplication::read_config_bool(QString p_name) -{ - return read_config(p_name) == "true"; + return config->theme(); } int AOApplication::read_blip_rate() { - QString result = read_config("blip_rate"); - - //note: the empty string converted to int will return 0 - if (result.toInt() <= 0) - return 1; - else - return result.toInt(); + return config->blip_rate(); } bool AOApplication::read_chatlog_newline() { - return read_config("chatlog_newline") == "true"; + return config->log_uses_newline_enabled(); } int AOApplication::get_default_music() { - QString f_result = read_config("default_music"); - - if (f_result == "") - return 50; - else return f_result.toInt(); + return config->music_volume(); } int AOApplication::get_default_sfx() { - QString f_result = read_config("default_sfx"); - - if (f_result == "") - return 50; - else return f_result.toInt(); + return config->effects_volume(); } int AOApplication::get_default_blip() { - QString f_result = read_config("default_blip"); - - if (f_result == "") - return 50; - else return f_result.toInt(); + return config->blips_volume(); } QStringList AOApplication::get_call_words() { - QStringList return_value; - - QFile callwords_ini; - - callwords_ini.setFileName(get_base_path() + "callwords.ini"); - - if (!callwords_ini.open(QIODevice::ReadOnly)) - return return_value; - - QTextStream in(&callwords_ini); - - while (!in.atEnd()) - { - QString line = in.readLine(); - return_value.append(line); - } - - return return_value; -} - -void AOApplication::write_theme(QString theme) -{ - QString filename = get_base_path() + "config.ini"; - QFile config_file(filename); - - if(!config_file.open(QIODevice::ReadOnly | QIODevice::Text)) - { - qDebug() << "Couldn't open config.ini"; - return; - } - - QTextStream in(&config_file); - - QByteArray t = ""; - - while(!in.atEnd()) - { - QString f_line = in.readLine(); - if(!f_line.startsWith("theme")) - { - t += f_line + "\n"; - continue; - } - - QStringList line_elements = f_line.split("="); - - if(line_elements.at(0).trimmed() != "theme") - { - t += f_line + "\n"; - continue; - } - - if(line_elements.size() < 2) - { - t += f_line +"\n"; - continue; - } - t += "theme = " + theme + "\n"; - } - config_file.close(); - - QFile ex(filename); - if(!ex.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) - { - qDebug() << "Couldn't open config.ini"; - return; - } - - ex.write(t); - ex.close(); +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + return config->callwords().split(" ", QString::SkipEmptyParts); +#else + return config->callwords().split(" ", Qt::SkipEmptyParts); +#endif } QString AOApplication::read_note(QString filename) @@ -750,24 +625,6 @@ QString AOApplication::get_char_shouts(QString p_char) return f_result.toLower(); } -int AOApplication::get_preanim_duration(QString p_char, QString p_emote) -{ - QString f_result = read_char_ini(p_char, p_emote, "[Time]", "[Emotions]"); - - if (f_result == "") - return -1; - else return f_result.toInt(); -} - -int AOApplication::get_ao2_preanim_duration(QString p_char, QString p_emote) -{ - QString f_result = read_char_ini(p_char, "%" + p_emote, "[Time]", "[Emotions]"); - - if (f_result == "") - return -1; - else return f_result.toInt(); -} - int AOApplication::get_emote_number(QString p_char) { QString f_result = read_char_ini(p_char, "number", "[Emotions]", "[Offsets]"); @@ -898,9 +755,7 @@ int AOApplication::get_text_delay(QString p_char, QString p_emote) bool AOApplication::get_blank_blip() { - QString f_result = read_config("blank_blip"); - - return f_result.startsWith("true"); + return config->blank_blips_enabled(); } QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) From 21f0483b995fef9f3bc8007a878bfbf9cab1621a Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 25 Aug 2020 09:15:41 -0400 Subject: [PATCH 047/842] Make AOScenes refresh on reload theme+Fix background not updating if exact same position after reload theme --- aoscene.cpp | 10 +++++++++- aoscene.h | 2 ++ courtroom_widgets.cpp | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/aoscene.cpp b/aoscene.cpp index 06ffe74d1..605cb339d 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -33,6 +33,14 @@ void AOScene::set_image(QString p_image) // do not update the movie if we're using the same file if (m_movie && m_movie->fileName() == target_path) return; + filename = target_path; + refresh(); +} + +void AOScene::refresh() +{ + if (filename.isEmpty()) + return; // clear previous this->clear(); @@ -43,7 +51,7 @@ void AOScene::set_image(QString p_image) // create new movie to run m_movie = new QMovie(this); setMovie(m_movie); - m_movie->setFileName(target_path); + m_movie->setFileName(filename); m_movie->setScaledSize(size()); m_movie->start(); } diff --git a/aoscene.h b/aoscene.h index a77fef3e7..8b965554a 100644 --- a/aoscene.h +++ b/aoscene.h @@ -14,10 +14,12 @@ class AOScene : public QLabel explicit AOScene(QWidget *parent, AOApplication *p_ao_app); void set_image(QString p_image); + void refresh(); private: AOApplication *ao_app = nullptr; QMovie *m_movie = nullptr; + QString filename = ""; }; #endif // AOSCENE_H diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 312cdb9e1..0b683ca2f 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -562,6 +562,7 @@ void Courtroom::set_widgets() ui_vp_background->move(0, 0); ui_vp_background->resize(ui_viewport->width(), ui_viewport->height()); + ui_vp_background->refresh(); ui_vp_speedlines->move(0, 0); ui_vp_speedlines->combo_resize(ui_viewport->width(), ui_viewport->height()); @@ -572,6 +573,7 @@ void Courtroom::set_widgets() //the AO2 desk element ui_vp_desk->move(0, 0); ui_vp_desk->resize(ui_viewport->width(), ui_viewport->height()); + ui_vp_desk->refresh(); ui_vp_evidence_display->move(0, 0); ui_vp_evidence_display->resize(ui_viewport->width(), ui_viewport->height()); From 50dd4e8d57f5c7c96423ee3665420343ea23040c Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 25 Aug 2020 10:40:15 -0400 Subject: [PATCH 048/842] Only refresh if widget changed size --- courtroom_widgets.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 0b683ca2f..049442ef2 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -558,11 +558,15 @@ void Courtroom::set_widgets() ui_background->resize(m_courtroom_width, m_courtroom_height); ui_background->set_image("courtroombackground.png"); + QSize original_viewport_size = ui_viewport->size(); set_size_and_pos(ui_viewport, "viewport"); ui_vp_background->move(0, 0); ui_vp_background->resize(ui_viewport->width(), ui_viewport->height()); - ui_vp_background->refresh(); + if (ui_vp_background->size() != original_viewport_size) + { + ui_vp_background->refresh(); + } ui_vp_speedlines->move(0, 0); ui_vp_speedlines->combo_resize(ui_viewport->width(), ui_viewport->height()); @@ -573,7 +577,10 @@ void Courtroom::set_widgets() //the AO2 desk element ui_vp_desk->move(0, 0); ui_vp_desk->resize(ui_viewport->width(), ui_viewport->height()); - ui_vp_desk->refresh(); + if (ui_vp_desk->size() != original_viewport_size) + { + ui_vp_desk->refresh(); + } ui_vp_evidence_display->move(0, 0); ui_vp_evidence_display->resize(ui_viewport->width(), ui_viewport->height()); From 4440b17278db62fa5720d0f14352754aaf36d93d Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 25 Aug 2020 11:49:56 -0400 Subject: [PATCH 049/842] Refresh characters on reload theme --- aocharmovie.cpp | 16 +++++++++++----- aocharmovie.h | 4 +++- courtroom_widgets.cpp | 4 ++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/aocharmovie.cpp b/aocharmovie.cpp index a556b4225..9c04f2c4c 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -50,11 +50,17 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bo if (found) break; } + shown = show; + filename = gif_path; + refresh(); +} +void AOCharMovie::refresh() +{ m_movie->stop(); - m_movie->setFileName(gif_path); + m_movie->setFileName(filename); - QImageReader *reader = new QImageReader(gif_path); + QImageReader *reader = new QImageReader(filename); movie_frames.clear(); QImage f_image = reader->read(); @@ -68,9 +74,9 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bo } delete reader; - this->show(); - if (!show) - this->hide(); + show(); + if (!shown) + hide(); m_movie->start(); } diff --git a/aocharmovie.h b/aocharmovie.h index 914ac1037..ad4ed32af 100644 --- a/aocharmovie.h +++ b/aocharmovie.h @@ -20,9 +20,9 @@ class AOCharMovie : public QLabel bool play_pre(QString p_char, QString p_emote, bool show); void play_talking(QString p_char, QString p_emote, bool show); void play_idle(QString p_char, QString p_emote, bool show); - void set_flipped(bool p_flipped) {m_flipped = p_flipped;} + void refresh(); void stop(); void combo_resize(int w, int h); @@ -39,6 +39,8 @@ class AOCharMovie : public QLabel bool m_flipped = false; bool play_once = true; + bool shown = true; + QString filename = ""; signals: void done(); diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 049442ef2..4c12cd1a7 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -573,6 +573,10 @@ void Courtroom::set_widgets() ui_vp_player_char->move(0, 0); ui_vp_player_char->combo_resize(ui_viewport->width(), ui_viewport->height()); + if (ui_vp_player_char->size() != original_viewport_size) + { + ui_vp_player_char->refresh(); + } //the AO2 desk element ui_vp_desk->move(0, 0); From 1e13fde88eea98d23839b27dcee6d8d807cec43d Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 25 Aug 2020 12:15:09 -0400 Subject: [PATCH 050/842] Reintroduce spectator button to char select+Explicitly notify the server of spectator choice --- charselect.cpp | 4 +++- courtroom.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/charselect.cpp b/charselect.cpp index b965ec5ac..43aac2135 100644 --- a/charselect.cpp +++ b/charselect.cpp @@ -182,7 +182,9 @@ void Courtroom::char_clicked(int n_char) } else { - ao_app->send_server_packet(new AOPacket("CC#" + QString::number(ao_app->s_pv) + "#" + QString::number(n_real_char) + "#" + get_hdid() + "#%")); + QString content = "CC#" + QString::number(ao_app->s_pv) + "#" + + QString::number(n_real_char) + "#" + get_hdid() + "#%"; + ao_app->send_server_packet(new AOPacket(content)); } } diff --git a/courtroom.cpp b/courtroom.cpp index 55fd10ae8..194f86374 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -2184,7 +2184,7 @@ void Courtroom::on_change_character_clicked() set_char_select(); ui_char_select_background->show(); - ui_spectator->hide(); + ui_spectator->show(); } void Courtroom::on_app_reload_theme_requested() @@ -2226,6 +2226,8 @@ void Courtroom::on_char_select_right_clicked() void Courtroom::on_spectator_clicked() { + QString content = "CC#" + QString::number(ao_app->s_pv) + "#-1#" + get_hdid() + "#%"; + ao_app->send_server_packet(new AOPacket(content)); enter_courtroom(-1); ui_emotes->hide(); From db203d0665f1698666d12cb5545c233080aa6753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Wed, 26 Aug 2020 02:45:01 +0200 Subject: [PATCH 051/842] Update branch - QString `filename` in `AOCharMovie` and `AOScene` were redundant because you could simply pick up the current file from `m_movie`. - bool `shown` is redundant since you're doing nothing with it and `refresh` is also redundant. - Renamed `flipped` to `mirrored` because it makes more sense. - Changed `AOScene` to not create and replace needlessly `QMovie` all the time since there is no point in doing so. - Removed the size check in `courtroom_widgets.cpp`, it's supposed to be for performance issues I'm sure but you may as well not bother. If you really want to do a size check, do it within the widget, not outside of it. --- aocharmovie.cpp | 77 +++++++++++++++++++++---------------------- aocharmovie.h | 16 +++------ aoscene.cpp | 33 +++++++------------ aoscene.h | 5 ++- courtroom.cpp | 4 +-- courtroom_widgets.cpp | 19 ++--------- 6 files changed, 60 insertions(+), 94 deletions(-) diff --git a/aocharmovie.cpp b/aocharmovie.cpp index 9c04f2c4c..faf282b81 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -21,12 +21,11 @@ AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_ connect(m_frame_timer, SIGNAL(timeout()), this, SLOT(timer_done())); } -void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bool show) +void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, bool p_visible) { - QString gif_path; - QStringList f_vec; + QString target_path; QStringList f_paths{ - ao_app->get_character_path(p_char) + emote_prefix + p_emote.toLower(), // .gif + ao_app->get_character_path(p_char) + p_emote_prefix + p_emote.toLower(), // .gif ao_app->get_character_path(p_char) + p_emote.toLower(), // .png ao_app->get_theme_variant_path() + "placeholder", // .gif ao_app->get_theme_path() + "placeholder", // .gif @@ -36,13 +35,13 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bo for (auto &f_file : f_paths) { bool found = false; - for (auto &ext : decltype(f_vec){".webp", ".apng", ".gif", ".png"}) + for (auto &ext : QStringList{".webp", ".apng", ".gif", ".png"}) { QString fullPath = f_file + ext; found = file_exists(fullPath); if (found) { - gif_path = fullPath; + target_path = fullPath; break; } } @@ -50,34 +49,25 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bo if (found) break; } - shown = show; - filename = gif_path; - refresh(); -} -void AOCharMovie::refresh() -{ - m_movie->stop(); - m_movie->setFileName(filename); - - QImageReader *reader = new QImageReader(filename); + show(); + if (!p_visible) + hide(); movie_frames.clear(); - QImage f_image = reader->read(); - while (!f_image.isNull()) - { - if (m_flipped) - movie_frames.append(f_image.mirrored(true, false)); - else - movie_frames.append(f_image); - f_image = reader->read(); + QImageReader *reader = new QImageReader(target_path); + for (int i = 0; i < reader->imageCount(); ++i) + { // optimize can be better, but I'll just keep it like that for now + QImage f_image = reader->read(); + if (m_mirror) + f_image = f_image.mirrored(true, false); + movie_frames.append(f_image); } delete reader; - show(); - if (!shown) - hide(); - + m_movie->stop(); + this->clear(); + m_movie->setFileName(target_path); m_movie->start(); } @@ -105,7 +95,7 @@ bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) { m_movie->stop(); this->clear(); - play_once = true; + m_play_once = true; m_movie->setFileName(f_file_path); play(p_char, p_emote, "", show); } @@ -113,26 +103,31 @@ bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) return f_file_exist; } -void AOCharMovie::play_talking(QString p_char, QString p_emote, bool show) +void AOCharMovie::play_talking(QString p_char, QString p_emote, bool p_visible) { QString gif_path = ao_app->get_character_path(p_char) + "(b)" + p_emote.toLower(); m_movie->stop(); this->clear(); - play_once = false; + m_play_once = false; m_movie->setFileName(gif_path); - play(p_char, p_emote, "(b)", show); + play(p_char, p_emote, "(b)", p_visible); } -void AOCharMovie::play_idle(QString p_char, QString p_emote, bool show) +void AOCharMovie::play_idle(QString p_char, QString p_emote, bool p_visible) { QString gif_path = ao_app->get_character_path(p_char) + "(a)" + p_emote.toLower(); this->clear(); m_movie->stop(); - play_once = false; + m_play_once = false; m_movie->setFileName(gif_path); - play(p_char, p_emote, "(a)", show); + play(p_char, p_emote, "(a)", p_visible); +} + +void AOCharMovie::set_mirror_enabled(bool p_enable) +{ + m_mirror = p_enable; } void AOCharMovie::stop() @@ -143,11 +138,13 @@ void AOCharMovie::stop() this->hide(); } -void AOCharMovie::combo_resize(int w, int h) +void AOCharMovie::combo_resize(QSize p_size) { - QSize f_size(w, h); - this->resize(f_size); - m_movie->setScaledSize(f_size); + resize(p_size); + m_movie->stop(); + m_frame_timer->stop(); + m_movie->setScaledSize(p_size); + m_movie->start(); } void AOCharMovie::on_frame_changed(int p_frame_num) @@ -159,7 +156,7 @@ void AOCharMovie::on_frame_changed(int p_frame_num) } // pre-anim only - if (play_once) + if (m_play_once) { int f_frame_count = m_movie->frameCount(); if (f_frame_count == 0 || p_frame_num == (f_frame_count - 1)) diff --git a/aocharmovie.h b/aocharmovie.h index ad4ed32af..ad49d92e7 100644 --- a/aocharmovie.h +++ b/aocharmovie.h @@ -20,13 +20,10 @@ class AOCharMovie : public QLabel bool play_pre(QString p_char, QString p_emote, bool show); void play_talking(QString p_char, QString p_emote, bool show); void play_idle(QString p_char, QString p_emote, bool show); - void set_flipped(bool p_flipped) {m_flipped = p_flipped;} - - void refresh(); + void set_mirror_enabled(bool p_enable); + void combo_resize(QSize p_size); void stop(); - void combo_resize(int w, int h); - private: AOApplication *ao_app = nullptr; @@ -34,13 +31,8 @@ class AOCharMovie : public QLabel QVector movie_frames; QTimer *m_frame_timer; - const int time_mod = 62; - - bool m_flipped = false; - - bool play_once = true; - bool shown = true; - QString filename = ""; + bool m_mirror = false; + bool m_play_once = false; signals: void done(); diff --git a/aoscene.cpp b/aoscene.cpp index 605cb339d..d52051253 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -6,10 +6,10 @@ #include #include -AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) - : QLabel(parent) +AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent), ao_app(p_ao_app) { - ao_app = p_ao_app; + m_movie = new QMovie(this); + setMovie(m_movie); } void AOScene::set_image(QString p_image) @@ -31,27 +31,18 @@ void AOScene::set_image(QString p_image) } // do not update the movie if we're using the same file - if (m_movie && m_movie->fileName() == target_path) + if (m_movie->fileName() == target_path) return; - filename = target_path; - refresh(); + m_movie->stop(); + this->clear(); + m_movie->setFileName(target_path); + m_movie->start(); } -void AOScene::refresh() +void AOScene::combo_resize(QSize p_size) { - if (filename.isEmpty()) - return; - - // clear previous - this->clear(); - - // delete current movie - delete m_movie; - - // create new movie to run - m_movie = new QMovie(this); - setMovie(m_movie); - m_movie->setFileName(filename); - m_movie->setScaledSize(size()); + resize(p_size); + m_movie->stop(); + m_movie->setScaledSize(p_size); m_movie->start(); } diff --git a/aoscene.h b/aoscene.h index 8b965554a..8cbc5e1bc 100644 --- a/aoscene.h +++ b/aoscene.h @@ -11,15 +11,14 @@ class AOScene : public QLabel Q_OBJECT public: - explicit AOScene(QWidget *parent, AOApplication *p_ao_app); + AOScene(QWidget *parent, AOApplication *p_ao_app); void set_image(QString p_image); - void refresh(); + void combo_resize(QSize p_size); private: AOApplication *ao_app = nullptr; QMovie *m_movie = nullptr; - QString filename = ""; }; #endif // AOSCENE_H diff --git a/courtroom.cpp b/courtroom.cpp index 55fd10ae8..4be3d1823 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -869,9 +869,9 @@ void Courtroom::handle_chatmessage_2() // handles IC int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); if (ao_app->flipping_enabled && m_chatmessage[FLIP].toInt() == 1) - ui_vp_player_char->set_flipped(true); + ui_vp_player_char->set_mirror_enabled(true); else - ui_vp_player_char->set_flipped(false); + ui_vp_player_char->set_mirror_enabled(false); switch (emote_mod) { diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 4c12cd1a7..692154b2f 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -558,33 +558,20 @@ void Courtroom::set_widgets() ui_background->resize(m_courtroom_width, m_courtroom_height); ui_background->set_image("courtroombackground.png"); - QSize original_viewport_size = ui_viewport->size(); set_size_and_pos(ui_viewport, "viewport"); ui_vp_background->move(0, 0); - ui_vp_background->resize(ui_viewport->width(), ui_viewport->height()); - if (ui_vp_background->size() != original_viewport_size) - { - ui_vp_background->refresh(); - } + ui_vp_background->combo_resize(ui_viewport->size()); ui_vp_speedlines->move(0, 0); ui_vp_speedlines->combo_resize(ui_viewport->width(), ui_viewport->height()); ui_vp_player_char->move(0, 0); - ui_vp_player_char->combo_resize(ui_viewport->width(), ui_viewport->height()); - if (ui_vp_player_char->size() != original_viewport_size) - { - ui_vp_player_char->refresh(); - } + ui_vp_player_char->combo_resize(ui_viewport->size()); //the AO2 desk element ui_vp_desk->move(0, 0); - ui_vp_desk->resize(ui_viewport->width(), ui_viewport->height()); - if (ui_vp_desk->size() != original_viewport_size) - { - ui_vp_desk->refresh(); - } + ui_vp_desk->combo_resize(ui_viewport->size()); ui_vp_evidence_display->move(0, 0); ui_vp_evidence_display->resize(ui_viewport->width(), ui_viewport->height()); From 5c5bc6b5744395f0b856bbe2ce66eba57b4bfb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Wed, 26 Aug 2020 03:03:59 +0200 Subject: [PATCH 052/842] Update aoscene.cpp --- aoscene.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/aoscene.cpp b/aoscene.cpp index d52051253..0b87b5384 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -34,7 +34,6 @@ void AOScene::set_image(QString p_image) if (m_movie->fileName() == target_path) return; m_movie->stop(); - this->clear(); m_movie->setFileName(target_path); m_movie->start(); } From 7e9f4deef3cd45d949c9c7af1bdb0e3458105944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Thu, 27 Aug 2020 02:33:20 +0200 Subject: [PATCH 053/842] Update AOPixmap, ... --- aocharmovie.cpp | 50 +++++++++++++++++++++---------------------- aocharmovie.h | 42 ++++++++++++++++++------------------ aoevidencedisplay.cpp | 2 +- aoimage.cpp | 4 ++-- aopixmap.cpp | 11 +++++++--- aopixmap.h | 6 ++++-- aoscene.cpp | 18 ++++++++-------- aoscene.h | 2 +- 8 files changed, 71 insertions(+), 64 deletions(-) diff --git a/aocharmovie.cpp b/aocharmovie.cpp index faf282b81..606ffb859 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -12,12 +12,12 @@ AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_ { ao_app = p_ao_app; - m_movie = new QMovie(this); + m_reader = new QMovie(this); m_frame_timer = new QTimer(this); m_frame_timer->setSingleShot(true); - connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(on_frame_changed(int))); + connect(m_reader, SIGNAL(frameChanged(int)), this, SLOT(on_frame_changed(int))); connect(m_frame_timer, SIGNAL(timeout()), this, SLOT(timer_done())); } @@ -26,10 +26,10 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, QString target_path; QStringList f_paths{ ao_app->get_character_path(p_char) + p_emote_prefix + p_emote.toLower(), // .gif - ao_app->get_character_path(p_char) + p_emote.toLower(), // .png - ao_app->get_theme_variant_path() + "placeholder", // .gif - ao_app->get_theme_path() + "placeholder", // .gif - ao_app->get_default_theme_path() + "placeholder" // .gif + ao_app->get_character_path(p_char) + p_emote.toLower(), // .png + ao_app->get_theme_variant_path() + "placeholder", // .gif + ao_app->get_theme_path() + "placeholder", // .gif + ao_app->get_default_theme_path() + "placeholder" // .gif }; for (auto &f_file : f_paths) @@ -65,16 +65,16 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, } delete reader; - m_movie->stop(); + m_reader->stop(); this->clear(); - m_movie->setFileName(target_path); - m_movie->start(); + m_reader->setFileName(target_path); + m_reader->start(); } bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) { QString f_file_path = ao_app->get_character_path(p_char) + p_emote.toLower(); - bool f_file_exist = false; + bool f_file_exist = false; { // figure out what extension the animation is using QString f_source_path = ao_app->get_character_path(p_char) + p_emote.toLower(); @@ -83,7 +83,7 @@ bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) QString f_target_path = f_source_path + i_ext; if (file_exists(f_target_path)) { - f_file_path = f_target_path; + f_file_path = f_target_path; f_file_exist = true; break; } @@ -93,10 +93,10 @@ bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) // play if it exist if (f_file_exist) { - m_movie->stop(); + m_reader->stop(); this->clear(); m_play_once = true; - m_movie->setFileName(f_file_path); + m_reader->setFileName(f_file_path); play(p_char, p_emote, "", show); } @@ -107,10 +107,10 @@ void AOCharMovie::play_talking(QString p_char, QString p_emote, bool p_visible) { QString gif_path = ao_app->get_character_path(p_char) + "(b)" + p_emote.toLower(); - m_movie->stop(); + m_reader->stop(); this->clear(); m_play_once = false; - m_movie->setFileName(gif_path); + m_reader->setFileName(gif_path); play(p_char, p_emote, "(b)", p_visible); } @@ -119,9 +119,9 @@ void AOCharMovie::play_idle(QString p_char, QString p_emote, bool p_visible) QString gif_path = ao_app->get_character_path(p_char) + "(a)" + p_emote.toLower(); this->clear(); - m_movie->stop(); + m_reader->stop(); m_play_once = false; - m_movie->setFileName(gif_path); + m_reader->setFileName(gif_path); play(p_char, p_emote, "(a)", p_visible); } @@ -133,7 +133,7 @@ void AOCharMovie::set_mirror_enabled(bool p_enable) 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 - m_movie->stop(); + m_reader->stop(); m_frame_timer->stop(); this->hide(); } @@ -141,10 +141,10 @@ void AOCharMovie::stop() void AOCharMovie::combo_resize(QSize p_size) { resize(p_size); - m_movie->stop(); + m_reader->stop(); m_frame_timer->stop(); - m_movie->setScaledSize(p_size); - m_movie->start(); + m_reader->setScaledSize(p_size); + m_reader->start(); } void AOCharMovie::on_frame_changed(int p_frame_num) @@ -152,20 +152,20 @@ void AOCharMovie::on_frame_changed(int p_frame_num) if (movie_frames.size() > p_frame_num) { AOPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(p_frame_num)); - this->setPixmap(f_pixmap.scaleToSize(this->size())); + this->setPixmap(f_pixmap.scale_to_size(this->size())); } // pre-anim only if (m_play_once) { - int f_frame_count = m_movie->frameCount(); + int f_frame_count = m_reader->frameCount(); if (f_frame_count == 0 || p_frame_num == (f_frame_count - 1)) { - int f_frame_delay = m_movie->nextFrameDelay(); + int f_frame_delay = m_reader->nextFrameDelay(); if (f_frame_delay < 0) f_frame_delay = 0; m_frame_timer->start(f_frame_delay); - m_movie->stop(); + m_reader->stop(); } } } diff --git a/aocharmovie.h b/aocharmovie.h index ad49d92e7..e4b4c1767 100644 --- a/aocharmovie.h +++ b/aocharmovie.h @@ -1,45 +1,45 @@ #ifndef AOCHARMOVIE_H #define AOCHARMOVIE_H -#include +#include "aopixmap.h" + #include +#include #include -#include "aopixmap.h" - class AOApplication; class AOCharMovie : public QLabel { - Q_OBJECT + Q_OBJECT public: - AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app); + AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app); - void play(QString p_char, QString p_emote, QString emote_prefix, bool show); - bool play_pre(QString p_char, QString p_emote, bool show); - void play_talking(QString p_char, QString p_emote, bool show); - void play_idle(QString p_char, QString p_emote, bool show); - void set_mirror_enabled(bool p_enable); - void combo_resize(QSize p_size); - void stop(); + void play(QString p_char, QString p_emote, QString emote_prefix, bool show); + bool play_pre(QString p_char, QString p_emote, bool show); + void play_talking(QString p_char, QString p_emote, bool show); + void play_idle(QString p_char, QString p_emote, bool show); + void set_mirror_enabled(bool p_enable); + void combo_resize(QSize p_size); + void stop(); private: - AOApplication *ao_app = nullptr; + AOApplication *ao_app = nullptr; - QMovie *m_movie; - QVector movie_frames; - QTimer *m_frame_timer; + QMovie *m_reader; + QVector movie_frames; + QTimer *m_frame_timer; - bool m_mirror = false; - bool m_play_once = false; + bool m_mirror = false; + bool m_play_once = false; signals: - void done(); + void done(); private slots: - void on_frame_changed(int n_frame); - void timer_done(); + void on_frame_changed(int n_frame); + void timer_done(); }; #endif // AOCHARMOVIE_H diff --git a/aoevidencedisplay.cpp b/aoevidencedisplay.cpp index 90c93a9c4..3fa032b4a 100644 --- a/aoevidencedisplay.cpp +++ b/aoevidencedisplay.cpp @@ -44,7 +44,7 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid evidence_icon->move(icon_dimensions.x, icon_dimensions.y); evidence_icon->resize(icon_dimensions.width, icon_dimensions.height); - evidence_icon->setPixmap(f_pixmap.scaleToSize(evidence_icon->size())); + evidence_icon->setPixmap(f_pixmap.scale_to_size(evidence_icon->size())); QString f_path = ao_app->get_image_path(gif_name); evidence_movie->setFileName(f_path); diff --git a/aoimage.cpp b/aoimage.cpp index 1f7cd5c92..0352ce1d8 100644 --- a/aoimage.cpp +++ b/aoimage.cpp @@ -13,7 +13,7 @@ void AOImage::set_image(QString p_image) { QString f_path = ao_app->get_image_path(p_image); AOPixmap f_pixmap(f_path); - this->setPixmap(f_pixmap.scaleToSize(size())); + this->setPixmap(f_pixmap.scale_to_size(size())); // Store final path if the path exists if (file_exists(f_path)) @@ -34,7 +34,7 @@ void AOImage::set_image_from_path(QString p_path) final_path = default_path; AOPixmap f_pixmap(final_path); - this->setPixmap(f_pixmap.scaleToSize(size())); + this->setPixmap(f_pixmap.scale_to_size(size())); // Store final path if the path exists if (file_exists(final_path)) diff --git a/aopixmap.cpp b/aopixmap.cpp index 4832bdb1d..dc875d9d5 100644 --- a/aopixmap.cpp +++ b/aopixmap.cpp @@ -4,8 +4,7 @@ AOPixmap::AOPixmap(QPixmap p_pixmap) : m_pixmap(p_pixmap) { if (m_pixmap.isNull()) { - m_pixmap = QPixmap(1, 1); - m_pixmap.fill(Qt::transparent); + clear(); } } @@ -14,7 +13,13 @@ AOPixmap::AOPixmap(QString p_file_path) : AOPixmap(QPixmap(p_file_path)) } -QPixmap AOPixmap::scaleToSize(QSize p_size) +void AOPixmap::clear() +{ + m_pixmap = QPixmap(1, 1); + m_pixmap.fill(Qt::transparent); +} + +QPixmap AOPixmap::scale_to_size(QSize p_size) { bool f_is_pixmap_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); return m_pixmap.scaled(p_size, Qt::IgnoreAspectRatio, f_is_pixmap_larger ? Qt::SmoothTransformation : Qt::FastTransformation); diff --git a/aopixmap.h b/aopixmap.h index 149eafe4d..057d9d810 100644 --- a/aopixmap.h +++ b/aopixmap.h @@ -6,10 +6,12 @@ class AOPixmap { public: - AOPixmap(QPixmap p_pixmap); + AOPixmap(QPixmap p_pixmap = QPixmap()); AOPixmap(QString p_file_path); - QPixmap scaleToSize(QSize p_size); + void clear(); + + QPixmap scale_to_size(QSize p_size); private: QPixmap m_pixmap; diff --git a/aoscene.cpp b/aoscene.cpp index 0b87b5384..04408f32c 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -8,8 +8,8 @@ AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent), ao_app(p_ao_app) { - m_movie = new QMovie(this); - setMovie(m_movie); + m_reader = new QMovie(this); + setMovie(m_reader); } void AOScene::set_image(QString p_image) @@ -31,17 +31,17 @@ void AOScene::set_image(QString p_image) } // do not update the movie if we're using the same file - if (m_movie->fileName() == target_path) + if (m_reader->fileName() == target_path) return; - m_movie->stop(); - m_movie->setFileName(target_path); - m_movie->start(); + m_reader->stop(); + m_reader->setFileName(target_path); + m_reader->start(); } void AOScene::combo_resize(QSize p_size) { resize(p_size); - m_movie->stop(); - m_movie->setScaledSize(p_size); - m_movie->start(); + m_reader->stop(); + m_reader->setScaledSize(p_size); + m_reader->start(); } diff --git a/aoscene.h b/aoscene.h index 8cbc5e1bc..5202dab91 100644 --- a/aoscene.h +++ b/aoscene.h @@ -18,7 +18,7 @@ class AOScene : public QLabel private: AOApplication *ao_app = nullptr; - QMovie *m_movie = nullptr; + QMovie *m_reader = nullptr; }; #endif // AOSCENE_H From 76ac6872b981051b0279bc3522bc13eaabd2df6d Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Thu, 27 Aug 2020 22:49:20 -0400 Subject: [PATCH 054/842] Text outline (#17) * get_font_size() -> get_font_property() to reflect current use * Make non trivial QLabels QTextEdits for outlines * Basic outline implementation for most courtroom widgets * Fix AOTimer outline+Explicitly not support OOC chatlog (has html) * Reallow characters to have custom colors for their shownames * Standardize IC chatlog font types+Make each fall back to ic_chatlog_color if not present * Allow for outlines in lobby for player count, description and loading text --- Attorney_Online_remake.pro | 4 +- aoapplication.h | 4 +- aoevidencedescription.cpp | 21 ++++++++++ aoevidencedescription.h | 23 +++++++++++ aotextarea.cpp | 6 ++- aotextedit.cpp | 21 ---------- aotextedit.h | 23 ----------- aotimer.cpp | 10 +++-- aotimer.h | 7 ++-- courtroom.cpp | 42 +++++++++++-------- courtroom.h | 15 +++++-- courtroom_widgets.cpp | 80 +++++++++++++++++++++++------------ evidence.cpp | 2 +- lobby.cpp | 85 +++++++++++++++++++++++--------------- lobby.h | 5 ++- text_file_functions.cpp | 4 +- 16 files changed, 208 insertions(+), 144 deletions(-) create mode 100644 aoevidencedescription.cpp create mode 100644 aoevidencedescription.h delete mode 100644 aotextedit.cpp delete mode 100644 aotextedit.h diff --git a/Attorney_Online_remake.pro b/Attorney_Online_remake.pro index 8431e7ff1..dc84e2ace 100644 --- a/Attorney_Online_remake.pro +++ b/Attorney_Online_remake.pro @@ -18,6 +18,7 @@ VERSION = 2.4.8.0 SOURCES += main.cpp\ aoconfig.cpp \ aoconfigpanel.cpp \ + aoevidencedescription.cpp \ aoguiloader.cpp \ aopixmap.cpp \ aotimer.cpp \ @@ -52,7 +53,6 @@ SOURCES += main.cpp\ charselect.cpp \ aotextarea.cpp \ aolineedit.cpp \ - aotextedit.cpp \ aoevidencedisplay.cpp \ discord_rich_presence.cpp \ aonotepad.cpp \ @@ -70,6 +70,7 @@ HEADERS += lobby.h \ aobasshandle.h \ aoconfig.h \ aoconfigpanel.h \ + aoevidencedescription.h \ aoexception.h \ aoguiloader.h \ aoimage.h \ @@ -103,7 +104,6 @@ HEADERS += lobby.h \ aoevidencebutton.h \ aotextarea.h \ aolineedit.h \ - aotextedit.h \ aoevidencedisplay.h \ discord_rich_presence.h \ discord-rpc.h \ diff --git a/aoapplication.h b/aoapplication.h index a39a208d2..adbd3c9d3 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -204,8 +204,8 @@ class AOApplication : public QApplication //Returns the dimensions of widget with specified identifier from p_file pos_size_type get_element_dimensions(QString p_identifier, QString p_file); - //Returns the value of font_size with p_identifier from p_file - int get_font_size(QString p_identifier, QString p_file); + //Returns the value of font property p_identifier from p_file + int get_font_property(QString p_identifier, QString p_file); //Returns the name of the font with p_identifier from p_file QString get_font_name(QString p_identifier, QString p_file); diff --git a/aoevidencedescription.cpp b/aoevidencedescription.cpp new file mode 100644 index 000000000..15b5af86e --- /dev/null +++ b/aoevidencedescription.cpp @@ -0,0 +1,21 @@ +#include "aoevidencedescription.h" + +AOEvidenceDescription::AOEvidenceDescription(QWidget *parent) : QPlainTextEdit(parent) +{ + this->setReadOnly(true); + + //connect(this, SIGNAL(returnPressed()), this, SLOT(on_enter_pressed())); +} + +void AOEvidenceDescription::mouseDoubleClickEvent(QMouseEvent *e) +{ + QPlainTextEdit::mouseDoubleClickEvent(e); + + this->setReadOnly(false); +} + +void AOEvidenceDescription::on_enter_pressed() +{ + this->setReadOnly(true); +} + diff --git a/aoevidencedescription.h b/aoevidencedescription.h new file mode 100644 index 000000000..3fedabce7 --- /dev/null +++ b/aoevidencedescription.h @@ -0,0 +1,23 @@ +#ifndef AOEVIDENCEDESCRIPTION_H +#define AOEVIDENCEDESCRIPTION_H + +#include + +class AOEvidenceDescription : public QPlainTextEdit +{ + Q_OBJECT +public: + AOEvidenceDescription(QWidget *parent); + +protected: + void mouseDoubleClickEvent(QMouseEvent *e); + +signals: + void double_clicked(); + +private slots: + void on_enter_pressed(); + +}; + +#endif // AOEVIDENCEDESCRIPTION_H diff --git a/aotextarea.cpp b/aotextarea.cpp index 40cc31480..c64769c15 100644 --- a/aotextarea.cpp +++ b/aotextarea.cpp @@ -23,7 +23,8 @@ void AOTextArea::append_chatmessage(QString p_name, QString p_message) //cheap workarounds ahoy p_message += " "; - QString result = p_message.toHtmlEscaped().replace("\n", "
").replace(omnis_dank_url_regex, "\\1" ); + QString result = p_message.toHtmlEscaped().replace("\n", "
"); + result = result.replace(omnis_dank_url_regex, "\\1" ); this->insertHtml(result); @@ -41,7 +42,8 @@ void AOTextArea::append_error(QString p_message) this->append(""); p_message += " "; - QString result = p_message.replace("\n", "
").replace(omnis_dank_url_regex, "\\1" ); + QString result = p_message.replace("\n", "
"); + result = result.replace(omnis_dank_url_regex, "\\1" ); this->insertHtml("" + result + ""); diff --git a/aotextedit.cpp b/aotextedit.cpp deleted file mode 100644 index 30e48b733..000000000 --- a/aotextedit.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "aotextedit.h" - -AOTextEdit::AOTextEdit(QWidget *parent) : QPlainTextEdit(parent) -{ - this->setReadOnly(true); - - //connect(this, SIGNAL(returnPressed()), this, SLOT(on_enter_pressed())); -} - -void AOTextEdit::mouseDoubleClickEvent(QMouseEvent *e) -{ - QPlainTextEdit::mouseDoubleClickEvent(e); - - this->setReadOnly(false); -} - -void AOTextEdit::on_enter_pressed() -{ - this->setReadOnly(true); -} - diff --git a/aotextedit.h b/aotextedit.h deleted file mode 100644 index 85909c6e1..000000000 --- a/aotextedit.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef AOTEXTEDIT_H -#define AOTEXTEDIT_H - -#include - -class AOTextEdit : public QPlainTextEdit -{ - Q_OBJECT -public: - AOTextEdit(QWidget *parent); - -protected: - void mouseDoubleClickEvent(QMouseEvent *e); - -signals: - void double_clicked(); - -private slots: - void on_enter_pressed(); - -}; - -#endif // AOTEXTEDIT_H diff --git a/aotimer.cpp b/aotimer.cpp index 32dd8ee8f..77f0657ee 100644 --- a/aotimer.cpp +++ b/aotimer.cpp @@ -1,11 +1,15 @@ #include "aotimer.h" #include -AOTimer::AOTimer(QWidget* p_parent, AOApplication *p_ao_app) : AOLabel(p_parent, p_ao_app) +AOTimer::AOTimer(QWidget* p_parent) : QTextEdit(p_parent) { // Adapted from: https://stackoverflow.com/questions/36679708/how-to-make-a-chronometer-in-qt-c - ao_app = p_ao_app; - this->setStyleSheet("QLabel { color : white; }"); + setStyleSheet("QLabel { color : white; }"); + setFrameStyle(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setReadOnly(true); + connect(&firing_timer, SIGNAL(timeout()), this, SLOT(update_time())); set_time(start_time); diff --git a/aotimer.h b/aotimer.h index 698bddcc8..82ea4c0cc 100644 --- a/aotimer.h +++ b/aotimer.h @@ -1,6 +1,7 @@ #ifndef AOTIMER_H #define AOTIMER_H +#include #include #include #include @@ -21,16 +22,14 @@ class ManualTimer { void perform_timestep() {current_time = current_time.addMSecs(timestep_length);} }; -class AOTimer : public AOLabel +class AOTimer : public QTextEdit { Q_OBJECT public: - AOTimer(QWidget* p_parent, AOApplication *p_ao_app); + AOTimer(QWidget *p_parent); private: - AOApplication *ao_app = nullptr; - ManualTimer old_manual_timer; // Pre-update manual timer ManualTimer manual_timer; QTimer firing_timer; diff --git a/courtroom.cpp b/courtroom.cpp index 21b37ca8b..5d4a99756 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -146,6 +146,9 @@ void Courtroom::enter_courtroom(int p_cid) ui_ic_chat_message->setEnabled(m_cid != -1); ui_ic_chat_message->setFocus(); + for (int i = 0; i < ui_timers.length(); ++i) + ui_timers[i]->redraw(); + set_widget_names(); set_widget_layers(); } @@ -307,7 +310,7 @@ void Courtroom::handle_music_anim() QString file_b = fonts_ini; pos_size_type res_a = ao_app->get_element_dimensions("music_name", file_a); pos_size_type res_b = ao_app->get_element_dimensions("music_area", file_a); - float speed = static_cast(ao_app->get_font_size("music_name_speed", file_b)); + float speed = static_cast(ao_app->get_font_property("music_name_speed", file_b)); QFont f_font = ui_vp_music_name->font(); QFontMetrics fm(f_font); @@ -822,18 +825,12 @@ void Courtroom::handle_chatmessage_2() // handles IC f_showname = m_chatmessage[SHOWNAME]; } + // Check if char.ini has color property, which overrides the theme's default showname color QString f_color = ao_app->read_char_ini(real_name, "color", "[Options]", "[Time]"); - if (f_color == "") - f_color = "rgb(" + ao_app->read_theme_ini("showname_color" , "courtroom_fonts.ini") + ")"; - - // is the font bold or not? // taken directly from function up there lol // kinda hacky - int bold = ao_app->get_font_size("showname_bold", fonts_ini); - QString is_bold = ""; - if(bold == 1) is_bold = "bold"; + set_qtextedit_font(ui_vp_showname, "showname", f_color); - ui_vp_showname->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: " + f_color + ";font: " + is_bold); ui_vp_showname->setText(f_showname); + ui_vp_showname->setAlignment(Qt::AlignVCenter); ui_vp_message->clear(); ui_vp_chatbox->hide(); @@ -1096,17 +1093,30 @@ void Courtroom::update_ic_log(bool p_reset_log) } // prepare the formats we need + // default color + QColor default_color = ao_app->get_color("ic_chatlog_color", fonts_ini); + QColor not_found_color = QColor(255, 255, 255); + QTextCharFormat name_format = ui_ic_chatlog->currentCharFormat(); name_format.setFontWeight(QFont::Bold); - name_format.setForeground(ao_app->get_color("chatlog_showname_color", fonts_ini)); + QColor showname_color = ao_app->get_color("ic_chatlog_showname_color", fonts_ini); + if (showname_color == not_found_color) + showname_color = default_color; + name_format.setForeground(showname_color); QTextCharFormat line_format = ui_ic_chatlog->currentCharFormat(); line_format.setFontWeight(QFont::Normal); - line_format.setForeground(ao_app->get_color("chatlog_message_color", fonts_ini)); + QColor message_color = ao_app->get_color("ic_chatlog_message_color", fonts_ini); + if (message_color == not_found_color) + message_color = default_color; + line_format.setForeground(message_color); QTextCharFormat system_format = ui_ic_chatlog->currentCharFormat(); system_format.setFontWeight(QFont::Normal); - system_format.setForeground(ao_app->get_color("system_msg", fonts_ini)); + QColor system_color = ao_app->get_color("ic_chatlog_system_color", fonts_ini); + if (system_color == not_found_color) + system_color = not_found_color; + system_format.setForeground(system_color); // need vscroll bar for cache QScrollBar *vscrollbar = ui_ic_chatlog->verticalScrollBar(); @@ -1331,14 +1341,12 @@ void Courtroom::chat_tick() //note: this is called fairly often(every 60 ms when char is talking) //do not perform heavy operations here QTextCharFormat vp_message_format = ui_vp_message->currentCharFormat(); - if (ao_app->read_theme_ini("enable_vp_message_outline", cc_config_ini) == "true") + if (ao_app->get_font_property("message_outline", fonts_ini) == 1) vp_message_format.setTextOutline(QPen(Qt::black, 1)); else vp_message_format.setTextOutline(Qt::NoPen); QString f_message = m_chatmessage[MESSAGE]; -// QString parsed_message = parse_message(f_message); -// qDebug() << "parsed:" << parsed_message; if (tick_pos >= f_message.size()) { @@ -1358,8 +1366,6 @@ void Courtroom::chat_tick() else { QString f_character = f_message.at(tick_pos); - //f_character = f_character.toHtmlEscaped(); - //qDebug() << f_character; if (f_character == " ") ui_vp_message->insertPlainText(" "); diff --git a/courtroom.h b/courtroom.h index 4c4219a19..129b33706 100644 --- a/courtroom.h +++ b/courtroom.h @@ -23,7 +23,7 @@ #include "aosfxplayer.h" #include "aoshoutplayer.h" #include "aotextarea.h" -#include "aotextedit.h" +#include "aoevidencedescription.h" #include "aotimer.h" #include "datatypes.h" @@ -72,8 +72,15 @@ class Courtroom : public QMainWindow //sets position of widgets based on theme ini files void set_widgets(); - //sets font size based on theme ini files + //sets font properties based on theme ini files void set_font(QWidget *widget, QString p_identifier); + //same as above, but use override color as color if it is not an empty string, otherwise use + //normal logic for color of set_font + void set_font(QWidget *widget, QString p_identifier, QString override_color); + //sets font properties for QTextEdit (same as above but also text outline) + void set_qtextedit_font(QTextEdit *widget, QString p_identifier); + //same as second set_font but for qtextedit + void set_qtextedit_font(QTextEdit *widget, QString p_identifier, QString override_color); //helper function that calls above function on the relevant widgets void set_fonts(); @@ -390,7 +397,7 @@ class Courtroom : public QMainWindow QTextEdit *ui_vp_notepad; AOImage *ui_vp_chatbox = nullptr; - QLabel *ui_vp_showname = nullptr; + QTextEdit *ui_vp_showname = nullptr; QTextEdit *ui_vp_message = nullptr; AOImage *ui_vp_testimony = nullptr; AOMovie *ui_vp_effect = nullptr; @@ -544,7 +551,7 @@ class Courtroom : public QMainWindow AOLineEdit *ui_evidence_image_name; AOButton *ui_evidence_image_button; AOButton *ui_evidence_x; - AOTextEdit *ui_evidence_description; + AOEvidenceDescription *ui_evidence_description; AOImage *ui_char_select_background; diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 692154b2f..430515511 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -85,7 +85,11 @@ void Courtroom::create_widgets() ui_vp_evidence_display = new AOEvidenceDisplay(this, ao_app); ui_vp_chatbox = new AOImage(this, ao_app); - ui_vp_showname = new QLabel(ui_vp_chatbox); + ui_vp_showname = new QTextEdit(ui_vp_chatbox); + ui_vp_showname->setFrameStyle(QFrame::NoFrame); + ui_vp_showname->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_showname->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_showname->setReadOnly(true); ui_vp_message = new QTextEdit(ui_vp_chatbox); ui_vp_message->setFrameStyle(QFrame::NoFrame); ui_vp_message->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -225,7 +229,7 @@ void Courtroom::create_widgets() ui_vp_notepad->setFrameStyle(QFrame::NoFrame); ui_timers.resize(1); - ui_timers[0] = new AOTimer(this, ao_app); + ui_timers[0] = new AOTimer(this); construct_evidence(); @@ -1019,7 +1023,7 @@ void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) template int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_item_number, - QString item_name) + QString item_name) { // &item_vector must be a vector of size at least 1! @@ -1044,7 +1048,7 @@ int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_ite item_vector.resize(new_item_number); for (int i=current_item_number; istackUnder(item_vector[i-1]); // index i-1 exists as i >= current_item_number == item_vector.size() >= 1 } @@ -1441,45 +1445,67 @@ void Courtroom::set_dropdowns() void Courtroom::set_font(QWidget *widget, QString p_identifier) { - QString design_file = fonts_ini; - int f_weight = ao_app->get_font_size(p_identifier, design_file); - QString class_name = widget->metaObject()->className(); + set_font(widget, p_identifier, ""); +} - QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); +void Courtroom::set_font(QWidget *widget, QString p_identifier, QString override_color) +{ + QString design_file = fonts_ini; + QString class_name = widget->metaObject()->className(); - widget->setFont(QFont(font_name, f_weight)); + int f_weight = ao_app->get_font_property(p_identifier, design_file); + QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); + widget->setFont(QFont(font_name, f_weight)); - QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); + if (override_color.isEmpty()) + { + QString color = ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); + if (color.isEmpty()) + color = "255, 255, 255"; + override_color = "rgba(" + color + ", 255)"; + } - int bold = ao_app->get_font_size(p_identifier + "_bold", design_file); // is the font bold or not? + int bold = ao_app->get_font_property(p_identifier + "_bold", design_file); + QString is_bold = (bold == 1 ? "bold" : ""); - QString is_bold = ""; - if(bold == 1) is_bold = "bold"; + QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + + "color: " + override_color + ";\n" + "font: " + is_bold + "; }"; + widget->setStyleSheet(style_sheet_string); +} - QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + - "color: rgba(" + - QString::number(f_color.red()) + ", " + - QString::number(f_color.green()) + ", " + - QString::number(f_color.blue()) + ", 255);\n" - "font: " + is_bold + "; }"; +void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier) +{ + set_qtextedit_font(widget, p_identifier, ""); +} + +void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier, QString override_color) +{ + set_font(widget, p_identifier, override_color); - widget->setStyleSheet(style_sheet_string); + QString design_file = fonts_ini; + QTextCharFormat widget_format = widget->currentCharFormat(); + if (ao_app->get_font_property(p_identifier + "_outline", design_file) == 1) + widget_format.setTextOutline(QPen(Qt::black, 1)); + else + widget_format.setTextOutline(Qt::NoPen); + widget->setCurrentCharFormat(widget_format); } void Courtroom::set_fonts() { - set_font(ui_vp_showname, "showname"); - set_font(ui_vp_message, "message"); - set_font(ui_ic_chatlog, "ic_chatlog"); - set_font(ui_server_chatlog, "server_chatlog"); + set_qtextedit_font(ui_vp_showname, "showname"); + set_qtextedit_font(ui_vp_message, "message"); + set_qtextedit_font(ui_ic_chatlog, "ic_chatlog"); + set_font(ui_server_chatlog, "server_chatlog"); // Chatlog does not support qtextedit because html set_font(ui_music_list, "music_list"); set_font(ui_area_list, "area_list"); set_font(ui_sfx_list, "sfx_list"); - set_font(ui_vp_music_name, "music_name"); - set_font(ui_vp_notepad, "notepad"); + set_qtextedit_font(ui_vp_music_name, "music_name"); + set_qtextedit_font(ui_vp_notepad, "notepad"); for (int i=0; isetText("Choose.."); ui_evidence_x = new AOButton(ui_evidence_overlay, ao_app); - ui_evidence_description = new AOTextEdit(ui_evidence_overlay); + ui_evidence_description = new AOEvidenceDescription(ui_evidence_overlay); ui_evidence_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "color: white;"); diff --git a/lobby.cpp b/lobby.cpp index e5ebbb7b5..900451f64 100644 --- a/lobby.cpp +++ b/lobby.cpp @@ -6,7 +6,9 @@ #include "networkmanager.h" #include +#include #include +#include Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() { @@ -20,10 +22,18 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_refresh = new AOButton(this, ao_app); ui_add_to_fav = new AOButton(this, ao_app); ui_connect = new AOButton(this, ao_app); - ui_version = new QLabel(this); + ui_version = new QTextEdit(this); + ui_version->setFrameStyle(QFrame::NoFrame); + ui_version->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_version->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_version->setReadOnly(true); ui_about = new AOButton(this, ao_app); ui_server_list = new QListWidget(this); - ui_player_count = new QLabel(this); + ui_player_count = new QTextEdit(this); + ui_player_count->setFrameStyle(QFrame::NoFrame); + ui_player_count->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_player_count->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_player_count->setReadOnly(true); ui_description = new AOTextArea(this); ui_chatbox = new AOTextArea(this); ui_chatbox->setOpenExternalLinks(true); @@ -66,8 +76,9 @@ void Lobby::set_widgets() qDebug() << "W: did not find lobby width or height in " << filename; // Most common symptom of bad config files and missing assets. - call_notice("It doesn't look like your client is set up correctly.\n" - "Did you download all resources correctly from tiny.cc/getao, " + call_notice("It doesn't look like your client is set up correctly " + "at " + QDir::currentPath() + "\n" + "Did you download all resources correctly from the DRO Discord " "including the large 'base' folder?"); this->resize(517, 666); @@ -170,12 +181,12 @@ void Lobby::set_size_and_pos(QWidget *p_widget, QString p_identifier) void Lobby::set_fonts() { - set_font(ui_player_count, "player_count"); - set_font(ui_description, "description"); + set_qtextedit_font(ui_player_count, "player_count"); + set_qtextedit_font(ui_description, "description"); set_font(ui_chatbox, "chatbox"); set_font(ui_chatname, "chatname"); set_font(ui_chatmessage, "chatmessage"); - set_font(ui_loading_text, "loading_text"); + set_qtextedit_font(ui_loading_text, "loading_text"); set_font(ui_server_list, "server_list"); } @@ -201,42 +212,49 @@ void Lobby::set_stylesheets() void Lobby::set_font(QWidget *widget, QString p_identifier) { QString design_file = "lobby_fonts.ini"; - int f_weight = ao_app->get_font_size(p_identifier, design_file); + + if (!(bool)ao_app->get_font_property("use_custom_fonts", design_file)) + return; + + int f_weight = ao_app->get_font_property(p_identifier, design_file); QString class_name = widget->metaObject()->className(); QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); - QFont font(font_name, f_weight); + widget->setFont(font); - bool use = (bool)ao_app->get_font_size("use_custom_fonts", design_file); - - if (use) - { - widget->setFont(font); - - QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); - - bool bold = (bool)ao_app->get_font_size(p_identifier + "_bold", design_file); // is the font bold or not? - bool center = (bool)ao_app->get_font_size(p_identifier + "_center", design_file); // should it be centered? + QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); - QString is_bold = ""; - if (bold) is_bold = "bold"; + bool bold = (bool)ao_app->get_font_property(p_identifier + "_bold", design_file); + QString is_bold = ""; + if (bold) is_bold = "bold"; - QString is_center = ""; - if (center) is_center = "qproperty-alignment: AlignCenter;"; + bool center = (bool)ao_app->get_font_property(p_identifier + "_center", design_file); + QString is_center = ""; + if (center) is_center = "qproperty-alignment: AlignCenter;"; - QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + - "color: rgba(" + - QString::number(f_color.red()) + ", " + - QString::number(f_color.green()) + ", " + - QString::number(f_color.blue()) + ", 255);\n" + - is_center + "\n" + - "font: " + is_bold + "; }"; + QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + + "color: rgba(" + + QString::number(f_color.red()) + ", " + + QString::number(f_color.green()) + ", " + + QString::number(f_color.blue()) + ", 255);\n" + + is_center + "\n" + + "font: " + is_bold + "; }"; - widget->setStyleSheet(style_sheet_string); - } + widget->setStyleSheet(style_sheet_string); +} - return; +void Lobby::set_qtextedit_font(QTextEdit *widget, QString p_identifier) +{ + set_font(widget, p_identifier); + + QString design_file = "lobby_fonts.ini"; + QTextCharFormat widget_format = widget->currentCharFormat(); + if (ao_app->get_font_property(p_identifier + "_outline", design_file) == 1) + widget_format.setTextOutline(QPen(Qt::black, 1)); + else + widget_format.setTextOutline(Qt::NoPen); + widget->setCurrentCharFormat(widget_format); } void Lobby::set_loading_text(QString p_text) @@ -444,6 +462,7 @@ void Lobby::set_player_count(int players_online, int max_players) { QString f_string = "Online: " + QString::number(players_online) + "/" + QString::number(max_players); ui_player_count->setText(f_string); + ui_player_count->setAlignment(Qt::AlignHCenter); ui_description->setText("Connected to " + f_last_server.name + "\n\n"); ui_description->append(f_last_server.desc); diff --git a/lobby.h b/lobby.h index 1293b2dbc..ff342a660 100644 --- a/lobby.h +++ b/lobby.h @@ -35,6 +35,7 @@ class Lobby : public QMainWindow void set_stylesheets(); void set_fonts(); void set_font(QWidget *widget, QString p_identifier); + void set_qtextedit_font(QTextEdit *widget, QString p_identifier); void show_loading_overlay() { ui_loading_background->show(); } void hide_loading_overlay() { ui_loading_background->hide(); } QString get_chatlog(); @@ -56,12 +57,12 @@ class Lobby : public QMainWindow AOButton *ui_add_to_fav; AOButton *ui_connect; - QLabel *ui_version; + QTextEdit *ui_version; AOButton *ui_about; QListWidget *ui_server_list; - QLabel *ui_player_count; + QTextEdit *ui_player_count; AOTextArea *ui_description; AOTextArea *ui_chatbox; diff --git a/text_file_functions.cpp b/text_file_functions.cpp index 871b32ed9..e951c534e 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -239,12 +239,12 @@ pos_size_type AOApplication::get_element_dimensions(QString p_identifier, QStrin return return_value; } -int AOApplication::get_font_size(QString p_identifier, QString p_file) +int AOApplication::get_font_property(QString p_identifier, QString p_file) { QString f_result = read_theme_ini(p_identifier, p_file); if (f_result.isEmpty()) - return 10; + return 0; return f_result.toInt(); } From eab09bc4f7767fa1250bd23334f9210cb12f0872 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Thu, 27 Aug 2020 22:50:11 -0400 Subject: [PATCH 055/842] Do not overwrite notepad text if no selected file+Readd notepad scrollbar if needed (#20) --- courtroom.cpp | 7 +++++++ courtroom_widgets.cpp | 3 --- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 5d4a99756..c7b5be773 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -507,12 +507,19 @@ void Courtroom::list_note_files() void Courtroom::load_note() { + //Do not attempt to load anything if no file was chosen. This makes it so that notepad text + //is kept in client if the user has decided not to choose a file to save to. Of course, this is + //ephimeral storage, it will be erased after the client closes or when the user decides to load + //a file. + if (current_file.isEmpty()) + return; QString f_text = ao_app->read_note(current_file); ui_vp_notepad->setText(f_text); } void Courtroom::save_note() { + QString f_text = ui_vp_notepad->toPlainText(); ao_app->write_note(f_text, current_file); diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 430515511..0b31aa775 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -585,9 +585,6 @@ void Courtroom::set_widgets() ui_vp_notepad_image->hide(); set_size_and_pos(ui_vp_notepad, "notepad"); - ui_vp_notepad->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_vp_notepad->verticalScrollBar()->hide(); - ui_vp_notepad->verticalScrollBar()->resize(0, 0); ui_vp_notepad->hide(); set_size_and_pos(ui_vp_showname, "showname"); From 7c915f49e2eb21bd08acf79a5e8af4899224746b Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Thu, 27 Aug 2020 22:51:24 -0400 Subject: [PATCH 056/842] Add callword use notifications in OOC with time, name, message and callword used (#22) --- aoapplication.h | 2 +- courtroom.cpp | 10 +++++++--- text_file_functions.cpp | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/aoapplication.h b/aoapplication.h index adbd3c9d3..d2367035b 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -134,7 +134,7 @@ class AOApplication : public QApplication QString get_username(); // returns a list of call words - QStringList get_call_words(); + QStringList get_callwords(); // returns whatever preanimations should always play or not bool get_always_pre_enabled(); diff --git a/courtroom.cpp b/courtroom.cpp index c7b5be773..2e949fee7 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1031,15 +1031,19 @@ void Courtroom::handle_chatmessage_3() } QString f_message = m_chatmessage[MESSAGE]; - QStringList call_words = ao_app->get_call_words(); + QStringList callwords = ao_app->get_callwords(); - for (QString word : call_words) + for (QString word : callwords) { if (f_message.contains(word, Qt::CaseInsensitive)) { m_system_player->play(ao_app->get_sfx("word_call")); ao_app->alert(this); - + ui_server_chatlog->append_chatmessage( + "CLIENT", + "[" + QTime::currentTime().toString("HH:mm") + "] " + + ui_vp_showname->text() + " has called you via your callword \"" + word + + "\": \"" + f_message + "\""); break; } } diff --git a/text_file_functions.cpp b/text_file_functions.cpp index e951c534e..480b036be 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -40,7 +40,7 @@ int AOApplication::get_default_blip() return config->blips_volume(); } -QStringList AOApplication::get_call_words() +QStringList AOApplication::get_callwords() { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) return config->callwords().split(" ", QString::SkipEmptyParts); From e3940d941e599e82469b71f12e32f2ec141171c8 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 29 Aug 2020 16:49:05 -0400 Subject: [PATCH 057/842] IC chat logs now report day in file --- courtroom.cpp | 2 +- courtroom.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 2e949fee7..19a58992f 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1042,7 +1042,7 @@ void Courtroom::handle_chatmessage_3() ui_server_chatlog->append_chatmessage( "CLIENT", "[" + QTime::currentTime().toString("HH:mm") + "] " + - ui_vp_showname->text() + " has called you via your callword \"" + word + + ui_vp_showname->toPlainText() + " has called you via your callword \"" + word + "\": \"" + f_message + "\""); break; } diff --git a/courtroom.h b/courtroom.h index 129b33706..5b32188d1 100644 --- a/courtroom.h +++ b/courtroom.h @@ -274,7 +274,7 @@ class Courtroom : public QMainWindow QTimer *testimony_hide_timer; //Generate a File Name based on the time you launched the client - QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM yyyy hh.mm.ss.z'.txt'"); + QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); //configuration files locations QString rpc_ini = "configs/rpccharlist.ini"; From a3ebef4349c7d670bd7ec7d2a6f5e851a150bf1f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 29 Aug 2020 16:50:07 -0400 Subject: [PATCH 058/842] Fix merge artifact --- courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courtroom.cpp b/courtroom.cpp index 2e949fee7..19a58992f 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1042,7 +1042,7 @@ void Courtroom::handle_chatmessage_3() ui_server_chatlog->append_chatmessage( "CLIENT", "[" + QTime::currentTime().toString("HH:mm") + "] " + - ui_vp_showname->text() + " has called you via your callword \"" + word + + ui_vp_showname->toPlainText() + " has called you via your callword \"" + word + "\": \"" + f_message + "\""); break; } From f820fdfe6fd55491b6e38c88b3a5f1aadbdba47b Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Sun, 30 Aug 2020 15:24:09 -0400 Subject: [PATCH 059/842] Missing config panel widgets (#24) * Add special IC Chatlog tab to config panel * Add music_change_log toggle to config_panel+Fix opposite behavior for it+Fix extraneous colon * Add always_pre option * Add chat_tick_interval option --- aoapplication.cpp | 6 +- aoconfig.cpp | 96 ++++++++++++++--- aoconfig.h | 11 ++ aoconfigpanel.cpp | 16 ++- aoconfigpanel.h | 5 + courtroom.cpp | 51 +++++---- courtroom.h | 5 +- datatypes.h | 6 +- res/ui/config_panel.ui | 236 +++++++++++++++++++++++++++++++---------- 9 files changed, 334 insertions(+), 98 deletions(-) diff --git a/aoapplication.cpp b/aoapplication.cpp index d18f0e6ae..df3cb3205 100644 --- a/aoapplication.cpp +++ b/aoapplication.cpp @@ -154,7 +154,7 @@ void AOApplication::toggle_config_panel() bool AOApplication::get_always_pre_enabled() { - return config->get_bool("always_pre", true); + return config->always_pre_enabled(); } bool AOApplication::get_first_person_enabled() @@ -174,7 +174,7 @@ int AOApplication::get_chatlog_max_lines() int AOApplication::get_chat_tick_interval() { - return config->get_number("chat_tick_interval", 60); + return config->chat_tick_interval(); } bool AOApplication::get_chatlog_newline() @@ -189,7 +189,7 @@ bool AOApplication::get_enable_logging_enabled() bool AOApplication::get_music_change_log_enabled() { - return config->get_bool("music_change_log", true); + return config->log_music_enabled(); } void AOApplication::add_favorite_server(int p_server) diff --git a/aoconfig.cpp b/aoconfig.cpp index 90e90bebb..35fb56943 100644 --- a/aoconfig.cpp +++ b/aoconfig.cpp @@ -24,9 +24,12 @@ class AOConfigPrivate : public QObject QString username; QString callwords; QString theme; + bool always_pre; + int chat_tick_interval; int log_max_lines; bool log_goes_downward; bool log_uses_newline; + bool log_music; bool log_is_recording; int effects_volume; int system_volume; @@ -68,6 +71,20 @@ public slots: theme = p_string; invoke_parents("theme_changed", Q_ARG(QString, p_string)); } + void set_always_pre(bool p_enabled) + { + if (always_pre == p_enabled) + return; + always_pre = p_enabled; + invoke_parents("always_pre_changed", Q_ARG(bool, p_enabled)); + } + void set_chat_tick_interval(int p_number) + { + if (chat_tick_interval == p_number) + return; + chat_tick_interval = p_number; + invoke_parents("chat_tick_interval_changed", Q_ARG(int, p_number)); + } void set_log_max_lines(int p_number) { if (log_max_lines == p_number) @@ -89,6 +106,13 @@ public slots: log_uses_newline = p_enabled; invoke_parents("log_uses_newline_changed", Q_ARG(bool, p_enabled)); } + void set_log_music(bool p_enabled) + { + if (log_music == p_enabled) + return; + log_music = p_enabled; + invoke_parents("log_music_changed", Q_ARG(bool, p_enabled)); + } void set_log_is_recording(bool p_enabled) { if (log_is_recording == p_enabled) @@ -140,28 +164,34 @@ public slots: } void read_file() { - username = cfg.value("username").toString(); - callwords = cfg.value("callwords").toString(); - theme = cfg.value("theme", "default").toString(); - log_max_lines = cfg.value("chatlog_limit", 200).toInt(); - log_goes_downward = cfg.value("chatlog_scrolldown", true).toBool(); - log_uses_newline = cfg.value("chatlog_newline").toBool(); - log_is_recording = cfg.value("enable_logging").toBool(); - effects_volume = cfg.value("default_sfx", 50).toInt(); - system_volume = cfg.value("default_system", 50).toInt(); - music_volume = cfg.value("default_music", 50).toInt(); - blips_volume = cfg.value("default_blip", 50).toInt(); - blip_rate = cfg.value("blip_rate", 1000000000).toInt(); - blank_blips = cfg.value("blank_blips").toBool(); + username = cfg.value("username").toString(); + callwords = cfg.value("callwords").toString(); + theme = cfg.value("theme", "default").toString(); + always_pre = cfg.value("always_pre", true).toBool(); + chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); + log_max_lines = cfg.value("chatlog_limit", 200).toInt(); + log_goes_downward = cfg.value("chatlog_scrolldown", true).toBool(); + log_uses_newline = cfg.value("chatlog_newline", false).toBool(); + log_music = cfg.value("music_change_log", true).toBool(); + log_is_recording = cfg.value("enable_logging", true).toBool(); + effects_volume = cfg.value("default_sfx", 50).toInt(); + system_volume = cfg.value("default_system", 50).toInt(); + music_volume = cfg.value("default_music", 50).toInt(); + blips_volume = cfg.value("default_blip", 50).toInt(); + blip_rate = cfg.value("blip_rate", 1000000000).toInt(); + blank_blips = cfg.value("blank_blips").toBool(); } void save_file() { cfg.setValue("username", username); cfg.setValue("callwords", callwords); cfg.setValue("theme", theme); + cfg.setValue("always_pre", always_pre); + cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("chatlog_limit", log_max_lines); cfg.setValue("chatlog_scrolldown", log_goes_downward); cfg.setValue("chatlog_newline", log_uses_newline); + cfg.setValue("music_change_log", log_music); cfg.setValue("enable_logging", log_is_recording); cfg.setValue("default_sfx", effects_volume); cfg.setValue("default_system", system_volume); @@ -235,6 +265,16 @@ QString AOConfig::theme() return d->theme; } +bool AOConfig::always_pre_enabled() +{ + return d->always_pre; +} + +int AOConfig::chat_tick_interval() +{ + return d->chat_tick_interval; +} + int AOConfig::log_max_lines() { return d->log_max_lines; @@ -250,6 +290,11 @@ bool AOConfig::log_uses_newline_enabled() return d->log_uses_newline; } +bool AOConfig::log_music_enabled() +{ + return d->log_music; +} + bool AOConfig::log_is_recording_enabled() { return d->log_is_recording; @@ -300,6 +345,21 @@ void AOConfig::set_theme(QString p_string) d->set_theme(p_string); } +void AOConfig::set_always_pre(int p_state) +{ + set_always_pre(p_state == Qt::Checked); +} + +void AOConfig::set_always_pre(bool p_enabled) +{ + d->set_always_pre(p_enabled); +} + +void AOConfig::set_chat_tick_interval(int p_number) +{ + d->set_chat_tick_interval(p_number); +} + void AOConfig::set_log_max_lines(int p_number) { d->set_log_max_lines(p_number); @@ -325,6 +385,16 @@ void AOConfig::set_log_uses_newline(int p_state) set_log_uses_newline(p_state == Qt::Checked); } +void AOConfig::set_log_music(bool p_enabled) +{ + d->set_log_music(p_enabled); +} + +void AOConfig::set_log_music(int p_state) +{ + set_log_music(p_state == Qt::Checked); +} + void AOConfig::set_log_is_recording(bool p_enabled) { d->set_log_is_recording(p_enabled); diff --git a/aoconfig.h b/aoconfig.h index 8cda3d762..266c83567 100644 --- a/aoconfig.h +++ b/aoconfig.h @@ -20,9 +20,12 @@ class AOConfig : public QObject QString username(); QString callwords(); QString theme(); + bool always_pre_enabled(); + int chat_tick_interval(); int log_max_lines(); bool log_goes_downward_enabled(); bool log_uses_newline_enabled(); + bool log_music_enabled(); bool log_is_recording_enabled(); int effects_volume(); int system_volume(); @@ -40,11 +43,16 @@ public slots: void set_username(QString p_string); void set_callwords(QString p_string); void set_theme(QString p_string); + void set_always_pre(bool p_enabled); + void set_always_pre(int p_state); + void set_chat_tick_interval(int p_number); void set_log_max_lines(int p_number); void set_log_goes_downward(bool p_enabled); void set_log_goes_downward(int p_state); void set_log_uses_newline(bool p_enabled); void set_log_uses_newline(int p_state); + void set_log_music(bool p_enabled); + void set_log_music(int p_state); void set_log_is_recording(bool p_enabled); void set_log_is_recording(int p_state); void set_effects_volume(int p_number); @@ -60,9 +68,12 @@ public slots: void username_changed(QString); void callwords_changed(QString); void theme_changed(QString); + void always_pre_changed(bool); + void chat_tick_interval_changed(int); void log_max_lines_changed(int); void log_goes_downward_changed(bool); void log_uses_newline_changed(bool); + void log_music_changed(bool); void log_is_recording_changed(bool); void effects_volume_changed(int); void system_volume_changed(int); diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp index abe547c35..e88f78ede 100644 --- a/aoconfigpanel.cpp +++ b/aoconfigpanel.cpp @@ -22,9 +22,14 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); w_theme = AO_GUI_WIDGET(QComboBox, "theme"); w_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); + w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); + w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); + + // IC Chatlog w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); w_log_goes_downward = AO_GUI_WIDGET(QCheckBox, "log_downward"); + w_log_music = AO_GUI_WIDGET(QCheckBox, "log_music"); w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); // audio @@ -46,9 +51,12 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); connect(m_config, SIGNAL(log_goes_downward_changed(bool)), w_log_goes_downward, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, SLOT(setChecked(bool))); connect(m_config, SIGNAL(effects_volume_changed(int)), w_effects, SLOT(setValue(int))); connect(m_config, SIGNAL(system_volume_changed(int)), w_system, SLOT(setValue(int))); @@ -63,9 +71,12 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); + connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); + connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); connect(w_log_goes_downward, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_goes_downward(int))); connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_uses_newline(int))); + connect(w_log_music, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_music(int))); connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_is_recording(int))); connect(w_effects, SIGNAL(valueChanged(int)), m_config, SLOT(set_effects_volume(int))); connect(w_effects, SIGNAL(valueChanged(int)), this, SLOT(on_effects_value_changed(int))); @@ -82,10 +93,13 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne w_username->setText(m_config->username()); w_callwords->setText(m_config->callwords()); w_theme->setCurrentText(m_config->theme()); + w_always_pre->setChecked(m_config->always_pre_enabled()); + w_chat_tick_interval->setValue(m_config->chat_tick_interval()); w_log_max_lines->setValue(m_config->log_max_lines()); - w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); w_log_goes_downward->setChecked(m_config->log_goes_downward_enabled()); w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); + w_log_music->setChecked(m_config->log_music_enabled()); + w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); w_effects->setValue(m_config->effects_volume()); w_system->setValue(m_config->system_volume()); w_music->setValue(m_config->music_volume()); diff --git a/aoconfigpanel.h b/aoconfigpanel.h index 389a56d5d..90665a38e 100644 --- a/aoconfigpanel.h +++ b/aoconfigpanel.h @@ -25,9 +25,14 @@ class AOConfigPanel : public QWidget QLineEdit *w_callwords = nullptr; QComboBox *w_theme = nullptr; QPushButton *w_reload_theme = nullptr; + QCheckBox *w_always_pre = nullptr; + QSpinBox *w_chat_tick_interval = nullptr; + + // IC Chatlog QSpinBox *w_log_max_lines = nullptr; QCheckBox *w_log_uses_newline = nullptr; QCheckBox *w_log_goes_downward = nullptr; + QCheckBox *w_log_music = nullptr; QCheckBox *w_log_is_recording = nullptr; // audio diff --git a/courtroom.cpp b/courtroom.cpp index 19a58992f..f66d7adcb 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -141,8 +141,6 @@ void Courtroom::enter_courtroom(int p_cid) ui_char_select_background->hide(); - chat_tick_interval = ao_app->get_chat_tick_interval(); - ui_ic_chat_message->setEnabled(m_cid != -1); ui_ic_chat_message->setFocus(); @@ -777,7 +775,7 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) if (is_system_speaking) append_system_text(m_chatmessage[MESSAGE]); else - append_ic_text(f_showname, m_chatmessage[MESSAGE], false); + append_ic_text(f_showname, m_chatmessage[MESSAGE], false, false); if(ao_config->log_is_recording_enabled()) save_textlog("[" + QTime::currentTime().toString() + "] " + f_showname + ": " + m_chatmessage[MESSAGE]); @@ -1153,7 +1151,14 @@ void Courtroom::update_ic_log(bool p_reset_log) } else { - cursor.insertText(record->name + (m_chatlog_newline ? QString(QChar::LineFeed) : ": "), name_format); + QString separator; + if (m_chatlog_newline) + separator = QString(QChar::LineFeed); + else if (!record->music) + separator = ": "; + else + separator = " "; + cursor.insertText(record->name + separator, name_format); cursor.insertText(record->line + QChar::LineFeed + (m_chatlog_newline ? QChar::LineFeed : QChar()), line_format); } } @@ -1225,10 +1230,10 @@ void Courtroom::update_ic_log(bool p_reset_log) } } -void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system) +void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music) { // record new entry - m_ic_records.append(std::make_shared(p_name, p_line, "", p_system)); + m_ic_records.append(std::make_shared(p_name, p_line, "", p_system, p_music)); // update update_ic_log(false); @@ -1238,7 +1243,7 @@ void Courtroom::append_system_text(QString p_line) { if (chatmessage_is_empty) return; - append_ic_text("", p_line, true); + append_ic_text("", p_line, true, false); } void Courtroom::play_preanim() @@ -1336,7 +1341,7 @@ void Courtroom::start_chat_ticking() tick_pos = 0; blip_pos = 0; - chat_tick_timer->start(chat_tick_interval); + chat_tick_timer->start(ao_app->get_chat_tick_interval()); QString f_gender = ao_app->get_gender(m_chatmessage[CHAR_NAME]); @@ -1631,30 +1636,32 @@ void Courtroom::handle_song(QStringList *p_contents) // If there is an empty showname, the client will use instead the default // showname of the character. QString f_showname; - if (f_contents.size() == 3) { - f_showname = f_contents.at(2); - } else { - f_showname = ""; + if (f_contents.size() == 3) + { + f_showname = f_contents.at(2); + } + else + { + f_showname = ""; } QString str_char; - if (f_showname.isEmpty()) { - str_char = ao_app->get_showname(char_list.at(n_char).name); - } else { - str_char = f_showname; + if (f_showname.isEmpty()) + { + str_char = ao_app->get_showname(char_list.at(n_char).name); + } + else + { + str_char = f_showname; } if (!mute_map.value(n_char)) { if (ao_app->get_music_change_log_enabled()) { - m_music_player->play(f_song); - } - else - { - append_ic_text(str_char, "has played a song: " + f_song, false); - m_music_player->play(f_song); + append_ic_text(str_char, "has played a song: " + f_song, false, true); } + m_music_player->play(f_song); } } diff --git a/courtroom.h b/courtroom.h index 129b33706..ec76f667e 100644 --- a/courtroom.h +++ b/courtroom.h @@ -171,7 +171,7 @@ class Courtroom : public QMainWindow //this function keeps the chatlog scrolled to the top unless there's text selected // or the user isn't already scrolled to the top void update_ic_log(bool p_reset_log); - void append_ic_text(QString p_name, QString p_line, bool p_system); + void append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music); void append_system_text(QString p_line); //prints who played the song to IC chat and plays said song(if found on local filesystem) @@ -244,9 +244,8 @@ class Courtroom : public QMainWindow //triggers ping_server() every 60 seconds QTimer *keepalive_timer; - //determines how fast messages tick onto screen + //maintains a timer for how fast messages tick onto screen QTimer *chat_tick_timer; - int chat_tick_interval = 60; //which tick position(character in chat message) we are at int tick_pos = 0; //used to determine how often blips sound diff --git a/datatypes.h b/datatypes.h index ea552c3aa..6f91589ab 100644 --- a/datatypes.h +++ b/datatypes.h @@ -10,10 +10,12 @@ struct record_type QString line; QString color; bool system = false; + bool music = false; record_type() = default; - record_type(QString p_name, QString p_line, QString p_color, bool p_is_system) - : name(p_name), line(p_line), system(p_is_system) + record_type(QString p_name, QString p_line, QString p_color, bool p_is_system, + bool p_is_music) + : name(p_name), line(p_line), system(p_is_system), music(p_is_music) { Q_UNUSED(p_color); } diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 777660c74..5048f7cce 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -121,82 +121,210 @@ - + - Log length: + Pre-animation always selected + + + true - - - line(s) - - - 0 - - - 10000 - - - 200 + + + - + - Log uses newline: + Chat tick interval - - - - - - - 0 - 0 - - - - - - - - Log is downward: + + true - - - - - 0 - 0 - + + + + ms - - - - - - Log is recorded: + + 10 - - - - - - - 0 - 0 - + + 999 + + IC Chatlog + + + + + 100 + 38 + 259 + 15 + + + + + 0 + 0 + + + + + + + 100 + 120 + 259 + 15 + + + + + 0 + 0 + + + + + + + 100 + 13 + 259 + 19 + + + + line(s) + + + 0 + + + 10000 + + + 200 + + + + + + 9 + 38 + 85 + 15 + + + + Log uses newline: + + + + + + 10 + 120 + 77 + 15 + + + + Log is recorded: + + + + + + 9 + 13 + 54 + 19 + + + + Log length: + + + + + + 9 + 59 + 84 + 15 + + + + Log is downward: + + + + + + 100 + 59 + 259 + 15 + + + + + 0 + 0 + + + + + + + 100 + 90 + 259 + 15 + + + + + 0 + 0 + + + + + + + + + + 10 + 80 + 77 + 31 + + + + Log shows music changes: + + + true + + + + Audio From 1c8be13d991321a5621682060055f8a81ec23a04 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Sun, 30 Aug 2020 15:24:23 -0400 Subject: [PATCH 060/842] Fix old AOScene not being refreshed when meant to do so (#25) --- aoscene.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/aoscene.cpp b/aoscene.cpp index 04408f32c..e85400f83 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -33,8 +33,14 @@ void AOScene::set_image(QString p_image) // do not update the movie if we're using the same file if (m_reader->fileName() == target_path) return; + m_reader->stop(); + delete m_reader; + + m_reader = new QMovie(this); + m_reader->setScaledSize(size()); m_reader->setFileName(target_path); + setMovie(m_reader); m_reader->start(); } From 36215ef1a6941e1de932b2fafd26450121ea94b0 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Sun, 30 Aug 2020 15:24:35 -0400 Subject: [PATCH 061/842] Fix AOButton not changing to hovering images when appropriate (#26) --- aobutton.cpp | 21 ++++++++++----------- courtroom_widgets.cpp | 5 +++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/aobutton.cpp b/aobutton.cpp index 61dddf70c..a145fbfeb 100644 --- a/aobutton.cpp +++ b/aobutton.cpp @@ -12,23 +12,22 @@ AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) : QPushButton(paren void AOButton::set_image(QString p_image) { - QString f_image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); - QString some_image_path = ao_app->get_image_path(p_image); - QString some_hover_image_path = ao_app->get_image_path(p_image + "_hover.png"); + image_path = ao_app->get_image_path(p_image); + // Get the path of the found image without the extension + QString image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); + QString hover_image_path = ao_app->get_image_path(image_name + "_hover.png"); - if (file_exists(some_image_path)) + if (file_exists(image_path)) { - image_path = some_image_path; - - if (file_exists(some_hover_image_path)) - this->setStyleSheet("QPushButton {border-image:url(\"" + some_image_path + "\");}" - "QPushButton:hover {border-image:url(\"" + some_hover_image_path + "\");}"); + if (file_exists(hover_image_path)) + this->setStyleSheet("QPushButton {border-image:url(\"" + image_path + "\");}" + "QPushButton:hover {border-image:url(\"" + hover_image_path + "\");}"); else - this->setStyleSheet("border-image:url(\"" + some_image_path + "\")"); + this->setStyleSheet("border-image:url(\"" + image_path + "\")"); } else { image_path = ""; - this->setStyleSheet("border-image:url(\"" + some_image_path + "\")"); + this->setStyleSheet("border-image:url(\"" + image_path + "\")"); } } diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 0b31aa775..2519e34f8 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -802,13 +802,14 @@ void Courtroom::set_widgets() ui_config_panel->setText("Config"); ui_config_panel->setStyleSheet(""); - ui_note_button->setText("><:"); + ui_note_button->setText("Notes"); ui_note_button->setStyleSheet(""); if (ao_app->read_theme_ini("enable_button_images", cc_config_ini) == "true") { // Set files, ask questions later - // set_image first tries the theme variant folder, then the theme folder, then falls back to the default theme + // set_image first tries the theme variant folder, then the theme folder, then falls back to + // the default theme ui_change_character->set_image("changecharacter.png"); if (!ui_change_character->image_path.isEmpty()) ui_change_character->setText(""); From 65441778a4021ed9775684a5016b152cbb10a044 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Sun, 30 Aug 2020 15:24:48 -0400 Subject: [PATCH 062/842] Remove area and character passwords (#27) --- aocharbutton.cpp | 12 ------------ aocharbutton.h | 2 -- charselect.cpp | 2 -- courtroom.h | 5 ----- courtroom_widgets.cpp | 6 ------ datatypes.h | 1 - 6 files changed, 28 deletions(-) diff --git a/aocharbutton.cpp b/aocharbutton.cpp index ba4454e3a..d5fff5535 100644 --- a/aocharbutton.cpp +++ b/aocharbutton.cpp @@ -16,18 +16,11 @@ AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, ui_taken->set_image("char_taken.png"); ui_taken->setAttribute(Qt::WA_TransparentForMouseEvents); ui_taken->hide(); - - ui_passworded = new AOImage(this, ao_app); - ui_passworded->resize(60, 60); - ui_passworded->set_image("char_passworded"); - ui_passworded->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_passworded->hide(); } void AOCharButton::reset() { ui_taken->hide(); - ui_passworded->hide(); } void AOCharButton::set_taken() @@ -35,11 +28,6 @@ void AOCharButton::set_taken() ui_taken->show(); } -void AOCharButton::set_passworded() -{ - ui_passworded->show(); -} - void AOCharButton::set_image(QString p_character) { QString image_path = ao_app->get_character_path(p_character) + "char_icon.png"; diff --git a/aocharbutton.h b/aocharbutton.h index c30c79baa..9bba0e647 100644 --- a/aocharbutton.h +++ b/aocharbutton.h @@ -18,7 +18,6 @@ class AOCharButton : public QPushButton void reset(); void set_taken(); - void set_passworded(); void set_image(QString p_character); signals: @@ -27,7 +26,6 @@ class AOCharButton : public QPushButton private: AOImage *ui_taken = nullptr; - AOImage *ui_passworded = nullptr; protected: void enterEvent(QEvent *e); diff --git a/charselect.cpp b/charselect.cpp index 43aac2135..5e887b9b6 100644 --- a/charselect.cpp +++ b/charselect.cpp @@ -17,8 +17,6 @@ void Courtroom::construct_char_select() ui_back_to_lobby = new AOButton(ui_char_select_background, ao_app); - ui_char_password = new QLineEdit(ui_char_select_background); - ui_char_select_left = new AOButton(ui_char_select_background, ao_app); ui_char_select_right = new AOButton(ui_char_select_background, ao_app); diff --git a/courtroom.h b/courtroom.h index ec76f667e..c78fb291c 100644 --- a/courtroom.h +++ b/courtroom.h @@ -426,14 +426,11 @@ class Courtroom : public QMainWindow QListWidget *ui_music_list; QListWidget *ui_sfx_list; - // QListWidget *ui_sfx_list; - QLineEdit *ui_ic_chat_message; QLineEdit *ui_ooc_chat_message; QLineEdit *ui_ooc_chat_name; - //QLineEdit *ui_area_password; QLineEdit *ui_music_search; QLineEdit *ui_sfx_search; @@ -561,8 +558,6 @@ class Courtroom : public QMainWindow AOButton *ui_back_to_lobby; - QLineEdit *ui_char_password; - AOButton *ui_char_select_left; AOButton *ui_char_select_right; diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 2519e34f8..8cbba63a5 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -129,8 +129,6 @@ void Courtroom::create_widgets() ui_ooc_chat_name->setPlaceholderText("Name"); ui_ooc_chat_name->setText(ao_config->username()); - //ui_area_password = new QLineEdit(this); - //ui_area_password->setFrame(false); ui_music_search = new QLineEdit(this); ui_music_search->setFrame(false); @@ -410,7 +408,6 @@ void Courtroom::reset_widget_names() {"evidence_buttons", ui_evidence_buttons}, {"char_select", ui_char_select_background}, {"back_to_lobby", ui_back_to_lobby}, - {"char_password", ui_char_password}, {"char_buttons", ui_char_buttons}, {"char_select_left", ui_char_select_left}, {"char_select_right", ui_char_select_right}, @@ -659,7 +656,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name"); ui_ooc_chat_name->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - //set_size_and_pos(ui_area_password, "area_password"); set_size_and_pos(ui_music_search, "music_search"); set_size_and_pos(ui_sfx_search, "sfx_search"); @@ -943,8 +939,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_back_to_lobby, "back_to_lobby"); ui_back_to_lobby->setText("Back to Lobby"); - set_size_and_pos(ui_char_password, "char_password"); - set_size_and_pos(ui_char_buttons, "char_buttons"); set_size_and_pos(ui_char_select_left, "char_select_left"); diff --git a/datatypes.h b/datatypes.h index 6f91589ab..5406a643f 100644 --- a/datatypes.h +++ b/datatypes.h @@ -81,7 +81,6 @@ struct area_type { QString name; QString background; - bool passworded; }; struct pos_type From 2cdbd687fe6df9eca6366b4b1203d9c3fb77e7d2 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Sun, 30 Aug 2020 15:32:30 -0400 Subject: [PATCH 063/842] Render (or not) specific highlight characters (#28) * Show highlight characters theme option * Allow each special character to define whether it should be rendered or not by theme * Render only if ", 1" -> Not render only if ", 0" to be more consistent --- courtroom.cpp | 42 +++++++++-------------------------------- courtroom.h | 2 -- text_file_functions.cpp | 13 ++++++++++++- 3 files changed, 21 insertions(+), 36 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index f66d7adcb..3f9a3fdb8 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1291,35 +1291,6 @@ void Courtroom::realization_done() ui_vp_effect->stop(); } -QString Courtroom::parse_message(QString message) -{ - QVector f_vec = ao_app->get_highlight_color(); - - for(int i=0; i")); - message.remove(i, 1); - message.insert(i, QString("")); - } - } - break; - } - } - } - - return message; -} - void Courtroom::start_chat_ticking() { ui_vp_message->clear(); @@ -1419,16 +1390,20 @@ void Courtroom::chat_tick() else if (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "true") { bool highlight_found = false; + bool render_character = true; + // render_character should only be false if the character is a highlight character + // specifically marked as a character that should not be rendered. QVector f_vec = ao_app->get_highlight_color(); if(m_color_stack.isEmpty()) m_color_stack.push(""); for(const auto& col : f_vec) { - if(f_character == col[0].trimmed()[0] && m_string_color != col[1].trimmed()) + if(f_character == col[0][0] && m_string_color != col[1]) { - m_color_stack.push(col[1].trimmed()); + m_color_stack.push(col[1]); m_string_color = m_color_stack.top(); highlight_found = true; + render_character = (col[2] != "0"); break; } } @@ -1447,16 +1422,17 @@ void Courtroom::chat_tick() for(const auto& col : f_vec) { - if(f_character == col[0].trimmed()[1] && !highlight_found) + if(f_character == col[0][1] && !highlight_found) { if(m_color_stack.size() > 1) m_color_stack.pop(); m_future_string_color = m_color_stack.top(); highlight_found = true; + render_character = (col[2] != "0"); break; } } - if (!highlight_found) + if (render_character) ui_vp_message->textCursor().insertText(f_character, vp_message_format); m_string_color = m_future_string_color; diff --git a/courtroom.h b/courtroom.h index c78fb291c..89a6cee2f 100644 --- a/courtroom.h +++ b/courtroom.h @@ -189,8 +189,6 @@ class Courtroom : public QMainWindow void play_preanim(); - QString parse_message(QString message); - //plays the witness testimony or cross examination animation based on argument void handle_wtce(QString p_wtce); diff --git a/text_file_functions.cpp b/text_file_functions.cpp index 480b036be..7956cad5e 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -355,7 +355,18 @@ QVector AOApplication::get_highlight_color() { if((line.startsWith("[") && line.endsWith("]"))) break; - f_vec.append(line.split("=")); + // Syntax + // OpenercharCloserchar = Color, Shown + // Shown is 1 if the character should be displayed in IC, 0 otherwise. If not present, + // assume 1. + QString chars = line.split("=")[0].trimmed(); + QString chars_parameters = line.mid(line.indexOf("=")+1); + QStringList parameters = chars_parameters.split(","); + for (int i=0; i Date: Sun, 30 Aug 2020 16:04:18 -0400 Subject: [PATCH 064/842] Correct position on character change (#23) * On character change, the position dropdown now reports the character position * Client now sends "SP" packet when dropdown changes+Remove /pos client command * Replace client sending SP packet with command for now. Replace after TSDR 4.3 releases --- courtroom.cpp | 25 ++++++++++++++++--------- courtroom.h | 3 +++ emotes.cpp | 12 ++++++------ packet_distribution.cpp | 8 ++++++++ 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 3f9a3fdb8..f03d2d621 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -40,6 +40,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() void Courtroom::enter_courtroom(int p_cid) { + bool changed_character = (m_cid != p_cid); m_cid = p_cid; QString f_char; @@ -96,10 +97,8 @@ void Courtroom::enter_courtroom(int p_cid) set_evidence_page(); - QString side = ao_app->get_char_side(f_char); - - // enable judge mechanics - set_judge_enabled(side == "jud"); + // Refresh character position and dropdown if needed + set_character_position(ao_app->get_char_side(f_char), changed_character); // Update widgets first, then check if everything is valid // This will also handle showing the correct shouts, effects and wtce buttons, and cycling @@ -1688,6 +1687,16 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) } } +void Courtroom::set_character_position(QString p_pos, bool refresh_dropdown) +{ + int index = ui_pos_dropdown->findData(p_pos); + if (index != -1 && refresh_dropdown) + ui_pos_dropdown->setCurrentIndex(index); + + // enable judge mechanics if appropriate + set_judge_enabled(p_pos == "jud"); +} + void Courtroom::mod_called(QString p_ip) { ui_server_chatlog->append(p_ip); @@ -1726,11 +1735,6 @@ void Courtroom::on_ooc_return_pressed() ao_config->set_username(ooc_name); } - - if (ooc_message.startsWith("/pos")) - { - set_judge_enabled(ooc_message.startsWith("/pos jud")); - } else if (ooc_message.startsWith("/login")) ui_guard->show(); else if (ooc_message.startsWith("/rainbow") && ao_app->yellow_text_enabled && !rainbow_appended) @@ -1879,6 +1883,9 @@ void Courtroom::on_pos_dropdown_changed(int p_index) set_judge_enabled(f_pos == "jud"); ao_app->send_server_packet(new AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); + // Uncomment later and remove above + // Will only work in TSDR 4.3+ servers + //ao_app->send_server_packet(new AOPacket("SP#" + f_pos + "#%")); } void Courtroom::on_mute_list_clicked(QModelIndex p_index) diff --git a/courtroom.h b/courtroom.h index 89a6cee2f..679e2eb91 100644 --- a/courtroom.h +++ b/courtroom.h @@ -104,6 +104,9 @@ class Courtroom : public QMainWindow //sets the evidence list member variable to argument void set_evidence_list(QVector &p_evi_list); + //sets the character position + void set_character_position(QString p_pos, bool refresh_dropdown); + //called when a DONE#% from the server was received void done_received(); diff --git a/emotes.cpp b/emotes.cpp index cd3194990..35a69dbdd 100644 --- a/emotes.cpp +++ b/emotes.cpp @@ -13,12 +13,12 @@ void Courtroom::construct_emotes() ui_emote_dropdown = new QComboBox(this); ui_pos_dropdown = new QComboBox(this); - ui_pos_dropdown->addItem("wit"); - ui_pos_dropdown->addItem("def"); - ui_pos_dropdown->addItem("pro"); - ui_pos_dropdown->addItem("jud"); - ui_pos_dropdown->addItem("hld"); - ui_pos_dropdown->addItem("hlp"); + ui_pos_dropdown->addItem("wit", "wit"); + ui_pos_dropdown->addItem("def", "def"); + ui_pos_dropdown->addItem("pro", "pro"); + ui_pos_dropdown->addItem("jud", "jud"); + ui_pos_dropdown->addItem("hld", "hld"); + ui_pos_dropdown->addItem("hlp", "hlp"); reconstruct_emotes(); } diff --git a/packet_distribution.cpp b/packet_distribution.cpp index dc0815066..311686f69 100644 --- a/packet_distribution.cpp +++ b/packet_distribution.cpp @@ -713,6 +713,14 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int timer_id = f_contents.at(0).toInt(); w_courtroom->pause_timer(timer_id); } + else if (header == "SP") + { + // Set position + if (f_contents.size() != 1) + goto end; + + w_courtroom->set_character_position(f_contents.at(0), true); + } end: From 308b42ca637696108ce0ecad54e77ec84530afba Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 31 Aug 2020 15:44:46 -0400 Subject: [PATCH 065/842] Guard to config panel (#29) * Add pending colons and reword callword label * Add server alerts widget * Remove references to guard * Move server alert tickbox above chat interval spinbox --- aoapplication.cpp | 5 +++++ aoapplication.h | 4 ++++ aoconfig.cpp | 25 +++++++++++++++++++++++++ aoconfig.h | 3 +++ aoconfigpanel.cpp | 4 ++++ aoconfigpanel.h | 1 + courtroom.cpp | 9 +-------- courtroom.h | 7 ++----- courtroom_widgets.cpp | 12 +----------- res/ui/config_panel.ui | 27 ++++++++++++++++++++++----- 10 files changed, 68 insertions(+), 29 deletions(-) diff --git a/aoapplication.cpp b/aoapplication.cpp index df3cb3205..8cfec8801 100644 --- a/aoapplication.cpp +++ b/aoapplication.cpp @@ -162,6 +162,11 @@ bool AOApplication::get_first_person_enabled() return config->get_bool("first_person", false); } +bool AOApplication::get_server_alerts_enabled() +{ + return config->server_alerts_enabled(); +} + bool AOApplication::get_chatlog_scrolldown() { return config->log_goes_downward_enabled(); diff --git a/aoapplication.h b/aoapplication.h index d2367035b..46563eea5 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -142,6 +142,10 @@ class AOApplication : public QApplication // returns whatever the client should simulate first person dialog bool get_first_person_enabled(); + // returns whether server alerts (ones that trigger a client alert other than callwords) + // should actually tigger a server alert or not + bool get_server_alerts_enabled(); + // returns if chatlog goes downward bool get_chatlog_scrolldown(); diff --git a/aoconfig.cpp b/aoconfig.cpp index 35fb56943..fe3c6f6e3 100644 --- a/aoconfig.cpp +++ b/aoconfig.cpp @@ -26,6 +26,7 @@ class AOConfigPrivate : public QObject QString theme; bool always_pre; int chat_tick_interval; + bool server_alerts; int log_max_lines; bool log_goes_downward; bool log_uses_newline; @@ -85,6 +86,13 @@ public slots: chat_tick_interval = p_number; invoke_parents("chat_tick_interval_changed", Q_ARG(int, p_number)); } + void set_server_alerts(bool p_enabled) + { + if (server_alerts == p_enabled) + return; + server_alerts = p_enabled; + invoke_parents("server_alerts_changed", Q_ARG(bool, p_enabled)); + } void set_log_max_lines(int p_number) { if (log_max_lines == p_number) @@ -169,6 +177,7 @@ public slots: theme = cfg.value("theme", "default").toString(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); + server_alerts = cfg.value("server_alerts", true).toBool(); log_max_lines = cfg.value("chatlog_limit", 200).toInt(); log_goes_downward = cfg.value("chatlog_scrolldown", true).toBool(); log_uses_newline = cfg.value("chatlog_newline", false).toBool(); @@ -188,6 +197,7 @@ public slots: cfg.setValue("theme", theme); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); + cfg.setValue("server_alerts", server_alerts); cfg.setValue("chatlog_limit", log_max_lines); cfg.setValue("chatlog_scrolldown", log_goes_downward); cfg.setValue("chatlog_newline", log_uses_newline); @@ -275,6 +285,11 @@ int AOConfig::chat_tick_interval() return d->chat_tick_interval; } +bool AOConfig::server_alerts_enabled() +{ + return d->server_alerts; +} + int AOConfig::log_max_lines() { return d->log_max_lines; @@ -360,6 +375,16 @@ void AOConfig::set_chat_tick_interval(int p_number) d->set_chat_tick_interval(p_number); } +void AOConfig::set_server_alerts(int p_state) +{ + set_server_alerts(p_state == Qt::Checked); +} + +void AOConfig::set_server_alerts(bool p_enabled) +{ + d->set_server_alerts(p_enabled); +} + void AOConfig::set_log_max_lines(int p_number) { d->set_log_max_lines(p_number); diff --git a/aoconfig.h b/aoconfig.h index 266c83567..39d66ae8f 100644 --- a/aoconfig.h +++ b/aoconfig.h @@ -22,6 +22,7 @@ class AOConfig : public QObject QString theme(); bool always_pre_enabled(); int chat_tick_interval(); + bool server_alerts_enabled(); int log_max_lines(); bool log_goes_downward_enabled(); bool log_uses_newline_enabled(); @@ -46,6 +47,8 @@ public slots: void set_always_pre(bool p_enabled); void set_always_pre(int p_state); void set_chat_tick_interval(int p_number); + void set_server_alerts(bool p_enabled); + void set_server_alerts(int p_state); void set_log_max_lines(int p_number); void set_log_goes_downward(bool p_enabled); void set_log_goes_downward(int p_state); diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp index e88f78ede..316ce2982 100644 --- a/aoconfigpanel.cpp +++ b/aoconfigpanel.cpp @@ -24,6 +24,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne w_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); + w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); // IC Chatlog w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); @@ -53,6 +54,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); + connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); connect(m_config, SIGNAL(log_goes_downward_changed(bool)), w_log_goes_downward, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, SLOT(setChecked(bool))); @@ -73,6 +75,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); + connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, SLOT(set_server_alerts(int))); connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); connect(w_log_goes_downward, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_goes_downward(int))); connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_uses_newline(int))); @@ -95,6 +98,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne w_theme->setCurrentText(m_config->theme()); w_always_pre->setChecked(m_config->always_pre_enabled()); w_chat_tick_interval->setValue(m_config->chat_tick_interval()); + w_server_alerts->setChecked(m_config->server_alerts_enabled()); w_log_max_lines->setValue(m_config->log_max_lines()); w_log_goes_downward->setChecked(m_config->log_goes_downward_enabled()); w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); diff --git a/aoconfigpanel.h b/aoconfigpanel.h index 90665a38e..2816fafc3 100644 --- a/aoconfigpanel.h +++ b/aoconfigpanel.h @@ -27,6 +27,7 @@ class AOConfigPanel : public QWidget QPushButton *w_reload_theme = nullptr; QCheckBox *w_always_pre = nullptr; QSpinBox *w_chat_tick_interval = nullptr; + QCheckBox *w_server_alerts = nullptr; // IC Chatlog QSpinBox *w_log_max_lines = nullptr; diff --git a/courtroom.cpp b/courtroom.cpp index f03d2d621..dda481151 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1700,7 +1700,7 @@ void Courtroom::set_character_position(QString p_pos, bool refresh_dropdown) void Courtroom::mod_called(QString p_ip) { ui_server_chatlog->append(p_ip); - if (ui_guard->isChecked()) + if (ao_app->get_server_alerts_enabled()) { m_system_player->play(ao_app->get_sfx("mod_call")); ao_app->alert(this); @@ -1735,8 +1735,6 @@ void Courtroom::on_ooc_return_pressed() ao_config->set_username(ooc_name); } - else if (ooc_message.startsWith("/login")) - ui_guard->show(); else if (ooc_message.startsWith("/rainbow") && ao_app->yellow_text_enabled && !rainbow_appended) { ui_text_color->addItem("Rainbow"); @@ -2284,11 +2282,6 @@ void Courtroom::on_flip_clicked() ui_ic_chat_message->setFocus(); } -void Courtroom::on_guard_clicked() -{ - ui_ic_chat_message->setFocus(); -} - void Courtroom::on_hidden_clicked() { ui_ic_chat_message->setFocus(); diff --git a/courtroom.h b/courtroom.h index 679e2eb91..1d63678a2 100644 --- a/courtroom.h +++ b/courtroom.h @@ -510,13 +510,12 @@ class Courtroom : public QMainWindow QCheckBox *ui_pre; QCheckBox *ui_flip; - QCheckBox *ui_guard; QCheckBox *ui_hidden; - QVector ui_checks; // 0 = pre, 1 = flip, 2 = guard, 3 = hidden + QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip QVector ui_label_images; - QVector label_images = {"Pre", "Flip", "Guard", "Hidden", "Music", "SFX", "Blip"}; + QVector label_images = {"Pre", "Flip", "Hidden", "Music", "SFX", "Blip"}; AOButton *ui_effect_flash = nullptr; AOButton *ui_effect_gloom = nullptr; @@ -711,8 +710,6 @@ private slots: void on_pre_clicked(); void on_flip_clicked(); - void on_guard_clicked(); - void on_hidden_clicked(); void on_sfx_list_clicked(); diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 8cbba63a5..1db5da368 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -179,7 +179,7 @@ void Courtroom::create_widgets() ui_config_panel = new AOButton(this, ao_app); ui_note_button = new AOButton(this, ao_app); - ui_label_images.resize(7); + ui_label_images.resize(label_images.size()); for(int i = 0; i < ui_label_images.size(); ++i) { ui_label_images[i] = new AOImage(this, ao_app); @@ -190,18 +190,13 @@ void Courtroom::create_widgets() ui_flip = new QCheckBox(this); ui_flip->setText("Flip"); ui_flip->hide(); - ui_guard = new QCheckBox(this); - ui_guard->setText("Guard"); - ui_guard->hide(); ui_hidden = new QCheckBox(this); ui_hidden->setText("Hidden"); // filling vectors with existing label/checkbox pointers ui_checks.push_back(ui_pre); ui_checks.push_back(ui_flip); - ui_checks.push_back(ui_guard); ui_checks.push_back(ui_hidden); - // ui_mute = new AOButton(this, ao_app); @@ -314,8 +309,6 @@ void Courtroom::connect_widgets() connect(ui_pre, SIGNAL(clicked()), this, SLOT(on_pre_clicked())); connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); - connect(ui_guard, SIGNAL(clicked()), this, SLOT(on_guard_clicked())); - connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_sfx_list_clicked())); @@ -392,7 +385,6 @@ void Courtroom::reset_widget_names() // Each ui_label_images[i] {"pre", ui_pre}, {"flip", ui_flip}, - {"guard", ui_guard}, {"hidden", ui_hidden}, {"mute_button", ui_mute}, {"defense_plus", ui_defense_plus}, @@ -832,8 +824,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_flip, "flip"); - set_size_and_pos(ui_guard, "guard"); - set_size_and_pos(ui_hidden, "hidden"); for(int i = 0; i < ui_label_images.size(); ++i) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 5048f7cce..39a121cb2 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -52,7 +52,10 @@ - Callwords: + Callwords (separate by spaces): + + + true @@ -123,7 +126,7 @@ - Pre-animation always selected + Pre-animation always selected: true @@ -137,17 +140,17 @@ - + - Chat tick interval + Chat tick interval: true - + ms @@ -160,6 +163,20 @@ + + + + Server alerts: + + + + + + + + + + From 2a4f41c46b5b2e1653c450f6ad7e63814ef5438b Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 31 Aug 2020 16:19:05 -0400 Subject: [PATCH 066/842] "IC Chatlog"->"IC" config panel widget+Move IC options from General to IC widget --- res/ui/config_panel.ui | 315 +++++++++++++++++------------------------ 1 file changed, 132 insertions(+), 183 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 39a121cb2..2e8e0b2db 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 0 + 1 @@ -123,224 +123,173 @@ + + + + + + + - + - Pre-animation always selected: + Server alerts: + + + + + + + + IC + + + + + + Log length: + + + + + + + line(s) + + + 0 + + + 10000 + + + 200 + + + + + + + Log uses newline: + + + + + + + + 0 + 0 + + + + + + + + Log is downward: + + + + + + + + 0 + 0 + + + + + + + + Log shows music changes: true - - + + + + + 0 + 0 + + - - + + - Chat tick interval: + Log is recorded: + + + + + + + + 0 + 0 + + + + + + + + Pre-animation always selected: true - - - - ms + + + + - - 10 + + false - - 999 + + + + + + Qt::Horizontal - + - Server alerts: + Chat tick interval: - - - + + + ms + + + 10 + + + 999 - - - IC Chatlog - - - - - 100 - 38 - 259 - 15 - - - - - 0 - 0 - - - - - - - 100 - 120 - 259 - 15 - - - - - 0 - 0 - - - - - - - 100 - 13 - 259 - 19 - - - - line(s) - - - 0 - - - 10000 - - - 200 - - - - - - 9 - 38 - 85 - 15 - - - - Log uses newline: - - - - - - 10 - 120 - 77 - 15 - - - - Log is recorded: - - - - - - 9 - 13 - 54 - 19 - - - - Log length: - - - - - - 9 - 59 - 84 - 15 - - - - Log is downward: - - - - - - 100 - 59 - 259 - 15 - - - - - 0 - 0 - - - - - - - 100 - 90 - 259 - 15 - - - - - 0 - 0 - - - - - - - - - - 10 - 80 - 77 - 31 - - - - Log shows music changes: - - - true - - - Audio From 47bcc5ae6cd69450bf40b5bf766ba37181792afe Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 31 Aug 2020 16:42:19 -0400 Subject: [PATCH 067/842] Add basic theme variant combobox --- aoapplication.cpp | 2 +- aoapplication.h | 5 +++-- aoconfig.cpp | 20 ++++++++++++++++++++ aoconfig.h | 2 ++ aoconfigpanel.cpp | 4 ++++ aoconfigpanel.h | 1 + path_functions.cpp | 2 +- res/ui/config_panel.ui | 30 ++++++++++++++++++++---------- text_file_functions.cpp | 5 +++++ 9 files changed, 57 insertions(+), 14 deletions(-) diff --git a/aoapplication.cpp b/aoapplication.cpp index 8cfec8801..d5c51688c 100644 --- a/aoapplication.cpp +++ b/aoapplication.cpp @@ -111,7 +111,7 @@ QString AOApplication::get_version_string() void AOApplication::set_theme_variant(QString p_variant) { - m_theme_variant = p_variant; + config->set_theme_variant(p_variant); emit reload_theme(); } diff --git a/aoapplication.h b/aoapplication.h index 46563eea5..792df6ee6 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -123,6 +123,9 @@ class AOApplication : public QApplication //Reads the theme from config.ini and loads it into the current_theme variable QString get_theme(); + //Reads the theme variant from config.ini and loads it into the current theme variant variable + QString get_theme_variant(); + //Returns the blip rate from config.ini int read_blip_rate(); @@ -303,8 +306,6 @@ class AOApplication : public QApplication const int MAJOR_VERSION = 4; const int MINOR_VERSION = 8; - QString m_theme_variant = ""; - QVector server_list; QVector favorite_list; diff --git a/aoconfig.cpp b/aoconfig.cpp index fe3c6f6e3..7a8a824b3 100644 --- a/aoconfig.cpp +++ b/aoconfig.cpp @@ -24,6 +24,7 @@ class AOConfigPrivate : public QObject QString username; QString callwords; QString theme; + QString theme_variant; bool always_pre; int chat_tick_interval; bool server_alerts; @@ -72,6 +73,13 @@ public slots: theme = p_string; invoke_parents("theme_changed", Q_ARG(QString, p_string)); } + void set_theme_variant(QString p_string) + { + if (theme_variant == p_string) + return; + theme_variant = p_string; + invoke_parents("theme_variant_changed", Q_ARG(QString, p_string)); + } void set_always_pre(bool p_enabled) { if (always_pre == p_enabled) @@ -175,6 +183,7 @@ public slots: username = cfg.value("username").toString(); callwords = cfg.value("callwords").toString(); theme = cfg.value("theme", "default").toString(); + theme_variant = cfg.value("theme_variant", "").toString(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); server_alerts = cfg.value("server_alerts", true).toBool(); @@ -195,6 +204,7 @@ public slots: cfg.setValue("username", username); cfg.setValue("callwords", callwords); cfg.setValue("theme", theme); + cfg.setValue("theme_variant", theme_variant); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("server_alerts", server_alerts); @@ -275,6 +285,11 @@ QString AOConfig::theme() return d->theme; } +QString AOConfig::theme_variant() +{ + return d->theme_variant; +} + bool AOConfig::always_pre_enabled() { return d->always_pre; @@ -360,6 +375,11 @@ void AOConfig::set_theme(QString p_string) d->set_theme(p_string); } +void AOConfig::set_theme_variant(QString p_string) +{ + d->set_theme_variant(p_string); +} + void AOConfig::set_always_pre(int p_state) { set_always_pre(p_state == Qt::Checked); diff --git a/aoconfig.h b/aoconfig.h index 39d66ae8f..49c1beba1 100644 --- a/aoconfig.h +++ b/aoconfig.h @@ -20,6 +20,7 @@ class AOConfig : public QObject QString username(); QString callwords(); QString theme(); + QString theme_variant(); bool always_pre_enabled(); int chat_tick_interval(); bool server_alerts_enabled(); @@ -44,6 +45,7 @@ public slots: void set_username(QString p_string); void set_callwords(QString p_string); void set_theme(QString p_string); + void set_theme_variant(QString p_string); void set_always_pre(bool p_enabled); void set_always_pre(int p_state); void set_chat_tick_interval(int p_number); diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp index 316ce2982..a531b33ba 100644 --- a/aoconfigpanel.cpp +++ b/aoconfigpanel.cpp @@ -21,6 +21,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne w_username = AO_GUI_WIDGET(QLineEdit, "username"); w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); w_theme = AO_GUI_WIDGET(QComboBox, "theme"); + w_theme_variant = AO_GUI_WIDGET(QComboBox, "theme_variant"); w_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); @@ -52,6 +53,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(theme_variant_changed(QString)), w_theme_variant, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); @@ -73,6 +75,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); + connect(w_theme_variant, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, SLOT(set_server_alerts(int))); @@ -96,6 +99,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne w_username->setText(m_config->username()); w_callwords->setText(m_config->callwords()); w_theme->setCurrentText(m_config->theme()); + w_theme_variant->setCurrentText(m_config->theme_variant()); w_always_pre->setChecked(m_config->always_pre_enabled()); w_chat_tick_interval->setValue(m_config->chat_tick_interval()); w_server_alerts->setChecked(m_config->server_alerts_enabled()); diff --git a/aoconfigpanel.h b/aoconfigpanel.h index 2816fafc3..4588c3007 100644 --- a/aoconfigpanel.h +++ b/aoconfigpanel.h @@ -25,6 +25,7 @@ class AOConfigPanel : public QWidget QLineEdit *w_callwords = nullptr; QComboBox *w_theme = nullptr; QPushButton *w_reload_theme = nullptr; + QComboBox *w_theme_variant = nullptr; QCheckBox *w_always_pre = nullptr; QSpinBox *w_chat_tick_interval = nullptr; QCheckBox *w_server_alerts = nullptr; diff --git a/path_functions.cpp b/path_functions.cpp index 711e1e25d..03c5d6515 100644 --- a/path_functions.cpp +++ b/path_functions.cpp @@ -43,7 +43,7 @@ QString AOApplication::get_theme_path() QString AOApplication::get_theme_variant_path() { - return get_base_path() + "themes/" + get_theme().toLower() + "/" + m_theme_variant.toLower() + "/"; + return get_base_path() + "themes/" + get_theme().toLower() + "/" + get_theme_variant() + "/"; } QString AOApplication::get_default_theme_path() diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 2e8e0b2db..9ccbf2c19 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 1 + 0 @@ -116,27 +116,37 @@ - - - - Qt::Horizontal - - - - + - + Server alerts: + + + + Theme variant: + + + + + + + Qt::Horizontal + + + + + + diff --git a/text_file_functions.cpp b/text_file_functions.cpp index 7956cad5e..74c561f20 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -15,6 +15,11 @@ QString AOApplication::get_theme() return config->theme(); } +QString AOApplication::get_theme_variant() +{ + return config->theme_variant(); +} + int AOApplication::read_blip_rate() { return config->blip_rate(); From 222e286e7f270fe721bad1f59e78cb65e72b5a89 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 31 Aug 2020 18:47:07 -0400 Subject: [PATCH 068/842] Fix theme variant being saved to theme rather than variant+Add theme change signal management to config panel --- aoapplication.cpp | 1 + aoconfigpanel.cpp | 5 +++++ aoconfigpanel.h | 3 +++ 3 files changed, 9 insertions(+) diff --git a/aoapplication.cpp b/aoapplication.cpp index d5c51688c..0c0218eb5 100644 --- a/aoapplication.cpp +++ b/aoapplication.cpp @@ -24,6 +24,7 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) config_panel = new AOConfigPanel; connect(config_panel, SIGNAL(reload_theme()), this, SLOT(on_config_reload_theme_requested())); + connect(this, SIGNAL(reload_theme()), config_panel, SLOT(on_config_reload_theme_requested())); config_panel->hide(); } diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp index a531b33ba..cf979b29a 100644 --- a/aoconfigpanel.cpp +++ b/aoconfigpanel.cpp @@ -165,3 +165,8 @@ void AOConfigPanel::on_blips_value_changed(int p_num) { w_blips_value->setText(QString::number(p_num) + "%"); } + +void AOConfigPanel::on_config_reload_theme_requested() +{ + refresh_theme_list(); +} diff --git a/aoconfigpanel.h b/aoconfigpanel.h index 4588c3007..39f90a4c1 100644 --- a/aoconfigpanel.h +++ b/aoconfigpanel.h @@ -55,6 +55,9 @@ class AOConfigPanel : public QWidget public: AOConfigPanel(QWidget *p_parent = nullptr); +public slots: + void on_config_reload_theme_requested(); + signals: void reload_theme(); From a4748102cf99bee54202c84a3f604aedb685063d Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 31 Aug 2020 18:54:08 -0400 Subject: [PATCH 069/842] Theme variant list updates on theme reload/change --- aoconfigpanel.cpp | 35 +++++++++++++++++++++++++++++++++-- aoconfigpanel.h | 1 + 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp index cf979b29a..b51404959 100644 --- a/aoconfigpanel.cpp +++ b/aoconfigpanel.cpp @@ -48,6 +48,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne // themes refresh_theme_list(); + refresh_theme_variant_list(); // input connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); @@ -75,7 +76,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(ne connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); - connect(w_theme_variant, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); + connect(w_theme_variant, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme_variant(QString))); connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, SLOT(set_server_alerts(int))); @@ -139,10 +140,39 @@ void AOConfigPanel::refresh_theme_list() w_theme->blockSignals(false); } +void AOConfigPanel::refresh_theme_variant_list() +{ + const QString p_prev_text = w_theme_variant->currentText(); + + // block signals + w_theme_variant->blockSignals(true); + w_theme_variant->clear(); + + // add empty entry indicating no variant chosen + w_theme_variant->addItem("", ""); + // themes + for (QString i_folder : QDir(QDir::currentPath() + "/base/themes/" + + m_config->theme()).entryList(QDir::Dirs)) + { + if (i_folder == "." || i_folder == "..") + continue; + w_theme_variant->addItem(i_folder, i_folder); + } + + // if the current theme does not have a variant folder for the current folder, add the variant + // to the combobox anyway. Selecting it will not do anything + if (w_theme_variant->findText(m_config->theme_variant()) == -1) + w_theme_variant->addItem(m_config->theme_variant(), m_config->theme_variant()); + // restore previous selection + w_theme_variant->setCurrentText(p_prev_text); + + // unblock + w_theme_variant->blockSignals(false); +} + void AOConfigPanel::on_reload_theme_clicked() { qDebug() << "reload theme clicked"; - refresh_theme_list(); emit reload_theme(); } @@ -169,4 +199,5 @@ void AOConfigPanel::on_blips_value_changed(int p_num) void AOConfigPanel::on_config_reload_theme_requested() { refresh_theme_list(); + refresh_theme_variant_list(); } diff --git a/aoconfigpanel.h b/aoconfigpanel.h index 39f90a4c1..ff85b1fd6 100644 --- a/aoconfigpanel.h +++ b/aoconfigpanel.h @@ -63,6 +63,7 @@ public slots: private: void refresh_theme_list(); + void refresh_theme_variant_list(); private slots: void on_reload_theme_clicked(); From 9ce05b3675d09413e94c9d121c4654d4ca159a32 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 31 Aug 2020 19:06:19 -0400 Subject: [PATCH 070/842] Clicking on a variant on the dropdown now loads it immediately --- aoapplication.cpp | 6 ++++++ aoapplication.h | 1 + aoconfig.h | 1 + courtroom_widgets.cpp | 1 + 4 files changed, 9 insertions(+) diff --git a/aoapplication.cpp b/aoapplication.cpp index 0c0218eb5..953a5a73c 100644 --- a/aoapplication.cpp +++ b/aoapplication.cpp @@ -21,6 +21,7 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) config = new AOConfig(this); connect(config, SIGNAL(theme_changed(QString)), this, SLOT(on_config_theme_changed())); + connect(config, SIGNAL(theme_variant_changed(QString)), this, SLOT(on_config_theme_variant_changed())); config_panel = new AOConfigPanel; connect(config_panel, SIGNAL(reload_theme()), this, SLOT(on_config_reload_theme_requested())); @@ -126,6 +127,11 @@ void AOApplication::on_config_reload_theme_requested() emit reload_theme(); } +void AOApplication::on_config_theme_variant_changed() +{ + emit reload_theme(); +} + void AOApplication::set_favorite_list() { favorite_list = read_serverlist_txt(); diff --git a/aoapplication.h b/aoapplication.h index 792df6ee6..5f88c7dca 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -315,6 +315,7 @@ private slots: void on_courtroom_destroyed(); void on_config_theme_changed(); void on_config_reload_theme_requested(); + void on_config_theme_variant_changed(); public slots: void server_disconnected(); diff --git a/aoconfig.h b/aoconfig.h index 49c1beba1..77f237ef1 100644 --- a/aoconfig.h +++ b/aoconfig.h @@ -73,6 +73,7 @@ public slots: void username_changed(QString); void callwords_changed(QString); void theme_changed(QString); + void theme_variant_changed(QString); void always_pre_changed(bool); void chat_tick_interval_changed(int); void log_max_lines_changed(int); diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 1db5da368..c557572bc 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -301,6 +301,7 @@ void Courtroom::connect_widgets() connect(ui_switch_area_music, SIGNAL(clicked()), this, SLOT(on_switch_area_music_clicked())); connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(set_theme(QString))); + connect(ao_config, SIGNAL(theme_variant_changed(QString)), this, SLOT(set_theme_variant(QString))); connect(ui_config_panel, SIGNAL(clicked()), this, SLOT(on_config_panel_clicked())); connect(ui_note_button, SIGNAL(clicked()), this, SLOT(on_note_button_clicked())); From 0d059c8e63a43f4ad0f8bd9096ca0aba91e23824 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 31 Aug 2020 19:09:56 -0400 Subject: [PATCH 071/842] Remove /variant OOC command --- courtroom.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index dda481151..3dd130300 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1760,18 +1760,6 @@ void Courtroom::on_ooc_return_pressed() { m_effects_player->play(ao_app->get_sfx("coinflip")); } - else if (ooc_message.startsWith("/variant")) - { - int space_location = ooc_message.indexOf(" "); - QString variant; - - if (space_location == -1) - variant = ""; - else - variant = ooc_message.mid(space_location+1); - - handle_theme_variant(variant); - } else if (ooc_message.startsWith("/tr ")) { // Timer resume From 63fdfeb34bd81553b7ec10140595216c5efb4f2f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 31 Aug 2020 20:24:03 -0400 Subject: [PATCH 072/842] Remove leftover code --- courtroom_widgets.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index c557572bc..f6eef5626 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -300,9 +300,6 @@ void Courtroom::connect_widgets() connect(ui_switch_area_music, SIGNAL(clicked()), this, SLOT(on_switch_area_music_clicked())); - connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(set_theme(QString))); - connect(ao_config, SIGNAL(theme_variant_changed(QString)), this, SLOT(set_theme_variant(QString))); - connect(ui_config_panel, SIGNAL(clicked()), this, SLOT(on_config_panel_clicked())); connect(ui_note_button, SIGNAL(clicked()), this, SLOT(on_note_button_clicked())); From 68e07770dc838ed46ae29765662af94c469df0cf Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 31 Aug 2020 21:43:45 -0400 Subject: [PATCH 073/842] Config panel is now raised to top if not part of layer file; put at 0, 0 if out of right/down border or made invisible --- courtroom_widgets.cpp | 68 ++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 1db5da368..a2b8f2947 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -526,6 +526,16 @@ void Courtroom::set_widget_layers() break; } } + // do special logic if config panel was not found in courtroom_layers. In particular, make it + // visible and raise it in front of all assets. This can help assist a theme designer who + // accidentally missed config_panel and would have become unable to reload themes had they + // closed the config panel + if (!recorded_widgets.contains("config_panel")) + { + ui_config_panel->setParent(this); + ui_config_panel->raise(); + ui_config_panel->setVisible(true); + } } void Courtroom::set_widgets() @@ -768,29 +778,23 @@ void Courtroom::set_widgets() } set_free_blocks(); - set_size_and_pos(ui_call_mod, "call_mod"); - + // Set the default values for the buttons, then determine if they should be replaced by images set_size_and_pos(ui_change_character, "change_character"); set_size_and_pos(ui_call_mod, "call_mod"); - set_size_and_pos(ui_config_panel, "config_panel"); set_size_and_pos(ui_note_button, "note_button"); - set_size_and_pos(ui_switch_area_music, "switch_area_music"); + set_size_and_pos(ui_config_panel, "config_panel"); - // Set the default values for the buttons, then try and determine if they should be replaced by images - ui_change_character->setText("Change character"); - ui_change_character->setStyleSheet(""); + ui_change_character->setText(""); + ui_call_mod->setText(""); + ui_switch_area_music->setText(""); + ui_config_panel->setText(""); + ui_note_button->setText(""); - ui_call_mod->setText("Call mod"); + ui_change_character->setStyleSheet(""); ui_call_mod->setStyleSheet(""); - - ui_switch_area_music->setText("A/M"); ui_switch_area_music->setStyleSheet(""); - - ui_config_panel->setText("Config"); ui_config_panel->setStyleSheet(""); - - ui_note_button->setText("Notes"); ui_note_button->setStyleSheet(""); if (ao_app->read_theme_ini("enable_button_images", cc_config_ini) == "true") @@ -799,24 +803,40 @@ void Courtroom::set_widgets() // set_image first tries the theme variant folder, then the theme folder, then falls back to // the default theme ui_change_character->set_image("changecharacter.png"); - if (!ui_change_character->image_path.isEmpty()) - ui_change_character->setText(""); + if (ui_change_character->image_path.isEmpty()) + ui_change_character->setText("Change Character"); ui_call_mod->set_image("callmod.png"); - if (!ui_call_mod->image_path.isEmpty()) - ui_call_mod->setText(""); + if (ui_call_mod->image_path.isEmpty()) + ui_call_mod->setText("Call Mod"); ui_switch_area_music->set_image("switch_area_music.png"); - if (!ui_switch_area_music->image_path.isEmpty()) - ui_switch_area_music->setText(""); + if (ui_switch_area_music->image_path.isEmpty()) + ui_switch_area_music->setText("A/M"); ui_config_panel->set_image("config_panel.png"); - if (!ui_config_panel->image_path.isEmpty()) - ui_config_panel->setText(""); + if (ui_config_panel->image_path.isEmpty()) + ui_config_panel->setText("Config"); ui_note_button->set_image("notebutton.png"); - if (!ui_note_button->image_path.isEmpty()) - ui_note_button->setText(""); + if (ui_note_button->image_path.isEmpty()) + ui_note_button->setText("Notes"); + } + + // The config panel has a special property. If it is displayed beyond the right or lower limit + // of the window, it will be moved to 0, 0 + // A similar behavior will occur if the button is hidden due to 'config_panel' not being found + // in courtroom_design.ini + // This is to assist with people who switch to incompatible and/or smaller themes and have the + // button disappear + if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || + !ui_config_panel->isVisible()) + { + ui_config_panel->setVisible(true); + ui_config_panel->move(0, 0); + // Moreover, if the width or height is invalid, change it to some fixed values + if (ui_config_panel->width() <= 0 || ui_config_panel->height() <= 0) + ui_config_panel->resize(64, 64); } set_size_and_pos(ui_pre, "pre"); From 7b7f6f6d5a69db0ba85049ef2e3589e79f3e5cb9 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 31 Aug 2020 21:56:38 -0400 Subject: [PATCH 074/842] Make variants be contained in their own folder within a theme (rather than folders in the theme folder) --- aoconfigpanel.cpp | 2 +- courtroom.cpp | 2 +- path_functions.cpp | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp index b51404959..365fc7c93 100644 --- a/aoconfigpanel.cpp +++ b/aoconfigpanel.cpp @@ -152,7 +152,7 @@ void AOConfigPanel::refresh_theme_variant_list() w_theme_variant->addItem("", ""); // themes for (QString i_folder : QDir(QDir::currentPath() + "/base/themes/" + - m_config->theme()).entryList(QDir::Dirs)) + m_config->theme() + "/variants/").entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; diff --git a/courtroom.cpp b/courtroom.cpp index 3dd130300..a75b970ea 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -790,7 +790,7 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) if (emote_mod == 0) m_chatmessage[EMOTE_MOD] = 1; - //handles cases 1-7 (5-7 are DRO only) + //handles cases 1-8 (5-8 are DRO only) if(objection_mod >= 1 && objection_mod <= ui_shouts.size() && ui_shouts.size() > 0) // check to prevent crashing { ui_vp_objection->play(shout_names.at(objection_mod-1), f_char, f_custom_theme); diff --git a/path_functions.cpp b/path_functions.cpp index 03c5d6515..37cf3b39f 100644 --- a/path_functions.cpp +++ b/path_functions.cpp @@ -43,7 +43,8 @@ QString AOApplication::get_theme_path() QString AOApplication::get_theme_variant_path() { - return get_base_path() + "themes/" + get_theme().toLower() + "/" + get_theme_variant() + "/"; + return get_base_path() + "themes/" + get_theme().toLower() + "/variants/" + get_theme_variant() + + "/"; } QString AOApplication::get_default_theme_path() From eae194fe07a6578c082d4194a65a8e160a5c36bc Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 5 Sep 2020 09:10:06 -0400 Subject: [PATCH 075/842] Add menu options for Purple and Pink text colors --- courtroom_widgets.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 231be7600..fd41faf0e 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -213,7 +213,11 @@ void Courtroom::create_widgets() ui_text_color->addItem("Orange"); ui_text_color->addItem("Blue"); if (ao_app->yellow_text_enabled) + { ui_text_color->addItem("Yellow"); + ui_text_color->addItem("Purple"); + ui_text_color->addItem("Pink"); + } ui_evidence_button = new AOButton(this, ao_app); From a6d92b4a753332e5e54783ef344f77dccd82c39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 5 Sep 2020 19:17:17 +0200 Subject: [PATCH 076/842] Update aoconfig.cpp --- aoconfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aoconfig.cpp b/aoconfig.cpp index 90e90bebb..016d4ed64 100644 --- a/aoconfig.cpp +++ b/aoconfig.cpp @@ -33,7 +33,7 @@ class AOConfigPrivate : public QObject int music_volume; int blips_volume; int blip_rate; - int blank_blips; + bool blank_blips; public: AOConfigPrivate() : QObject(qApp), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat) From e1c6781675fb9a869d8a1a8bf044bd452e130311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 5 Sep 2020 19:39:37 +0200 Subject: [PATCH 077/842] Update main.cpp --- main.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/main.cpp b/main.cpp index 347db528d..3d581fa25 100644 --- a/main.cpp +++ b/main.cpp @@ -21,7 +21,14 @@ int main(int argc, char *argv[]) #endif AOApplication app(argc, argv); +#if defined(Q_OS_WIN) QPluginLoader apng("imageformats/qapng.dll"); +#elif defined(Q_OS_UNIX) + QPluginLoader apng("imageformats/qapng.so"); +#elif defined(Q_OS_MAC) + QPluginLoader apng("imageformats/qapng.a"); +#endif + if (!apng.load()) { #ifdef QT_NO_DEBUG From 7ff5237682702637193ea0a6eee3ff484405121f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 5 Sep 2020 19:42:44 +0200 Subject: [PATCH 078/842] Update main.cpp --- main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index 3d581fa25..3ca9527b1 100644 --- a/main.cpp +++ b/main.cpp @@ -24,9 +24,9 @@ int main(int argc, char *argv[]) #if defined(Q_OS_WIN) QPluginLoader apng("imageformats/qapng.dll"); #elif defined(Q_OS_UNIX) - QPluginLoader apng("imageformats/qapng.so"); + QPluginLoader apng("imageformats/libqapng.so"); #elif defined(Q_OS_MAC) - QPluginLoader apng("imageformats/qapng.a"); + QPluginLoader apng("imageformats/libqapng.a"); #endif if (!apng.load()) From 0711c6ed798d79bc4dbb213d3ee5e86a06c42c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 5 Sep 2020 19:45:44 +0200 Subject: [PATCH 079/842] Update main.cpp --- main.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/main.cpp b/main.cpp index 3ca9527b1..cd6f7f893 100644 --- a/main.cpp +++ b/main.cpp @@ -21,18 +21,11 @@ int main(int argc, char *argv[]) #endif AOApplication app(argc, argv); -#if defined(Q_OS_WIN) - QPluginLoader apng("imageformats/qapng.dll"); -#elif defined(Q_OS_UNIX) - QPluginLoader apng("imageformats/libqapng.so"); -#elif defined(Q_OS_MAC) - QPluginLoader apng("imageformats/libqapng.a"); -#endif - + QPluginLoader apng("qapng"); if (!apng.load()) { #ifdef QT_NO_DEBUG - call_error(QString("APNG plugin has encountered an error: %s").arg(apng.errorString())); + call_error("APNG plugin could not be loaded."); #endif } From 6531a621d7e656170c4ce0ecb3a581e31aaf81d5 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 5 Sep 2020 21:49:13 -0400 Subject: [PATCH 080/842] Make the theme dropdown box only show folder names rather than folders and files --- aoconfigpanel.cpp | 2 +- courtroom_widgets.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp index 365fc7c93..82362777b 100644 --- a/aoconfigpanel.cpp +++ b/aoconfigpanel.cpp @@ -126,7 +126,7 @@ void AOConfigPanel::refresh_theme_list() w_theme->clear(); // themes - for (QString i_folder : QDir(QDir::currentPath() + "/base/themes").entryList()) + for (QString i_folder : QDir(QDir::currentPath() + "/base/themes").entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 231be7600..89f7708db 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -151,7 +151,7 @@ void Courtroom::create_widgets() ui_defense_bar = new AOImage(this, ao_app); ui_prosecution_bar = new AOImage(this, ao_app); - load_shouts(); // Reads from theme, deletes old shouts if needed and creates new ones + load_shouts(); // Readds from theme, deletes old shouts if needed and creates new ones ui_shout_up = new AOButton(this, ao_app); ui_shout_up->setProperty("cycle_id", 1); From bc6ddbf1f295560e244c26982ead72b633a4d02d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Mon, 7 Sep 2020 11:40:29 +0200 Subject: [PATCH 081/842] Update courtroom.cpp - Added [[fallthrough]] for default case. - Added break to white case. --- courtroom.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/courtroom.cpp b/courtroom.cpp index a75b970ea..cea930d20 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1530,11 +1530,13 @@ void Courtroom::set_text_color() ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setRgb(218, 124, 128); break; - default: + default: qDebug() << "W: undefined text color: " << m_chatmessage[TEXT_COLOR]; + [[fallthrough]]; case WHITE: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setRgb(213, 213, 213); + break; } } From 5e35051ad2b0142cf3450a399991dac64182031f Mon Sep 17 00:00:00 2001 From: Cerapter Date: Mon, 7 Sep 2020 22:57:51 +0200 Subject: [PATCH 082/842] Adjust formatting to be the same as Vanilla --- .clang-format | 2 + aoabstractplayer.cpp | 8 +- aoabstractplayer.h | 5 +- aoapplication.cpp | 197 +++-- aoapplication.h | 420 +++++----- aobasshandle.cpp | 50 +- aobasshandle.h | 15 +- aoblipplayer.cpp | 9 +- aoblipplayer.h | 7 +- aobutton.cpp | 15 +- aobutton.h | 3 +- aocharbutton.cpp | 79 +- aocharbutton.h | 25 +- aocharmovie.cpp | 245 +++--- aocharmovie.h | 39 +- aoconfig.cpp | 619 +++++++-------- aoconfig.h | 147 ++-- aoconfigpanel.cpp | 363 +++++---- aoconfigpanel.h | 95 ++- aoemotebutton.cpp | 40 +- aoemotebutton.h | 7 +- aoevidencebutton.cpp | 23 +- aoevidencebutton.h | 8 +- aoevidencedescription.cpp | 11 +- aoevidencedescription.h | 4 +- aoevidencedisplay.cpp | 34 +- aoevidencedisplay.h | 7 +- aoexception.cpp | 6 +- aoexception.h | 13 +- aoguiloader.cpp | 26 +- aoguiloader.h | 12 +- aoimage.h | 5 +- aolabel.h | 4 +- aolineedit.cpp | 5 +- aolineedit.h | 5 +- aomovie.cpp | 55 +- aomovie.h | 3 +- aomusicplayer.cpp | 42 +- aomusicplayer.h | 15 +- aonotearea.cpp | 56 +- aonotearea.h | 10 +- aonotepad.cpp | 7 +- aonotepad.h | 5 +- aonotepicker.cpp | 41 +- aonotepicker.h | 7 +- aopacket.cpp | 24 +- aopacket.h | 7 +- aopixmap.cpp | 23 +- aopixmap.h | 13 +- aoscene.cpp | 55 +- aoscene.h | 15 +- aosfxplayer.cpp | 29 +- aosfxplayer.h | 11 +- aoshoutplayer.cpp | 58 +- aoshoutplayer.h | 11 +- aotextarea.cpp | 44 +- aotextarea.h | 6 +- aotimer.cpp | 86 +- aotimer.h | 29 +- audio_functions.cpp | 37 +- bass.h | 1568 +++++++++++++++++++------------------ charselect.cpp | 260 +++--- courtroom.cpp | 1297 +++++++++++++++--------------- courtroom.h | 1104 +++++++++++++------------- courtroom_widgets.cpp | 1161 +++++++++++++-------------- datatypes.h | 165 ++-- debug_functions.cpp | 6 +- discord-rpc.h | 64 +- discord_register.h | 26 +- discord_rich_presence.cpp | 62 +- discord_rich_presence.h | 13 +- discord_rpc.h | 64 +- emotes.cpp | 124 ++- encryption_functions.cpp | 25 +- evidence.cpp | 100 +-- file_functions.cpp | 7 +- hardware_functions.cpp | 18 +- hex_functions.cpp | 136 ++-- hex_functions.h | 11 +- lobby.cpp | 561 ++++++------- lobby.h | 117 ++- main.cpp | 27 +- misc_functions.cpp | 4 +- networkmanager.cpp | 142 ++-- networkmanager.h | 11 +- packet_distribution.cpp | 428 +++++----- path_functions.cpp | 59 +- text_file_functions.cpp | 337 ++++---- 88 files changed, 5477 insertions(+), 5662 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..ddf0a58f1 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle : LLVM +BreakBeforeBraces : Stroustrup diff --git a/aoabstractplayer.cpp b/aoabstractplayer.cpp index 59b5b0fe6..1207446fd 100644 --- a/aoabstractplayer.cpp +++ b/aoabstractplayer.cpp @@ -1,14 +1,12 @@ #include "aoabstractplayer.h" AOAbstractPlayer::AOAbstractPlayer(QObject *p_parent, AOApplication *p_ao_app) - : QObject(p_parent), ao_app(p_ao_app) -{} - -int AOAbstractPlayer::get_volume() + : QObject(p_parent), ao_app(p_ao_app) { - return m_volume; } +int AOAbstractPlayer::get_volume() { return m_volume; } + void AOAbstractPlayer::set_volume(int p_volume) { m_volume = p_volume; diff --git a/aoabstractplayer.h b/aoabstractplayer.h index 925fba738..a9856ff1c 100644 --- a/aoabstractplayer.h +++ b/aoabstractplayer.h @@ -7,8 +7,7 @@ #include "aoapplication.h" #include "aobasshandle.h" -class AOAbstractPlayer : public QObject -{ +class AOAbstractPlayer : public QObject { Q_OBJECT public: @@ -25,7 +24,7 @@ public slots: void new_volume(int p_volume); protected: - AOApplication* ao_app = nullptr; + AOApplication *ao_app = nullptr; private: int m_volume = 0; diff --git a/aoapplication.cpp b/aoapplication.cpp index 953a5a73c..f90eb59bf 100644 --- a/aoapplication.cpp +++ b/aoapplication.cpp @@ -1,32 +1,37 @@ #include "aoapplication.h" -#include "lobby.h" #include "courtroom.h" -#include "networkmanager.h" #include "debug_functions.h" +#include "lobby.h" +#include "networkmanager.h" #include "aoconfig.h" #include "aoconfigpanel.h" #include -#include #include +#include AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) { - discord = new AttorneyOnline::Discord(); - - net_manager = new NetworkManager(this); - connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), SLOT(ms_connect_finished(bool, bool))); - - config = new AOConfig(this); - connect(config, SIGNAL(theme_changed(QString)), this, SLOT(on_config_theme_changed())); - connect(config, SIGNAL(theme_variant_changed(QString)), this, SLOT(on_config_theme_variant_changed())); - - config_panel = new AOConfigPanel; - connect(config_panel, SIGNAL(reload_theme()), this, SLOT(on_config_reload_theme_requested())); - connect(this, SIGNAL(reload_theme()), config_panel, SLOT(on_config_reload_theme_requested())); - config_panel->hide(); + discord = new AttorneyOnline::Discord(); + + net_manager = new NetworkManager(this); + connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), + SLOT(ms_connect_finished(bool, bool))); + + config = new AOConfig(this); + connect(config, SIGNAL(theme_changed(QString)), this, + SLOT(on_config_theme_changed())); + connect(config, SIGNAL(theme_variant_changed(QString)), this, + SLOT(on_config_theme_variant_changed())); + + config_panel = new AOConfigPanel; + connect(config_panel, SIGNAL(reload_theme()), this, + SLOT(on_config_reload_theme_requested())); + connect(this, SIGNAL(reload_theme()), config_panel, + SLOT(on_config_reload_theme_requested())); + config_panel->hide(); } AOApplication::~AOApplication() @@ -39,8 +44,7 @@ AOApplication::~AOApplication() void AOApplication::construct_lobby() { - if (lobby_constructed) - { + if (lobby_constructed) { qDebug() << "W: lobby was attempted constructed when it already exists"; return; } @@ -49,8 +53,8 @@ void AOApplication::construct_lobby() lobby_constructed = true; QRect screenGeometry = QApplication::desktop()->screenGeometry(); - int x = (screenGeometry.width()-w_lobby->width()) / 2; - int y = (screenGeometry.height()-w_lobby->height()) / 2; + int x = (screenGeometry.width() - w_lobby->width()) / 2; + int y = (screenGeometry.height() - w_lobby->height()) / 2; w_lobby->move(x, y); discord->state_lobby(); @@ -60,8 +64,7 @@ void AOApplication::construct_lobby() void AOApplication::destruct_lobby() { - if(!lobby_constructed) - { + if (!lobby_constructed) { qDebug() << "W: lobby was attempted destructed when it did not exist"; return; } @@ -72,69 +75,59 @@ void AOApplication::destruct_lobby() void AOApplication::construct_courtroom() { - if (courtroom_constructed) - { + if (courtroom_constructed) { qDebug() << "W: courtroom was attempted constructed when it already exists"; return; } w_courtroom = new Courtroom(this); connect(w_courtroom, SIGNAL(closing()), this, SLOT(on_courtroom_closing())); - connect(w_courtroom, SIGNAL(destroyed()), this, SLOT(on_courtroom_destroyed())); + connect(w_courtroom, SIGNAL(destroyed()), this, + SLOT(on_courtroom_destroyed())); courtroom_constructed = true; QRect screenGeometry = QApplication::desktop()->screenGeometry(); - int x = (screenGeometry.width()-w_courtroom->width()) / 2; - int y = (screenGeometry.height()-w_courtroom->height()) / 2; + int x = (screenGeometry.width() - w_courtroom->width()) / 2; + int y = (screenGeometry.height() - w_courtroom->height()) / 2; w_courtroom->move(x, y); } void AOApplication::destruct_courtroom() { - // destruct courtroom - if (courtroom_constructed) - { - delete w_courtroom; - courtroom_constructed = false; - } - else - { - qDebug() << "W: courtroom was attempted destructed when it did not exist"; - } + // destruct courtroom + if (courtroom_constructed) { + delete w_courtroom; + courtroom_constructed = false; + } + else { + qDebug() << "W: courtroom was attempted destructed when it did not exist"; + } - // gracefully close our connection to the current server - net_manager->disconnect_from_server(); + // gracefully close our connection to the current server + net_manager->disconnect_from_server(); } QString AOApplication::get_version_string() { - return QString::number(RELEASE) + "." + QString::number(MAJOR_VERSION) + "." + QString::number(MINOR_VERSION); + return QString::number(RELEASE) + "." + QString::number(MAJOR_VERSION) + "." + + QString::number(MINOR_VERSION); } void AOApplication::set_theme_variant(QString p_variant) { - config->set_theme_variant(p_variant); - emit reload_theme(); + config->set_theme_variant(p_variant); + emit reload_theme(); } -void AOApplication::on_config_theme_changed() -{ - emit reload_theme(); -} +void AOApplication::on_config_theme_changed() { emit reload_theme(); } -void AOApplication::on_config_reload_theme_requested() -{ - emit reload_theme(); -} +void AOApplication::on_config_reload_theme_requested() { emit reload_theme(); } -void AOApplication::on_config_theme_variant_changed() -{ - emit reload_theme(); -} +void AOApplication::on_config_theme_variant_changed() { emit reload_theme(); } void AOApplication::set_favorite_list() { - favorite_list = read_serverlist_txt(); + favorite_list = read_serverlist_txt(); } QString AOApplication::get_current_char() @@ -147,67 +140,63 @@ QString AOApplication::get_current_char() void AOApplication::toggle_config_panel() { - config_panel->setVisible(!config_panel->isVisible()); - if (config_panel->isVisible()) - { - QRect screenGeometry = QApplication::desktop()->screenGeometry(); - int x = (screenGeometry.width()-config_panel->width()) / 2; - int y = (screenGeometry.height()-config_panel->height()) / 2; - config_panel->setFocus(); - config_panel->raise(); - config_panel->move(x, y); - } + config_panel->setVisible(!config_panel->isVisible()); + if (config_panel->isVisible()) { + QRect screenGeometry = QApplication::desktop()->screenGeometry(); + int x = (screenGeometry.width() - config_panel->width()) / 2; + int y = (screenGeometry.height() - config_panel->height()) / 2; + config_panel->setFocus(); + config_panel->raise(); + config_panel->move(x, y); + } } bool AOApplication::get_always_pre_enabled() { - return config->always_pre_enabled(); + return config->always_pre_enabled(); } bool AOApplication::get_first_person_enabled() { - return config->get_bool("first_person", false); + return config->get_bool("first_person", false); } bool AOApplication::get_server_alerts_enabled() { - return config->server_alerts_enabled(); + return config->server_alerts_enabled(); } bool AOApplication::get_chatlog_scrolldown() { - return config->log_goes_downward_enabled(); + return config->log_goes_downward_enabled(); } -int AOApplication::get_chatlog_max_lines() -{ - return config->log_max_lines(); -} +int AOApplication::get_chatlog_max_lines() { return config->log_max_lines(); } int AOApplication::get_chat_tick_interval() { - return config->chat_tick_interval(); + return config->chat_tick_interval(); } bool AOApplication::get_chatlog_newline() { - return config->log_uses_newline_enabled(); + return config->log_uses_newline_enabled(); } bool AOApplication::get_enable_logging_enabled() { - return config->log_is_recording_enabled(); + return config->log_is_recording_enabled(); } bool AOApplication::get_music_change_log_enabled() { - return config->log_music_enabled(); + return config->log_music_enabled(); } void AOApplication::add_favorite_server(int p_server) { - if (p_server < 0 || p_server >= server_list.size()) - return; + if (p_server < 0 || p_server >= server_list.size()) + return; server_type fav_server = server_list.at(p_server); @@ -220,11 +209,11 @@ void AOApplication::add_favorite_server(int p_server) void AOApplication::server_disconnected() { - if (!courtroom_constructed) - return; - call_notice("Disconnected from server."); - construct_lobby(); - destruct_courtroom(); + if (!courtroom_constructed) + return; + call_notice("Disconnected from server."); + construct_lobby(); + destruct_courtroom(); } void AOApplication::loading_cancelled() @@ -236,39 +225,33 @@ void AOApplication::loading_cancelled() void AOApplication::ms_connect_finished(bool connected, bool will_retry) { - if (connected) - { + if (connected) { AOPacket *f_packet = new AOPacket("ALL#%"); send_ms_packet(f_packet); } - else - { - if (!lobby_constructed) - { + else { + if (!lobby_constructed) { return; } - else if (will_retry) - { - w_lobby->append_error("Error connecting to master server. Will try again in " - + QString::number(net_manager->ms_reconnect_delay_ms / 1000.f) + " seconds."); + else if (will_retry) { + w_lobby->append_error( + "Error connecting to master server. Will try again in " + + QString::number(net_manager->ms_reconnect_delay_ms / 1000.f) + + " seconds."); } - else - { + else { call_error("There was an error connecting to the master server.\n" - "We deploy multiple master servers to mitigate any possible downtime, " - "but the client appears to have exhausted all possible methods of finding " + "We deploy multiple master servers to mitigate any possible " + "downtime, " + "but the client appears to have exhausted all possible " + "methods of finding " "and connecting to one.\n" - "Please check your Internet connection and firewall, and please try again."); + "Please check your Internet connection and firewall, and " + "please try again."); } } } -void AOApplication::on_courtroom_closing() -{ - config_panel->hide(); -} +void AOApplication::on_courtroom_closing() { config_panel->hide(); } -void AOApplication::on_courtroom_destroyed() -{ - config_panel->hide(); -} +void AOApplication::on_courtroom_destroyed() { config_panel->hide(); } diff --git a/aoapplication.h b/aoapplication.h index 5f88c7dca..c455055cf 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -15,311 +15,315 @@ class Courtroom; class AOConfig; class AOConfigPanel; -class AOApplication : public QApplication -{ - Q_OBJECT +class AOApplication : public QApplication { + Q_OBJECT public: - AOApplication(int &argc, char **argv); - ~AOApplication(); + AOApplication(int &argc, char **argv); + ~AOApplication(); - NetworkManager *net_manager = nullptr; - Lobby *w_lobby = nullptr; - Courtroom *w_courtroom = nullptr; - AttorneyOnline::Discord *discord = nullptr; - AOConfig *config = nullptr; - AOConfigPanel *config_panel = nullptr; + NetworkManager *net_manager = nullptr; + Lobby *w_lobby = nullptr; + Courtroom *w_courtroom = nullptr; + AttorneyOnline::Discord *discord = nullptr; + AOConfig *config = nullptr; + AOConfigPanel *config_panel = nullptr; - bool lobby_constructed = false; - bool courtroom_constructed = false; + bool lobby_constructed = false; + bool courtroom_constructed = false; - void construct_lobby(); - void destruct_lobby(); + void construct_lobby(); + void destruct_lobby(); - void construct_courtroom(); - void destruct_courtroom(); + void construct_courtroom(); + void destruct_courtroom(); - void ms_packet_received(AOPacket *p_packet); - void server_packet_received(AOPacket *p_packet); + void ms_packet_received(AOPacket *p_packet); + void server_packet_received(AOPacket *p_packet); - void send_ms_packet(AOPacket *p_packet); - void send_server_packet(AOPacket *p_packet, bool encoded = true); + void send_ms_packet(AOPacket *p_packet); + void send_server_packet(AOPacket *p_packet, bool encoded = true); - /////////////////server metadata////////////////// + /////////////////server metadata////////////////// - unsigned int s_decryptor = 5; - bool encryption_needed = true; + unsigned int s_decryptor = 5; + bool encryption_needed = true; - bool yellow_text_enabled = false; - bool prezoom_enabled = false; - bool flipping_enabled = false; - bool custom_objection_enabled = false; - bool improved_loading_enabled = false; - bool desk_mod_enabled = false; - bool evidence_enabled = false; + bool yellow_text_enabled = false; + bool prezoom_enabled = false; + bool flipping_enabled = false; + bool custom_objection_enabled = false; + bool improved_loading_enabled = false; + bool desk_mod_enabled = false; + bool evidence_enabled = false; - ///////////////loading info/////////////////// + ///////////////loading info/////////////////// - //player number, it's hardly used but might be needed for some old servers - int s_pv = 0; + // player number, it's hardly used but might be needed for some old servers + int s_pv = 0; - QString server_software = ""; + QString server_software = ""; - int char_list_size = 0; - int loaded_chars = 0; - int evidence_list_size = 0; - int loaded_evidence = 0; - int music_list_size = 0; - int loaded_music = 0; + int char_list_size = 0; + int loaded_chars = 0; + int evidence_list_size = 0; + int loaded_evidence = 0; + int music_list_size = 0; + int loaded_music = 0; - bool courtroom_loaded = false; + bool courtroom_loaded = false; - //////////////////versioning/////////////// + //////////////////versioning/////////////// - int get_release() { return RELEASE; } - int get_major_version() { return MAJOR_VERSION; } - int get_minor_version() { return MINOR_VERSION; } - QString get_version_string(); + int get_release() { return RELEASE; } + int get_major_version() { return MAJOR_VERSION; } + int get_minor_version() { return MINOR_VERSION; } + QString get_version_string(); - /////////////////////////////////////////// + /////////////////////////////////////////// - void set_favorite_list(); - QVector &get_favorite_list() { return favorite_list; } - void add_favorite_server(int p_server); + void set_favorite_list(); + QVector &get_favorite_list() { return favorite_list; } + void add_favorite_server(int p_server); - void set_server_list(); - QVector &get_server_list() { return server_list; } + void set_server_list(); + QVector &get_server_list() { return server_list; } - //reads the theme from config.ini and sets it accordingly - void set_theme_name(QString p_name); + // reads the theme from config.ini and sets it accordingly + void set_theme_name(QString p_name); - //Returns the character the player has currently selected - QString get_current_char(); + // Returns the character the player has currently selected + QString get_current_char(); - //implementation in path_functions.cpp - QString get_base_path(); - QString get_data_path(); - QString get_theme_path(); - QString get_theme_variant_path(); - QString get_default_theme_path(); - QString get_character_path(QString p_character); - QString get_demothings_path(); - QString get_sounds_path(); - QString get_music_path(QString p_song); - QString get_background_path(); - QString get_default_background_path(); - QString get_evidence_path(); + // implementation in path_functions.cpp + QString get_base_path(); + QString get_data_path(); + QString get_theme_path(); + QString get_theme_variant_path(); + QString get_default_theme_path(); + QString get_character_path(QString p_character); + QString get_demothings_path(); + QString get_sounds_path(); + QString get_music_path(QString p_song); + QString get_background_path(); + QString get_default_background_path(); + QString get_evidence_path(); - ////// Functions for accessing the config panel ////// + ////// Functions for accessing the config panel ////// - void toggle_config_panel(); + void toggle_config_panel(); - ////// Functions for reading and writing files ////// - // Implementations file_functions.cpp + ////// Functions for reading and writing files ////// + // Implementations file_functions.cpp - //Returns text from note file - QString read_note(QString filename); + // Returns text from note file + QString read_note(QString filename); - //Reads the theme from config.ini and loads it into the current_theme variable - QString get_theme(); + // Reads the theme from config.ini and loads it into the current_theme + // variable + QString get_theme(); - //Reads the theme variant from config.ini and loads it into the current theme variant variable - QString get_theme_variant(); + // Reads the theme variant from config.ini and loads it into the current theme + // variant variable + QString get_theme_variant(); - //Returns the blip rate from config.ini - int read_blip_rate(); + // Returns the blip rate from config.ini + int read_blip_rate(); - // returns whatever we want newlines or ':' to be appended in front of names - // in the ic chat log - bool read_chatlog_newline(); + // returns whatever we want newlines or ':' to be appended in front of names + // in the ic chat log + bool read_chatlog_newline(); - // returns the user name - QString get_username(); + // returns the user name + QString get_username(); - // returns a list of call words - QStringList get_callwords(); + // returns a list of call words + QStringList get_callwords(); - // returns whatever preanimations should always play or not - bool get_always_pre_enabled(); + // returns whatever preanimations should always play or not + bool get_always_pre_enabled(); - // returns whatever the client should simulate first person dialog - bool get_first_person_enabled(); + // returns whatever the client should simulate first person dialog + bool get_first_person_enabled(); - // returns whether server alerts (ones that trigger a client alert other than callwords) - // should actually tigger a server alert or not - bool get_server_alerts_enabled(); + // returns whether server alerts (ones that trigger a client alert other than + // callwords) should actually tigger a server alert or not + bool get_server_alerts_enabled(); - // returns if chatlog goes downward - bool get_chatlog_scrolldown(); + // returns if chatlog goes downward + bool get_chatlog_scrolldown(); - int get_chatlog_max_lines(); + int get_chatlog_max_lines(); - int get_chat_tick_interval(); + int get_chat_tick_interval(); - bool get_chatlog_newline(); + bool get_chatlog_newline(); - bool get_enable_logging_enabled(); + bool get_enable_logging_enabled(); - //Returns the value of default_sfx in config.ini - int get_default_sfx(); + // Returns the value of default_sfx in config.ini + int get_default_sfx(); - //Returns the value of default_music in config.ini - int get_default_music(); + // Returns the value of default_music in config.ini + int get_default_music(); - // returns whatever music is logged within the chatlog - bool get_music_change_log_enabled(); + // returns whatever music is logged within the chatlog + bool get_music_change_log_enabled(); - //Returns the value of default_blip in config.ini - int get_default_blip(); + // Returns the value of default_blip in config.ini + int get_default_blip(); - //Returns true if blank blips is enabled in config.ini and false otherwise - bool get_blank_blip(); + // Returns true if blank blips is enabled in config.ini and false otherwise + bool get_blank_blip(); - // TODO document what this does - QStringList get_sfx_list(); + // TODO document what this does + QStringList get_sfx_list(); - //Appends the argument string to serverlist.txt - void write_to_serverlist_txt(QString p_line); + // Appends the argument string to serverlist.txt + void write_to_serverlist_txt(QString p_line); - //Writes to note file - void write_note(QString p_text, QString filename); + // Writes to note file + void write_note(QString p_text, QString filename); - //appends to note file - void append_note(QString p_line, QString filename); + // appends to note file + void append_note(QString p_line, QString filename); - //Overwrites config.ini with new theme - void write_theme(QString theme); + // Overwrites config.ini with new theme + void write_theme(QString theme); - //Set the theme variant - void set_theme_variant(QString m_theme_variant); + // Set the theme variant + void set_theme_variant(QString m_theme_variant); - //Returns the contents of serverlist.txt - QVector read_serverlist_txt(); + // Returns the contents of serverlist.txt + QVector read_serverlist_txt(); - //Returns the value of p_identifier in the design.ini file in p_design_path - QString read_design_ini(QString p_identifier, QString p_design_path); + // Returns the value of p_identifier in the design.ini file in p_design_path + QString read_design_ini(QString p_identifier, QString p_design_path); - //Returns the value of p_identifier from p_file in either a theme variant subfolder, a theme folder, or default theme folder - QString read_theme_ini(QString p_identifier, QString p_file); + // Returns the value of p_identifier from p_file in either a theme variant + // subfolder, a theme folder, or default theme folder + QString read_theme_ini(QString p_identifier, QString p_file); - //Helper function for returning an int in a file inside of the theme folder - int get_design_ini_value(QString p_identifier, QString p_design_file); + // Helper function for returning an int in a file inside of the theme folder + int get_design_ini_value(QString p_identifier, QString p_design_file); - //Returns the coordinates of widget with p_identifier from p_file - QPoint get_button_spacing(QString p_identifier, QString p_file); + // Returns the coordinates of widget with p_identifier from p_file + QPoint get_button_spacing(QString p_identifier, QString p_file); - //Returns the dimensions of widget with specified identifier from p_file - pos_size_type get_element_dimensions(QString p_identifier, QString p_file); + // Returns the dimensions of widget with specified identifier from p_file + pos_size_type get_element_dimensions(QString p_identifier, QString p_file); - //Returns the value of font property p_identifier from p_file - int get_font_property(QString p_identifier, QString p_file); + // Returns the value of font property p_identifier from p_file + int get_font_property(QString p_identifier, QString p_file); - //Returns the name of the font with p_identifier from p_file - QString get_font_name(QString p_identifier, QString p_file); + // Returns the name of the font with p_identifier from p_file + QString get_font_name(QString p_identifier, QString p_file); - //Returns the color with p_identifier from p_file - QColor get_color(QString p_identifier, QString p_file); + // Returns the color with p_identifier from p_file + QColor get_color(QString p_identifier, QString p_file); - //Returns the sfx with p_identifier from sounds.ini in the current theme path - QString get_sfx(QString p_identifier); + // Returns the sfx with p_identifier from sounds.ini in the current theme path + QString get_sfx(QString p_identifier); - //Returns the value of p_search_line within target_tag and terminator_tag - QString read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag); + // Returns the value of p_search_line within target_tag and terminator_tag + QString read_char_ini(QString p_char, QString p_search_line, + QString target_tag, QString terminator_tag); - //Returns the text between target_tag and terminator_tag in p_file - QString get_stylesheet(QString target_tag, QString p_file); + // Returns the text between target_tag and terminator_tag in p_file + QString get_stylesheet(QString target_tag, QString p_file); - //Returns string list (characters, color) from p_file - QVector get_highlight_color(); + // Returns string list (characters, color) from p_file + QVector get_highlight_color(); - //Returns special button on cc_config according to index - QString get_spbutton(QString p_tag, int index); + // Returns special button on cc_config according to index + QString get_spbutton(QString p_tag, int index); - //Returns effect on cc_config according to index - QStringList get_effect(int index); + // Returns effect on cc_config according to index + QStringList get_effect(int index); - //Returns wtce on cc_config according to index - QStringList get_wtce(int index); + // Returns wtce on cc_config according to index + QStringList get_wtce(int index); - //Returns the side of the p_char character from that characters ini file - QString get_char_side(QString p_char); + // Returns the side of the p_char character from that characters ini file + QString get_char_side(QString p_char); - //Returns the showname from the ini of p_char - QString get_showname(QString p_char); + // Returns the showname from the ini of p_char + QString get_showname(QString p_char); - //Returns showname from showname.ini - QString read_showname(QString p_char); + // Returns showname from showname.ini + QString read_showname(QString p_char); - //Returns the value of chat from the specific p_char's ini file - QString get_chat(QString p_char); + // Returns the value of chat from the specific p_char's ini file + QString get_chat(QString p_char); - //Returns the value of shouts from the specified p_char's ini file - QString get_char_shouts(QString p_char); + // Returns the value of shouts from the specified p_char's ini file + QString get_char_shouts(QString p_char); - //Not in use - int get_text_delay(QString p_char, QString p_emote); + // Not in use + int get_text_delay(QString p_char, QString p_emote); - //Returns the name of p_char - QString get_char_name(QString p_char); + // Returns the name of p_char + QString get_char_name(QString p_char); - //Returns the total amount of emotes of p_char - int get_emote_number(QString p_char); + // Returns the total amount of emotes of p_char + int get_emote_number(QString p_char); - //Returns the emote comment of p_char's p_emote - QString get_emote_comment(QString p_char, int p_emote); + // Returns the emote comment of p_char's p_emote + QString get_emote_comment(QString p_char, int p_emote); - //Returns the base name of p_char's p_emote - QString get_emote(QString p_char, int p_emote); + // Returns the base name of p_char's p_emote + QString get_emote(QString p_char, int p_emote); - //Returns the preanimation name of p_char's p_emote - QString get_pre_emote(QString p_char, int p_emote); + // Returns the preanimation name of p_char's p_emote + QString get_pre_emote(QString p_char, int p_emote); - //Returns x,y offset for effect p_effect - QStringList get_effect_offset(QString p_char, int p_effect); + // Returns x,y offset for effect p_effect + QStringList get_effect_offset(QString p_char, int p_effect); - //Returns overlay at p_effect in char_path/overlay - QStringList get_overlay(QString p_char, int p_effect); + // Returns overlay at p_effect in char_path/overlay + QStringList get_overlay(QString p_char, int p_effect); - //Returns the sfx of p_char's p_emote - QString get_sfx_name(QString p_char, int p_emote); + // Returns the sfx of p_char's p_emote + QString get_sfx_name(QString p_char, int p_emote); - //Not in use - int get_sfx_delay(QString p_char, int p_emote); + // Not in use + int get_sfx_delay(QString p_char, int p_emote); - //Returns the modifier for p_char's p_emote - int get_emote_mod(QString p_char, int p_emote); + // Returns the modifier for p_char's p_emote + int get_emote_mod(QString p_char, int p_emote); - //Returns the desk modifier for p_char's p_emote - int get_desk_mod(QString p_char, int p_emote); + // Returns the desk modifier for p_char's p_emote + int get_desk_mod(QString p_char, int p_emote); - //Returns p_char's gender - QString get_gender(QString p_char); + // Returns p_char's gender + QString get_gender(QString p_char); - //Get the location of p_image, which is either in a theme variant subfolder, a theme folder, or default theme folder - QString get_image_path(QString p_image); + // Get the location of p_image, which is either in a theme variant subfolder, + // a theme folder, or default theme folder + QString get_image_path(QString p_image); signals: - void reload_theme(); + void reload_theme(); private: - const int RELEASE = 2; - const int MAJOR_VERSION = 4; - const int MINOR_VERSION = 8; + const int RELEASE = 2; + const int MAJOR_VERSION = 4; + const int MINOR_VERSION = 8; - QVector server_list; - QVector favorite_list; + QVector server_list; + QVector favorite_list; private slots: - void ms_connect_finished(bool connected, bool will_retry); - void on_courtroom_closing(); - void on_courtroom_destroyed(); - void on_config_theme_changed(); - void on_config_reload_theme_requested(); - void on_config_theme_variant_changed(); + void ms_connect_finished(bool connected, bool will_retry); + void on_courtroom_closing(); + void on_courtroom_destroyed(); + void on_config_theme_changed(); + void on_config_reload_theme_requested(); + void on_config_theme_variant_changed(); public slots: - void server_disconnected(); - void loading_cancelled(); + void server_disconnected(); + void loading_cancelled(); }; #endif // AOAPPLICATION_H diff --git a/aobasshandle.cpp b/aobasshandle.cpp index 996d01679..db380867e 100644 --- a/aobasshandle.cpp +++ b/aobasshandle.cpp @@ -1,25 +1,22 @@ #include "aobasshandle.h" -AOBassHandle::AOBassHandle(QObject *p_parent) - : QObject(p_parent) -{} +AOBassHandle::AOBassHandle(QObject *p_parent) : QObject(p_parent) {} -AOBassHandle::AOBassHandle(QString p_file, bool p_suicide, QObject *p_parent) noexcept(false) - : QObject(p_parent) +AOBassHandle::AOBassHandle(QString p_file, bool p_suicide, + QObject *p_parent) noexcept(false) + : QObject(p_parent) { set_file(p_file, p_suicide); } AOBassHandle::~AOBassHandle() { - if (m_handle && m_sync) - { + if (m_handle && m_sync) { BASS_ChannelRemoveSync(m_handle, m_sync); } // nothing will go wrong if the handle isn't initialized, I promise...! - if (m_handle) - { + if (m_handle) { BASS_StreamFree(m_handle); } } @@ -35,10 +32,7 @@ void AOBassHandle::suicide() delete this; } -QString AOBassHandle::get_file() -{ - return m_file; -} +QString AOBassHandle::get_file() { return m_file; } void AOBassHandle::set_file(QString p_file, bool p_suicide) noexcept(false) { @@ -48,13 +42,15 @@ void AOBassHandle::set_file(QString p_file, bool p_suicide) noexcept(false) m_file = p_file; // create a handle based on the file - m_handle = BASS_StreamCreateFile(FALSE, m_file.utf16(), 0, 0, BASS_UNICODE|BASS_ASYNCFILE); + m_handle = BASS_StreamCreateFile(FALSE, m_file.utf16(), 0, 0, + BASS_UNICODE | BASS_ASYNCFILE); if (!m_handle) - throw AOException(QString("%1 could not be initialized to play").arg(p_file)); + throw AOException( + QString("%1 could not be initialized to play").arg(p_file)); - m_sync = BASS_ChannelSetSync(m_handle, BASS_SYNC_END, 0, &AOBassHandle::static_sync, this); - if (!m_sync) - { + m_sync = BASS_ChannelSetSync(m_handle, BASS_SYNC_END, 0, + &AOBassHandle::static_sync, this); + if (!m_sync) { // free stream since we can't sync BASS_StreamFree(m_handle); @@ -68,23 +64,17 @@ void AOBassHandle::set_file(QString p_file, bool p_suicide) noexcept(false) void AOBassHandle::set_volume(int p_volume) { - BASS_ChannelSetAttribute(m_handle, BASS_ATTRIB_VOL, p_volume/100.0f); + BASS_ChannelSetAttribute(m_handle, BASS_ATTRIB_VOL, p_volume / 100.0f); } -void AOBassHandle::play() -{ - BASS_ChannelPlay(m_handle, FALSE); -} +void AOBassHandle::play() { BASS_ChannelPlay(m_handle, FALSE); } -void AOBassHandle::stop() -{ - BASS_ChannelStop(m_handle); -} +void AOBassHandle::stop() { BASS_ChannelStop(m_handle); } -void CALLBACK AOBassHandle::static_sync(HSYNC handle, DWORD channel, DWORD data, void *user) +void CALLBACK AOBassHandle::static_sync(HSYNC handle, DWORD channel, DWORD data, + void *user) { - if (auto self = static_cast(user)) - { + if (auto self = static_cast(user)) { self->sync(data); } } diff --git a/aobasshandle.h b/aobasshandle.h index d461c3f23..4471e9a3a 100644 --- a/aobasshandle.h +++ b/aobasshandle.h @@ -13,20 +13,21 @@ * The class may destroy itself once the audio provided is done playing. */ -class AOBassHandle : public QObject -{ +class AOBassHandle : public QObject { Q_OBJECT public: AOBassHandle(QObject *p_parent = nullptr); - AOBassHandle(QString p_file, bool p_suicide, QObject *p_parent = nullptr) noexcept(false); + AOBassHandle(QString p_file, bool p_suicide, + QObject *p_parent = nullptr) noexcept(false); ~AOBassHandle(); QString get_file(); void set_file(QString p_file, bool p_suicide = false) noexcept(false); // static - static void CALLBACK static_sync(HSYNC handle, DWORD channel, DWORD data, void *user); + static void CALLBACK static_sync(HSYNC handle, DWORD channel, DWORD data, + void *user); public slots: void play(); @@ -40,9 +41,9 @@ public slots: private: QString m_file; - HSTREAM m_handle = 0; - HSYNC m_sync = 0; - bool m_suicide = false; + HSTREAM m_handle = 0; + HSYNC m_sync = 0; + bool m_suicide = false; void sync(DWORD data); diff --git a/aoblipplayer.cpp b/aoblipplayer.cpp index 9e7d0e5d9..ebaa06461 100644 --- a/aoblipplayer.cpp +++ b/aoblipplayer.cpp @@ -10,11 +10,11 @@ void AOBlipPlayer::set_blips(QString p_sfx) { QString f_path = ao_app->get_sounds_path() + p_sfx.toLower(); - for (int n_stream = 0 ; n_stream < BLIP_COUNT ; ++n_stream) - { + for (int n_stream = 0; n_stream < BLIP_COUNT; ++n_stream) { BASS_StreamFree(m_stream_list[n_stream]); - m_stream_list[n_stream] = BASS_StreamCreateFile(FALSE, f_path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); + m_stream_list[n_stream] = BASS_StreamCreateFile( + FALSE, f_path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); } set_volume(m_volume); @@ -38,8 +38,7 @@ void AOBlipPlayer::set_volume(int p_value) float volume = p_value / 100.0f; - for (int n_stream = 0 ; n_stream < BLIP_COUNT ; ++n_stream) - { + for (int n_stream = 0; n_stream < BLIP_COUNT; ++n_stream) { BASS_ChannelSetAttribute(m_stream_list[n_stream], BASS_ATTRIB_VOL, volume); } } diff --git a/aoblipplayer.h b/aoblipplayer.h index 6b75ba289..1547b1885 100644 --- a/aoblipplayer.h +++ b/aoblipplayer.h @@ -1,17 +1,16 @@ #ifndef AOBLIPPLAYER_H #define AOBLIPPLAYER_H -#include "bass.h" #include "aoapplication.h" +#include "bass.h" +#include #include #include -#include const int BLIP_COUNT = 5; -class AOBlipPlayer -{ +class AOBlipPlayer { public: AOBlipPlayer(QWidget *parent, AOApplication *p_ao_app); diff --git a/aobutton.cpp b/aobutton.cpp index a145fbfeb..8d680259e 100644 --- a/aobutton.cpp +++ b/aobutton.cpp @@ -5,7 +5,8 @@ #include -AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) : QPushButton(parent) +AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) + : QPushButton(parent) { ao_app = p_ao_app; } @@ -17,16 +18,16 @@ void AOButton::set_image(QString p_image) QString image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); QString hover_image_path = ao_app->get_image_path(image_name + "_hover.png"); - if (file_exists(image_path)) - { + if (file_exists(image_path)) { if (file_exists(hover_image_path)) - this->setStyleSheet("QPushButton {border-image:url(\"" + image_path + "\");}" - "QPushButton:hover {border-image:url(\"" + hover_image_path + "\");}"); + this->setStyleSheet("QPushButton {border-image:url(\"" + image_path + + "\");}" + "QPushButton:hover {border-image:url(\"" + + hover_image_path + "\");}"); else this->setStyleSheet("border-image:url(\"" + image_path + "\")"); } - else - { + else { image_path = ""; this->setStyleSheet("border-image:url(\"" + image_path + "\")"); } diff --git a/aobutton.h b/aobutton.h index 894e23447..c16d3f9e6 100644 --- a/aobutton.h +++ b/aobutton.h @@ -5,8 +5,7 @@ #include -class AOButton : public QPushButton -{ +class AOButton : public QPushButton { Q_OBJECT public: diff --git a/aocharbutton.cpp b/aocharbutton.cpp index d5fff5535..30b5a8a05 100644 --- a/aocharbutton.cpp +++ b/aocharbutton.cpp @@ -4,62 +4,59 @@ #include -AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos) : QPushButton(parent) +AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, + int y_pos) + : QPushButton(parent) { - ao_app = p_ao_app; + ao_app = p_ao_app; - this->resize(60, 60); - this->move(x_pos, y_pos); + this->resize(60, 60); + this->move(x_pos, y_pos); - ui_taken = new AOImage(this, ao_app); - ui_taken->resize(60, 60); - ui_taken->set_image("char_taken.png"); - ui_taken->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_taken->hide(); + ui_taken = new AOImage(this, ao_app); + ui_taken->resize(60, 60); + ui_taken->set_image("char_taken.png"); + ui_taken->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_taken->hide(); } -void AOCharButton::reset() -{ - ui_taken->hide(); -} +void AOCharButton::reset() { ui_taken->hide(); } -void AOCharButton::set_taken() -{ - ui_taken->show(); -} +void AOCharButton::set_taken() { ui_taken->show(); } void AOCharButton::set_image(QString p_character) { - QString image_path = ao_app->get_character_path(p_character) + "char_icon.png"; - QString legacy_path = ao_app->get_demothings_path() + p_character.toLower() + "_char_icon.png"; - QString alt_path = ao_app->get_demothings_path() + p_character.toLower() + "_off.png"; - - this->setText(""); - - if (file_exists(image_path)) - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - else if (file_exists(legacy_path)) - { - this->setStyleSheet("border-image:url(\"" + legacy_path + "\")"); - //ninja optimization - QFile::copy(legacy_path, image_path); - } - else - { - this->setStyleSheet("border-image:url()"); - this->setText(p_character); - } + QString image_path = + ao_app->get_character_path(p_character) + "char_icon.png"; + QString legacy_path = + ao_app->get_demothings_path() + p_character.toLower() + "_char_icon.png"; + QString alt_path = + ao_app->get_demothings_path() + p_character.toLower() + "_off.png"; + + this->setText(""); + + if (file_exists(image_path)) + this->setStyleSheet("border-image:url(\"" + image_path + "\")"); + else if (file_exists(legacy_path)) { + this->setStyleSheet("border-image:url(\"" + legacy_path + "\")"); + // ninja optimization + QFile::copy(legacy_path, image_path); + } + else { + this->setStyleSheet("border-image:url()"); + this->setText(p_character); + } } void AOCharButton::enterEvent(QEvent *e) { - setFlat(false); - QPushButton::enterEvent(e); - emit mouse_entered(this); + setFlat(false); + QPushButton::enterEvent(e); + emit mouse_entered(this); } void AOCharButton::leaveEvent(QEvent *e) { - QPushButton::leaveEvent(e); - emit mouse_left(); + QPushButton::leaveEvent(e); + emit mouse_left(); } diff --git a/aocharbutton.h b/aocharbutton.h index 9bba0e647..c1ac9ea31 100644 --- a/aocharbutton.h +++ b/aocharbutton.h @@ -8,28 +8,27 @@ #include #include -class AOCharButton : public QPushButton -{ - Q_OBJECT +class AOCharButton : public QPushButton { + Q_OBJECT public: - AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos); - AOApplication *ao_app = nullptr; + AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos); + AOApplication *ao_app = nullptr; - void reset(); - void set_taken(); - void set_image(QString p_character); + void reset(); + void set_taken(); + void set_image(QString p_character); signals: - void mouse_entered(AOCharButton *p_caller); - void mouse_left(); + void mouse_entered(AOCharButton *p_caller); + void mouse_left(); private: - AOImage *ui_taken = nullptr; + AOImage *ui_taken = nullptr; protected: - void enterEvent(QEvent *e); - void leaveEvent(QEvent *e); + void enterEvent(QEvent *e); + void leaveEvent(QEvent *e); }; #endif // AOCHARBUTTON_H diff --git a/aocharmovie.cpp b/aocharmovie.cpp index 606ffb859..a79757272 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -8,169 +8,162 @@ #include #include -AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) +AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) + : QLabel(p_parent) { - ao_app = p_ao_app; + ao_app = p_ao_app; - m_reader = new QMovie(this); + m_reader = new QMovie(this); - m_frame_timer = new QTimer(this); - m_frame_timer->setSingleShot(true); + m_frame_timer = new QTimer(this); + m_frame_timer->setSingleShot(true); - connect(m_reader, SIGNAL(frameChanged(int)), this, SLOT(on_frame_changed(int))); - connect(m_frame_timer, SIGNAL(timeout()), this, SLOT(timer_done())); + connect(m_reader, SIGNAL(frameChanged(int)), this, + SLOT(on_frame_changed(int))); + connect(m_frame_timer, SIGNAL(timeout()), this, SLOT(timer_done())); } -void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, bool p_visible) +void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, + bool p_visible) { - QString target_path; - QStringList f_paths{ - ao_app->get_character_path(p_char) + p_emote_prefix + p_emote.toLower(), // .gif - ao_app->get_character_path(p_char) + p_emote.toLower(), // .png - ao_app->get_theme_variant_path() + "placeholder", // .gif - ao_app->get_theme_path() + "placeholder", // .gif - ao_app->get_default_theme_path() + "placeholder" // .gif - }; - - for (auto &f_file : f_paths) - { - bool found = false; - for (auto &ext : QStringList{".webp", ".apng", ".gif", ".png"}) - { - QString fullPath = f_file + ext; - found = file_exists(fullPath); - if (found) - { - target_path = fullPath; - break; - } - } - - if (found) - break; + QString target_path; + QStringList f_paths{ + ao_app->get_character_path(p_char) + p_emote_prefix + + p_emote.toLower(), // .gif + ao_app->get_character_path(p_char) + p_emote.toLower(), // .png + ao_app->get_theme_variant_path() + "placeholder", // .gif + ao_app->get_theme_path() + "placeholder", // .gif + ao_app->get_default_theme_path() + "placeholder" // .gif + }; + + for (auto &f_file : f_paths) { + bool found = false; + for (auto &ext : QStringList{".webp", ".apng", ".gif", ".png"}) { + QString fullPath = f_file + ext; + found = file_exists(fullPath); + if (found) { + target_path = fullPath; + break; + } } - show(); - if (!p_visible) - hide(); - - movie_frames.clear(); - QImageReader *reader = new QImageReader(target_path); - for (int i = 0; i < reader->imageCount(); ++i) - { // optimize can be better, but I'll just keep it like that for now - QImage f_image = reader->read(); - if (m_mirror) - f_image = f_image.mirrored(true, false); - movie_frames.append(f_image); - } - delete reader; - - m_reader->stop(); - this->clear(); - m_reader->setFileName(target_path); - m_reader->start(); + if (found) + break; + } + + show(); + if (!p_visible) + hide(); + + movie_frames.clear(); + QImageReader *reader = new QImageReader(target_path); + for (int i = 0; i < reader->imageCount(); + ++i) { // optimize can be better, but I'll just keep it like that for now + QImage f_image = reader->read(); + if (m_mirror) + f_image = f_image.mirrored(true, false); + movie_frames.append(f_image); + } + delete reader; + + m_reader->stop(); + this->clear(); + m_reader->setFileName(target_path); + m_reader->start(); } bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) { - QString f_file_path = ao_app->get_character_path(p_char) + p_emote.toLower(); - bool f_file_exist = false; - - { // figure out what extension the animation is using - QString f_source_path = ao_app->get_character_path(p_char) + p_emote.toLower(); - for (QString &i_ext : QStringList{".webp", ".apng", ".gif", ".png"}) - { - QString f_target_path = f_source_path + i_ext; - if (file_exists(f_target_path)) - { - f_file_path = f_target_path; - f_file_exist = true; - break; - } - } + QString f_file_path = ao_app->get_character_path(p_char) + p_emote.toLower(); + bool f_file_exist = false; + + { // figure out what extension the animation is using + QString f_source_path = + ao_app->get_character_path(p_char) + p_emote.toLower(); + for (QString &i_ext : QStringList{".webp", ".apng", ".gif", ".png"}) { + QString f_target_path = f_source_path + i_ext; + if (file_exists(f_target_path)) { + f_file_path = f_target_path; + f_file_exist = true; + break; + } } + } - // play if it exist - if (f_file_exist) - { - m_reader->stop(); - this->clear(); - m_play_once = true; - m_reader->setFileName(f_file_path); - play(p_char, p_emote, "", show); - } + // play if it exist + if (f_file_exist) { + m_reader->stop(); + this->clear(); + m_play_once = true; + m_reader->setFileName(f_file_path); + play(p_char, p_emote, "", show); + } - return f_file_exist; + return f_file_exist; } void AOCharMovie::play_talking(QString p_char, QString p_emote, bool p_visible) { - QString gif_path = ao_app->get_character_path(p_char) + "(b)" + p_emote.toLower(); - - m_reader->stop(); - this->clear(); - m_play_once = false; - m_reader->setFileName(gif_path); - play(p_char, p_emote, "(b)", p_visible); + QString gif_path = + ao_app->get_character_path(p_char) + "(b)" + p_emote.toLower(); + + m_reader->stop(); + this->clear(); + m_play_once = false; + m_reader->setFileName(gif_path); + play(p_char, p_emote, "(b)", p_visible); } void AOCharMovie::play_idle(QString p_char, QString p_emote, bool p_visible) { - QString gif_path = ao_app->get_character_path(p_char) + "(a)" + p_emote.toLower(); - - this->clear(); - m_reader->stop(); - m_play_once = false; - m_reader->setFileName(gif_path); - play(p_char, p_emote, "(a)", p_visible); + QString gif_path = + ao_app->get_character_path(p_char) + "(a)" + p_emote.toLower(); + + this->clear(); + m_reader->stop(); + m_play_once = false; + m_reader->setFileName(gif_path); + play(p_char, p_emote, "(a)", p_visible); } -void AOCharMovie::set_mirror_enabled(bool p_enable) -{ - m_mirror = p_enable; -} +void AOCharMovie::set_mirror_enabled(bool p_enable) { m_mirror = p_enable; } 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 - m_reader->stop(); - m_frame_timer->stop(); - this->hide(); + // for all intents and purposes, stopping is the same as hiding. at no point + // do we want a frozen gif to display + m_reader->stop(); + m_frame_timer->stop(); + this->hide(); } void AOCharMovie::combo_resize(QSize p_size) { - resize(p_size); - m_reader->stop(); - m_frame_timer->stop(); - m_reader->setScaledSize(p_size); - m_reader->start(); + resize(p_size); + m_reader->stop(); + m_frame_timer->stop(); + m_reader->setScaledSize(p_size); + m_reader->start(); } void AOCharMovie::on_frame_changed(int p_frame_num) { - if (movie_frames.size() > p_frame_num) - { - AOPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(p_frame_num)); - this->setPixmap(f_pixmap.scale_to_size(this->size())); - } - - // pre-anim only - if (m_play_once) - { - int f_frame_count = m_reader->frameCount(); - if (f_frame_count == 0 || p_frame_num == (f_frame_count - 1)) - { - int f_frame_delay = m_reader->nextFrameDelay(); - if (f_frame_delay < 0) - f_frame_delay = 0; - m_frame_timer->start(f_frame_delay); - m_reader->stop(); - } + if (movie_frames.size() > p_frame_num) { + AOPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(p_frame_num)); + this->setPixmap(f_pixmap.scale_to_size(this->size())); + } + + // pre-anim only + if (m_play_once) { + int f_frame_count = m_reader->frameCount(); + if (f_frame_count == 0 || p_frame_num == (f_frame_count - 1)) { + int f_frame_delay = m_reader->nextFrameDelay(); + if (f_frame_delay < 0) + f_frame_delay = 0; + m_frame_timer->start(f_frame_delay); + m_reader->stop(); } + } } -void AOCharMovie::timer_done() -{ - done(); -} +void AOCharMovie::timer_done() { done(); } diff --git a/aocharmovie.h b/aocharmovie.h index e4b4c1767..0dcd8d31b 100644 --- a/aocharmovie.h +++ b/aocharmovie.h @@ -9,37 +9,36 @@ class AOApplication; -class AOCharMovie : public QLabel -{ - Q_OBJECT +class AOCharMovie : public QLabel { + Q_OBJECT public: - AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app); + AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app); - void play(QString p_char, QString p_emote, QString emote_prefix, bool show); - bool play_pre(QString p_char, QString p_emote, bool show); - void play_talking(QString p_char, QString p_emote, bool show); - void play_idle(QString p_char, QString p_emote, bool show); - void set_mirror_enabled(bool p_enable); - void combo_resize(QSize p_size); - void stop(); + void play(QString p_char, QString p_emote, QString emote_prefix, bool show); + bool play_pre(QString p_char, QString p_emote, bool show); + void play_talking(QString p_char, QString p_emote, bool show); + void play_idle(QString p_char, QString p_emote, bool show); + void set_mirror_enabled(bool p_enable); + void combo_resize(QSize p_size); + void stop(); private: - AOApplication *ao_app = nullptr; + AOApplication *ao_app = nullptr; - QMovie *m_reader; - QVector movie_frames; - QTimer *m_frame_timer; + QMovie *m_reader; + QVector movie_frames; + QTimer *m_frame_timer; - bool m_mirror = false; - bool m_play_once = false; + bool m_mirror = false; + bool m_play_once = false; signals: - void done(); + void done(); private slots: - void on_frame_changed(int n_frame); - void timer_done(); + void on_frame_changed(int n_frame); + void timer_done(); }; #endif // AOCHARMOVIE_H diff --git a/aoconfig.cpp b/aoconfig.cpp index 03ed52bb9..8583d09f0 100644 --- a/aoconfig.cpp +++ b/aoconfig.cpp @@ -10,226 +10,225 @@ but hey, when has ao2 ever cared? Wait, am I using the term wrong? Nice. */ -class AOConfigPrivate : public QObject -{ - Q_OBJECT - - friend AOConfig; - - QSettings cfg; - // hate me more - QVector parents; - - // data - QString username; - QString callwords; - QString theme; - QString theme_variant; - bool always_pre; - int chat_tick_interval; - bool server_alerts; - int log_max_lines; - bool log_goes_downward; - bool log_uses_newline; - bool log_music; - bool log_is_recording; - int effects_volume; - int system_volume; - int music_volume; - int blips_volume; - int blip_rate; - bool blank_blips; +class AOConfigPrivate : public QObject { + Q_OBJECT + + friend AOConfig; + + QSettings cfg; + // hate me more + QVector parents; + + // data + QString username; + QString callwords; + QString theme; + QString theme_variant; + bool always_pre; + int chat_tick_interval; + bool server_alerts; + int log_max_lines; + bool log_goes_downward; + bool log_uses_newline; + bool log_music; + bool log_is_recording; + int effects_volume; + int system_volume; + int music_volume; + int blips_volume; + int blip_rate; + bool blank_blips; public: - AOConfigPrivate() : QObject(qApp), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat) - { - read_file(); - } - ~AOConfigPrivate() - { - save_file(); - } - - // setters + AOConfigPrivate() + : QObject(qApp), + cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat) + { + read_file(); + } + ~AOConfigPrivate() { save_file(); } + + // setters public slots: - void set_username(QString p_string) - { - if (username == p_string) - return; - username = p_string; - invoke_parents("username_changed", Q_ARG(QString, p_string)); - } - void set_callwords(QString p_string) - { - if (callwords == p_string) - return; - callwords = p_string; - invoke_parents("callwords_changed", Q_ARG(QString, p_string)); - } - void set_theme(QString p_string) - { - if (theme == p_string) - return; - theme = p_string; - invoke_parents("theme_changed", Q_ARG(QString, p_string)); - } - void set_theme_variant(QString p_string) - { - if (theme_variant == p_string) - return; - theme_variant = p_string; - invoke_parents("theme_variant_changed", Q_ARG(QString, p_string)); - } - void set_always_pre(bool p_enabled) - { - if (always_pre == p_enabled) - return; - always_pre = p_enabled; - invoke_parents("always_pre_changed", Q_ARG(bool, p_enabled)); - } - void set_chat_tick_interval(int p_number) - { - if (chat_tick_interval == p_number) - return; - chat_tick_interval = p_number; - invoke_parents("chat_tick_interval_changed", Q_ARG(int, p_number)); - } - void set_server_alerts(bool p_enabled) - { - if (server_alerts == p_enabled) - return; - server_alerts = p_enabled; - invoke_parents("server_alerts_changed", Q_ARG(bool, p_enabled)); - } - void set_log_max_lines(int p_number) - { - if (log_max_lines == p_number) - return; - log_max_lines = p_number; - invoke_parents("log_max_lines_changed", Q_ARG(int, p_number)); - } - void set_log_goes_downward(bool p_enabled) - { - if (log_goes_downward == p_enabled) - return; - log_goes_downward = p_enabled; - invoke_parents("log_goes_downward_changed", Q_ARG(bool, p_enabled)); - } - void set_log_uses_newline(bool p_enabled) - { - if (log_uses_newline == p_enabled) - return; - log_uses_newline = p_enabled; - invoke_parents("log_uses_newline_changed", Q_ARG(bool, p_enabled)); - } - void set_log_music(bool p_enabled) - { - if (log_music == p_enabled) - return; - log_music = p_enabled; - invoke_parents("log_music_changed", Q_ARG(bool, p_enabled)); - } - void set_log_is_recording(bool p_enabled) - { - if (log_is_recording == p_enabled) - return; - log_is_recording = p_enabled; - invoke_parents("log_is_recording_changed", Q_ARG(bool, p_enabled)); - } - void set_effects_volume(int p_number) - { - if (effects_volume == p_number) - return; - effects_volume = p_number; - invoke_parents("effects_volume_changed", Q_ARG(int, p_number)); - } - void set_system_volume(int p_number) - { - if (system_volume == p_number) - return; - system_volume = p_number; - invoke_parents("system_volume_changed", Q_ARG(int, p_number)); - } - void set_music_volume(int p_number) - { - if (music_volume == p_number) - return; - music_volume = p_number; - invoke_parents("music_volume_changed", Q_ARG(int, p_number)); - } - void set_blips_volume(int p_number) - { - if (blips_volume == p_number) - return; - blips_volume = p_number; - invoke_parents("blips_volume_changed", Q_ARG(int, p_number)); - } - void set_blip_rate(int p_number) - { - if (blip_rate == p_number) - return; - blip_rate = p_number; - invoke_parents("blip_rate_changed", Q_ARG(int, p_number)); - } - void set_blank_blips(bool p_enabled) - { - if (blank_blips == p_enabled) - return; - blank_blips = p_enabled; - invoke_parents("blank_blips_changed", Q_ARG(bool, p_enabled)); - } - void read_file() - { - username = cfg.value("username").toString(); - callwords = cfg.value("callwords").toString(); - theme = cfg.value("theme", "default").toString(); - theme_variant = cfg.value("theme_variant", "").toString(); - always_pre = cfg.value("always_pre", true).toBool(); - chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); - server_alerts = cfg.value("server_alerts", true).toBool(); - log_max_lines = cfg.value("chatlog_limit", 200).toInt(); - log_goes_downward = cfg.value("chatlog_scrolldown", true).toBool(); - log_uses_newline = cfg.value("chatlog_newline", false).toBool(); - log_music = cfg.value("music_change_log", true).toBool(); - log_is_recording = cfg.value("enable_logging", true).toBool(); - effects_volume = cfg.value("default_sfx", 50).toInt(); - system_volume = cfg.value("default_system", 50).toInt(); - music_volume = cfg.value("default_music", 50).toInt(); - blips_volume = cfg.value("default_blip", 50).toInt(); - blip_rate = cfg.value("blip_rate", 1000000000).toInt(); - blank_blips = cfg.value("blank_blips").toBool(); - } - void save_file() - { - cfg.setValue("username", username); - cfg.setValue("callwords", callwords); - cfg.setValue("theme", theme); - cfg.setValue("theme_variant", theme_variant); - cfg.setValue("always_pre", always_pre); - cfg.setValue("chat_tick_interval", chat_tick_interval); - cfg.setValue("server_alerts", server_alerts); - cfg.setValue("chatlog_limit", log_max_lines); - cfg.setValue("chatlog_scrolldown", log_goes_downward); - cfg.setValue("chatlog_newline", log_uses_newline); - cfg.setValue("music_change_log", log_music); - cfg.setValue("enable_logging", log_is_recording); - cfg.setValue("default_sfx", effects_volume); - cfg.setValue("default_system", system_volume); - cfg.setValue("default_music", music_volume); - cfg.setValue("default_blip", blips_volume); - cfg.setValue("blip_rate", blip_rate); - cfg.setValue("blank_blips", blank_blips); - cfg.sync(); - } + void set_username(QString p_string) + { + if (username == p_string) + return; + username = p_string; + invoke_parents("username_changed", Q_ARG(QString, p_string)); + } + void set_callwords(QString p_string) + { + if (callwords == p_string) + return; + callwords = p_string; + invoke_parents("callwords_changed", Q_ARG(QString, p_string)); + } + void set_theme(QString p_string) + { + if (theme == p_string) + return; + theme = p_string; + invoke_parents("theme_changed", Q_ARG(QString, p_string)); + } + void set_theme_variant(QString p_string) + { + if (theme_variant == p_string) + return; + theme_variant = p_string; + invoke_parents("theme_variant_changed", Q_ARG(QString, p_string)); + } + void set_always_pre(bool p_enabled) + { + if (always_pre == p_enabled) + return; + always_pre = p_enabled; + invoke_parents("always_pre_changed", Q_ARG(bool, p_enabled)); + } + void set_chat_tick_interval(int p_number) + { + if (chat_tick_interval == p_number) + return; + chat_tick_interval = p_number; + invoke_parents("chat_tick_interval_changed", Q_ARG(int, p_number)); + } + void set_server_alerts(bool p_enabled) + { + if (server_alerts == p_enabled) + return; + server_alerts = p_enabled; + invoke_parents("server_alerts_changed", Q_ARG(bool, p_enabled)); + } + void set_log_max_lines(int p_number) + { + if (log_max_lines == p_number) + return; + log_max_lines = p_number; + invoke_parents("log_max_lines_changed", Q_ARG(int, p_number)); + } + void set_log_goes_downward(bool p_enabled) + { + if (log_goes_downward == p_enabled) + return; + log_goes_downward = p_enabled; + invoke_parents("log_goes_downward_changed", Q_ARG(bool, p_enabled)); + } + void set_log_uses_newline(bool p_enabled) + { + if (log_uses_newline == p_enabled) + return; + log_uses_newline = p_enabled; + invoke_parents("log_uses_newline_changed", Q_ARG(bool, p_enabled)); + } + void set_log_music(bool p_enabled) + { + if (log_music == p_enabled) + return; + log_music = p_enabled; + invoke_parents("log_music_changed", Q_ARG(bool, p_enabled)); + } + void set_log_is_recording(bool p_enabled) + { + if (log_is_recording == p_enabled) + return; + log_is_recording = p_enabled; + invoke_parents("log_is_recording_changed", Q_ARG(bool, p_enabled)); + } + void set_effects_volume(int p_number) + { + if (effects_volume == p_number) + return; + effects_volume = p_number; + invoke_parents("effects_volume_changed", Q_ARG(int, p_number)); + } + void set_system_volume(int p_number) + { + if (system_volume == p_number) + return; + system_volume = p_number; + invoke_parents("system_volume_changed", Q_ARG(int, p_number)); + } + void set_music_volume(int p_number) + { + if (music_volume == p_number) + return; + music_volume = p_number; + invoke_parents("music_volume_changed", Q_ARG(int, p_number)); + } + void set_blips_volume(int p_number) + { + if (blips_volume == p_number) + return; + blips_volume = p_number; + invoke_parents("blips_volume_changed", Q_ARG(int, p_number)); + } + void set_blip_rate(int p_number) + { + if (blip_rate == p_number) + return; + blip_rate = p_number; + invoke_parents("blip_rate_changed", Q_ARG(int, p_number)); + } + void set_blank_blips(bool p_enabled) + { + if (blank_blips == p_enabled) + return; + blank_blips = p_enabled; + invoke_parents("blank_blips_changed", Q_ARG(bool, p_enabled)); + } + void read_file() + { + username = cfg.value("username").toString(); + callwords = cfg.value("callwords").toString(); + theme = cfg.value("theme", "default").toString(); + theme_variant = cfg.value("theme_variant", "").toString(); + always_pre = cfg.value("always_pre", true).toBool(); + chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); + server_alerts = cfg.value("server_alerts", true).toBool(); + log_max_lines = cfg.value("chatlog_limit", 200).toInt(); + log_goes_downward = cfg.value("chatlog_scrolldown", true).toBool(); + log_uses_newline = cfg.value("chatlog_newline", false).toBool(); + log_music = cfg.value("music_change_log", true).toBool(); + log_is_recording = cfg.value("enable_logging", true).toBool(); + effects_volume = cfg.value("default_sfx", 50).toInt(); + system_volume = cfg.value("default_system", 50).toInt(); + music_volume = cfg.value("default_music", 50).toInt(); + blips_volume = cfg.value("default_blip", 50).toInt(); + blip_rate = cfg.value("blip_rate", 1000000000).toInt(); + blank_blips = cfg.value("blank_blips").toBool(); + } + void save_file() + { + cfg.setValue("username", username); + cfg.setValue("callwords", callwords); + cfg.setValue("theme", theme); + cfg.setValue("theme_variant", theme_variant); + cfg.setValue("always_pre", always_pre); + cfg.setValue("chat_tick_interval", chat_tick_interval); + cfg.setValue("server_alerts", server_alerts); + cfg.setValue("chatlog_limit", log_max_lines); + cfg.setValue("chatlog_scrolldown", log_goes_downward); + cfg.setValue("chatlog_newline", log_uses_newline); + cfg.setValue("music_change_log", log_music); + cfg.setValue("enable_logging", log_is_recording); + cfg.setValue("default_sfx", effects_volume); + cfg.setValue("default_system", system_volume); + cfg.setValue("default_music", music_volume); + cfg.setValue("default_blip", blips_volume); + cfg.setValue("blip_rate", blip_rate); + cfg.setValue("blank_blips", blank_blips); + cfg.sync(); + } private: - void invoke_parents(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)) - { - for (QObject *i_parent : parents) - { - QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), p_arg1); - } + void invoke_parents(QString p_method_name, + QGenericArgument p_arg1 = QGenericArgument(nullptr)) + { + for (QObject *i_parent : parents) { + QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), + p_arg1); } + } }; /*! @@ -239,256 +238,174 @@ static AOConfigPrivate *d = nullptr; AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) { - // init if not created yet - if (d == nullptr) - { - d = new AOConfigPrivate; - } + // init if not created yet + if (d == nullptr) { + d = new AOConfigPrivate; + } - // ao2 is the pinnacle of thread security - d->parents.append(this); + // ao2 is the pinnacle of thread security + d->parents.append(this); } AOConfig::~AOConfig() { - // totally safe! - d->parents.removeAll(this); + // totally safe! + d->parents.removeAll(this); } QString AOConfig::get_string(QString p_name, QString p_default) { - return d->cfg.value(p_name, p_default).toString(); + return d->cfg.value(p_name, p_default).toString(); } bool AOConfig::get_bool(QString p_name, bool p_default) { - return d->cfg.value(p_name, p_default).toBool(); + return d->cfg.value(p_name, p_default).toBool(); } int AOConfig::get_number(QString p_name, int p_default) { - return d->cfg.value(p_name, p_default).toInt(); + return d->cfg.value(p_name, p_default).toInt(); } -QString AOConfig::username() -{ - return d->username; -} +QString AOConfig::username() { return d->username; } -QString AOConfig::callwords() -{ - return d->callwords; -} +QString AOConfig::callwords() { return d->callwords; } -QString AOConfig::theme() -{ - return d->theme; -} +QString AOConfig::theme() { return d->theme; } -QString AOConfig::theme_variant() -{ - return d->theme_variant; -} +QString AOConfig::theme_variant() { return d->theme_variant; } -bool AOConfig::always_pre_enabled() -{ - return d->always_pre; -} +bool AOConfig::always_pre_enabled() { return d->always_pre; } -int AOConfig::chat_tick_interval() -{ - return d->chat_tick_interval; -} +int AOConfig::chat_tick_interval() { return d->chat_tick_interval; } -bool AOConfig::server_alerts_enabled() -{ - return d->server_alerts; -} +bool AOConfig::server_alerts_enabled() { return d->server_alerts; } -int AOConfig::log_max_lines() -{ - return d->log_max_lines; -} +int AOConfig::log_max_lines() { return d->log_max_lines; } -bool AOConfig::log_goes_downward_enabled() -{ - return d->log_goes_downward; -} +bool AOConfig::log_goes_downward_enabled() { return d->log_goes_downward; } -bool AOConfig::log_uses_newline_enabled() -{ - return d->log_uses_newline; -} +bool AOConfig::log_uses_newline_enabled() { return d->log_uses_newline; } -bool AOConfig::log_music_enabled() -{ - return d->log_music; -} +bool AOConfig::log_music_enabled() { return d->log_music; } -bool AOConfig::log_is_recording_enabled() -{ - return d->log_is_recording; -} +bool AOConfig::log_is_recording_enabled() { return d->log_is_recording; } -int AOConfig::effects_volume() -{ - return d->effects_volume; -} +int AOConfig::effects_volume() { return d->effects_volume; } -int AOConfig::system_volume() -{ - return d->system_volume; -} +int AOConfig::system_volume() { return d->system_volume; } -int AOConfig::music_volume() -{ - return d->music_volume; -} +int AOConfig::music_volume() { return d->music_volume; } -int AOConfig::blips_volume() -{ - return d->blips_volume; -} +int AOConfig::blips_volume() { return d->blips_volume; } -int AOConfig::blip_rate() -{ - return d->blip_rate; -} +int AOConfig::blip_rate() { return d->blip_rate; } -bool AOConfig::blank_blips_enabled() -{ - return d->blank_blips; -} +bool AOConfig::blank_blips_enabled() { return d->blank_blips; } -void AOConfig::set_username(QString p_string) -{ - d->set_username(p_string); -} +void AOConfig::set_username(QString p_string) { d->set_username(p_string); } -void AOConfig::set_callwords(QString p_string) -{ - d->set_callwords(p_string); -} +void AOConfig::set_callwords(QString p_string) { d->set_callwords(p_string); } -void AOConfig::set_theme(QString p_string) -{ - d->set_theme(p_string); -} +void AOConfig::set_theme(QString p_string) { d->set_theme(p_string); } void AOConfig::set_theme_variant(QString p_string) { - d->set_theme_variant(p_string); + d->set_theme_variant(p_string); } void AOConfig::set_always_pre(int p_state) { - set_always_pre(p_state == Qt::Checked); + set_always_pre(p_state == Qt::Checked); } -void AOConfig::set_always_pre(bool p_enabled) -{ - d->set_always_pre(p_enabled); -} +void AOConfig::set_always_pre(bool p_enabled) { d->set_always_pre(p_enabled); } void AOConfig::set_chat_tick_interval(int p_number) { - d->set_chat_tick_interval(p_number); + d->set_chat_tick_interval(p_number); } void AOConfig::set_server_alerts(int p_state) { - set_server_alerts(p_state == Qt::Checked); + set_server_alerts(p_state == Qt::Checked); } void AOConfig::set_server_alerts(bool p_enabled) { - d->set_server_alerts(p_enabled); + d->set_server_alerts(p_enabled); } void AOConfig::set_log_max_lines(int p_number) { - d->set_log_max_lines(p_number); + d->set_log_max_lines(p_number); } void AOConfig::set_log_goes_downward(bool p_enabled) { - d->set_log_goes_downward(p_enabled); + d->set_log_goes_downward(p_enabled); } void AOConfig::set_log_goes_downward(int p_state) { - set_log_goes_downward(p_state == Qt::Checked); + set_log_goes_downward(p_state == Qt::Checked); } void AOConfig::set_log_uses_newline(bool p_enabled) { - d->set_log_uses_newline(p_enabled); + d->set_log_uses_newline(p_enabled); } void AOConfig::set_log_uses_newline(int p_state) { - set_log_uses_newline(p_state == Qt::Checked); + set_log_uses_newline(p_state == Qt::Checked); } -void AOConfig::set_log_music(bool p_enabled) -{ - d->set_log_music(p_enabled); -} +void AOConfig::set_log_music(bool p_enabled) { d->set_log_music(p_enabled); } void AOConfig::set_log_music(int p_state) { - set_log_music(p_state == Qt::Checked); + set_log_music(p_state == Qt::Checked); } void AOConfig::set_log_is_recording(bool p_enabled) { - d->set_log_is_recording(p_enabled); + d->set_log_is_recording(p_enabled); } void AOConfig::set_log_is_recording(int p_state) { - set_log_is_recording(p_state == Qt::Checked); + set_log_is_recording(p_state == Qt::Checked); } void AOConfig::set_effects_volume(int p_number) { - d->set_effects_volume(p_number); + d->set_effects_volume(p_number); } void AOConfig::set_system_volume(int p_number) { - d->set_system_volume(p_number); + d->set_system_volume(p_number); } -void AOConfig::set_music_volume(int p_number) -{ - d->set_music_volume(p_number); -} +void AOConfig::set_music_volume(int p_number) { d->set_music_volume(p_number); } -void AOConfig::set_blips_volume(int p_number) -{ - d->set_blips_volume(p_number); -} +void AOConfig::set_blips_volume(int p_number) { d->set_blips_volume(p_number); } -void AOConfig::set_blip_rate(int p_number) -{ - d->set_blip_rate(p_number); -} +void AOConfig::set_blip_rate(int p_number) { d->set_blip_rate(p_number); } void AOConfig::set_blank_blips(bool p_enabled) { - d->set_blank_blips(p_enabled); + d->set_blank_blips(p_enabled); } void AOConfig::set_blank_blips(int p_state) { - set_blank_blips(p_state == Qt::Checked); + set_blank_blips(p_state == Qt::Checked); } -void AOConfig::save_file() -{ - d->save_file(); -} +void AOConfig::save_file() { d->save_file(); } // moc #include "aoconfig.moc" diff --git a/aoconfig.h b/aoconfig.h index 77f237ef1..9278ba22c 100644 --- a/aoconfig.h +++ b/aoconfig.h @@ -3,90 +3,89 @@ // qt #include -class AOConfig : public QObject -{ - Q_OBJECT +class AOConfig : public QObject { + Q_OBJECT public: - AOConfig(QObject *p_parent = nullptr); - ~AOConfig(); + AOConfig(QObject *p_parent = nullptr); + ~AOConfig(); - // generic getters - QString get_string(QString p_name, QString p_default = nullptr); - bool get_bool(QString p_name, bool p_default = false); - int get_number(QString p_name, int p_default = 0); + // generic getters + QString get_string(QString p_name, QString p_default = nullptr); + bool get_bool(QString p_name, bool p_default = false); + int get_number(QString p_name, int p_default = 0); - // getters - QString username(); - QString callwords(); - QString theme(); - QString theme_variant(); - bool always_pre_enabled(); - int chat_tick_interval(); - bool server_alerts_enabled(); - int log_max_lines(); - bool log_goes_downward_enabled(); - bool log_uses_newline_enabled(); - bool log_music_enabled(); - bool log_is_recording_enabled(); - int effects_volume(); - int system_volume(); - int music_volume(); - int blips_volume(); - int blip_rate(); - bool blank_blips_enabled(); + // getters + QString username(); + QString callwords(); + QString theme(); + QString theme_variant(); + bool always_pre_enabled(); + int chat_tick_interval(); + bool server_alerts_enabled(); + int log_max_lines(); + bool log_goes_downward_enabled(); + bool log_uses_newline_enabled(); + bool log_music_enabled(); + bool log_is_recording_enabled(); + int effects_volume(); + int system_volume(); + int music_volume(); + int blips_volume(); + int blip_rate(); + bool blank_blips_enabled(); - // io + // io public slots: - void save_file(); + void save_file(); - // setters + // setters public slots: - void set_username(QString p_string); - void set_callwords(QString p_string); - void set_theme(QString p_string); - void set_theme_variant(QString p_string); - void set_always_pre(bool p_enabled); - void set_always_pre(int p_state); - void set_chat_tick_interval(int p_number); - void set_server_alerts(bool p_enabled); - void set_server_alerts(int p_state); - void set_log_max_lines(int p_number); - void set_log_goes_downward(bool p_enabled); - void set_log_goes_downward(int p_state); - void set_log_uses_newline(bool p_enabled); - void set_log_uses_newline(int p_state); - void set_log_music(bool p_enabled); - void set_log_music(int p_state); - void set_log_is_recording(bool p_enabled); - void set_log_is_recording(int p_state); - void set_effects_volume(int p_number); - void set_system_volume(int p_number); - void set_music_volume(int p_number); - void set_blips_volume(int p_number); - void set_blip_rate(int p_number); - void set_blank_blips(bool p_enabled); - void set_blank_blips(int p_state); + void set_username(QString p_string); + void set_callwords(QString p_string); + void set_theme(QString p_string); + void set_theme_variant(QString p_string); + void set_always_pre(bool p_enabled); + void set_always_pre(int p_state); + void set_chat_tick_interval(int p_number); + void set_server_alerts(bool p_enabled); + void set_server_alerts(int p_state); + void set_log_max_lines(int p_number); + void set_log_goes_downward(bool p_enabled); + void set_log_goes_downward(int p_state); + void set_log_uses_newline(bool p_enabled); + void set_log_uses_newline(int p_state); + void set_log_music(bool p_enabled); + void set_log_music(int p_state); + void set_log_is_recording(bool p_enabled); + void set_log_is_recording(int p_state); + void set_effects_volume(int p_number); + void set_system_volume(int p_number); + void set_music_volume(int p_number); + void set_blips_volume(int p_number); + void set_blip_rate(int p_number); + void set_blank_blips(bool p_enabled); + void set_blank_blips(int p_state); - // signals + // signals signals: - void username_changed(QString); - void callwords_changed(QString); - void theme_changed(QString); - void theme_variant_changed(QString); - void always_pre_changed(bool); - void chat_tick_interval_changed(int); - void log_max_lines_changed(int); - void log_goes_downward_changed(bool); - void log_uses_newline_changed(bool); - void log_music_changed(bool); - void log_is_recording_changed(bool); - void effects_volume_changed(int); - void system_volume_changed(int); - void music_volume_changed(int); - void blips_volume_changed(int); - void blip_rate_changed(int); - void blank_blips_changed(bool); + void username_changed(QString); + void callwords_changed(QString); + void theme_changed(QString); + void theme_variant_changed(QString); + void always_pre_changed(bool); + void chat_tick_interval_changed(int); + void log_max_lines_changed(int); + void log_goes_downward_changed(bool); + void log_uses_newline_changed(bool); + void log_music_changed(bool); + void log_is_recording_changed(bool); + void effects_volume_changed(int); + void system_volume_changed(int); + void music_volume_changed(int); + void blips_volume_changed(int); + void blip_rate_changed(int); + void blank_blips_changed(bool); }; #endif // AOCONFIG_H diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp index 82362777b..3032907c0 100644 --- a/aoconfigpanel.cpp +++ b/aoconfigpanel.cpp @@ -3,201 +3,244 @@ #include #include -AOConfigPanel::AOConfigPanel(QWidget *p_parent) : QWidget(p_parent), m_config(new AOConfig(this)) +AOConfigPanel::AOConfigPanel(QWidget *p_parent) + : QWidget(p_parent), m_config(new AOConfig(this)) { - setWindowTitle(tr("Config")); - setWindowFlag(Qt::WindowMinMaxButtonsHint, false); - - AOGuiLoader loader; - loader.load_from_file(":res/ui/config_panel.ui", this); - - // tab - setFocusProxy(AO_GUI_WIDGET(QTabWidget, "tab_widget")); - - // save - w_save = AO_GUI_WIDGET(QPushButton, "save"); - - // general - w_username = AO_GUI_WIDGET(QLineEdit, "username"); - w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); - w_theme = AO_GUI_WIDGET(QComboBox, "theme"); - w_theme_variant = AO_GUI_WIDGET(QComboBox, "theme_variant"); - w_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); - w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); - w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); - w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); - - // IC Chatlog - w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); - w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); - w_log_goes_downward = AO_GUI_WIDGET(QCheckBox, "log_downward"); - w_log_music = AO_GUI_WIDGET(QCheckBox, "log_music"); - w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); - - // audio - w_effects = AO_GUI_WIDGET(QSlider, "effects"); - w_effects_value = AO_GUI_WIDGET(QLabel, "effects_value"); - w_system = AO_GUI_WIDGET(QSlider, "system"); - w_system_value = AO_GUI_WIDGET(QLabel, "system_value"); - w_music = AO_GUI_WIDGET(QSlider, "music"); - w_music_value = AO_GUI_WIDGET(QLabel, "music_value"); - w_blips = AO_GUI_WIDGET(QSlider, "blips"); - w_blips_value = AO_GUI_WIDGET(QLabel, "blips_value"); - w_blip_rate = AO_GUI_WIDGET(QSpinBox, "blip_rate"); - w_blank_blips = AO_GUI_WIDGET(QCheckBox, "blank_blips"); - - // themes - refresh_theme_list(); - refresh_theme_variant_list(); - - // input - connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); - connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); - connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(theme_variant_changed(QString)), w_theme_variant, SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); - connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); - connect(m_config, SIGNAL(log_goes_downward_changed(bool)), w_log_goes_downward, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(effects_volume_changed(int)), w_effects, SLOT(setValue(int))); - connect(m_config, SIGNAL(system_volume_changed(int)), w_system, SLOT(setValue(int))); - connect(m_config, SIGNAL(music_volume_changed(int)), w_music, SLOT(setValue(int))); - connect(m_config, SIGNAL(blips_volume_changed(int)), w_blips, SLOT(setValue(int))); - connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, SLOT(setValue(int))); - connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, SLOT(setChecked(bool))); - - // output - connect(w_save, SIGNAL(clicked()), m_config, SLOT(save_file())); - connect(w_username, SIGNAL(textEdited(QString)), m_config, SLOT(set_username(QString))); - connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); - connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); - connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); - connect(w_theme_variant, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme_variant(QString))); - connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); - connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); - connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, SLOT(set_server_alerts(int))); - connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); - connect(w_log_goes_downward, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_goes_downward(int))); - connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_uses_newline(int))); - connect(w_log_music, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_music(int))); - connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_is_recording(int))); - connect(w_effects, SIGNAL(valueChanged(int)), m_config, SLOT(set_effects_volume(int))); - connect(w_effects, SIGNAL(valueChanged(int)), this, SLOT(on_effects_value_changed(int))); - connect(w_system, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_volume(int))); - connect(w_system, SIGNAL(valueChanged(int)), this, SLOT(on_system_value_changed(int))); - connect(w_music, SIGNAL(valueChanged(int)), m_config, SLOT(set_music_volume(int))); - connect(w_music, SIGNAL(valueChanged(int)), this, SLOT(on_music_value_changed(int))); - connect(w_blips, SIGNAL(valueChanged(int)), m_config, SLOT(set_blips_volume(int))); - connect(w_blips, SIGNAL(valueChanged(int)), this, SLOT(on_blips_value_changed(int))); - connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_rate(int))); - connect(w_blank_blips, SIGNAL(stateChanged(int)), m_config, SLOT(set_blank_blips(int))); - - // set values - w_username->setText(m_config->username()); - w_callwords->setText(m_config->callwords()); - w_theme->setCurrentText(m_config->theme()); - w_theme_variant->setCurrentText(m_config->theme_variant()); - w_always_pre->setChecked(m_config->always_pre_enabled()); - w_chat_tick_interval->setValue(m_config->chat_tick_interval()); - w_server_alerts->setChecked(m_config->server_alerts_enabled()); - w_log_max_lines->setValue(m_config->log_max_lines()); - w_log_goes_downward->setChecked(m_config->log_goes_downward_enabled()); - w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); - w_log_music->setChecked(m_config->log_music_enabled()); - w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); - w_effects->setValue(m_config->effects_volume()); - w_system->setValue(m_config->system_volume()); - w_music->setValue(m_config->music_volume()); - w_blips->setValue(m_config->blips_volume()); - w_blip_rate->setValue(m_config->blip_rate()); - w_blank_blips->setChecked(m_config->blank_blips_enabled()); + setWindowTitle(tr("Config")); + setWindowFlag(Qt::WindowMinMaxButtonsHint, false); + + AOGuiLoader loader; + loader.load_from_file(":res/ui/config_panel.ui", this); + + // tab + setFocusProxy(AO_GUI_WIDGET(QTabWidget, "tab_widget")); + + // save + w_save = AO_GUI_WIDGET(QPushButton, "save"); + + // general + w_username = AO_GUI_WIDGET(QLineEdit, "username"); + w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); + w_theme = AO_GUI_WIDGET(QComboBox, "theme"); + w_theme_variant = AO_GUI_WIDGET(QComboBox, "theme_variant"); + w_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); + w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); + w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); + w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); + + // IC Chatlog + w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); + w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); + w_log_goes_downward = AO_GUI_WIDGET(QCheckBox, "log_downward"); + w_log_music = AO_GUI_WIDGET(QCheckBox, "log_music"); + w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); + + // audio + w_effects = AO_GUI_WIDGET(QSlider, "effects"); + w_effects_value = AO_GUI_WIDGET(QLabel, "effects_value"); + w_system = AO_GUI_WIDGET(QSlider, "system"); + w_system_value = AO_GUI_WIDGET(QLabel, "system_value"); + w_music = AO_GUI_WIDGET(QSlider, "music"); + w_music_value = AO_GUI_WIDGET(QLabel, "music_value"); + w_blips = AO_GUI_WIDGET(QSlider, "blips"); + w_blips_value = AO_GUI_WIDGET(QLabel, "blips_value"); + w_blip_rate = AO_GUI_WIDGET(QSpinBox, "blip_rate"); + w_blank_blips = AO_GUI_WIDGET(QCheckBox, "blank_blips"); + + // themes + refresh_theme_list(); + refresh_theme_variant_list(); + + // input + connect(m_config, SIGNAL(username_changed(QString)), w_username, + SLOT(setText(QString))); + connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, + SLOT(setText(QString))); + connect(m_config, SIGNAL(theme_changed(QString)), w_theme, + SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(theme_variant_changed(QString)), w_theme_variant, + SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(chat_tick_interval_changed(int)), + w_chat_tick_interval, SLOT(setValue(int))); + connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, + SLOT(setValue(int))); + connect(m_config, SIGNAL(log_goes_downward_changed(bool)), + w_log_goes_downward, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(effects_volume_changed(int)), w_effects, + SLOT(setValue(int))); + connect(m_config, SIGNAL(system_volume_changed(int)), w_system, + SLOT(setValue(int))); + connect(m_config, SIGNAL(music_volume_changed(int)), w_music, + SLOT(setValue(int))); + connect(m_config, SIGNAL(blips_volume_changed(int)), w_blips, + SLOT(setValue(int))); + connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, + SLOT(setValue(int))); + connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, + SLOT(setChecked(bool))); + + // output + connect(w_save, SIGNAL(clicked()), m_config, SLOT(save_file())); + connect(w_username, SIGNAL(textEdited(QString)), m_config, + SLOT(set_username(QString))); + connect(w_callwords, SIGNAL(textEdited(QString)), m_config, + SLOT(set_callwords(QString))); + connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, + SLOT(set_theme(QString))); + connect(w_reload_theme, SIGNAL(clicked()), this, + SLOT(on_reload_theme_clicked())); + connect(w_theme_variant, SIGNAL(currentIndexChanged(QString)), m_config, + SLOT(set_theme_variant(QString))); + connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, + SLOT(set_always_pre(int))); + connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, + SLOT(set_chat_tick_interval(int))); + connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, + SLOT(set_server_alerts(int))); + connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, + SLOT(set_log_max_lines(int))); + connect(w_log_goes_downward, SIGNAL(stateChanged(int)), m_config, + SLOT(set_log_goes_downward(int))); + connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, + SLOT(set_log_uses_newline(int))); + connect(w_log_music, SIGNAL(stateChanged(int)), m_config, + SLOT(set_log_music(int))); + connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, + SLOT(set_log_is_recording(int))); + connect(w_effects, SIGNAL(valueChanged(int)), m_config, + SLOT(set_effects_volume(int))); + connect(w_effects, SIGNAL(valueChanged(int)), this, + SLOT(on_effects_value_changed(int))); + connect(w_system, SIGNAL(valueChanged(int)), m_config, + SLOT(set_system_volume(int))); + connect(w_system, SIGNAL(valueChanged(int)), this, + SLOT(on_system_value_changed(int))); + connect(w_music, SIGNAL(valueChanged(int)), m_config, + SLOT(set_music_volume(int))); + connect(w_music, SIGNAL(valueChanged(int)), this, + SLOT(on_music_value_changed(int))); + connect(w_blips, SIGNAL(valueChanged(int)), m_config, + SLOT(set_blips_volume(int))); + connect(w_blips, SIGNAL(valueChanged(int)), this, + SLOT(on_blips_value_changed(int))); + connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, + SLOT(set_blip_rate(int))); + connect(w_blank_blips, SIGNAL(stateChanged(int)), m_config, + SLOT(set_blank_blips(int))); + + // set values + w_username->setText(m_config->username()); + w_callwords->setText(m_config->callwords()); + w_theme->setCurrentText(m_config->theme()); + w_theme_variant->setCurrentText(m_config->theme_variant()); + w_always_pre->setChecked(m_config->always_pre_enabled()); + w_chat_tick_interval->setValue(m_config->chat_tick_interval()); + w_server_alerts->setChecked(m_config->server_alerts_enabled()); + w_log_max_lines->setValue(m_config->log_max_lines()); + w_log_goes_downward->setChecked(m_config->log_goes_downward_enabled()); + w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); + w_log_music->setChecked(m_config->log_music_enabled()); + w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); + w_effects->setValue(m_config->effects_volume()); + w_system->setValue(m_config->system_volume()); + w_music->setValue(m_config->music_volume()); + w_blips->setValue(m_config->blips_volume()); + w_blip_rate->setValue(m_config->blip_rate()); + w_blank_blips->setChecked(m_config->blank_blips_enabled()); } void AOConfigPanel::refresh_theme_list() { - const QString p_prev_text = w_theme->currentText(); + const QString p_prev_text = w_theme->currentText(); - // block signals - w_theme->blockSignals(true); - w_theme->clear(); + // block signals + w_theme->blockSignals(true); + w_theme->clear(); - // themes - for (QString i_folder : QDir(QDir::currentPath() + "/base/themes").entryList(QDir::Dirs)) - { - if (i_folder == "." || i_folder == "..") - continue; - w_theme->addItem(i_folder); - } + // themes + for (QString i_folder : + QDir(QDir::currentPath() + "/base/themes").entryList(QDir::Dirs)) { + if (i_folder == "." || i_folder == "..") + continue; + w_theme->addItem(i_folder); + } - // restore previous selection - w_theme->setCurrentText(p_prev_text); + // restore previous selection + w_theme->setCurrentText(p_prev_text); - // unblock - w_theme->blockSignals(false); + // unblock + w_theme->blockSignals(false); } void AOConfigPanel::refresh_theme_variant_list() { - const QString p_prev_text = w_theme_variant->currentText(); - - // block signals - w_theme_variant->blockSignals(true); - w_theme_variant->clear(); - - // add empty entry indicating no variant chosen - w_theme_variant->addItem("", ""); - // themes - for (QString i_folder : QDir(QDir::currentPath() + "/base/themes/" + - m_config->theme() + "/variants/").entryList(QDir::Dirs)) - { - if (i_folder == "." || i_folder == "..") - continue; - w_theme_variant->addItem(i_folder, i_folder); - } - - // if the current theme does not have a variant folder for the current folder, add the variant - // to the combobox anyway. Selecting it will not do anything - if (w_theme_variant->findText(m_config->theme_variant()) == -1) - w_theme_variant->addItem(m_config->theme_variant(), m_config->theme_variant()); - // restore previous selection - w_theme_variant->setCurrentText(p_prev_text); - - // unblock - w_theme_variant->blockSignals(false); + const QString p_prev_text = w_theme_variant->currentText(); + + // block signals + w_theme_variant->blockSignals(true); + w_theme_variant->clear(); + + // add empty entry indicating no variant chosen + w_theme_variant->addItem("", ""); + // themes + for (QString i_folder : QDir(QDir::currentPath() + "/base/themes/" + + m_config->theme() + "/variants/") + .entryList(QDir::Dirs)) { + if (i_folder == "." || i_folder == "..") + continue; + w_theme_variant->addItem(i_folder, i_folder); + } + + // if the current theme does not have a variant folder for the current folder, + // add the variant to the combobox anyway. Selecting it will not do anything + if (w_theme_variant->findText(m_config->theme_variant()) == -1) + w_theme_variant->addItem(m_config->theme_variant(), + m_config->theme_variant()); + // restore previous selection + w_theme_variant->setCurrentText(p_prev_text); + + // unblock + w_theme_variant->blockSignals(false); } void AOConfigPanel::on_reload_theme_clicked() { - qDebug() << "reload theme clicked"; - emit reload_theme(); + qDebug() << "reload theme clicked"; + emit reload_theme(); } void AOConfigPanel::on_effects_value_changed(int p_num) { - w_effects_value->setText(QString::number(p_num) + "%"); + w_effects_value->setText(QString::number(p_num) + "%"); } void AOConfigPanel::on_system_value_changed(int p_num) { - w_system_value->setText(QString::number(p_num) + "%"); + w_system_value->setText(QString::number(p_num) + "%"); } void AOConfigPanel::on_music_value_changed(int p_num) { - w_music_value->setText(QString::number(p_num) + "%"); + w_music_value->setText(QString::number(p_num) + "%"); } void AOConfigPanel::on_blips_value_changed(int p_num) { - w_blips_value->setText(QString::number(p_num) + "%"); + w_blips_value->setText(QString::number(p_num) + "%"); } void AOConfigPanel::on_config_reload_theme_requested() { - refresh_theme_list(); - refresh_theme_variant_list(); + refresh_theme_list(); + refresh_theme_variant_list(); } diff --git a/aoconfigpanel.h b/aoconfigpanel.h index ff85b1fd6..7473bed9e 100644 --- a/aoconfigpanel.h +++ b/aoconfigpanel.h @@ -1,76 +1,75 @@ #ifndef AOCONFIGPANEL_H #define AOCONFIGPANEL_H // qt -#include -#include +#include #include +#include +#include #include -#include -#include #include +#include #include -#include +#include // src -#include "aoguiloader.h" #include "aoconfig.h" +#include "aoguiloader.h" -class AOConfigPanel : public QWidget -{ - Q_OBJECT +class AOConfigPanel : public QWidget { + Q_OBJECT - AOConfig *m_config = nullptr; + AOConfig *m_config = nullptr; - // general - QLineEdit *w_username = nullptr; - QLineEdit *w_callwords = nullptr; - QComboBox *w_theme = nullptr; - QPushButton *w_reload_theme = nullptr; - QComboBox *w_theme_variant = nullptr; - QCheckBox *w_always_pre = nullptr; - QSpinBox *w_chat_tick_interval = nullptr; - QCheckBox *w_server_alerts = nullptr; + // general + QLineEdit *w_username = nullptr; + QLineEdit *w_callwords = nullptr; + QComboBox *w_theme = nullptr; + QPushButton *w_reload_theme = nullptr; + QComboBox *w_theme_variant = nullptr; + QCheckBox *w_always_pre = nullptr; + QSpinBox *w_chat_tick_interval = nullptr; + QCheckBox *w_server_alerts = nullptr; - // IC Chatlog - QSpinBox *w_log_max_lines = nullptr; - QCheckBox *w_log_uses_newline = nullptr; - QCheckBox *w_log_goes_downward = nullptr; - QCheckBox *w_log_music = nullptr; - QCheckBox *w_log_is_recording = nullptr; + // IC Chatlog + QSpinBox *w_log_max_lines = nullptr; + QCheckBox *w_log_uses_newline = nullptr; + QCheckBox *w_log_goes_downward = nullptr; + QCheckBox *w_log_music = nullptr; + QCheckBox *w_log_is_recording = nullptr; - // audio - QSlider *w_effects = nullptr; - QLabel *w_effects_value = nullptr; - QSlider *w_system = nullptr; - QLabel *w_system_value = nullptr; - QSlider *w_music = nullptr; - QLabel *w_music_value = nullptr; - QSlider *w_blips = nullptr; - QLabel *w_blips_value = nullptr; - QSpinBox *w_blip_rate = nullptr; - QCheckBox *w_blank_blips = nullptr; + // audio + QSlider *w_effects = nullptr; + QLabel *w_effects_value = nullptr; + QSlider *w_system = nullptr; + QLabel *w_system_value = nullptr; + QSlider *w_music = nullptr; + QLabel *w_music_value = nullptr; + QSlider *w_blips = nullptr; + QLabel *w_blips_value = nullptr; + QSpinBox *w_blip_rate = nullptr; + QCheckBox *w_blank_blips = nullptr; - // save - QPushButton *w_save = nullptr; + // save + QPushButton *w_save = nullptr; public: - AOConfigPanel(QWidget *p_parent = nullptr); + AOConfigPanel(QWidget *p_parent = nullptr); public slots: - void on_config_reload_theme_requested(); + void on_config_reload_theme_requested(); signals: - void reload_theme(); + void reload_theme(); private: - void refresh_theme_list(); - void refresh_theme_variant_list(); + void refresh_theme_list(); + void refresh_theme_variant_list(); private slots: - void on_reload_theme_clicked(); - void on_effects_value_changed(int p_num); - void on_system_value_changed(int p_num); - void on_music_value_changed(int p_num); - void on_blips_value_changed(int p_num); + void on_reload_theme_clicked(); + void on_effects_value_changed(int p_num); + void on_system_value_changed(int p_num); + void on_music_value_changed(int p_num); + void on_blips_value_changed(int p_num); }; #endif // AOCONFIGPANEL_H diff --git a/aoemotebutton.cpp b/aoemotebutton.cpp index 81a73d7a4..d65111955 100644 --- a/aoemotebutton.cpp +++ b/aoemotebutton.cpp @@ -3,7 +3,9 @@ #include "file_functions.h" #include -AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) +AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, + int p_x, int p_y) + : QPushButton(p_parent) { ao_app = p_ao_app; @@ -16,33 +18,33 @@ AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x void AOEmoteButton::set_image(QString p_char, int p_emote, QString suffix) { QString emotion_number = QString::number(p_emote + 1); - QString image_path = ao_app->get_character_path(p_char) + "emotions/ao2/button" + emotion_number + suffix; - QString alt_path = ao_app->get_character_path(p_char) + "emotions/button" + emotion_number + suffix; - QString hover_path = ao_app->get_character_path(p_char) + "emotions/hovers/button" + emotion_number + "_hover" + suffix; + QString image_path = ao_app->get_character_path(p_char) + + "emotions/ao2/button" + emotion_number + suffix; + QString alt_path = ao_app->get_character_path(p_char) + "emotions/button" + + emotion_number + suffix; + QString hover_path = ao_app->get_character_path(p_char) + + "emotions/hovers/button" + emotion_number + "_hover" + + suffix; - if (file_exists(image_path)) - { + if (file_exists(image_path)) { this->setText(""); this->setStyleSheet("border-image:url(\"" + image_path + "\")"); } - else if (file_exists(alt_path)) - { + else if (file_exists(alt_path)) { this->setText(""); - if(file_exists(hover_path)) - { - this->setStyleSheet("QPushButton {border-image:url(\"" + alt_path + "\");}" - "QPushButton:hover {border-image:url(\"" + hover_path + "\");}"); + if (file_exists(hover_path)) { + this->setStyleSheet("QPushButton {border-image:url(\"" + alt_path + + "\");}" + "QPushButton:hover {border-image:url(\"" + + hover_path + "\");}"); } - else this->setStyleSheet("border-image:url(\"" + alt_path + "\")"); + else + this->setStyleSheet("border-image:url(\"" + alt_path + "\")"); } - else - { + else { this->setText(ao_app->get_emote_comment(p_char, p_emote)); this->setStyleSheet("border-image:url(\"\")"); } } -void AOEmoteButton::on_clicked() -{ - emote_clicked(m_id); -} +void AOEmoteButton::on_clicked() { emote_clicked(m_id); } diff --git a/aoemotebutton.h b/aoemotebutton.h index 5ea435f48..b65e3fc83 100644 --- a/aoemotebutton.h +++ b/aoemotebutton.h @@ -5,8 +5,7 @@ #include "aoapplication.h" -class AOEmoteButton : public QPushButton -{ +class AOEmoteButton : public QPushButton { Q_OBJECT public: @@ -14,8 +13,8 @@ class AOEmoteButton : public QPushButton void set_image(QString p_char, int p_emote, QString suffix); - void set_id(int p_id) {m_id = p_id;} - int get_id() {return m_id;} + void set_id(int p_id) { m_id = p_id; } + int get_id() { return m_id; } private: AOApplication *ao_app = nullptr; diff --git a/aoevidencebutton.cpp b/aoevidencebutton.cpp index 5767395f8..adb983a37 100644 --- a/aoevidencebutton.cpp +++ b/aoevidencebutton.cpp @@ -4,7 +4,9 @@ #include -AOEvidenceButton::AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) +AOEvidenceButton::AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, + int p_x, int p_y) + : QPushButton(p_parent) { ao_app = p_ao_app; @@ -39,13 +41,11 @@ void AOEvidenceButton::set_image(QString p_image) { QString image_path = ao_app->get_evidence_path() + p_image; - if (file_exists(image_path)) - { + if (file_exists(image_path)) { this->setText(""); this->setStyleSheet("border-image:url(\"" + image_path + "\")"); } - else - { + else { this->setText(p_image); this->setStyleSheet(""); } @@ -66,10 +66,7 @@ void AOEvidenceButton::set_selected(bool p_selected) ui_selected->hide(); } -void AOEvidenceButton::on_clicked() -{ - evidence_clicked(m_id); -} +void AOEvidenceButton::on_clicked() { evidence_clicked(m_id); } void AOEvidenceButton::mouseDoubleClickEvent(QMouseEvent *e) { @@ -79,19 +76,19 @@ void AOEvidenceButton::mouseDoubleClickEvent(QMouseEvent *e) void AOEvidenceButton::dragLeaveEvent(QMouseEvent *e) { - //QWidget::dragLeaveEvent(e); + // QWidget::dragLeaveEvent(e); qDebug() << "drag leave event"; } void AOEvidenceButton::dragEnterEvent(QMouseEvent *e) { - //QWidget::dragEnterEvent(e); + // QWidget::dragEnterEvent(e); qDebug() << "drag enter event"; } -void AOEvidenceButton::enterEvent(QEvent * e) +void AOEvidenceButton::enterEvent(QEvent *e) { ui_selector->show(); @@ -101,7 +98,7 @@ void AOEvidenceButton::enterEvent(QEvent * e) QPushButton::enterEvent(e); } -void AOEvidenceButton::leaveEvent(QEvent * e) +void AOEvidenceButton::leaveEvent(QEvent *e) { ui_selector->hide(); diff --git a/aoevidencebutton.h b/aoevidencebutton.h index 8af8999d6..20e3c275d 100644 --- a/aoevidencebutton.h +++ b/aoevidencebutton.h @@ -7,17 +7,17 @@ #include #include -class AOEvidenceButton : public QPushButton -{ +class AOEvidenceButton : public QPushButton { Q_OBJECT public: - AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y); + AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, + int p_y); void reset(); void set_image(QString p_image); void set_theme_image(QString p_image); - void set_id(int p_id) {m_id = p_id;} + void set_id(int p_id) { m_id = p_id; } void set_selected(bool p_selected); diff --git a/aoevidencedescription.cpp b/aoevidencedescription.cpp index 15b5af86e..f697547af 100644 --- a/aoevidencedescription.cpp +++ b/aoevidencedescription.cpp @@ -1,10 +1,11 @@ #include "aoevidencedescription.h" -AOEvidenceDescription::AOEvidenceDescription(QWidget *parent) : QPlainTextEdit(parent) +AOEvidenceDescription::AOEvidenceDescription(QWidget *parent) + : QPlainTextEdit(parent) { this->setReadOnly(true); - //connect(this, SIGNAL(returnPressed()), this, SLOT(on_enter_pressed())); + // connect(this, SIGNAL(returnPressed()), this, SLOT(on_enter_pressed())); } void AOEvidenceDescription::mouseDoubleClickEvent(QMouseEvent *e) @@ -14,8 +15,4 @@ void AOEvidenceDescription::mouseDoubleClickEvent(QMouseEvent *e) this->setReadOnly(false); } -void AOEvidenceDescription::on_enter_pressed() -{ - this->setReadOnly(true); -} - +void AOEvidenceDescription::on_enter_pressed() { this->setReadOnly(true); } diff --git a/aoevidencedescription.h b/aoevidencedescription.h index 3fedabce7..e0c924821 100644 --- a/aoevidencedescription.h +++ b/aoevidencedescription.h @@ -3,8 +3,7 @@ #include -class AOEvidenceDescription : public QPlainTextEdit -{ +class AOEvidenceDescription : public QPlainTextEdit { Q_OBJECT public: AOEvidenceDescription(QWidget *parent); @@ -17,7 +16,6 @@ class AOEvidenceDescription : public QPlainTextEdit private slots: void on_enter_pressed(); - }; #endif // AOEVIDENCEDESCRIPTION_H diff --git a/aoevidencedisplay.cpp b/aoevidencedisplay.cpp index 3fa032b4a..77c4e508d 100644 --- a/aoevidencedisplay.cpp +++ b/aoevidencedisplay.cpp @@ -2,11 +2,12 @@ #include "aoevidencedisplay.h" -#include "file_functions.h" #include "datatypes.h" +#include "file_functions.h" #include "misc_functions.h" -AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) +AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) + : QLabel(p_parent) { ao_app = p_ao_app; @@ -14,10 +15,12 @@ AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) evidence_icon = new QLabel(this); sfx_player = new AOSfxPlayer(this, ao_app); - connect(evidence_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); + connect(evidence_movie, SIGNAL(frameChanged(int)), this, + SLOT(frame_change(int))); } -void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_side) +void AOEvidenceDisplay::show_evidence(QString p_evidence_image, + bool is_left_side) { this->reset(); @@ -29,18 +32,17 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid QString gif_name; QString icon_identifier; - if (is_left_side) - { + if (is_left_side) { icon_identifier = "left_evidence_icon"; gif_name = "evidence_appear_left.gif"; } - else - { + else { icon_identifier = "right_evidence_icon"; gif_name = "evidence_appear_right.gif"; } - pos_size_type icon_dimensions = ao_app->get_element_dimensions(icon_identifier, "courtroom_design.ini"); + pos_size_type icon_dimensions = + ao_app->get_element_dimensions(icon_identifier, "courtroom_design.ini"); evidence_icon->move(icon_dimensions.x, icon_dimensions.y); evidence_icon->resize(icon_dimensions.width, icon_dimensions.height); @@ -48,7 +50,7 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid QString f_path = ao_app->get_image_path(gif_name); evidence_movie->setFileName(f_path); - if(evidence_movie->frameCount() < 1) + if (evidence_movie->frameCount() < 1) return; this->setMovie(evidence_movie); @@ -59,9 +61,8 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid void AOEvidenceDisplay::frame_change(int p_frame) { - if (p_frame == (evidence_movie->frameCount() - 1)) - { - //we need this or else the last frame wont show + if (p_frame == (evidence_movie->frameCount() - 1)) { + // we need this or else the last frame wont show delay(evidence_movie->nextFrameDelay()); evidence_movie->stop(); @@ -79,9 +80,4 @@ void AOEvidenceDisplay::reset() this->clear(); } -QLabel* AOEvidenceDisplay::get_evidence_icon() -{ - return evidence_icon; -} - - +QLabel *AOEvidenceDisplay::get_evidence_icon() { return evidence_icon; } diff --git a/aoevidencedisplay.h b/aoevidencedisplay.h index 199012100..8c31c1bb2 100644 --- a/aoevidencedisplay.h +++ b/aoevidencedisplay.h @@ -5,18 +5,17 @@ #include #include "aoapplication.h" -#include "aosfxplayer.h" #include "aopixmap.h" +#include "aosfxplayer.h" -class AOEvidenceDisplay : public QLabel -{ +class AOEvidenceDisplay : public QLabel { Q_OBJECT public: AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app); void show_evidence(QString p_evidence_image, bool is_left_side); - QLabel* get_evidence_icon(); + QLabel *get_evidence_icon(); void reset(); private: diff --git a/aoexception.cpp b/aoexception.cpp index 334f1275a..456fbd64e 100644 --- a/aoexception.cpp +++ b/aoexception.cpp @@ -1,10 +1,8 @@ #include "aoexception.h" -AOException::AOException(QString p_msg) - : m_msg(p_msg) -{} +AOException::AOException(QString p_msg) : m_msg(p_msg) {} const char *AOException::what() const noexcept { - return m_msg.toStdString().c_str(); + return m_msg.toStdString().c_str(); } diff --git a/aoexception.h b/aoexception.h index de0a55d84..d29046d15 100644 --- a/aoexception.h +++ b/aoexception.h @@ -5,17 +5,16 @@ #include -class AOException : public std::exception -{ +class AOException : public std::exception { public: - AOException() = default; - AOException(QString p_msg); - virtual ~AOException() noexcept = default; + AOException() = default; + AOException(QString p_msg); + virtual ~AOException() noexcept = default; - virtual const char *what() const noexcept; + virtual const char *what() const noexcept; private: - QString m_msg; + QString m_msg; }; #endif // AOEXCEPTION_HPP diff --git a/aoguiloader.cpp b/aoguiloader.cpp index e657a790e..d99a486cc 100644 --- a/aoguiloader.cpp +++ b/aoguiloader.cpp @@ -5,26 +5,24 @@ AOGuiLoader::AOGuiLoader(QObject *p_parent) : QUiLoader(p_parent) { - // padding + // padding } QWidget *AOGuiLoader::load_from_file(QString p_file_path, QWidget *p_parent) { - QWidget *r_widget = nullptr; + QWidget *r_widget = nullptr; - QFile f_file(p_file_path); - if (f_file.open(QIODevice::ReadOnly)) - { - r_widget = load(&f_file, p_parent); + QFile f_file(p_file_path); + if (f_file.open(QIODevice::ReadOnly)) { + r_widget = load(&f_file, p_parent); - // lazily replace the parent's layout with our own - if (p_parent != nullptr) - { - QVBoxLayout *f_parent_layout = new QVBoxLayout(p_parent); - f_parent_layout->addWidget(r_widget); - p_parent->setLayout(f_parent_layout); - } + // lazily replace the parent's layout with our own + if (p_parent != nullptr) { + QVBoxLayout *f_parent_layout = new QVBoxLayout(p_parent); + f_parent_layout->addWidget(r_widget); + p_parent->setLayout(f_parent_layout); } + } - return r_widget; + return r_widget; } diff --git a/aoguiloader.h b/aoguiloader.h index d2fc965a4..4413cd05c 100644 --- a/aoguiloader.h +++ b/aoguiloader.h @@ -5,17 +5,15 @@ #include // we could make a smarter system but let's keep it simple ;) -#define AO_GUI_WIDGET(p_type, p_name) \ - findChild(p_name) +#define AO_GUI_WIDGET(p_type, p_name) findChild(p_name) -class AOGuiLoader : public QUiLoader -{ - Q_OBJECT +class AOGuiLoader : public QUiLoader { + Q_OBJECT public: - AOGuiLoader(QObject *p_parent = nullptr); + AOGuiLoader(QObject *p_parent = nullptr); - QWidget *load_from_file(QString p_file_path, QWidget *p_parent = nullptr); + QWidget *load_from_file(QString p_file_path, QWidget *p_parent = nullptr); }; #endif diff --git a/aoimage.h b/aoimage.h index a60c371e0..97f903cff 100644 --- a/aoimage.h +++ b/aoimage.h @@ -1,4 +1,4 @@ -//This class represents a static theme-dependent image +// This class represents a static theme-dependent image #ifndef AOIMAGE_H #define AOIMAGE_H @@ -8,8 +8,7 @@ #include -class AOImage : public QLabel -{ +class AOImage : public QLabel { public: AOImage(QWidget *parent, AOApplication *p_ao_app); diff --git a/aolabel.h b/aolabel.h index fef51e816..b54e2a56f 100644 --- a/aolabel.h +++ b/aolabel.h @@ -5,9 +5,7 @@ #include - -class AOLabel : public QLabel -{ +class AOLabel : public QLabel { public: AOLabel(QWidget *parent, AOApplication *p_ao_app); diff --git a/aolineedit.cpp b/aolineedit.cpp index f6026e142..211d9f784 100644 --- a/aolineedit.cpp +++ b/aolineedit.cpp @@ -15,7 +15,4 @@ void AOLineEdit::mouseDoubleClickEvent(QMouseEvent *e) this->setReadOnly(false); } -void AOLineEdit::on_enter_pressed() -{ - this->setReadOnly(true); -} +void AOLineEdit::on_enter_pressed() { this->setReadOnly(true); } diff --git a/aolineedit.h b/aolineedit.h index ce17680d5..70f1f978b 100644 --- a/aolineedit.h +++ b/aolineedit.h @@ -4,8 +4,7 @@ #include #include -class AOLineEdit : public QLineEdit -{ +class AOLineEdit : public QLineEdit { Q_OBJECT public: @@ -19,8 +18,6 @@ class AOLineEdit : public QLineEdit private slots: void on_enter_pressed(); - - }; #endif // AOLINEEDIT_H diff --git a/aomovie.cpp b/aomovie.cpp index af8d681c3..42f8e2bc5 100644 --- a/aomovie.cpp +++ b/aomovie.cpp @@ -1,7 +1,7 @@ #include "aomovie.h" -#include "file_functions.h" #include "courtroom.h" +#include "file_functions.h" #include "misc_functions.h" AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) @@ -15,15 +15,9 @@ AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); } -AOMovie::~AOMovie() -{ - delete m_movie; -} +AOMovie::~AOMovie() { delete m_movie; } -void AOMovie::set_play_once(bool p_play_once) -{ - play_once = p_play_once; -} +void AOMovie::set_play_once(bool p_play_once) { play_once = p_play_once; } void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) { @@ -32,8 +26,8 @@ void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) QString file_path = ""; // Remove ! at the beginning of p_file if needed - // This is an indicator that the file is not selectable in the current theme (variant) but - // is still usable by other people + // This is an indicator that the file is not selectable in the current theme + // (variant) but is still usable by other people if (p_file.length() > 0 && p_file.at(0) == "!") p_file = p_file.remove(0, 1); @@ -43,27 +37,23 @@ void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) else custom_path = ao_app->get_character_path(p_char) + p_file + "_bubble"; - QStringList f_paths{ - custom_path, - ao_app->get_character_path(p_char) + "overlay/" + p_file, - ao_app->get_base_path() + "themes/" + p_custom_theme + "/" + p_file, - ao_app->get_theme_variant_path() + p_file, - ao_app->get_theme_path() + p_file, - ao_app->get_default_theme_path() + p_file, - ao_app->get_theme_variant_path() + "placeholder", - ao_app->get_theme_path() + "placeholder", - ao_app->get_default_theme_path() + "placeholder" - }; - - for(auto &f_file : f_paths) - { + QStringList f_paths{custom_path, + ao_app->get_character_path(p_char) + "overlay/" + p_file, + ao_app->get_base_path() + "themes/" + p_custom_theme + + "/" + p_file, + ao_app->get_theme_variant_path() + p_file, + ao_app->get_theme_path() + p_file, + ao_app->get_default_theme_path() + p_file, + ao_app->get_theme_variant_path() + "placeholder", + ao_app->get_theme_path() + "placeholder", + ao_app->get_default_theme_path() + "placeholder"}; + + for (auto &f_file : f_paths) { bool found = false; - for (auto &ext : decltype(f_vec){".webp", ".apng", ".gif", ".png"}) - { + for (auto &ext : decltype(f_vec){".webp", ".apng", ".gif", ".png"}) { QString fullPath = f_file + ext; found = file_exists(fullPath); - if (found) - { + if (found) { file_path = fullPath; break; } @@ -89,14 +79,13 @@ void AOMovie::stop() void AOMovie::frame_change(int n_frame) { - if (n_frame == (m_movie->frameCount() - 1) && play_once) - { - //we need this or else the last frame wont show + if (n_frame == (m_movie->frameCount() - 1) && play_once) { + // we need this or else the last frame wont show delay(m_movie->nextFrameDelay()); this->stop(); - //signal connected to courtroom object, let it figure out what to do + // signal connected to courtroom object, let it figure out what to do emit done(); } } diff --git a/aomovie.h b/aomovie.h index c890783ce..4f9b3e2e8 100644 --- a/aomovie.h +++ b/aomovie.h @@ -7,8 +7,7 @@ class Courtroom; class AOApplication; -class AOMovie : public QLabel -{ +class AOMovie : public QLabel { Q_OBJECT public: diff --git a/aomusicplayer.cpp b/aomusicplayer.cpp index 47f2a26f8..70682d186 100644 --- a/aomusicplayer.cpp +++ b/aomusicplayer.cpp @@ -6,34 +6,34 @@ AOMusicPlayer::AOMusicPlayer(QObject *p_parent, AOApplication *p_ao_app) : AOAbstractPlayer(p_parent, p_ao_app) -{} +{ +} void AOMusicPlayer::play(QString p_file) { - QString f_file = ao_app->get_music_path(p_file); + QString f_file = ao_app->get_music_path(p_file); - stop(); + stop(); - m_file = f_file; + m_file = f_file; - try { // create new song - AOBassHandle *handle = new AOBassHandle(m_file, false, this); - connect(this, &AOMusicPlayer::new_volume, handle, &AOBassHandle::set_volume); - connect(this, &AOMusicPlayer::stopping, handle, &AOBassHandle::stop); + try { // create new song + AOBassHandle *handle = new AOBassHandle(m_file, false, this); + connect(this, &AOMusicPlayer::new_volume, handle, + &AOBassHandle::set_volume); + connect(this, &AOMusicPlayer::stopping, handle, &AOBassHandle::stop); - // delete previous - if (m_handle) - delete m_handle; + // delete previous + if (m_handle) + delete m_handle; - m_handle = handle; - m_handle->set_volume(get_volume()); - m_handle->play(); - } catch(const std::exception &e_exception) { - qDebug() << e_exception.what(); - } + m_handle = handle; + m_handle->set_volume(get_volume()); + m_handle->play(); + } + catch (const std::exception &e_exception) { + qDebug() << e_exception.what(); + } } -void AOMusicPlayer::stop() -{ - emit stopping(); -} +void AOMusicPlayer::stop() { emit stopping(); } diff --git a/aomusicplayer.h b/aomusicplayer.h index f8f48716a..07b6fced1 100644 --- a/aomusicplayer.h +++ b/aomusicplayer.h @@ -3,19 +3,18 @@ #include "aoabstractplayer.h" -class AOMusicPlayer : public AOAbstractPlayer -{ - Q_OBJECT +class AOMusicPlayer : public AOAbstractPlayer { + Q_OBJECT public: - AOMusicPlayer(QObject *p_parent, AOApplication *p_ao_app); + AOMusicPlayer(QObject *p_parent, AOApplication *p_ao_app); - void play(QString p_file); - void stop(); + void play(QString p_file); + void stop(); private: - AOBassHandle* m_handle = nullptr; - QString m_file; + AOBassHandle *m_handle = nullptr; + QString m_file; }; #endif // AOMUSICPLAYER_H diff --git a/aonotearea.cpp b/aonotearea.cpp index 679aa3756..2d3bf72dd 100644 --- a/aonotearea.cpp +++ b/aonotearea.cpp @@ -1,18 +1,19 @@ -#include "aonotepicker.h" #include "aonotearea.h" +#include "aonotepicker.h" #include "courtroom.h" #include -AONoteArea::AONoteArea(QWidget *p_parent, AOApplication *p_ao_app) : AOImage(p_parent, p_ao_app) +AONoteArea::AONoteArea(QWidget *p_parent, AOApplication *p_ao_app) + : AOImage(p_parent, p_ao_app) { ao_app = p_ao_app; } void Courtroom::on_add_button_clicked() { - if(ui_note_area->m_layout->count() > 6) + if (ui_note_area->m_layout->count() > 6) return; AONotePicker *f_notepicker = new AONotePicker(ui_note_area, ao_app); @@ -27,7 +28,7 @@ void Courtroom::on_add_button_clicked() f_notepicker->m_layout = f_layout; f_notepicker->m_delete_button = f_delete; f_notepicker->m_hover = f_hover; - f_notepicker->setProperty("index", ui_note_area->m_layout->count()-1); + f_notepicker->setProperty("index", ui_note_area->m_layout->count() - 1); f_button->set_image("note_edit.png"); f_delete->set_image("note_delete.png"); @@ -44,17 +45,18 @@ void Courtroom::on_add_button_clicked() f_notepicker->setLayout(f_layout); ui_note_area->m_layout->addWidget(f_notepicker); - if(contains_add_button) - { - ui_note_area->m_layout->removeWidget(ui_note_area->add_button); - ui_note_area->m_layout->addWidget(ui_note_area->add_button); - set_note_files(); - } + if (contains_add_button) { + ui_note_area->m_layout->removeWidget(ui_note_area->add_button); + ui_note_area->m_layout->addWidget(ui_note_area->add_button); + set_note_files(); + } set_dropdown(f_line, "[LINE EDIT]"); - connect(f_button, SIGNAL(clicked(bool)), this, SLOT(on_set_file_button_clicked())); - connect(f_delete, SIGNAL(clicked(bool)), this, SLOT(on_delete_button_clicked())); + connect(f_button, SIGNAL(clicked(bool)), this, + SLOT(on_set_file_button_clicked())); + connect(f_delete, SIGNAL(clicked(bool)), this, + SLOT(on_delete_button_clicked())); connect(f_hover, SIGNAL(clicked(bool)), this, SLOT(on_file_selected())); } @@ -63,33 +65,31 @@ void Courtroom::set_note_files() QString filename = ao_app->get_base_path() + "configs/filesabstract.ini"; QFile config_file(filename); - if(!config_file.open(QIODevice::ReadOnly | QIODevice::Text)) - { - qDebug() << "Couldn't open" << filename; - return; + if (!config_file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qDebug() << "Couldn't open" << filename; + return; } QTextStream in(&config_file); QByteArray t = ""; - for(int i = 0; i < ui_note_area->m_layout->count()-1; ++i) - { - AONotePicker *f_notepicker = static_cast(ui_note_area->m_layout->itemAt(i)->widget()); - QString f_filestring = f_notepicker->real_file; - QString f_filename = f_notepicker->m_line->text(); - - t += QString::number(i) + " = " + f_filestring + " = " + f_filename + "\n\n"; - } + for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) { + AONotePicker *f_notepicker = static_cast( + ui_note_area->m_layout->itemAt(i)->widget()); + QString f_filestring = f_notepicker->real_file; + QString f_filename = f_notepicker->m_line->text(); + t += + QString::number(i) + " = " + f_filestring + " = " + f_filename + "\n\n"; + } config_file.close(); QFile ex(filename); - if(!ex.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) - { - qDebug() << "Couldn't open" << filename; - return; + if (!ex.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + qDebug() << "Couldn't open" << filename; + return; } ex.write(t); diff --git a/aonotearea.h b/aonotearea.h index 69a86ccbe..cc57ca4ae 100644 --- a/aonotearea.h +++ b/aonotearea.h @@ -2,18 +2,17 @@ #define AONOTEAREA_HPP #include -#include #include -#include +#include #include +#include #include -#include #include +#include #include "aoapplication.h" -class AONoteArea : public AOImage -{ +class AONoteArea : public AOImage { Q_OBJECT public: @@ -25,7 +24,6 @@ class AONoteArea : public AOImage private: AOApplication *ao_app; void set_layout(); - }; #endif // AONOTEAREA_HPP diff --git a/aonotepad.cpp b/aonotepad.cpp index 8a1297168..6af28cce5 100644 --- a/aonotepad.cpp +++ b/aonotepad.cpp @@ -1,5 +1,6 @@ #include "aonotepad.h" -AONotepad::AONotepad(QWidget* p_parent, AOApplication *p_ao_app) - : QTextEdit(p_parent), ao_app(p_ao_app) -{} +AONotepad::AONotepad(QWidget *p_parent, AOApplication *p_ao_app) + : QTextEdit(p_parent), ao_app(p_ao_app) +{ +} diff --git a/aonotepad.h b/aonotepad.h index de04e182f..306fff7d0 100644 --- a/aonotepad.h +++ b/aonotepad.h @@ -5,12 +5,11 @@ #include "aoapplication.h" -class AONotepad : public QTextEdit -{ +class AONotepad : public QTextEdit { Q_OBJECT public: - AONotepad(QWidget* p_parent, AOApplication *p_ao_app); + AONotepad(QWidget *p_parent, AOApplication *p_ao_app); private: AOApplication *ao_app = nullptr; diff --git a/aonotepicker.cpp b/aonotepicker.cpp index 92dc067aa..635dafe74 100644 --- a/aonotepicker.cpp +++ b/aonotepicker.cpp @@ -2,48 +2,47 @@ #include "courtroom.h" -#include #include +#include -AONotePicker::AONotePicker(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) +AONotePicker::AONotePicker(QWidget *p_parent, AOApplication *p_ao_app) + : QLabel(p_parent) { ao_app = p_ao_app; } void Courtroom::on_file_selected() { - for(int i=0; i < ui_note_area->m_layout->count() -1; ++i) - { - AONotePicker *f_notepicker = static_cast(ui_note_area->m_layout->itemAt(i)->widget()); - f_notepicker->m_hover->set_image("note_select.png"); - } - - AOButton *f_button = static_cast(sender()); - AONotePicker *f_notepicker = static_cast(f_button->parent()); + for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) { + AONotePicker *f_notepicker = static_cast( + ui_note_area->m_layout->itemAt(i)->widget()); + f_notepicker->m_hover->set_image("note_select.png"); + } + + AOButton *f_button = static_cast(sender()); + AONotePicker *f_notepicker = static_cast(f_button->parent()); current_file = f_notepicker->real_file; load_note(); f_button->set_image("note_select_selected.png"); } - void Courtroom::on_set_file_button_clicked() { - AOButton *f_button = static_cast(sender()); - AONotePicker *f_notepicker = static_cast(f_button->parent()); + AOButton *f_button = static_cast(sender()); + AONotePicker *f_notepicker = static_cast(f_button->parent()); QString f_filename = QFileDialog::getOpenFileName(this, "Open File"); - if(f_filename != "") - { - f_notepicker->m_line->setText(f_filename); - f_notepicker->real_file = f_filename; + if (f_filename != "") { + f_notepicker->m_line->setText(f_filename); + f_notepicker->real_file = f_filename; - set_note_files(); - } + set_note_files(); + } } void Courtroom::on_delete_button_clicked() { - AOButton *f_button = static_cast(sender()); - AONotePicker *f_notepicker = static_cast(f_button->parent()); + AOButton *f_button = static_cast(sender()); + AONotePicker *f_notepicker = static_cast(f_button->parent()); ui_note_area->m_layout->removeWidget(f_notepicker); delete f_notepicker; set_note_files(); diff --git a/aonotepicker.h b/aonotepicker.h index c9f0592c6..f2173ad42 100644 --- a/aonotepicker.h +++ b/aonotepicker.h @@ -1,13 +1,12 @@ #ifndef AONOTEPICKER_HPP #define AONOTEPICKER_HPP +#include "aobutton.h" +#include #include #include -#include -#include "aobutton.h" -class AONotePicker : public QLabel -{ +class AONotePicker : public QLabel { Q_OBJECT public: diff --git a/aopacket.cpp b/aopacket.cpp index 73036f58b..304523e50 100644 --- a/aopacket.cpp +++ b/aopacket.cpp @@ -10,8 +10,7 @@ AOPacket::AOPacket(QString p_packet_string) m_header = packet_contents.at(0); - for(int n_string = 1 ; n_string < packet_contents.size() - 1 ; ++n_string) - { + for (int n_string = 1; n_string < packet_contents.size() - 1; ++n_string) { m_contents.append(packet_contents.at(n_string)); } } @@ -26,14 +25,12 @@ QString AOPacket::to_string() { QString f_string = m_header; - for (QString i_string : m_contents) - { + for (QString i_string : m_contents) { f_string += ("#" + i_string); } f_string += "#%"; - if (encrypted) return "#" + f_string; else @@ -56,10 +53,12 @@ void AOPacket::decrypt_header(unsigned int p_key) void AOPacket::net_encode() { - for (int n_element = 0 ; n_element < m_contents.size() ; ++n_element) - { + for (int n_element = 0; n_element < m_contents.size(); ++n_element) { QString f_element = m_contents.at(n_element); - f_element.replace("#", "").replace("%", "").replace("$", "").replace("&", ""); + f_element.replace("#", "") + .replace("%", "") + .replace("$", "") + .replace("&", ""); m_contents.removeAt(n_element); m_contents.insert(n_element, f_element); @@ -68,13 +67,14 @@ void AOPacket::net_encode() void AOPacket::net_decode() { - for (int n_element = 0 ; n_element < m_contents.size() ; ++n_element) - { + for (int n_element = 0; n_element < m_contents.size(); ++n_element) { QString f_element = m_contents.at(n_element); - f_element.replace("", "#").replace("", "%").replace("", "$").replace("", "&"); + f_element.replace("", "#") + .replace("", "%") + .replace("", "$") + .replace("", "&"); m_contents.removeAt(n_element); m_contents.insert(n_element, f_element); } } - diff --git a/aopacket.h b/aopacket.h index 6e328ac86..d1e8aad27 100644 --- a/aopacket.h +++ b/aopacket.h @@ -4,14 +4,13 @@ #include #include -class AOPacket -{ +class AOPacket { public: AOPacket(QString p_packet_string); AOPacket(QString header, QStringList &p_contents); - QString get_header() {return m_header;} - QStringList &get_contents() {return m_contents;} + QString get_header() { return m_header; } + QStringList &get_contents() { return m_contents; } QString to_string(); void encrypt_header(unsigned int p_key); diff --git a/aopixmap.cpp b/aopixmap.cpp index dc875d9d5..69692bfbc 100644 --- a/aopixmap.cpp +++ b/aopixmap.cpp @@ -2,25 +2,24 @@ AOPixmap::AOPixmap(QPixmap p_pixmap) : m_pixmap(p_pixmap) { - if (m_pixmap.isNull()) - { - clear(); - } + if (m_pixmap.isNull()) { + clear(); + } } -AOPixmap::AOPixmap(QString p_file_path) : AOPixmap(QPixmap(p_file_path)) -{ - -} +AOPixmap::AOPixmap(QString p_file_path) : AOPixmap(QPixmap(p_file_path)) {} void AOPixmap::clear() { - m_pixmap = QPixmap(1, 1); - m_pixmap.fill(Qt::transparent); + m_pixmap = QPixmap(1, 1); + m_pixmap.fill(Qt::transparent); } QPixmap AOPixmap::scale_to_size(QSize p_size) { - bool f_is_pixmap_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); - return m_pixmap.scaled(p_size, Qt::IgnoreAspectRatio, f_is_pixmap_larger ? Qt::SmoothTransformation : Qt::FastTransformation); + bool f_is_pixmap_larger = + m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); + return m_pixmap.scaled(p_size, Qt::IgnoreAspectRatio, + f_is_pixmap_larger ? Qt::SmoothTransformation + : Qt::FastTransformation); } diff --git a/aopixmap.h b/aopixmap.h index 057d9d810..8d0bf543c 100644 --- a/aopixmap.h +++ b/aopixmap.h @@ -3,18 +3,17 @@ // qt #include -class AOPixmap -{ +class AOPixmap { public: - AOPixmap(QPixmap p_pixmap = QPixmap()); - AOPixmap(QString p_file_path); + AOPixmap(QPixmap p_pixmap = QPixmap()); + AOPixmap(QString p_file_path); - void clear(); + void clear(); - QPixmap scale_to_size(QSize p_size); + QPixmap scale_to_size(QSize p_size); private: - QPixmap m_pixmap; + QPixmap m_pixmap; }; #endif diff --git a/aoscene.cpp b/aoscene.cpp index e85400f83..a12554192 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -6,48 +6,47 @@ #include #include -AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent), ao_app(p_ao_app) +AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) + : QLabel(parent), ao_app(p_ao_app) { - m_reader = new QMovie(this); - setMovie(m_reader); + m_reader = new QMovie(this); + setMovie(m_reader); } void AOScene::set_image(QString p_image) { - QString target_path = ao_app->get_default_background_path() + p_image; + QString target_path = ao_app->get_default_background_path() + p_image; - // background specific path - QString background_path = ao_app->get_background_path() + p_image; + // background specific path + QString background_path = ao_app->get_background_path() + p_image; - for (auto &ext : QVector{".webp", ".apng", ".gif", ".png"}) - { - QString full_background_path = background_path + ext; + for (auto &ext : QVector{".webp", ".apng", ".gif", ".png"}) { + QString full_background_path = background_path + ext; - if (file_exists(full_background_path)) - { - target_path = full_background_path; - break; - } + if (file_exists(full_background_path)) { + target_path = full_background_path; + break; } + } - // do not update the movie if we're using the same file - if (m_reader->fileName() == target_path) - return; + // do not update the movie if we're using the same file + if (m_reader->fileName() == target_path) + return; - m_reader->stop(); - delete m_reader; + m_reader->stop(); + delete m_reader; - m_reader = new QMovie(this); - m_reader->setScaledSize(size()); - m_reader->setFileName(target_path); - setMovie(m_reader); - m_reader->start(); + m_reader = new QMovie(this); + m_reader->setScaledSize(size()); + m_reader->setFileName(target_path); + setMovie(m_reader); + m_reader->start(); } void AOScene::combo_resize(QSize p_size) { - resize(p_size); - m_reader->stop(); - m_reader->setScaledSize(p_size); - m_reader->start(); + resize(p_size); + m_reader->stop(); + m_reader->setScaledSize(p_size); + m_reader->start(); } diff --git a/aoscene.h b/aoscene.h index 5202dab91..ddf3f3e8f 100644 --- a/aoscene.h +++ b/aoscene.h @@ -6,19 +6,18 @@ class Courtroom; class AOApplication; -class AOScene : public QLabel -{ - Q_OBJECT +class AOScene : public QLabel { + Q_OBJECT public: - AOScene(QWidget *parent, AOApplication *p_ao_app); + AOScene(QWidget *parent, AOApplication *p_ao_app); - void set_image(QString p_image); - void combo_resize(QSize p_size); + void set_image(QString p_image); + void combo_resize(QSize p_size); private: - AOApplication *ao_app = nullptr; - QMovie *m_reader = nullptr; + AOApplication *ao_app = nullptr; + QMovie *m_reader = nullptr; }; #endif // AOSCENE_H diff --git a/aosfxplayer.cpp b/aosfxplayer.cpp index cd191a67e..5617772d9 100644 --- a/aosfxplayer.cpp +++ b/aosfxplayer.cpp @@ -6,24 +6,23 @@ AOSfxPlayer::AOSfxPlayer(QObject *p_parent, AOApplication *p_ao_app) : AOAbstractPlayer(p_parent, p_ao_app) -{} +{ +} void AOSfxPlayer::play(QString p_name) { - QString f_file = ao_app->get_sounds_path() + p_name.toLower(); + QString f_file = ao_app->get_sounds_path() + p_name.toLower(); - try { - AOBassHandle *handle = new AOBassHandle(f_file, true, this); - connect(this, &AOSfxPlayer::new_volume, handle, &AOBassHandle::set_volume); - connect(this, &AOSfxPlayer::stopping, handle, &AOBassHandle::stop); - handle->set_volume(get_volume()); - handle->play(); - } catch(const std::exception &e_exception) { - qDebug() << e_exception.what(); - } + try { + AOBassHandle *handle = new AOBassHandle(f_file, true, this); + connect(this, &AOSfxPlayer::new_volume, handle, &AOBassHandle::set_volume); + connect(this, &AOSfxPlayer::stopping, handle, &AOBassHandle::stop); + handle->set_volume(get_volume()); + handle->play(); + } + catch (const std::exception &e_exception) { + qDebug() << e_exception.what(); + } } -void AOSfxPlayer::stop() -{ - emit stopping(); -} +void AOSfxPlayer::stop() { emit stopping(); } diff --git a/aosfxplayer.h b/aosfxplayer.h index ceb358c92..21c26967e 100644 --- a/aosfxplayer.h +++ b/aosfxplayer.h @@ -3,15 +3,14 @@ #include "aoabstractplayer.h" -class AOSfxPlayer : public AOAbstractPlayer -{ - Q_OBJECT +class AOSfxPlayer : public AOAbstractPlayer { + Q_OBJECT public: - AOSfxPlayer(QObject *p_parent, AOApplication *p_ao_app); + AOSfxPlayer(QObject *p_parent, AOApplication *p_ao_app); - void play(QString p_file); - void stop(); + void play(QString p_file); + void stop(); }; #endif // AOSFXPLAYER_H diff --git a/aoshoutplayer.cpp b/aoshoutplayer.cpp index 70c6eeb5a..ceae0a47c 100644 --- a/aoshoutplayer.cpp +++ b/aoshoutplayer.cpp @@ -5,37 +5,37 @@ AOShoutPlayer::AOShoutPlayer(QObject *p_parent, AOApplication *p_ao_app) : AOAbstractPlayer(p_parent, p_ao_app) -{} - -void AOShoutPlayer::play(QString p_name, QString p_char) { - QString f_file; - - QString char_path = ao_app->get_character_path(p_char) + p_name.toLower(); - QString theme_path = ao_app->get_image_path(p_name.toLower()); - - qDebug() << char_path; - qDebug() << theme_path; - - if(file_exists(char_path)) - f_file = char_path; - else if (file_exists(theme_path)) - f_file = theme_path; - else - f_file = ""; - - try { - AOBassHandle *handle = new AOBassHandle(f_file, true, this); - connect(this, &AOShoutPlayer::new_volume, handle, &AOBassHandle::set_volume); - connect(this, &AOShoutPlayer::stopping, handle, &AOBassHandle::stop); - handle->set_volume(get_volume()); - handle->play(); - } catch(const std::exception &e_exception) { - qDebug() << e_exception.what(); - } } -void AOShoutPlayer::stop() +void AOShoutPlayer::play(QString p_name, QString p_char) { - emit stopping(); + QString f_file; + + QString char_path = ao_app->get_character_path(p_char) + p_name.toLower(); + QString theme_path = ao_app->get_image_path(p_name.toLower()); + + qDebug() << char_path; + qDebug() << theme_path; + + if (file_exists(char_path)) + f_file = char_path; + else if (file_exists(theme_path)) + f_file = theme_path; + else + f_file = ""; + + try { + AOBassHandle *handle = new AOBassHandle(f_file, true, this); + connect(this, &AOShoutPlayer::new_volume, handle, + &AOBassHandle::set_volume); + connect(this, &AOShoutPlayer::stopping, handle, &AOBassHandle::stop); + handle->set_volume(get_volume()); + handle->play(); + } + catch (const std::exception &e_exception) { + qDebug() << e_exception.what(); + } } + +void AOShoutPlayer::stop() { emit stopping(); } diff --git a/aoshoutplayer.h b/aoshoutplayer.h index de55a2786..534c8e23c 100644 --- a/aoshoutplayer.h +++ b/aoshoutplayer.h @@ -3,15 +3,14 @@ #include "aoabstractplayer.h" -class AOShoutPlayer : public AOAbstractPlayer -{ - Q_OBJECT +class AOShoutPlayer : public AOAbstractPlayer { + Q_OBJECT public: - AOShoutPlayer(QObject *p_parent, AOApplication *p_ao_app); + AOShoutPlayer(QObject *p_parent, AOApplication *p_ao_app); - void play(QString p_name, QString p_char); - void stop(); + void play(QString p_name, QString p_char); + void stop(); }; #endif // AOSHOUTPLAYER_HPP diff --git a/aotextarea.cpp b/aotextarea.cpp index c64769c15..394328099 100644 --- a/aotextarea.cpp +++ b/aotextarea.cpp @@ -1,30 +1,28 @@ #include "aotextarea.h" +#include +#include #include #include -#include -#include -AOTextArea::AOTextArea(QWidget *p_parent) : QTextBrowser(p_parent) -{ - -} +AOTextArea::AOTextArea(QWidget *p_parent) : QTextBrowser(p_parent) {} void AOTextArea::append_chatmessage(QString p_name, QString p_message) { const QTextCursor old_cursor = this->textCursor(); const int old_scrollbar_value = this->verticalScrollBar()->value(); - const bool is_scrolled_down = old_scrollbar_value == this->verticalScrollBar()->maximum(); + const bool is_scrolled_down = + old_scrollbar_value == this->verticalScrollBar()->maximum(); this->moveCursor(QTextCursor::End); this->append(""); this->insertHtml("" + p_name.toHtmlEscaped() + ": "); - //cheap workarounds ahoy + // cheap workarounds ahoy p_message += " "; QString result = p_message.toHtmlEscaped().replace("\n", "
"); - result = result.replace(omnis_dank_url_regex, "\\1" ); + result = result.replace(omnis_dank_url_regex, "\\1"); this->insertHtml(result); @@ -35,7 +33,8 @@ void AOTextArea::append_error(QString p_message) { const QTextCursor old_cursor = this->textCursor(); const int old_scrollbar_value = this->verticalScrollBar()->value(); - const bool is_scrolled_down = old_scrollbar_value == this->verticalScrollBar()->maximum(); + const bool is_scrolled_down = + old_scrollbar_value == this->verticalScrollBar()->maximum(); this->moveCursor(QTextCursor::End); @@ -43,25 +42,26 @@ void AOTextArea::append_error(QString p_message) p_message += " "; QString result = p_message.replace("\n", "
"); - result = result.replace(omnis_dank_url_regex, "\\1" ); + result = result.replace(omnis_dank_url_regex, "\\1"); this->insertHtml("" + result + ""); this->auto_scroll(old_cursor, old_scrollbar_value, is_scrolled_down); } -void AOTextArea::auto_scroll(QTextCursor old_cursor, int old_scrollbar_value, bool is_scrolled_down) +void AOTextArea::auto_scroll(QTextCursor old_cursor, int old_scrollbar_value, + bool is_scrolled_down) { - if (old_cursor.hasSelection() || !is_scrolled_down) - { - // The user has selected text or scrolled away from the bottom: maintain position. - this->setTextCursor(old_cursor); - this->verticalScrollBar()->setValue(old_scrollbar_value); + if (old_cursor.hasSelection() || !is_scrolled_down) { + // The user has selected text or scrolled away from the bottom: maintain + // position. + this->setTextCursor(old_cursor); + this->verticalScrollBar()->setValue(old_scrollbar_value); } - else - { - // The user hasn't selected any text and the scrollbar is at the bottom: scroll to the bottom. - this->moveCursor(QTextCursor::End); - this->verticalScrollBar()->setValue(this->verticalScrollBar()->maximum()); + else { + // The user hasn't selected any text and the scrollbar is at the bottom: + // scroll to the bottom. + this->moveCursor(QTextCursor::End); + this->verticalScrollBar()->setValue(this->verticalScrollBar()->maximum()); } } diff --git a/aotextarea.h b/aotextarea.h index 32635fdb0..237ede660 100644 --- a/aotextarea.h +++ b/aotextarea.h @@ -3,8 +3,7 @@ #include -class AOTextArea : public QTextBrowser -{ +class AOTextArea : public QTextBrowser { public: AOTextArea(QWidget *p_parent = nullptr); @@ -14,7 +13,8 @@ class AOTextArea : public QTextBrowser private: const QRegExp omnis_dank_url_regex = QRegExp("\\b(https?://\\S+\\.\\S+)\\b"); - void auto_scroll(QTextCursor old_cursor, int scrollbar_value, bool is_scrolled_down); + void auto_scroll(QTextCursor old_cursor, int scrollbar_value, + bool is_scrolled_down); }; #endif // AOTEXTAREA_H diff --git a/aotimer.cpp b/aotimer.cpp index 77f0657ee..eff2099e5 100644 --- a/aotimer.cpp +++ b/aotimer.cpp @@ -1,9 +1,10 @@ #include "aotimer.h" #include -AOTimer::AOTimer(QWidget* p_parent) : QTextEdit(p_parent) +AOTimer::AOTimer(QWidget *p_parent) : QTextEdit(p_parent) { - // Adapted from: https://stackoverflow.com/questions/36679708/how-to-make-a-chronometer-in-qt-c + // Adapted from: + // https://stackoverflow.com/questions/36679708/how-to-make-a-chronometer-in-qt-c setStyleSheet("QLabel { color : white; }"); setFrameStyle(QFrame::NoFrame); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -25,52 +26,41 @@ void AOTimer::update_time() // Check for underflow/overflow - // This one checks overflows, as the updated manual timer would appear to have a past time - // compared to the manual timer a step ago (represented by old_manual_timer) - if (manual_timer.get_timestep_length() > 0) - { - if (manual_timer.get_time().operator<(old_manual_timer.get_time())) - { - set_time(QTime(0, 0)); - firing_timer.stop(); - redraw(); - return; - } + // This one checks overflows, as the updated manual timer would appear to have + // a past time compared to the manual timer a step ago (represented by + // old_manual_timer) + if (manual_timer.get_timestep_length() > 0) { + if (manual_timer.get_time().operator<(old_manual_timer.get_time())) { + set_time(QTime(0, 0)); + firing_timer.stop(); + redraw(); + return; + } } - // This one checks underflows, as the updated manual timer would appear to have a future time - // compared to the manual timer a step ago (represented by old_manual_timer) - else if (manual_timer.get_timestep_length() < 0) - { - if (manual_timer.get_time().operator>(old_manual_timer.get_time())) - { - set_time(QTime(0, 0)); - firing_timer.stop(); - redraw(); - return; - } + // This one checks underflows, as the updated manual timer would appear to + // have a future time compared to the manual timer a step ago (represented by + // old_manual_timer) + else if (manual_timer.get_timestep_length() < 0) { + if (manual_timer.get_time().operator>(old_manual_timer.get_time())) { + set_time(QTime(0, 0)); + firing_timer.stop(); + redraw(); + return; + } } - // If no overflow/underflow, now update the old manual timer so it matches the current timer - // Redraw and return + // If no overflow/underflow, now update the old manual timer so it matches the + // current timer Redraw and return old_manual_timer.perform_timestep(); redraw(); firing_timer.start(firing_timer_length); } -void AOTimer::set() -{ - set_time(start_time); -} +void AOTimer::set() { set_time(start_time); } -void AOTimer::resume() -{ - firing_timer.start(firing_timer_length); -} +void AOTimer::resume() { firing_timer.start(firing_timer_length); } -void AOTimer::pause() -{ - firing_timer.stop(); -} +void AOTimer::pause() { firing_timer.stop(); } void AOTimer::redraw() { @@ -93,22 +83,26 @@ void AOTimer::set_timestep_length(int new_timestep_length) void AOTimer::set_firing_interval(int new_firing_interval) { /* - * Update the firing interval of the steptimer for all future timesteps. If a timestep - * has started when this function is scheduled to run, the current timestep will be - * readapted to finish in this new firing interval as follows: + * Update the firing interval of the steptimer for all future timesteps. If a + * timestep has started when this function is scheduled to run, the current + * timestep will be readapted to finish in this new firing interval as + * follows: * - Assume length x of the 'current' timestep with orig has elapsed. - * - Then, the current timestep will be adapted to have length max(0, new_interval-x) + * - Then, the current timestep will be adapted to have length max(0, + * new_interval-x) */ // Update time spent so far and new future firing interval - time_spent_in_timestep += (firing_timer_length - firing_timer.remainingTime()); + time_spent_in_timestep += + (firing_timer_length - firing_timer.remainingTime()); firing_timer_length = new_firing_interval; - // For this timestep however, the firing interval will be shorter than firing_timer_length - // to account for the fact the timer may have already been running a bit in this timestep + // For this timestep however, the firing interval will be shorter than + // firing_timer_length to account for the fact the timer may have already been + // running a bit in this timestep if (new_firing_interval < time_spent_in_timestep) firing_timer.start(0); else - firing_timer.start(new_firing_interval-time_spent_in_timestep); + firing_timer.start(new_firing_interval - time_spent_in_timestep); } void AOTimer::set_concentrate_mode() diff --git a/aotimer.h b/aotimer.h index 82ea4c0cc..6487a748e 100644 --- a/aotimer.h +++ b/aotimer.h @@ -1,29 +1,34 @@ #ifndef AOTIMER_H #define AOTIMER_H +#include #include -#include #include #include -#include -#include +#include #include +#include class ManualTimer { QTime current_time; int timestep_length; - public: - QTime get_time() { return current_time; } - int get_timestep_length() { return timestep_length; } - - void set_time(QTime new_time) { current_time = new_time; } - void set_timestep_length(int new_timestep_length) { timestep_length = new_timestep_length ;} - void perform_timestep() {current_time = current_time.addMSecs(timestep_length);} +public: + QTime get_time() { return current_time; } + int get_timestep_length() { return timestep_length; } + + void set_time(QTime new_time) { current_time = new_time; } + void set_timestep_length(int new_timestep_length) + { + timestep_length = new_timestep_length; + } + void perform_timestep() + { + current_time = current_time.addMSecs(timestep_length); + } }; -class AOTimer : public QTextEdit -{ +class AOTimer : public QTextEdit { Q_OBJECT public: diff --git a/audio_functions.cpp b/audio_functions.cpp index 38e4afa61..79bb34ac3 100644 --- a/audio_functions.cpp +++ b/audio_functions.cpp @@ -1,59 +1,56 @@ #include "courtroom.h" -bool Courtroom::is_audio_muted() -{ - return m_audio_mute; -} +bool Courtroom::is_audio_muted() { return m_audio_mute; } void Courtroom::set_audio_mute_enabled(bool p_enabled) { - if (m_audio_mute == p_enabled) - return; - m_audio_mute = p_enabled; + if (m_audio_mute == p_enabled) + return; + m_audio_mute = p_enabled; - // restore volume - set_effects_volume(ao_config->effects_volume()); - set_music_volume(ao_config->music_volume()); - set_blips_volume(ao_config->blips_volume()); + // restore volume + set_effects_volume(ao_config->effects_volume()); + set_music_volume(ao_config->music_volume()); + set_blips_volume(ao_config->blips_volume()); } void Courtroom::set_effects_volume(int p_volume) { - m_effects_player->set_volume(is_audio_muted() ? 0 : p_volume); - m_shouts_player->set_volume(is_audio_muted() ? 0 : p_volume); + m_effects_player->set_volume(is_audio_muted() ? 0 : p_volume); + m_shouts_player->set_volume(is_audio_muted() ? 0 : p_volume); } void Courtroom::set_system_volume(int p_volume) { - m_system_player->set_volume(p_volume); + m_system_player->set_volume(p_volume); } void Courtroom::set_music_volume(int p_volume) { - m_music_player->set_volume(is_audio_muted() ? 0 : p_volume); + m_music_player->set_volume(is_audio_muted() ? 0 : p_volume); } void Courtroom::set_blips_volume(int p_volume) { - m_blips_player->set_volume(is_audio_muted() ? 0 : p_volume); + m_blips_player->set_volume(is_audio_muted() ? 0 : p_volume); } void Courtroom::on_config_effects_volume_changed(int p_volume) { - set_effects_volume(p_volume); + set_effects_volume(p_volume); } void Courtroom::on_config_system_volume_changed(int p_volume) { - set_system_volume(p_volume); + set_system_volume(p_volume); } void Courtroom::on_config_music_volume_changed(int p_volume) { - set_music_volume(p_volume); + set_music_volume(p_volume); } void Courtroom::on_config_blips_volume_changed(int p_volume) { - set_blips_volume(p_volume); + set_blips_volume(p_volume); } diff --git a/bass.h b/bass.h index 06195de2f..89e0a6316 100644 --- a/bass.h +++ b/bass.h @@ -1,8 +1,8 @@ /* - BASS 2.4 C/C++ header file - Copyright (c) 1999-2016 Un4seen Developments Ltd. + BASS 2.4 C/C++ header file + Copyright (c) 1999-2016 Un4seen Developments Ltd. - See the BASS.CHM file for more detailed documentation + See the BASS.CHM file for more detailed documentation */ #ifndef BASS_H @@ -27,19 +27,19 @@ typedef int BOOL; #define FALSE 0 #endif #define LOBYTE(a) (BYTE)(a) -#define HIBYTE(a) (BYTE)((a)>>8) +#define HIBYTE(a) (BYTE)((a) >> 8) #define LOWORD(a) (WORD)(a) -#define HIWORD(a) (WORD)((a)>>16) -#define MAKEWORD(a,b) (WORD)(((a)&0xff)|((b)<<8)) -#define MAKELONG(a,b) (DWORD)(((a)&0xffff)|((b)<<16)) +#define HIWORD(a) (WORD)((a) >> 16) +#define MAKEWORD(a, b) (WORD)(((a)&0xff) | ((b) << 8)) +#define MAKELONG(a, b) (DWORD)(((a)&0xffff) | ((b) << 16)) #endif #ifdef __cplusplus extern "C" { #endif -#define BASSVERSION 0x204 // API version -#define BASSVERSIONTEXT "2.4" +#define BASSVERSION 0x204 // API version +#define BASSVERSIONTEXT "2.4" #ifndef BASSDEF #define BASSDEF(f) WINAPI f @@ -47,509 +47,531 @@ extern "C" { #define NOBASSOVERLOADS #endif -typedef DWORD HMUSIC; // MOD music handle -typedef DWORD HSAMPLE; // sample handle -typedef DWORD HCHANNEL; // playing sample's channel handle -typedef DWORD HSTREAM; // sample stream handle -typedef DWORD HRECORD; // recording handle -typedef DWORD HSYNC; // synchronizer handle -typedef DWORD HDSP; // DSP handle -typedef DWORD HFX; // DX8 effect handle -typedef DWORD HPLUGIN; // Plugin handle +typedef DWORD HMUSIC; // MOD music handle +typedef DWORD HSAMPLE; // sample handle +typedef DWORD HCHANNEL; // playing sample's channel handle +typedef DWORD HSTREAM; // sample stream handle +typedef DWORD HRECORD; // recording handle +typedef DWORD HSYNC; // synchronizer handle +typedef DWORD HDSP; // DSP handle +typedef DWORD HFX; // DX8 effect handle +typedef DWORD HPLUGIN; // Plugin handle // Error codes returned by BASS_ErrorGetCode -#define BASS_OK 0 // all is OK -#define BASS_ERROR_MEM 1 // memory error -#define BASS_ERROR_FILEOPEN 2 // can't open the file -#define BASS_ERROR_DRIVER 3 // can't find a free/valid driver -#define BASS_ERROR_BUFLOST 4 // the sample buffer was lost -#define BASS_ERROR_HANDLE 5 // invalid handle -#define BASS_ERROR_FORMAT 6 // unsupported sample format -#define BASS_ERROR_POSITION 7 // invalid position -#define BASS_ERROR_INIT 8 // BASS_Init has not been successfully called -#define BASS_ERROR_START 9 // BASS_Start has not been successfully called -#define BASS_ERROR_SSL 10 // SSL/HTTPS support isn't available -#define BASS_ERROR_ALREADY 14 // already initialized/paused/whatever -#define BASS_ERROR_NOCHAN 18 // can't get a free channel -#define BASS_ERROR_ILLTYPE 19 // an illegal type was specified -#define BASS_ERROR_ILLPARAM 20 // an illegal parameter was specified -#define BASS_ERROR_NO3D 21 // no 3D support -#define BASS_ERROR_NOEAX 22 // no EAX support -#define BASS_ERROR_DEVICE 23 // illegal device number -#define BASS_ERROR_NOPLAY 24 // not playing -#define BASS_ERROR_FREQ 25 // illegal sample rate -#define BASS_ERROR_NOTFILE 27 // the stream is not a file stream -#define BASS_ERROR_NOHW 29 // no hardware voices available -#define BASS_ERROR_EMPTY 31 // the MOD music has no sequence data -#define BASS_ERROR_NONET 32 // no internet connection could be opened -#define BASS_ERROR_CREATE 33 // couldn't create the file -#define BASS_ERROR_NOFX 34 // effects are not available -#define BASS_ERROR_NOTAVAIL 37 // requested data is not available -#define BASS_ERROR_DECODE 38 // the channel is/isn't a "decoding channel" -#define BASS_ERROR_DX 39 // a sufficient DirectX version is not installed -#define BASS_ERROR_TIMEOUT 40 // connection timedout -#define BASS_ERROR_FILEFORM 41 // unsupported file format -#define BASS_ERROR_SPEAKER 42 // unavailable speaker -#define BASS_ERROR_VERSION 43 // invalid BASS version (used by add-ons) -#define BASS_ERROR_CODEC 44 // codec is not available/supported -#define BASS_ERROR_ENDED 45 // the channel/file has ended -#define BASS_ERROR_BUSY 46 // the device is busy -#define BASS_ERROR_UNKNOWN -1 // some other mystery problem +#define BASS_OK 0 // all is OK +#define BASS_ERROR_MEM 1 // memory error +#define BASS_ERROR_FILEOPEN 2 // can't open the file +#define BASS_ERROR_DRIVER 3 // can't find a free/valid driver +#define BASS_ERROR_BUFLOST 4 // the sample buffer was lost +#define BASS_ERROR_HANDLE 5 // invalid handle +#define BASS_ERROR_FORMAT 6 // unsupported sample format +#define BASS_ERROR_POSITION 7 // invalid position +#define BASS_ERROR_INIT 8 // BASS_Init has not been successfully called +#define BASS_ERROR_START 9 // BASS_Start has not been successfully called +#define BASS_ERROR_SSL 10 // SSL/HTTPS support isn't available +#define BASS_ERROR_ALREADY 14 // already initialized/paused/whatever +#define BASS_ERROR_NOCHAN 18 // can't get a free channel +#define BASS_ERROR_ILLTYPE 19 // an illegal type was specified +#define BASS_ERROR_ILLPARAM 20 // an illegal parameter was specified +#define BASS_ERROR_NO3D 21 // no 3D support +#define BASS_ERROR_NOEAX 22 // no EAX support +#define BASS_ERROR_DEVICE 23 // illegal device number +#define BASS_ERROR_NOPLAY 24 // not playing +#define BASS_ERROR_FREQ 25 // illegal sample rate +#define BASS_ERROR_NOTFILE 27 // the stream is not a file stream +#define BASS_ERROR_NOHW 29 // no hardware voices available +#define BASS_ERROR_EMPTY 31 // the MOD music has no sequence data +#define BASS_ERROR_NONET 32 // no internet connection could be opened +#define BASS_ERROR_CREATE 33 // couldn't create the file +#define BASS_ERROR_NOFX 34 // effects are not available +#define BASS_ERROR_NOTAVAIL 37 // requested data is not available +#define BASS_ERROR_DECODE 38 // the channel is/isn't a "decoding channel" +#define BASS_ERROR_DX 39 // a sufficient DirectX version is not installed +#define BASS_ERROR_TIMEOUT 40 // connection timedout +#define BASS_ERROR_FILEFORM 41 // unsupported file format +#define BASS_ERROR_SPEAKER 42 // unavailable speaker +#define BASS_ERROR_VERSION 43 // invalid BASS version (used by add-ons) +#define BASS_ERROR_CODEC 44 // codec is not available/supported +#define BASS_ERROR_ENDED 45 // the channel/file has ended +#define BASS_ERROR_BUSY 46 // the device is busy +#define BASS_ERROR_UNKNOWN -1 // some other mystery problem // BASS_SetConfig options -#define BASS_CONFIG_BUFFER 0 -#define BASS_CONFIG_UPDATEPERIOD 1 -#define BASS_CONFIG_GVOL_SAMPLE 4 -#define BASS_CONFIG_GVOL_STREAM 5 -#define BASS_CONFIG_GVOL_MUSIC 6 -#define BASS_CONFIG_CURVE_VOL 7 -#define BASS_CONFIG_CURVE_PAN 8 -#define BASS_CONFIG_FLOATDSP 9 -#define BASS_CONFIG_3DALGORITHM 10 -#define BASS_CONFIG_NET_TIMEOUT 11 -#define BASS_CONFIG_NET_BUFFER 12 -#define BASS_CONFIG_PAUSE_NOPLAY 13 -#define BASS_CONFIG_NET_PREBUF 15 -#define BASS_CONFIG_NET_PASSIVE 18 -#define BASS_CONFIG_REC_BUFFER 19 -#define BASS_CONFIG_NET_PLAYLIST 21 -#define BASS_CONFIG_MUSIC_VIRTUAL 22 -#define BASS_CONFIG_VERIFY 23 -#define BASS_CONFIG_UPDATETHREADS 24 -#define BASS_CONFIG_DEV_BUFFER 27 -#define BASS_CONFIG_VISTA_TRUEPOS 30 -#define BASS_CONFIG_IOS_MIXAUDIO 34 -#define BASS_CONFIG_DEV_DEFAULT 36 -#define BASS_CONFIG_NET_READTIMEOUT 37 -#define BASS_CONFIG_VISTA_SPEAKERS 38 -#define BASS_CONFIG_IOS_SPEAKER 39 -#define BASS_CONFIG_MF_DISABLE 40 -#define BASS_CONFIG_HANDLES 41 -#define BASS_CONFIG_UNICODE 42 -#define BASS_CONFIG_SRC 43 -#define BASS_CONFIG_SRC_SAMPLE 44 -#define BASS_CONFIG_ASYNCFILE_BUFFER 45 -#define BASS_CONFIG_OGG_PRESCAN 47 -#define BASS_CONFIG_MF_VIDEO 48 -#define BASS_CONFIG_AIRPLAY 49 -#define BASS_CONFIG_DEV_NONSTOP 50 -#define BASS_CONFIG_IOS_NOCATEGORY 51 -#define BASS_CONFIG_VERIFY_NET 52 -#define BASS_CONFIG_DEV_PERIOD 53 -#define BASS_CONFIG_FLOAT 54 -#define BASS_CONFIG_NET_SEEK 56 +#define BASS_CONFIG_BUFFER 0 +#define BASS_CONFIG_UPDATEPERIOD 1 +#define BASS_CONFIG_GVOL_SAMPLE 4 +#define BASS_CONFIG_GVOL_STREAM 5 +#define BASS_CONFIG_GVOL_MUSIC 6 +#define BASS_CONFIG_CURVE_VOL 7 +#define BASS_CONFIG_CURVE_PAN 8 +#define BASS_CONFIG_FLOATDSP 9 +#define BASS_CONFIG_3DALGORITHM 10 +#define BASS_CONFIG_NET_TIMEOUT 11 +#define BASS_CONFIG_NET_BUFFER 12 +#define BASS_CONFIG_PAUSE_NOPLAY 13 +#define BASS_CONFIG_NET_PREBUF 15 +#define BASS_CONFIG_NET_PASSIVE 18 +#define BASS_CONFIG_REC_BUFFER 19 +#define BASS_CONFIG_NET_PLAYLIST 21 +#define BASS_CONFIG_MUSIC_VIRTUAL 22 +#define BASS_CONFIG_VERIFY 23 +#define BASS_CONFIG_UPDATETHREADS 24 +#define BASS_CONFIG_DEV_BUFFER 27 +#define BASS_CONFIG_VISTA_TRUEPOS 30 +#define BASS_CONFIG_IOS_MIXAUDIO 34 +#define BASS_CONFIG_DEV_DEFAULT 36 +#define BASS_CONFIG_NET_READTIMEOUT 37 +#define BASS_CONFIG_VISTA_SPEAKERS 38 +#define BASS_CONFIG_IOS_SPEAKER 39 +#define BASS_CONFIG_MF_DISABLE 40 +#define BASS_CONFIG_HANDLES 41 +#define BASS_CONFIG_UNICODE 42 +#define BASS_CONFIG_SRC 43 +#define BASS_CONFIG_SRC_SAMPLE 44 +#define BASS_CONFIG_ASYNCFILE_BUFFER 45 +#define BASS_CONFIG_OGG_PRESCAN 47 +#define BASS_CONFIG_MF_VIDEO 48 +#define BASS_CONFIG_AIRPLAY 49 +#define BASS_CONFIG_DEV_NONSTOP 50 +#define BASS_CONFIG_IOS_NOCATEGORY 51 +#define BASS_CONFIG_VERIFY_NET 52 +#define BASS_CONFIG_DEV_PERIOD 53 +#define BASS_CONFIG_FLOAT 54 +#define BASS_CONFIG_NET_SEEK 56 // BASS_SetConfigPtr options -#define BASS_CONFIG_NET_AGENT 16 -#define BASS_CONFIG_NET_PROXY 17 -#define BASS_CONFIG_IOS_NOTIFY 46 +#define BASS_CONFIG_NET_AGENT 16 +#define BASS_CONFIG_NET_PROXY 17 +#define BASS_CONFIG_IOS_NOTIFY 46 // BASS_Init flags -#define BASS_DEVICE_8BITS 1 // 8 bit -#define BASS_DEVICE_MONO 2 // mono -#define BASS_DEVICE_3D 4 // enable 3D functionality -#define BASS_DEVICE_16BITS 8 // limit output to 16 bit -#define BASS_DEVICE_LATENCY 0x100 // calculate device latency (BASS_INFO struct) -#define BASS_DEVICE_CPSPEAKERS 0x400 // detect speakers via Windows control panel -#define BASS_DEVICE_SPEAKERS 0x800 // force enabling of speaker assignment -#define BASS_DEVICE_NOSPEAKER 0x1000 // ignore speaker arrangement -#define BASS_DEVICE_DMIX 0x2000 // use ALSA "dmix" plugin -#define BASS_DEVICE_FREQ 0x4000 // set device sample rate -#define BASS_DEVICE_STEREO 0x8000 // limit output to stereo +#define BASS_DEVICE_8BITS 1 // 8 bit +#define BASS_DEVICE_MONO 2 // mono +#define BASS_DEVICE_3D 4 // enable 3D functionality +#define BASS_DEVICE_16BITS 8 // limit output to 16 bit +#define BASS_DEVICE_LATENCY 0x100 // calculate device latency (BASS_INFO struct) +#define BASS_DEVICE_CPSPEAKERS \ + 0x400 // detect speakers via Windows control panel +#define BASS_DEVICE_SPEAKERS 0x800 // force enabling of speaker assignment +#define BASS_DEVICE_NOSPEAKER 0x1000 // ignore speaker arrangement +#define BASS_DEVICE_DMIX 0x2000 // use ALSA "dmix" plugin +#define BASS_DEVICE_FREQ 0x4000 // set device sample rate +#define BASS_DEVICE_STEREO 0x8000 // limit output to stereo // DirectSound interfaces (for use with BASS_GetDSoundObject) -#define BASS_OBJECT_DS 1 // IDirectSound -#define BASS_OBJECT_DS3DL 2 // IDirectSound3DListener +#define BASS_OBJECT_DS 1 // IDirectSound +#define BASS_OBJECT_DS3DL 2 // IDirectSound3DListener // Device info structure typedef struct { -#if defined(_WIN32_WCE) || (WINAPI_FAMILY && WINAPI_FAMILY!=WINAPI_FAMILY_DESKTOP_APP) - const wchar_t *name; // description - const wchar_t *driver; // driver +#if defined(_WIN32_WCE) || \ + (WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) + const wchar_t *name; // description + const wchar_t *driver; // driver #else - const char *name; // description - const char *driver; // driver + const char *name; // description + const char *driver; // driver #endif - DWORD flags; + DWORD flags; } BASS_DEVICEINFO; // BASS_DEVICEINFO flags -#define BASS_DEVICE_ENABLED 1 -#define BASS_DEVICE_DEFAULT 2 -#define BASS_DEVICE_INIT 4 - -#define BASS_DEVICE_TYPE_MASK 0xff000000 -#define BASS_DEVICE_TYPE_NETWORK 0x01000000 -#define BASS_DEVICE_TYPE_SPEAKERS 0x02000000 -#define BASS_DEVICE_TYPE_LINE 0x03000000 -#define BASS_DEVICE_TYPE_HEADPHONES 0x04000000 -#define BASS_DEVICE_TYPE_MICROPHONE 0x05000000 -#define BASS_DEVICE_TYPE_HEADSET 0x06000000 -#define BASS_DEVICE_TYPE_HANDSET 0x07000000 -#define BASS_DEVICE_TYPE_DIGITAL 0x08000000 -#define BASS_DEVICE_TYPE_SPDIF 0x09000000 -#define BASS_DEVICE_TYPE_HDMI 0x0a000000 -#define BASS_DEVICE_TYPE_DISPLAYPORT 0x40000000 - -// BASS_GetDeviceInfo flags -#define BASS_DEVICES_AIRPLAY 0x1000000 - +#define BASS_DEVICE_ENABLED 1 +#define BASS_DEVICE_DEFAULT 2 +#define BASS_DEVICE_INIT 4 + +#define BASS_DEVICE_TYPE_MASK 0xff000000 +#define BASS_DEVICE_TYPE_NETWORK 0x01000000 +#define BASS_DEVICE_TYPE_SPEAKERS 0x02000000 +#define BASS_DEVICE_TYPE_LINE 0x03000000 +#define BASS_DEVICE_TYPE_HEADPHONES 0x04000000 +#define BASS_DEVICE_TYPE_MICROPHONE 0x05000000 +#define BASS_DEVICE_TYPE_HEADSET 0x06000000 +#define BASS_DEVICE_TYPE_HANDSET 0x07000000 +#define BASS_DEVICE_TYPE_DIGITAL 0x08000000 +#define BASS_DEVICE_TYPE_SPDIF 0x09000000 +#define BASS_DEVICE_TYPE_HDMI 0x0a000000 +#define BASS_DEVICE_TYPE_DISPLAYPORT 0x40000000 + +// BASS_GetDeviceInfo flags +#define BASS_DEVICES_AIRPLAY 0x1000000 + typedef struct { - DWORD flags; // device capabilities (DSCAPS_xxx flags) - DWORD hwsize; // size of total device hardware memory - DWORD hwfree; // size of free device hardware memory - DWORD freesam; // number of free sample slots in the hardware - DWORD free3d; // number of free 3D sample slots in the hardware - DWORD minrate; // min sample rate supported by the hardware - DWORD maxrate; // max sample rate supported by the hardware - BOOL eax; // device supports EAX? (always FALSE if BASS_DEVICE_3D was not used) - DWORD minbuf; // recommended minimum buffer length in ms (requires BASS_DEVICE_LATENCY) - DWORD dsver; // DirectSound version - DWORD latency; // delay (in ms) before start of playback (requires BASS_DEVICE_LATENCY) - DWORD initflags; // BASS_Init "flags" parameter - DWORD speakers; // number of speakers available - DWORD freq; // current output rate + DWORD flags; // device capabilities (DSCAPS_xxx flags) + DWORD hwsize; // size of total device hardware memory + DWORD hwfree; // size of free device hardware memory + DWORD freesam; // number of free sample slots in the hardware + DWORD free3d; // number of free 3D sample slots in the hardware + DWORD minrate; // min sample rate supported by the hardware + DWORD maxrate; // max sample rate supported by the hardware + BOOL + eax; // device supports EAX? (always FALSE if BASS_DEVICE_3D was not used) + DWORD minbuf; // recommended minimum buffer length in ms (requires + // BASS_DEVICE_LATENCY) + DWORD dsver; // DirectSound version + DWORD latency; // delay (in ms) before start of playback (requires + // BASS_DEVICE_LATENCY) + DWORD initflags; // BASS_Init "flags" parameter + DWORD speakers; // number of speakers available + DWORD freq; // current output rate } BASS_INFO; // BASS_INFO flags (from DSOUND.H) -#define DSCAPS_CONTINUOUSRATE 0x00000010 // supports all sample rates between min/maxrate -#define DSCAPS_EMULDRIVER 0x00000020 // device does NOT have hardware DirectSound support -#define DSCAPS_CERTIFIED 0x00000040 // device driver has been certified by Microsoft -#define DSCAPS_SECONDARYMONO 0x00000100 // mono -#define DSCAPS_SECONDARYSTEREO 0x00000200 // stereo -#define DSCAPS_SECONDARY8BIT 0x00000400 // 8 bit -#define DSCAPS_SECONDARY16BIT 0x00000800 // 16 bit +#define DSCAPS_CONTINUOUSRATE \ + 0x00000010 // supports all sample rates between min/maxrate +#define DSCAPS_EMULDRIVER \ + 0x00000020 // device does NOT have hardware DirectSound support +#define DSCAPS_CERTIFIED \ + 0x00000040 // device driver has been certified by Microsoft +#define DSCAPS_SECONDARYMONO 0x00000100 // mono +#define DSCAPS_SECONDARYSTEREO 0x00000200 // stereo +#define DSCAPS_SECONDARY8BIT 0x00000400 // 8 bit +#define DSCAPS_SECONDARY16BIT 0x00000800 // 16 bit // Recording device info structure typedef struct { - DWORD flags; // device capabilities (DSCCAPS_xxx flags) - DWORD formats; // supported standard formats (WAVE_FORMAT_xxx flags) - DWORD inputs; // number of inputs - BOOL singlein; // TRUE = only 1 input can be set at a time - DWORD freq; // current input rate + DWORD flags; // device capabilities (DSCCAPS_xxx flags) + DWORD formats; // supported standard formats (WAVE_FORMAT_xxx flags) + DWORD inputs; // number of inputs + BOOL singlein; // TRUE = only 1 input can be set at a time + DWORD freq; // current input rate } BASS_RECORDINFO; // BASS_RECORDINFO flags (from DSOUND.H) -#define DSCCAPS_EMULDRIVER DSCAPS_EMULDRIVER // device does NOT have hardware DirectSound recording support -#define DSCCAPS_CERTIFIED DSCAPS_CERTIFIED // device driver has been certified by Microsoft +#define DSCCAPS_EMULDRIVER \ + DSCAPS_EMULDRIVER // device does NOT have hardware DirectSound recording + // support +#define DSCCAPS_CERTIFIED \ + DSCAPS_CERTIFIED // device driver has been certified by Microsoft // defines for formats field of BASS_RECORDINFO (from MMSYSTEM.H) #ifndef WAVE_FORMAT_1M08 -#define WAVE_FORMAT_1M08 0x00000001 /* 11.025 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_1S08 0x00000002 /* 11.025 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_1M16 0x00000004 /* 11.025 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_1S16 0x00000008 /* 11.025 kHz, Stereo, 16-bit */ -#define WAVE_FORMAT_2M08 0x00000010 /* 22.05 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_2S08 0x00000020 /* 22.05 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_2M16 0x00000040 /* 22.05 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_2S16 0x00000080 /* 22.05 kHz, Stereo, 16-bit */ -#define WAVE_FORMAT_4M08 0x00000100 /* 44.1 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_4S08 0x00000200 /* 44.1 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_4M16 0x00000400 /* 44.1 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_4S16 0x00000800 /* 44.1 kHz, Stereo, 16-bit */ +#define WAVE_FORMAT_1M08 0x00000001 /* 11.025 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_1S08 0x00000002 /* 11.025 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_1M16 0x00000004 /* 11.025 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_1S16 0x00000008 /* 11.025 kHz, Stereo, 16-bit */ +#define WAVE_FORMAT_2M08 0x00000010 /* 22.05 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_2S08 0x00000020 /* 22.05 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_2M16 0x00000040 /* 22.05 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_2S16 0x00000080 /* 22.05 kHz, Stereo, 16-bit */ +#define WAVE_FORMAT_4M08 0x00000100 /* 44.1 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_4S08 0x00000200 /* 44.1 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_4M16 0x00000400 /* 44.1 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_4S16 0x00000800 /* 44.1 kHz, Stereo, 16-bit */ #endif // Sample info structure typedef struct { - DWORD freq; // default playback rate - float volume; // default volume (0-1) - float pan; // default pan (-1=left, 0=middle, 1=right) - DWORD flags; // BASS_SAMPLE_xxx flags - DWORD length; // length (in bytes) - DWORD max; // maximum simultaneous playbacks - DWORD origres; // original resolution bits - DWORD chans; // number of channels - DWORD mingap; // minimum gap (ms) between creating channels - DWORD mode3d; // BASS_3DMODE_xxx mode - float mindist; // minimum distance - float maxdist; // maximum distance - DWORD iangle; // angle of inside projection cone - DWORD oangle; // angle of outside projection cone - float outvol; // delta-volume outside the projection cone - DWORD vam; // voice allocation/management flags (BASS_VAM_xxx) - DWORD priority; // priority (0=lowest, 0xffffffff=highest) + DWORD freq; // default playback rate + float volume; // default volume (0-1) + float pan; // default pan (-1=left, 0=middle, 1=right) + DWORD flags; // BASS_SAMPLE_xxx flags + DWORD length; // length (in bytes) + DWORD max; // maximum simultaneous playbacks + DWORD origres; // original resolution bits + DWORD chans; // number of channels + DWORD mingap; // minimum gap (ms) between creating channels + DWORD mode3d; // BASS_3DMODE_xxx mode + float mindist; // minimum distance + float maxdist; // maximum distance + DWORD iangle; // angle of inside projection cone + DWORD oangle; // angle of outside projection cone + float outvol; // delta-volume outside the projection cone + DWORD vam; // voice allocation/management flags (BASS_VAM_xxx) + DWORD priority; // priority (0=lowest, 0xffffffff=highest) } BASS_SAMPLE; -#define BASS_SAMPLE_8BITS 1 // 8 bit -#define BASS_SAMPLE_FLOAT 256 // 32 bit floating-point -#define BASS_SAMPLE_MONO 2 // mono -#define BASS_SAMPLE_LOOP 4 // looped -#define BASS_SAMPLE_3D 8 // 3D functionality -#define BASS_SAMPLE_SOFTWARE 16 // not using hardware mixing -#define BASS_SAMPLE_MUTEMAX 32 // mute at max distance (3D only) -#define BASS_SAMPLE_VAM 64 // DX7 voice allocation & management -#define BASS_SAMPLE_FX 128 // old implementation of DX8 effects -#define BASS_SAMPLE_OVER_VOL 0x10000 // override lowest volume -#define BASS_SAMPLE_OVER_POS 0x20000 // override longest playing -#define BASS_SAMPLE_OVER_DIST 0x30000 // override furthest from listener (3D only) - -#define BASS_STREAM_PRESCAN 0x20000 // enable pin-point seeking/length (MP3/MP2/MP1) -#define BASS_MP3_SETPOS BASS_STREAM_PRESCAN -#define BASS_STREAM_AUTOFREE 0x40000 // automatically free the stream when it stop/ends -#define BASS_STREAM_RESTRATE 0x80000 // restrict the download rate of internet file streams -#define BASS_STREAM_BLOCK 0x100000 // download/play internet file stream in small blocks -#define BASS_STREAM_DECODE 0x200000 // don't play the stream, only decode (BASS_ChannelGetData) -#define BASS_STREAM_STATUS 0x800000 // give server status info (HTTP/ICY tags) in DOWNLOADPROC - -#define BASS_MUSIC_FLOAT BASS_SAMPLE_FLOAT -#define BASS_MUSIC_MONO BASS_SAMPLE_MONO -#define BASS_MUSIC_LOOP BASS_SAMPLE_LOOP -#define BASS_MUSIC_3D BASS_SAMPLE_3D -#define BASS_MUSIC_FX BASS_SAMPLE_FX -#define BASS_MUSIC_AUTOFREE BASS_STREAM_AUTOFREE -#define BASS_MUSIC_DECODE BASS_STREAM_DECODE -#define BASS_MUSIC_PRESCAN BASS_STREAM_PRESCAN // calculate playback length -#define BASS_MUSIC_CALCLEN BASS_MUSIC_PRESCAN -#define BASS_MUSIC_RAMP 0x200 // normal ramping -#define BASS_MUSIC_RAMPS 0x400 // sensitive ramping -#define BASS_MUSIC_SURROUND 0x800 // surround sound -#define BASS_MUSIC_SURROUND2 0x1000 // surround sound (mode 2) -#define BASS_MUSIC_FT2PAN 0x2000 // apply FastTracker 2 panning to XM files -#define BASS_MUSIC_FT2MOD 0x2000 // play .MOD as FastTracker 2 does -#define BASS_MUSIC_PT1MOD 0x4000 // play .MOD as ProTracker 1 does -#define BASS_MUSIC_NONINTER 0x10000 // non-interpolated sample mixing -#define BASS_MUSIC_SINCINTER 0x800000 // sinc interpolated sample mixing -#define BASS_MUSIC_POSRESET 0x8000 // stop all notes when moving position -#define BASS_MUSIC_POSRESETEX 0x400000 // stop all notes and reset bmp/etc when moving position -#define BASS_MUSIC_STOPBACK 0x80000 // stop the music on a backwards jump effect -#define BASS_MUSIC_NOSAMPLE 0x100000 // don't load the samples +#define BASS_SAMPLE_8BITS 1 // 8 bit +#define BASS_SAMPLE_FLOAT 256 // 32 bit floating-point +#define BASS_SAMPLE_MONO 2 // mono +#define BASS_SAMPLE_LOOP 4 // looped +#define BASS_SAMPLE_3D 8 // 3D functionality +#define BASS_SAMPLE_SOFTWARE 16 // not using hardware mixing +#define BASS_SAMPLE_MUTEMAX 32 // mute at max distance (3D only) +#define BASS_SAMPLE_VAM 64 // DX7 voice allocation & management +#define BASS_SAMPLE_FX 128 // old implementation of DX8 effects +#define BASS_SAMPLE_OVER_VOL 0x10000 // override lowest volume +#define BASS_SAMPLE_OVER_POS 0x20000 // override longest playing +#define BASS_SAMPLE_OVER_DIST \ + 0x30000 // override furthest from listener (3D only) + +#define BASS_STREAM_PRESCAN \ + 0x20000 // enable pin-point seeking/length (MP3/MP2/MP1) +#define BASS_MP3_SETPOS BASS_STREAM_PRESCAN +#define BASS_STREAM_AUTOFREE \ + 0x40000 // automatically free the stream when it stop/ends +#define BASS_STREAM_RESTRATE \ + 0x80000 // restrict the download rate of internet file streams +#define BASS_STREAM_BLOCK \ + 0x100000 // download/play internet file stream in small blocks +#define BASS_STREAM_DECODE \ + 0x200000 // don't play the stream, only decode (BASS_ChannelGetData) +#define BASS_STREAM_STATUS \ + 0x800000 // give server status info (HTTP/ICY tags) in DOWNLOADPROC + +#define BASS_MUSIC_FLOAT BASS_SAMPLE_FLOAT +#define BASS_MUSIC_MONO BASS_SAMPLE_MONO +#define BASS_MUSIC_LOOP BASS_SAMPLE_LOOP +#define BASS_MUSIC_3D BASS_SAMPLE_3D +#define BASS_MUSIC_FX BASS_SAMPLE_FX +#define BASS_MUSIC_AUTOFREE BASS_STREAM_AUTOFREE +#define BASS_MUSIC_DECODE BASS_STREAM_DECODE +#define BASS_MUSIC_PRESCAN BASS_STREAM_PRESCAN // calculate playback length +#define BASS_MUSIC_CALCLEN BASS_MUSIC_PRESCAN +#define BASS_MUSIC_RAMP 0x200 // normal ramping +#define BASS_MUSIC_RAMPS 0x400 // sensitive ramping +#define BASS_MUSIC_SURROUND 0x800 // surround sound +#define BASS_MUSIC_SURROUND2 0x1000 // surround sound (mode 2) +#define BASS_MUSIC_FT2PAN 0x2000 // apply FastTracker 2 panning to XM files +#define BASS_MUSIC_FT2MOD 0x2000 // play .MOD as FastTracker 2 does +#define BASS_MUSIC_PT1MOD 0x4000 // play .MOD as ProTracker 1 does +#define BASS_MUSIC_NONINTER 0x10000 // non-interpolated sample mixing +#define BASS_MUSIC_SINCINTER 0x800000 // sinc interpolated sample mixing +#define BASS_MUSIC_POSRESET 0x8000 // stop all notes when moving position +#define BASS_MUSIC_POSRESETEX \ + 0x400000 // stop all notes and reset bmp/etc when moving position +#define BASS_MUSIC_STOPBACK 0x80000 // stop the music on a backwards jump effect +#define BASS_MUSIC_NOSAMPLE 0x100000 // don't load the samples // Speaker assignment flags -#define BASS_SPEAKER_FRONT 0x1000000 // front speakers -#define BASS_SPEAKER_REAR 0x2000000 // rear/side speakers -#define BASS_SPEAKER_CENLFE 0x3000000 // center & LFE speakers (5.1) -#define BASS_SPEAKER_REAR2 0x4000000 // rear center speakers (7.1) -#define BASS_SPEAKER_N(n) ((n)<<24) // n'th pair of speakers (max 15) -#define BASS_SPEAKER_LEFT 0x10000000 // modifier: left -#define BASS_SPEAKER_RIGHT 0x20000000 // modifier: right -#define BASS_SPEAKER_FRONTLEFT BASS_SPEAKER_FRONT|BASS_SPEAKER_LEFT -#define BASS_SPEAKER_FRONTRIGHT BASS_SPEAKER_FRONT|BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_REARLEFT BASS_SPEAKER_REAR|BASS_SPEAKER_LEFT -#define BASS_SPEAKER_REARRIGHT BASS_SPEAKER_REAR|BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_CENTER BASS_SPEAKER_CENLFE|BASS_SPEAKER_LEFT -#define BASS_SPEAKER_LFE BASS_SPEAKER_CENLFE|BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_REAR2LEFT BASS_SPEAKER_REAR2|BASS_SPEAKER_LEFT -#define BASS_SPEAKER_REAR2RIGHT BASS_SPEAKER_REAR2|BASS_SPEAKER_RIGHT - -#define BASS_ASYNCFILE 0x40000000 -#define BASS_UNICODE 0x80000000 - -#define BASS_RECORD_PAUSE 0x8000 // start recording paused -#define BASS_RECORD_ECHOCANCEL 0x2000 -#define BASS_RECORD_AGC 0x4000 +#define BASS_SPEAKER_FRONT 0x1000000 // front speakers +#define BASS_SPEAKER_REAR 0x2000000 // rear/side speakers +#define BASS_SPEAKER_CENLFE 0x3000000 // center & LFE speakers (5.1) +#define BASS_SPEAKER_REAR2 0x4000000 // rear center speakers (7.1) +#define BASS_SPEAKER_N(n) ((n) << 24) // n'th pair of speakers (max 15) +#define BASS_SPEAKER_LEFT 0x10000000 // modifier: left +#define BASS_SPEAKER_RIGHT 0x20000000 // modifier: right +#define BASS_SPEAKER_FRONTLEFT BASS_SPEAKER_FRONT | BASS_SPEAKER_LEFT +#define BASS_SPEAKER_FRONTRIGHT BASS_SPEAKER_FRONT | BASS_SPEAKER_RIGHT +#define BASS_SPEAKER_REARLEFT BASS_SPEAKER_REAR | BASS_SPEAKER_LEFT +#define BASS_SPEAKER_REARRIGHT BASS_SPEAKER_REAR | BASS_SPEAKER_RIGHT +#define BASS_SPEAKER_CENTER BASS_SPEAKER_CENLFE | BASS_SPEAKER_LEFT +#define BASS_SPEAKER_LFE BASS_SPEAKER_CENLFE | BASS_SPEAKER_RIGHT +#define BASS_SPEAKER_REAR2LEFT BASS_SPEAKER_REAR2 | BASS_SPEAKER_LEFT +#define BASS_SPEAKER_REAR2RIGHT BASS_SPEAKER_REAR2 | BASS_SPEAKER_RIGHT + +#define BASS_ASYNCFILE 0x40000000 +#define BASS_UNICODE 0x80000000 + +#define BASS_RECORD_PAUSE 0x8000 // start recording paused +#define BASS_RECORD_ECHOCANCEL 0x2000 +#define BASS_RECORD_AGC 0x4000 // DX7 voice allocation & management flags -#define BASS_VAM_HARDWARE 1 -#define BASS_VAM_SOFTWARE 2 -#define BASS_VAM_TERM_TIME 4 -#define BASS_VAM_TERM_DIST 8 -#define BASS_VAM_TERM_PRIO 16 +#define BASS_VAM_HARDWARE 1 +#define BASS_VAM_SOFTWARE 2 +#define BASS_VAM_TERM_TIME 4 +#define BASS_VAM_TERM_DIST 8 +#define BASS_VAM_TERM_PRIO 16 // Channel info structure typedef struct { - DWORD freq; // default playback rate - DWORD chans; // channels - DWORD flags; // BASS_SAMPLE/STREAM/MUSIC/SPEAKER flags - DWORD ctype; // type of channel - DWORD origres; // original resolution - HPLUGIN plugin; // plugin - HSAMPLE sample; // sample - const char *filename; // filename + DWORD freq; // default playback rate + DWORD chans; // channels + DWORD flags; // BASS_SAMPLE/STREAM/MUSIC/SPEAKER flags + DWORD ctype; // type of channel + DWORD origres; // original resolution + HPLUGIN plugin; // plugin + HSAMPLE sample; // sample + const char *filename; // filename } BASS_CHANNELINFO; // BASS_CHANNELINFO types -#define BASS_CTYPE_SAMPLE 1 -#define BASS_CTYPE_RECORD 2 -#define BASS_CTYPE_STREAM 0x10000 -#define BASS_CTYPE_STREAM_OGG 0x10002 -#define BASS_CTYPE_STREAM_MP1 0x10003 -#define BASS_CTYPE_STREAM_MP2 0x10004 -#define BASS_CTYPE_STREAM_MP3 0x10005 -#define BASS_CTYPE_STREAM_AIFF 0x10006 -#define BASS_CTYPE_STREAM_CA 0x10007 -#define BASS_CTYPE_STREAM_MF 0x10008 -#define BASS_CTYPE_STREAM_WAV 0x40000 // WAVE flag, LOWORD=codec -#define BASS_CTYPE_STREAM_WAV_PCM 0x50001 -#define BASS_CTYPE_STREAM_WAV_FLOAT 0x50003 -#define BASS_CTYPE_MUSIC_MOD 0x20000 -#define BASS_CTYPE_MUSIC_MTM 0x20001 -#define BASS_CTYPE_MUSIC_S3M 0x20002 -#define BASS_CTYPE_MUSIC_XM 0x20003 -#define BASS_CTYPE_MUSIC_IT 0x20004 -#define BASS_CTYPE_MUSIC_MO3 0x00100 // MO3 flag +#define BASS_CTYPE_SAMPLE 1 +#define BASS_CTYPE_RECORD 2 +#define BASS_CTYPE_STREAM 0x10000 +#define BASS_CTYPE_STREAM_OGG 0x10002 +#define BASS_CTYPE_STREAM_MP1 0x10003 +#define BASS_CTYPE_STREAM_MP2 0x10004 +#define BASS_CTYPE_STREAM_MP3 0x10005 +#define BASS_CTYPE_STREAM_AIFF 0x10006 +#define BASS_CTYPE_STREAM_CA 0x10007 +#define BASS_CTYPE_STREAM_MF 0x10008 +#define BASS_CTYPE_STREAM_WAV 0x40000 // WAVE flag, LOWORD=codec +#define BASS_CTYPE_STREAM_WAV_PCM 0x50001 +#define BASS_CTYPE_STREAM_WAV_FLOAT 0x50003 +#define BASS_CTYPE_MUSIC_MOD 0x20000 +#define BASS_CTYPE_MUSIC_MTM 0x20001 +#define BASS_CTYPE_MUSIC_S3M 0x20002 +#define BASS_CTYPE_MUSIC_XM 0x20003 +#define BASS_CTYPE_MUSIC_IT 0x20004 +#define BASS_CTYPE_MUSIC_MO3 0x00100 // MO3 flag typedef struct { - DWORD ctype; // channel type -#if defined(_WIN32_WCE) || (WINAPI_FAMILY && WINAPI_FAMILY!=WINAPI_FAMILY_DESKTOP_APP) - const wchar_t *name; // format description - const wchar_t *exts; // file extension filter (*.ext1;*.ext2;etc...) + DWORD ctype; // channel type +#if defined(_WIN32_WCE) || \ + (WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) + const wchar_t *name; // format description + const wchar_t *exts; // file extension filter (*.ext1;*.ext2;etc...) #else - const char *name; // format description - const char *exts; // file extension filter (*.ext1;*.ext2;etc...) + const char *name; // format description + const char *exts; // file extension filter (*.ext1;*.ext2;etc...) #endif } BASS_PLUGINFORM; typedef struct { - DWORD version; // version (same form as BASS_GetVersion) - DWORD formatc; // number of formats - const BASS_PLUGINFORM *formats; // the array of formats + DWORD version; // version (same form as BASS_GetVersion) + DWORD formatc; // number of formats + const BASS_PLUGINFORM *formats; // the array of formats } BASS_PLUGININFO; // 3D vector (for 3D positions/velocities/orientations) typedef struct BASS_3DVECTOR { #ifdef __cplusplus - BASS_3DVECTOR() {}; - BASS_3DVECTOR(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}; + BASS_3DVECTOR(){}; + BASS_3DVECTOR(float _x, float _y, float _z) : x(_x), y(_y), z(_z){}; #endif - float x; // +=right, -=left - float y; // +=up, -=down - float z; // +=front, -=behind + float x; // +=right, -=left + float y; // +=up, -=down + float z; // +=front, -=behind } BASS_3DVECTOR; // 3D channel modes -#define BASS_3DMODE_NORMAL 0 // normal 3D processing -#define BASS_3DMODE_RELATIVE 1 // position is relative to the listener -#define BASS_3DMODE_OFF 2 // no 3D processing +#define BASS_3DMODE_NORMAL 0 // normal 3D processing +#define BASS_3DMODE_RELATIVE 1 // position is relative to the listener +#define BASS_3DMODE_OFF 2 // no 3D processing // software 3D mixing algorithms (used with BASS_CONFIG_3DALGORITHM) -#define BASS_3DALG_DEFAULT 0 -#define BASS_3DALG_OFF 1 -#define BASS_3DALG_FULL 2 -#define BASS_3DALG_LIGHT 3 +#define BASS_3DALG_DEFAULT 0 +#define BASS_3DALG_OFF 1 +#define BASS_3DALG_FULL 2 +#define BASS_3DALG_LIGHT 3 // EAX environments, use with BASS_SetEAXParameters -enum -{ - EAX_ENVIRONMENT_GENERIC, - EAX_ENVIRONMENT_PADDEDCELL, - EAX_ENVIRONMENT_ROOM, - EAX_ENVIRONMENT_BATHROOM, - EAX_ENVIRONMENT_LIVINGROOM, - EAX_ENVIRONMENT_STONEROOM, - EAX_ENVIRONMENT_AUDITORIUM, - EAX_ENVIRONMENT_CONCERTHALL, - EAX_ENVIRONMENT_CAVE, - EAX_ENVIRONMENT_ARENA, - EAX_ENVIRONMENT_HANGAR, - EAX_ENVIRONMENT_CARPETEDHALLWAY, - EAX_ENVIRONMENT_HALLWAY, - EAX_ENVIRONMENT_STONECORRIDOR, - EAX_ENVIRONMENT_ALLEY, - EAX_ENVIRONMENT_FOREST, - EAX_ENVIRONMENT_CITY, - EAX_ENVIRONMENT_MOUNTAINS, - EAX_ENVIRONMENT_QUARRY, - EAX_ENVIRONMENT_PLAIN, - EAX_ENVIRONMENT_PARKINGLOT, - EAX_ENVIRONMENT_SEWERPIPE, - EAX_ENVIRONMENT_UNDERWATER, - EAX_ENVIRONMENT_DRUGGED, - EAX_ENVIRONMENT_DIZZY, - EAX_ENVIRONMENT_PSYCHOTIC, - - EAX_ENVIRONMENT_COUNT // total number of environments +enum { + EAX_ENVIRONMENT_GENERIC, + EAX_ENVIRONMENT_PADDEDCELL, + EAX_ENVIRONMENT_ROOM, + EAX_ENVIRONMENT_BATHROOM, + EAX_ENVIRONMENT_LIVINGROOM, + EAX_ENVIRONMENT_STONEROOM, + EAX_ENVIRONMENT_AUDITORIUM, + EAX_ENVIRONMENT_CONCERTHALL, + EAX_ENVIRONMENT_CAVE, + EAX_ENVIRONMENT_ARENA, + EAX_ENVIRONMENT_HANGAR, + EAX_ENVIRONMENT_CARPETEDHALLWAY, + EAX_ENVIRONMENT_HALLWAY, + EAX_ENVIRONMENT_STONECORRIDOR, + EAX_ENVIRONMENT_ALLEY, + EAX_ENVIRONMENT_FOREST, + EAX_ENVIRONMENT_CITY, + EAX_ENVIRONMENT_MOUNTAINS, + EAX_ENVIRONMENT_QUARRY, + EAX_ENVIRONMENT_PLAIN, + EAX_ENVIRONMENT_PARKINGLOT, + EAX_ENVIRONMENT_SEWERPIPE, + EAX_ENVIRONMENT_UNDERWATER, + EAX_ENVIRONMENT_DRUGGED, + EAX_ENVIRONMENT_DIZZY, + EAX_ENVIRONMENT_PSYCHOTIC, + + EAX_ENVIRONMENT_COUNT // total number of environments }; // EAX presets, usage: BASS_SetEAXParameters(EAX_PRESET_xxx) -#define EAX_PRESET_GENERIC EAX_ENVIRONMENT_GENERIC,0.5F,1.493F,0.5F -#define EAX_PRESET_PADDEDCELL EAX_ENVIRONMENT_PADDEDCELL,0.25F,0.1F,0.0F -#define EAX_PRESET_ROOM EAX_ENVIRONMENT_ROOM,0.417F,0.4F,0.666F -#define EAX_PRESET_BATHROOM EAX_ENVIRONMENT_BATHROOM,0.653F,1.499F,0.166F -#define EAX_PRESET_LIVINGROOM EAX_ENVIRONMENT_LIVINGROOM,0.208F,0.478F,0.0F -#define EAX_PRESET_STONEROOM EAX_ENVIRONMENT_STONEROOM,0.5F,2.309F,0.888F -#define EAX_PRESET_AUDITORIUM EAX_ENVIRONMENT_AUDITORIUM,0.403F,4.279F,0.5F -#define EAX_PRESET_CONCERTHALL EAX_ENVIRONMENT_CONCERTHALL,0.5F,3.961F,0.5F -#define EAX_PRESET_CAVE EAX_ENVIRONMENT_CAVE,0.5F,2.886F,1.304F -#define EAX_PRESET_ARENA EAX_ENVIRONMENT_ARENA,0.361F,7.284F,0.332F -#define EAX_PRESET_HANGAR EAX_ENVIRONMENT_HANGAR,0.5F,10.0F,0.3F -#define EAX_PRESET_CARPETEDHALLWAY EAX_ENVIRONMENT_CARPETEDHALLWAY,0.153F,0.259F,2.0F -#define EAX_PRESET_HALLWAY EAX_ENVIRONMENT_HALLWAY,0.361F,1.493F,0.0F -#define EAX_PRESET_STONECORRIDOR EAX_ENVIRONMENT_STONECORRIDOR,0.444F,2.697F,0.638F -#define EAX_PRESET_ALLEY EAX_ENVIRONMENT_ALLEY,0.25F,1.752F,0.776F -#define EAX_PRESET_FOREST EAX_ENVIRONMENT_FOREST,0.111F,3.145F,0.472F -#define EAX_PRESET_CITY EAX_ENVIRONMENT_CITY,0.111F,2.767F,0.224F -#define EAX_PRESET_MOUNTAINS EAX_ENVIRONMENT_MOUNTAINS,0.194F,7.841F,0.472F -#define EAX_PRESET_QUARRY EAX_ENVIRONMENT_QUARRY,1.0F,1.499F,0.5F -#define EAX_PRESET_PLAIN EAX_ENVIRONMENT_PLAIN,0.097F,2.767F,0.224F -#define EAX_PRESET_PARKINGLOT EAX_ENVIRONMENT_PARKINGLOT,0.208F,1.652F,1.5F -#define EAX_PRESET_SEWERPIPE EAX_ENVIRONMENT_SEWERPIPE,0.652F,2.886F,0.25F -#define EAX_PRESET_UNDERWATER EAX_ENVIRONMENT_UNDERWATER,1.0F,1.499F,0.0F -#define EAX_PRESET_DRUGGED EAX_ENVIRONMENT_DRUGGED,0.875F,8.392F,1.388F -#define EAX_PRESET_DIZZY EAX_ENVIRONMENT_DIZZY,0.139F,17.234F,0.666F -#define EAX_PRESET_PSYCHOTIC EAX_ENVIRONMENT_PSYCHOTIC,0.486F,7.563F,0.806F - -typedef DWORD (CALLBACK STREAMPROC)(HSTREAM handle, void *buffer, DWORD length, void *user); -/* User stream callback function. NOTE: A stream function should obviously be as quick -as possible, other streams (and MOD musics) can't be mixed until it's finished. -handle : The stream that needs writing -buffer : Buffer to write the samples in -length : Number of bytes to write -user : The 'user' parameter value given when calling BASS_StreamCreate -RETURN : Number of bytes written. Set the BASS_STREAMPROC_END flag to end - the stream. */ - -#define BASS_STREAMPROC_END 0x80000000 // end of user stream flag +#define EAX_PRESET_GENERIC EAX_ENVIRONMENT_GENERIC, 0.5F, 1.493F, 0.5F +#define EAX_PRESET_PADDEDCELL EAX_ENVIRONMENT_PADDEDCELL, 0.25F, 0.1F, 0.0F +#define EAX_PRESET_ROOM EAX_ENVIRONMENT_ROOM, 0.417F, 0.4F, 0.666F +#define EAX_PRESET_BATHROOM EAX_ENVIRONMENT_BATHROOM, 0.653F, 1.499F, 0.166F +#define EAX_PRESET_LIVINGROOM EAX_ENVIRONMENT_LIVINGROOM, 0.208F, 0.478F, 0.0F +#define EAX_PRESET_STONEROOM EAX_ENVIRONMENT_STONEROOM, 0.5F, 2.309F, 0.888F +#define EAX_PRESET_AUDITORIUM EAX_ENVIRONMENT_AUDITORIUM, 0.403F, 4.279F, 0.5F +#define EAX_PRESET_CONCERTHALL EAX_ENVIRONMENT_CONCERTHALL, 0.5F, 3.961F, 0.5F +#define EAX_PRESET_CAVE EAX_ENVIRONMENT_CAVE, 0.5F, 2.886F, 1.304F +#define EAX_PRESET_ARENA EAX_ENVIRONMENT_ARENA, 0.361F, 7.284F, 0.332F +#define EAX_PRESET_HANGAR EAX_ENVIRONMENT_HANGAR, 0.5F, 10.0F, 0.3F +#define EAX_PRESET_CARPETEDHALLWAY \ + EAX_ENVIRONMENT_CARPETEDHALLWAY, 0.153F, 0.259F, 2.0F +#define EAX_PRESET_HALLWAY EAX_ENVIRONMENT_HALLWAY, 0.361F, 1.493F, 0.0F +#define EAX_PRESET_STONECORRIDOR \ + EAX_ENVIRONMENT_STONECORRIDOR, 0.444F, 2.697F, 0.638F +#define EAX_PRESET_ALLEY EAX_ENVIRONMENT_ALLEY, 0.25F, 1.752F, 0.776F +#define EAX_PRESET_FOREST EAX_ENVIRONMENT_FOREST, 0.111F, 3.145F, 0.472F +#define EAX_PRESET_CITY EAX_ENVIRONMENT_CITY, 0.111F, 2.767F, 0.224F +#define EAX_PRESET_MOUNTAINS EAX_ENVIRONMENT_MOUNTAINS, 0.194F, 7.841F, 0.472F +#define EAX_PRESET_QUARRY EAX_ENVIRONMENT_QUARRY, 1.0F, 1.499F, 0.5F +#define EAX_PRESET_PLAIN EAX_ENVIRONMENT_PLAIN, 0.097F, 2.767F, 0.224F +#define EAX_PRESET_PARKINGLOT EAX_ENVIRONMENT_PARKINGLOT, 0.208F, 1.652F, 1.5F +#define EAX_PRESET_SEWERPIPE EAX_ENVIRONMENT_SEWERPIPE, 0.652F, 2.886F, 0.25F +#define EAX_PRESET_UNDERWATER EAX_ENVIRONMENT_UNDERWATER, 1.0F, 1.499F, 0.0F +#define EAX_PRESET_DRUGGED EAX_ENVIRONMENT_DRUGGED, 0.875F, 8.392F, 1.388F +#define EAX_PRESET_DIZZY EAX_ENVIRONMENT_DIZZY, 0.139F, 17.234F, 0.666F +#define EAX_PRESET_PSYCHOTIC EAX_ENVIRONMENT_PSYCHOTIC, 0.486F, 7.563F, 0.806F + +typedef DWORD(CALLBACK STREAMPROC)(HSTREAM handle, void *buffer, DWORD length, + void *user); +/* User stream callback function. NOTE: A stream function should obviously be as +quick as possible, other streams (and MOD musics) can't be mixed until it's +finished. handle : The stream that needs writing buffer : Buffer to write the +samples in length : Number of bytes to write user : The 'user' parameter value +given when calling BASS_StreamCreate RETURN : Number of bytes written. Set the +BASS_STREAMPROC_END flag to end the stream. */ + +#define BASS_STREAMPROC_END 0x80000000 // end of user stream flag // special STREAMPROCs -#define STREAMPROC_DUMMY (STREAMPROC*)0 // "dummy" stream -#define STREAMPROC_PUSH (STREAMPROC*)-1 // push stream +#define STREAMPROC_DUMMY (STREAMPROC *)0 // "dummy" stream +#define STREAMPROC_PUSH (STREAMPROC *)-1 // push stream // BASS_StreamCreateFileUser file systems -#define STREAMFILE_NOBUFFER 0 -#define STREAMFILE_BUFFER 1 -#define STREAMFILE_BUFFERPUSH 2 +#define STREAMFILE_NOBUFFER 0 +#define STREAMFILE_BUFFER 1 +#define STREAMFILE_BUFFERPUSH 2 // User file stream callback functions -typedef void (CALLBACK FILECLOSEPROC)(void *user); -typedef QWORD (CALLBACK FILELENPROC)(void *user); -typedef DWORD (CALLBACK FILEREADPROC)(void *buffer, DWORD length, void *user); -typedef BOOL (CALLBACK FILESEEKPROC)(QWORD offset, void *user); +typedef void(CALLBACK FILECLOSEPROC)(void *user); +typedef QWORD(CALLBACK FILELENPROC)(void *user); +typedef DWORD(CALLBACK FILEREADPROC)(void *buffer, DWORD length, void *user); +typedef BOOL(CALLBACK FILESEEKPROC)(QWORD offset, void *user); typedef struct { - FILECLOSEPROC *close; - FILELENPROC *length; - FILEREADPROC *read; - FILESEEKPROC *seek; + FILECLOSEPROC *close; + FILELENPROC *length; + FILEREADPROC *read; + FILESEEKPROC *seek; } BASS_FILEPROCS; // BASS_StreamPutFileData options -#define BASS_FILEDATA_END 0 // end & close the file +#define BASS_FILEDATA_END 0 // end & close the file // BASS_StreamGetFilePosition modes -#define BASS_FILEPOS_CURRENT 0 -#define BASS_FILEPOS_DECODE BASS_FILEPOS_CURRENT -#define BASS_FILEPOS_DOWNLOAD 1 -#define BASS_FILEPOS_END 2 -#define BASS_FILEPOS_START 3 -#define BASS_FILEPOS_CONNECTED 4 -#define BASS_FILEPOS_BUFFER 5 -#define BASS_FILEPOS_SOCKET 6 -#define BASS_FILEPOS_ASYNCBUF 7 -#define BASS_FILEPOS_SIZE 8 - -typedef void (CALLBACK DOWNLOADPROC)(const void *buffer, DWORD length, void *user); +#define BASS_FILEPOS_CURRENT 0 +#define BASS_FILEPOS_DECODE BASS_FILEPOS_CURRENT +#define BASS_FILEPOS_DOWNLOAD 1 +#define BASS_FILEPOS_END 2 +#define BASS_FILEPOS_START 3 +#define BASS_FILEPOS_CONNECTED 4 +#define BASS_FILEPOS_BUFFER 5 +#define BASS_FILEPOS_SOCKET 6 +#define BASS_FILEPOS_ASYNCBUF 7 +#define BASS_FILEPOS_SIZE 8 + +typedef void(CALLBACK DOWNLOADPROC)(const void *buffer, DWORD length, + void *user); /* Internet stream download callback function. buffer : Buffer containing the downloaded data... NULL=end of download length : Number of bytes in the buffer user : The 'user' parameter value given when calling BASS_StreamCreateURL */ // BASS_ChannelSetSync types -#define BASS_SYNC_POS 0 -#define BASS_SYNC_END 2 -#define BASS_SYNC_META 4 -#define BASS_SYNC_SLIDE 5 -#define BASS_SYNC_STALL 6 -#define BASS_SYNC_DOWNLOAD 7 -#define BASS_SYNC_FREE 8 -#define BASS_SYNC_SETPOS 11 -#define BASS_SYNC_MUSICPOS 10 -#define BASS_SYNC_MUSICINST 1 -#define BASS_SYNC_MUSICFX 3 -#define BASS_SYNC_OGG_CHANGE 12 -#define BASS_SYNC_MIXTIME 0x40000000 // flag: sync at mixtime, else at playtime -#define BASS_SYNC_ONETIME 0x80000000 // flag: sync only once, else continuously - -typedef void (CALLBACK SYNCPROC)(HSYNC handle, DWORD channel, DWORD data, void *user); +#define BASS_SYNC_POS 0 +#define BASS_SYNC_END 2 +#define BASS_SYNC_META 4 +#define BASS_SYNC_SLIDE 5 +#define BASS_SYNC_STALL 6 +#define BASS_SYNC_DOWNLOAD 7 +#define BASS_SYNC_FREE 8 +#define BASS_SYNC_SETPOS 11 +#define BASS_SYNC_MUSICPOS 10 +#define BASS_SYNC_MUSICINST 1 +#define BASS_SYNC_MUSICFX 3 +#define BASS_SYNC_OGG_CHANGE 12 +#define BASS_SYNC_MIXTIME 0x40000000 // flag: sync at mixtime, else at playtime +#define BASS_SYNC_ONETIME 0x80000000 // flag: sync only once, else continuously + +typedef void(CALLBACK SYNCPROC)(HSYNC handle, DWORD channel, DWORD data, + void *user); /* Sync callback function. NOTE: a sync callback function should be very quick as other syncs can't be processed until it has finished. If the sync is a "mixtime" sync, then other streams and MOD musics can't be mixed until @@ -559,7 +581,8 @@ channel: Channel that the sync occured in data : Additional data associated with the sync's occurance user : The 'user' parameter given when calling BASS_ChannelSetSync */ -typedef void (CALLBACK DSPPROC)(HDSP handle, DWORD channel, void *buffer, DWORD length, void *user); +typedef void(CALLBACK DSPPROC)(HDSP handle, DWORD channel, void *buffer, + DWORD length, void *user); /* DSP callback function. NOTE: A DSP function should obviously be as quick as possible... other DSP functions, streams and MOD musics can not be processed until it's finished. @@ -569,7 +592,8 @@ buffer : Buffer to apply the DSP to length : Number of bytes in the buffer user : The 'user' parameter given when calling BASS_ChannelSetDSP */ -typedef BOOL (CALLBACK RECORDPROC)(HRECORD handle, const void *buffer, DWORD length, void *user); +typedef BOOL(CALLBACK RECORDPROC)(HRECORD handle, const void *buffer, + DWORD length, void *user); /* Recording callback function. handle : The recording handle buffer : Buffer containing the recorded sample data @@ -578,321 +602,327 @@ user : The 'user' parameter value given when calling BASS_RecordStart RETURN : TRUE = continue recording, FALSE = stop */ // BASS_ChannelIsActive return values -#define BASS_ACTIVE_STOPPED 0 -#define BASS_ACTIVE_PLAYING 1 -#define BASS_ACTIVE_STALLED 2 -#define BASS_ACTIVE_PAUSED 3 +#define BASS_ACTIVE_STOPPED 0 +#define BASS_ACTIVE_PLAYING 1 +#define BASS_ACTIVE_STALLED 2 +#define BASS_ACTIVE_PAUSED 3 // Channel attributes -#define BASS_ATTRIB_FREQ 1 -#define BASS_ATTRIB_VOL 2 -#define BASS_ATTRIB_PAN 3 -#define BASS_ATTRIB_EAXMIX 4 -#define BASS_ATTRIB_NOBUFFER 5 -#define BASS_ATTRIB_VBR 6 -#define BASS_ATTRIB_CPU 7 -#define BASS_ATTRIB_SRC 8 -#define BASS_ATTRIB_NET_RESUME 9 -#define BASS_ATTRIB_SCANINFO 10 -#define BASS_ATTRIB_NORAMP 11 -#define BASS_ATTRIB_BITRATE 12 -#define BASS_ATTRIB_MUSIC_AMPLIFY 0x100 -#define BASS_ATTRIB_MUSIC_PANSEP 0x101 -#define BASS_ATTRIB_MUSIC_PSCALER 0x102 -#define BASS_ATTRIB_MUSIC_BPM 0x103 -#define BASS_ATTRIB_MUSIC_SPEED 0x104 +#define BASS_ATTRIB_FREQ 1 +#define BASS_ATTRIB_VOL 2 +#define BASS_ATTRIB_PAN 3 +#define BASS_ATTRIB_EAXMIX 4 +#define BASS_ATTRIB_NOBUFFER 5 +#define BASS_ATTRIB_VBR 6 +#define BASS_ATTRIB_CPU 7 +#define BASS_ATTRIB_SRC 8 +#define BASS_ATTRIB_NET_RESUME 9 +#define BASS_ATTRIB_SCANINFO 10 +#define BASS_ATTRIB_NORAMP 11 +#define BASS_ATTRIB_BITRATE 12 +#define BASS_ATTRIB_MUSIC_AMPLIFY 0x100 +#define BASS_ATTRIB_MUSIC_PANSEP 0x101 +#define BASS_ATTRIB_MUSIC_PSCALER 0x102 +#define BASS_ATTRIB_MUSIC_BPM 0x103 +#define BASS_ATTRIB_MUSIC_SPEED 0x104 #define BASS_ATTRIB_MUSIC_VOL_GLOBAL 0x105 -#define BASS_ATTRIB_MUSIC_ACTIVE 0x106 -#define BASS_ATTRIB_MUSIC_VOL_CHAN 0x200 // + channel # -#define BASS_ATTRIB_MUSIC_VOL_INST 0x300 // + instrument # +#define BASS_ATTRIB_MUSIC_ACTIVE 0x106 +#define BASS_ATTRIB_MUSIC_VOL_CHAN 0x200 // + channel # +#define BASS_ATTRIB_MUSIC_VOL_INST 0x300 // + instrument # // BASS_ChannelGetData flags -#define BASS_DATA_AVAILABLE 0 // query how much data is buffered -#define BASS_DATA_FIXED 0x20000000 // flag: return 8.24 fixed-point data -#define BASS_DATA_FLOAT 0x40000000 // flag: return floating-point sample data -#define BASS_DATA_FFT256 0x80000000 // 256 sample FFT -#define BASS_DATA_FFT512 0x80000001 // 512 FFT -#define BASS_DATA_FFT1024 0x80000002 // 1024 FFT -#define BASS_DATA_FFT2048 0x80000003 // 2048 FFT -#define BASS_DATA_FFT4096 0x80000004 // 4096 FFT -#define BASS_DATA_FFT8192 0x80000005 // 8192 FFT -#define BASS_DATA_FFT16384 0x80000006 // 16384 FFT -#define BASS_DATA_FFT32768 0x80000007 // 32768 FFT -#define BASS_DATA_FFT_INDIVIDUAL 0x10 // FFT flag: FFT for each channel, else all combined -#define BASS_DATA_FFT_NOWINDOW 0x20 // FFT flag: no Hanning window -#define BASS_DATA_FFT_REMOVEDC 0x40 // FFT flag: pre-remove DC bias -#define BASS_DATA_FFT_COMPLEX 0x80 // FFT flag: return complex data +#define BASS_DATA_AVAILABLE 0 // query how much data is buffered +#define BASS_DATA_FIXED 0x20000000 // flag: return 8.24 fixed-point data +#define BASS_DATA_FLOAT 0x40000000 // flag: return floating-point sample data +#define BASS_DATA_FFT256 0x80000000 // 256 sample FFT +#define BASS_DATA_FFT512 0x80000001 // 512 FFT +#define BASS_DATA_FFT1024 0x80000002 // 1024 FFT +#define BASS_DATA_FFT2048 0x80000003 // 2048 FFT +#define BASS_DATA_FFT4096 0x80000004 // 4096 FFT +#define BASS_DATA_FFT8192 0x80000005 // 8192 FFT +#define BASS_DATA_FFT16384 0x80000006 // 16384 FFT +#define BASS_DATA_FFT32768 0x80000007 // 32768 FFT +#define BASS_DATA_FFT_INDIVIDUAL \ + 0x10 // FFT flag: FFT for each channel, else all combined +#define BASS_DATA_FFT_NOWINDOW 0x20 // FFT flag: no Hanning window +#define BASS_DATA_FFT_REMOVEDC 0x40 // FFT flag: pre-remove DC bias +#define BASS_DATA_FFT_COMPLEX 0x80 // FFT flag: return complex data // BASS_ChannelGetLevelEx flags -#define BASS_LEVEL_MONO 1 -#define BASS_LEVEL_STEREO 2 -#define BASS_LEVEL_RMS 4 +#define BASS_LEVEL_MONO 1 +#define BASS_LEVEL_STEREO 2 +#define BASS_LEVEL_RMS 4 // BASS_ChannelGetTags types : what's returned -#define BASS_TAG_ID3 0 // ID3v1 tags : TAG_ID3 structure -#define BASS_TAG_ID3V2 1 // ID3v2 tags : variable length block -#define BASS_TAG_OGG 2 // OGG comments : series of null-terminated UTF-8 strings -#define BASS_TAG_HTTP 3 // HTTP headers : series of null-terminated ANSI strings -#define BASS_TAG_ICY 4 // ICY headers : series of null-terminated ANSI strings -#define BASS_TAG_META 5 // ICY metadata : ANSI string -#define BASS_TAG_APE 6 // APE tags : series of null-terminated UTF-8 strings -#define BASS_TAG_MP4 7 // MP4/iTunes metadata : series of null-terminated UTF-8 strings -#define BASS_TAG_WMA 8 // WMA tags : series of null-terminated UTF-8 strings -#define BASS_TAG_VENDOR 9 // OGG encoder : UTF-8 string -#define BASS_TAG_LYRICS3 10 // Lyric3v2 tag : ASCII string -#define BASS_TAG_CA_CODEC 11 // CoreAudio codec info : TAG_CA_CODEC structure -#define BASS_TAG_MF 13 // Media Foundation tags : series of null-terminated UTF-8 strings -#define BASS_TAG_WAVEFORMAT 14 // WAVE format : WAVEFORMATEEX structure -#define BASS_TAG_RIFF_INFO 0x100 // RIFF "INFO" tags : series of null-terminated ANSI strings -#define BASS_TAG_RIFF_BEXT 0x101 // RIFF/BWF "bext" tags : TAG_BEXT structure -#define BASS_TAG_RIFF_CART 0x102 // RIFF/BWF "cart" tags : TAG_CART structure -#define BASS_TAG_RIFF_DISP 0x103 // RIFF "DISP" text tag : ANSI string -#define BASS_TAG_APE_BINARY 0x1000 // + index #, binary APE tag : TAG_APE_BINARY structure -#define BASS_TAG_MUSIC_NAME 0x10000 // MOD music name : ANSI string -#define BASS_TAG_MUSIC_MESSAGE 0x10001 // MOD message : ANSI string -#define BASS_TAG_MUSIC_ORDERS 0x10002 // MOD order list : BYTE array of pattern numbers -#define BASS_TAG_MUSIC_AUTH 0x10003 // MOD author : UTF-8 string -#define BASS_TAG_MUSIC_INST 0x10100 // + instrument #, MOD instrument name : ANSI string -#define BASS_TAG_MUSIC_SAMPLE 0x10300 // + sample #, MOD sample name : ANSI string - +#define BASS_TAG_ID3 0 // ID3v1 tags : TAG_ID3 structure +#define BASS_TAG_ID3V2 1 // ID3v2 tags : variable length block +#define BASS_TAG_OGG 2 // OGG comments : series of null-terminated UTF-8 strings +#define BASS_TAG_HTTP 3 // HTTP headers : series of null-terminated ANSI strings +#define BASS_TAG_ICY 4 // ICY headers : series of null-terminated ANSI strings +#define BASS_TAG_META 5 // ICY metadata : ANSI string +#define BASS_TAG_APE 6 // APE tags : series of null-terminated UTF-8 strings +#define BASS_TAG_MP4 \ + 7 // MP4/iTunes metadata : series of null-terminated UTF-8 strings +#define BASS_TAG_WMA 8 // WMA tags : series of null-terminated UTF-8 strings +#define BASS_TAG_VENDOR 9 // OGG encoder : UTF-8 string +#define BASS_TAG_LYRICS3 10 // Lyric3v2 tag : ASCII string +#define BASS_TAG_CA_CODEC 11 // CoreAudio codec info : TAG_CA_CODEC structure +#define BASS_TAG_MF \ + 13 // Media Foundation tags : series of null-terminated UTF-8 strings +#define BASS_TAG_WAVEFORMAT 14 // WAVE format : WAVEFORMATEEX structure +#define BASS_TAG_RIFF_INFO \ + 0x100 // RIFF "INFO" tags : series of null-terminated ANSI strings +#define BASS_TAG_RIFF_BEXT 0x101 // RIFF/BWF "bext" tags : TAG_BEXT structure +#define BASS_TAG_RIFF_CART 0x102 // RIFF/BWF "cart" tags : TAG_CART structure +#define BASS_TAG_RIFF_DISP 0x103 // RIFF "DISP" text tag : ANSI string +#define BASS_TAG_APE_BINARY \ + 0x1000 // + index #, binary APE tag : TAG_APE_BINARY structure +#define BASS_TAG_MUSIC_NAME 0x10000 // MOD music name : ANSI string +#define BASS_TAG_MUSIC_MESSAGE 0x10001 // MOD message : ANSI string +#define BASS_TAG_MUSIC_ORDERS \ + 0x10002 // MOD order list : BYTE array of pattern numbers +#define BASS_TAG_MUSIC_AUTH 0x10003 // MOD author : UTF-8 string +#define BASS_TAG_MUSIC_INST \ + 0x10100 // + instrument #, MOD instrument name : ANSI string +#define BASS_TAG_MUSIC_SAMPLE \ + 0x10300 // + sample #, MOD sample name : ANSI string + // ID3v1 tag structure typedef struct { - char id[3]; - char title[30]; - char artist[30]; - char album[30]; - char year[4]; - char comment[30]; - BYTE genre; + char id[3]; + char title[30]; + char artist[30]; + char album[30]; + char year[4]; + char comment[30]; + BYTE genre; } TAG_ID3; - + // Binary APE tag structure -typedef struct { - const char *key; - const void *data; - DWORD length; -} TAG_APE_BINARY; - -// BWF "bext" tag structure +typedef struct { + const char *key; + const void *data; + DWORD length; +} TAG_APE_BINARY; + +// BWF "bext" tag structure #ifdef _MSC_VER #pragma warning(push) -#pragma warning(disable:4200) +#pragma warning(disable : 4200) #endif -#pragma pack(push,1) +#pragma pack(push, 1) typedef struct { - char Description[256]; // description - char Originator[32]; // name of the originator - char OriginatorReference[32]; // reference of the originator - char OriginationDate[10]; // date of creation (yyyy-mm-dd) - char OriginationTime[8]; // time of creation (hh-mm-ss) - QWORD TimeReference; // first sample count since midnight (little-endian) - WORD Version; // BWF version (little-endian) - BYTE UMID[64]; // SMPTE UMID - BYTE Reserved[190]; -#if defined(__GNUC__) && __GNUC__<3 - char CodingHistory[0]; // history -#elif 1 // change to 0 if compiler fails the following line - char CodingHistory[]; // history + char Description[256]; // description + char Originator[32]; // name of the originator + char OriginatorReference[32]; // reference of the originator + char OriginationDate[10]; // date of creation (yyyy-mm-dd) + char OriginationTime[8]; // time of creation (hh-mm-ss) + QWORD TimeReference; // first sample count since midnight (little-endian) + WORD Version; // BWF version (little-endian) + BYTE UMID[64]; // SMPTE UMID + BYTE Reserved[190]; +#if defined(__GNUC__) && __GNUC__ < 3 + char CodingHistory[0]; // history +#elif 1 // change to 0 if compiler fails the following line + char CodingHistory[]; // history #else - char CodingHistory[1]; // history + char CodingHistory[1]; // history #endif } TAG_BEXT; -#pragma pack(pop) - -// BWF "cart" tag structures -typedef struct -{ - DWORD dwUsage; // FOURCC timer usage ID - DWORD dwValue; // timer value in samples from head -} TAG_CART_TIMER; - -typedef struct -{ - char Version[4]; // version of the data structure - char Title[64]; // title of cart audio sequence - char Artist[64]; // artist or creator name - char CutID[64]; // cut number identification - char ClientID[64]; // client identification - char Category[64]; // category ID, PSA, NEWS, etc - char Classification[64]; // classification or auxiliary key - char OutCue[64]; // out cue text - char StartDate[10]; // yyyy-mm-dd - char StartTime[8]; // hh:mm:ss - char EndDate[10]; // yyyy-mm-dd - char EndTime[8]; // hh:mm:ss - char ProducerAppID[64]; // name of vendor or application - char ProducerAppVersion[64]; // version of producer application - char UserDef[64]; // user defined text - DWORD dwLevelReference; // sample value for 0 dB reference - TAG_CART_TIMER PostTimer[8]; // 8 time markers after head - char Reserved[276]; - char URL[1024]; // uniform resource locator -#if defined(__GNUC__) && __GNUC__<3 - char TagText[0]; // free form text for scripts or tags -#elif 1 // change to 0 if compiler fails the following line - char TagText[]; // free form text for scripts or tags -#else - char TagText[1]; // free form text for scripts or tags -#endif -} TAG_CART; -#ifdef _MSC_VER -#pragma warning(pop) -#endif - +#pragma pack(pop) + +// BWF "cart" tag structures +typedef struct { + DWORD dwUsage; // FOURCC timer usage ID + DWORD dwValue; // timer value in samples from head +} TAG_CART_TIMER; + +typedef struct { + char Version[4]; // version of the data structure + char Title[64]; // title of cart audio sequence + char Artist[64]; // artist or creator name + char CutID[64]; // cut number identification + char ClientID[64]; // client identification + char Category[64]; // category ID, PSA, NEWS, etc + char Classification[64]; // classification or auxiliary key + char OutCue[64]; // out cue text + char StartDate[10]; // yyyy-mm-dd + char StartTime[8]; // hh:mm:ss + char EndDate[10]; // yyyy-mm-dd + char EndTime[8]; // hh:mm:ss + char ProducerAppID[64]; // name of vendor or application + char ProducerAppVersion[64]; // version of producer application + char UserDef[64]; // user defined text + DWORD dwLevelReference; // sample value for 0 dB reference + TAG_CART_TIMER PostTimer[8]; // 8 time markers after head + char Reserved[276]; + char URL[1024]; // uniform resource locator +#if defined(__GNUC__) && __GNUC__ < 3 + char TagText[0]; // free form text for scripts or tags +#elif 1 // change to 0 if compiler fails the following line + char TagText[]; // free form text for scripts or tags +#else + char TagText[1]; // free form text for scripts or tags +#endif +} TAG_CART; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + // CoreAudio codec info structure typedef struct { - DWORD ftype; // file format - DWORD atype; // audio format - const char *name; // description + DWORD ftype; // file format + DWORD atype; // audio format + const char *name; // description } TAG_CA_CODEC; -#ifndef _WAVEFORMATEX_ -#define _WAVEFORMATEX_ -#pragma pack(push,1) -typedef struct tWAVEFORMATEX -{ - WORD wFormatTag; - WORD nChannels; - DWORD nSamplesPerSec; - DWORD nAvgBytesPerSec; - WORD nBlockAlign; - WORD wBitsPerSample; - WORD cbSize; -} WAVEFORMATEX, *PWAVEFORMATEX, *LPWAVEFORMATEX; -typedef const WAVEFORMATEX *LPCWAVEFORMATEX; -#pragma pack(pop) -#endif - +#ifndef _WAVEFORMATEX_ +#define _WAVEFORMATEX_ +#pragma pack(push, 1) +typedef struct tWAVEFORMATEX { + WORD wFormatTag; + WORD nChannels; + DWORD nSamplesPerSec; + DWORD nAvgBytesPerSec; + WORD nBlockAlign; + WORD wBitsPerSample; + WORD cbSize; +} WAVEFORMATEX, *PWAVEFORMATEX, *LPWAVEFORMATEX; +typedef const WAVEFORMATEX *LPCWAVEFORMATEX; +#pragma pack(pop) +#endif + // BASS_ChannelGetLength/GetPosition/SetPosition modes -#define BASS_POS_BYTE 0 // byte position -#define BASS_POS_MUSIC_ORDER 1 // order.row position, MAKELONG(order,row) -#define BASS_POS_OGG 3 // OGG bitstream number -#define BASS_POS_INEXACT 0x8000000 // flag: allow seeking to inexact position -#define BASS_POS_DECODE 0x10000000 // flag: get the decoding (not playing) position -#define BASS_POS_DECODETO 0x20000000 // flag: decode to the position instead of seeking -#define BASS_POS_SCAN 0x40000000 // flag: scan to the position +#define BASS_POS_BYTE 0 // byte position +#define BASS_POS_MUSIC_ORDER 1 // order.row position, MAKELONG(order,row) +#define BASS_POS_OGG 3 // OGG bitstream number +#define BASS_POS_INEXACT 0x8000000 // flag: allow seeking to inexact position +#define BASS_POS_DECODE \ + 0x10000000 // flag: get the decoding (not playing) position +#define BASS_POS_DECODETO \ + 0x20000000 // flag: decode to the position instead of seeking +#define BASS_POS_SCAN 0x40000000 // flag: scan to the position // BASS_RecordSetInput flags -#define BASS_INPUT_OFF 0x10000 -#define BASS_INPUT_ON 0x20000 - -#define BASS_INPUT_TYPE_MASK 0xff000000 -#define BASS_INPUT_TYPE_UNDEF 0x00000000 -#define BASS_INPUT_TYPE_DIGITAL 0x01000000 -#define BASS_INPUT_TYPE_LINE 0x02000000 -#define BASS_INPUT_TYPE_MIC 0x03000000 -#define BASS_INPUT_TYPE_SYNTH 0x04000000 -#define BASS_INPUT_TYPE_CD 0x05000000 -#define BASS_INPUT_TYPE_PHONE 0x06000000 -#define BASS_INPUT_TYPE_SPEAKER 0x07000000 -#define BASS_INPUT_TYPE_WAVE 0x08000000 -#define BASS_INPUT_TYPE_AUX 0x09000000 -#define BASS_INPUT_TYPE_ANALOG 0x0a000000 +#define BASS_INPUT_OFF 0x10000 +#define BASS_INPUT_ON 0x20000 + +#define BASS_INPUT_TYPE_MASK 0xff000000 +#define BASS_INPUT_TYPE_UNDEF 0x00000000 +#define BASS_INPUT_TYPE_DIGITAL 0x01000000 +#define BASS_INPUT_TYPE_LINE 0x02000000 +#define BASS_INPUT_TYPE_MIC 0x03000000 +#define BASS_INPUT_TYPE_SYNTH 0x04000000 +#define BASS_INPUT_TYPE_CD 0x05000000 +#define BASS_INPUT_TYPE_PHONE 0x06000000 +#define BASS_INPUT_TYPE_SPEAKER 0x07000000 +#define BASS_INPUT_TYPE_WAVE 0x08000000 +#define BASS_INPUT_TYPE_AUX 0x09000000 +#define BASS_INPUT_TYPE_ANALOG 0x0a000000 // DX8 effect types, use with BASS_ChannelSetFX -enum -{ - BASS_FX_DX8_CHORUS, - BASS_FX_DX8_COMPRESSOR, - BASS_FX_DX8_DISTORTION, - BASS_FX_DX8_ECHO, - BASS_FX_DX8_FLANGER, - BASS_FX_DX8_GARGLE, - BASS_FX_DX8_I3DL2REVERB, - BASS_FX_DX8_PARAMEQ, - BASS_FX_DX8_REVERB +enum { + BASS_FX_DX8_CHORUS, + BASS_FX_DX8_COMPRESSOR, + BASS_FX_DX8_DISTORTION, + BASS_FX_DX8_ECHO, + BASS_FX_DX8_FLANGER, + BASS_FX_DX8_GARGLE, + BASS_FX_DX8_I3DL2REVERB, + BASS_FX_DX8_PARAMEQ, + BASS_FX_DX8_REVERB }; typedef struct { - float fWetDryMix; - float fDepth; - float fFeedback; - float fFrequency; - DWORD lWaveform; // 0=triangle, 1=sine - float fDelay; - DWORD lPhase; // BASS_DX8_PHASE_xxx + float fWetDryMix; + float fDepth; + float fFeedback; + float fFrequency; + DWORD lWaveform; // 0=triangle, 1=sine + float fDelay; + DWORD lPhase; // BASS_DX8_PHASE_xxx } BASS_DX8_CHORUS; typedef struct { - float fGain; - float fAttack; - float fRelease; - float fThreshold; - float fRatio; - float fPredelay; + float fGain; + float fAttack; + float fRelease; + float fThreshold; + float fRatio; + float fPredelay; } BASS_DX8_COMPRESSOR; typedef struct { - float fGain; - float fEdge; - float fPostEQCenterFrequency; - float fPostEQBandwidth; - float fPreLowpassCutoff; + float fGain; + float fEdge; + float fPostEQCenterFrequency; + float fPostEQBandwidth; + float fPreLowpassCutoff; } BASS_DX8_DISTORTION; typedef struct { - float fWetDryMix; - float fFeedback; - float fLeftDelay; - float fRightDelay; - BOOL lPanDelay; + float fWetDryMix; + float fFeedback; + float fLeftDelay; + float fRightDelay; + BOOL lPanDelay; } BASS_DX8_ECHO; typedef struct { - float fWetDryMix; - float fDepth; - float fFeedback; - float fFrequency; - DWORD lWaveform; // 0=triangle, 1=sine - float fDelay; - DWORD lPhase; // BASS_DX8_PHASE_xxx + float fWetDryMix; + float fDepth; + float fFeedback; + float fFrequency; + DWORD lWaveform; // 0=triangle, 1=sine + float fDelay; + DWORD lPhase; // BASS_DX8_PHASE_xxx } BASS_DX8_FLANGER; typedef struct { - DWORD dwRateHz; // Rate of modulation in hz - DWORD dwWaveShape; // 0=triangle, 1=square + DWORD dwRateHz; // Rate of modulation in hz + DWORD dwWaveShape; // 0=triangle, 1=square } BASS_DX8_GARGLE; typedef struct { - int lRoom; // [-10000, 0] default: -1000 mB - int lRoomHF; // [-10000, 0] default: 0 mB - float flRoomRolloffFactor; // [0.0, 10.0] default: 0.0 - float flDecayTime; // [0.1, 20.0] default: 1.49s - float flDecayHFRatio; // [0.1, 2.0] default: 0.83 - int lReflections; // [-10000, 1000] default: -2602 mB - float flReflectionsDelay; // [0.0, 0.3] default: 0.007 s - int lReverb; // [-10000, 2000] default: 200 mB - float flReverbDelay; // [0.0, 0.1] default: 0.011 s - float flDiffusion; // [0.0, 100.0] default: 100.0 % - float flDensity; // [0.0, 100.0] default: 100.0 % - float flHFReference; // [20.0, 20000.0] default: 5000.0 Hz + int lRoom; // [-10000, 0] default: -1000 mB + int lRoomHF; // [-10000, 0] default: 0 mB + float flRoomRolloffFactor; // [0.0, 10.0] default: 0.0 + float flDecayTime; // [0.1, 20.0] default: 1.49s + float flDecayHFRatio; // [0.1, 2.0] default: 0.83 + int lReflections; // [-10000, 1000] default: -2602 mB + float flReflectionsDelay; // [0.0, 0.3] default: 0.007 s + int lReverb; // [-10000, 2000] default: 200 mB + float flReverbDelay; // [0.0, 0.1] default: 0.011 s + float flDiffusion; // [0.0, 100.0] default: 100.0 % + float flDensity; // [0.0, 100.0] default: 100.0 % + float flHFReference; // [20.0, 20000.0] default: 5000.0 Hz } BASS_DX8_I3DL2REVERB; typedef struct { - float fCenter; - float fBandwidth; - float fGain; + float fCenter; + float fBandwidth; + float fGain; } BASS_DX8_PARAMEQ; typedef struct { - float fInGain; // [-96.0,0.0] default: 0.0 dB - float fReverbMix; // [-96.0,0.0] default: 0.0 db - float fReverbTime; // [0.001,3000.0] default: 1000.0 ms - float fHighFreqRTRatio; // [0.001,0.999] default: 0.001 + float fInGain; // [-96.0,0.0] default: 0.0 dB + float fReverbMix; // [-96.0,0.0] default: 0.0 db + float fReverbTime; // [0.001,3000.0] default: 1000.0 ms + float fHighFreqRTRatio; // [0.001,0.999] default: 0.001 } BASS_DX8_REVERB; -#define BASS_DX8_PHASE_NEG_180 0 -#define BASS_DX8_PHASE_NEG_90 1 -#define BASS_DX8_PHASE_ZERO 2 -#define BASS_DX8_PHASE_90 3 -#define BASS_DX8_PHASE_180 4 - -typedef void (CALLBACK IOSNOTIFYPROC)(DWORD status); -/* iOS notification callback function. -status : The notification (BASS_IOSNOTIFY_xxx) */ - -#define BASS_IOSNOTIFY_INTERRUPT 1 // interruption started -#define BASS_IOSNOTIFY_INTERRUPT_END 2 // interruption ended +#define BASS_DX8_PHASE_NEG_180 0 +#define BASS_DX8_PHASE_NEG_90 1 +#define BASS_DX8_PHASE_ZERO 2 +#define BASS_DX8_PHASE_90 3 +#define BASS_DX8_PHASE_180 4 + +typedef void(CALLBACK IOSNOTIFYPROC)(DWORD status); +/* iOS notification callback function. +status : The notification (BASS_IOSNOTIFY_xxx) */ + +#define BASS_IOSNOTIFY_INTERRUPT 1 // interruption started +#define BASS_IOSNOTIFY_INTERRUPT_END 2 // interruption ended BOOL BASSDEF(BASS_SetConfig)(DWORD option, DWORD value); DWORD BASSDEF(BASS_GetConfig)(DWORD option); @@ -901,15 +931,19 @@ void *BASSDEF(BASS_GetConfigPtr)(DWORD option); DWORD BASSDEF(BASS_GetVersion)(); int BASSDEF(BASS_ErrorGetCode)(); BOOL BASSDEF(BASS_GetDeviceInfo)(DWORD device, BASS_DEVICEINFO *info); -#if defined(_WIN32) && !defined(_WIN32_WCE) && !(WINAPI_FAMILY && WINAPI_FAMILY!=WINAPI_FAMILY_DESKTOP_APP) -BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, HWND win, const GUID *dsguid); +#if defined(_WIN32) && !defined(_WIN32_WCE) && \ + !(WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) +BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, HWND win, + const GUID *dsguid); #else -BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, void *win, void *dsguid); +BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, void *win, + void *dsguid); #endif BOOL BASSDEF(BASS_SetDevice)(DWORD device); DWORD BASSDEF(BASS_GetDevice)(); BOOL BASSDEF(BASS_Free)(); -#if defined(_WIN32) && !defined(_WIN32_WCE) && !(WINAPI_FAMILY && WINAPI_FAMILY!=WINAPI_FAMILY_DESKTOP_APP) +#if defined(_WIN32) && !defined(_WIN32_WCE) && \ + !(WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) void *BASSDEF(BASS_GetDSoundObject)(DWORD object); #endif BOOL BASSDEF(BASS_GetInfo)(BASS_INFO *info); @@ -927,19 +961,29 @@ const BASS_PLUGININFO *BASSDEF(BASS_PluginGetInfo)(HPLUGIN handle); BOOL BASSDEF(BASS_Set3DFactors)(float distf, float rollf, float doppf); BOOL BASSDEF(BASS_Get3DFactors)(float *distf, float *rollf, float *doppf); -BOOL BASSDEF(BASS_Set3DPosition)(const BASS_3DVECTOR *pos, const BASS_3DVECTOR *vel, const BASS_3DVECTOR *front, const BASS_3DVECTOR *top); -BOOL BASSDEF(BASS_Get3DPosition)(BASS_3DVECTOR *pos, BASS_3DVECTOR *vel, BASS_3DVECTOR *front, BASS_3DVECTOR *top); +BOOL BASSDEF(BASS_Set3DPosition)(const BASS_3DVECTOR *pos, + const BASS_3DVECTOR *vel, + const BASS_3DVECTOR *front, + const BASS_3DVECTOR *top); +BOOL BASSDEF(BASS_Get3DPosition)(BASS_3DVECTOR *pos, BASS_3DVECTOR *vel, + BASS_3DVECTOR *front, BASS_3DVECTOR *top); void BASSDEF(BASS_Apply3D)(); -#if defined(_WIN32) && !defined(_WIN32_WCE) && !(WINAPI_FAMILY && WINAPI_FAMILY!=WINAPI_FAMILY_DESKTOP_APP) -BOOL BASSDEF(BASS_SetEAXParameters)(int env, float vol, float decay, float damp); -BOOL BASSDEF(BASS_GetEAXParameters)(DWORD *env, float *vol, float *decay, float *damp); +#if defined(_WIN32) && !defined(_WIN32_WCE) && \ + !(WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) +BOOL BASSDEF(BASS_SetEAXParameters)(int env, float vol, float decay, + float damp); +BOOL BASSDEF(BASS_GetEAXParameters)(DWORD *env, float *vol, float *decay, + float *damp); #endif -HMUSIC BASSDEF(BASS_MusicLoad)(BOOL mem, const void *file, QWORD offset, DWORD length, DWORD flags, DWORD freq); +HMUSIC BASSDEF(BASS_MusicLoad)(BOOL mem, const void *file, QWORD offset, + DWORD length, DWORD flags, DWORD freq); BOOL BASSDEF(BASS_MusicFree)(HMUSIC handle); -HSAMPLE BASSDEF(BASS_SampleLoad)(BOOL mem, const void *file, QWORD offset, DWORD length, DWORD max, DWORD flags); -HSAMPLE BASSDEF(BASS_SampleCreate)(DWORD length, DWORD freq, DWORD chans, DWORD max, DWORD flags); +HSAMPLE BASSDEF(BASS_SampleLoad)(BOOL mem, const void *file, QWORD offset, + DWORD length, DWORD max, DWORD flags); +HSAMPLE BASSDEF(BASS_SampleCreate)(DWORD length, DWORD freq, DWORD chans, + DWORD max, DWORD flags); BOOL BASSDEF(BASS_SampleFree)(HSAMPLE handle); BOOL BASSDEF(BASS_SampleSetData)(HSAMPLE handle, const void *buffer); BOOL BASSDEF(BASS_SampleGetData)(HSAMPLE handle, void *buffer); @@ -949,14 +993,22 @@ HCHANNEL BASSDEF(BASS_SampleGetChannel)(HSAMPLE handle, BOOL onlynew); DWORD BASSDEF(BASS_SampleGetChannels)(HSAMPLE handle, HCHANNEL *channels); BOOL BASSDEF(BASS_SampleStop)(HSAMPLE handle); -HSTREAM BASSDEF(BASS_StreamCreate)(DWORD freq, DWORD chans, DWORD flags, STREAMPROC *proc, void *user); -HSTREAM BASSDEF(BASS_StreamCreateFile)(BOOL mem, const void *file, QWORD offset, QWORD length, DWORD flags); -HSTREAM BASSDEF(BASS_StreamCreateURL)(const char *url, DWORD offset, DWORD flags, DOWNLOADPROC *proc, void *user); -HSTREAM BASSDEF(BASS_StreamCreateFileUser)(DWORD system, DWORD flags, const BASS_FILEPROCS *proc, void *user); +HSTREAM BASSDEF(BASS_StreamCreate)(DWORD freq, DWORD chans, DWORD flags, + STREAMPROC *proc, void *user); +HSTREAM BASSDEF(BASS_StreamCreateFile)(BOOL mem, const void *file, QWORD offset, + QWORD length, DWORD flags); +HSTREAM BASSDEF(BASS_StreamCreateURL)(const char *url, DWORD offset, + DWORD flags, DOWNLOADPROC *proc, + void *user); +HSTREAM BASSDEF(BASS_StreamCreateFileUser)(DWORD system, DWORD flags, + const BASS_FILEPROCS *proc, + void *user); BOOL BASSDEF(BASS_StreamFree)(HSTREAM handle); QWORD BASSDEF(BASS_StreamGetFilePosition)(HSTREAM handle, DWORD mode); -DWORD BASSDEF(BASS_StreamPutData)(HSTREAM handle, const void *buffer, DWORD length); -DWORD BASSDEF(BASS_StreamPutFileData)(HSTREAM handle, const void *buffer, DWORD length); +DWORD BASSDEF(BASS_StreamPutData)(HSTREAM handle, const void *buffer, + DWORD length); +DWORD BASSDEF(BASS_StreamPutFileData)(HSTREAM handle, const void *buffer, + DWORD length); BOOL BASSDEF(BASS_RecordGetDeviceInfo)(DWORD device, BASS_DEVICEINFO *info); BOOL BASSDEF(BASS_RecordInit)(int device); @@ -967,7 +1019,8 @@ BOOL BASSDEF(BASS_RecordGetInfo)(BASS_RECORDINFO *info); const char *BASSDEF(BASS_RecordGetInputName)(int input); BOOL BASSDEF(BASS_RecordSetInput)(int input, DWORD flags, float volume); DWORD BASSDEF(BASS_RecordGetInput)(int input, float *volume); -HRECORD BASSDEF(BASS_RecordStart)(DWORD freq, DWORD chans, DWORD flags, RECORDPROC *proc, void *user); +HRECORD BASSDEF(BASS_RecordStart)(DWORD freq, DWORD chans, DWORD flags, + RECORDPROC *proc, void *user); double BASSDEF(BASS_ChannelBytes2Seconds)(DWORD handle, QWORD pos); QWORD BASSDEF(BASS_ChannelSeconds2Bytes)(DWORD handle, double pos); @@ -983,24 +1036,39 @@ BOOL BASSDEF(BASS_ChannelPlay)(DWORD handle, BOOL restart); BOOL BASSDEF(BASS_ChannelStop)(DWORD handle); BOOL BASSDEF(BASS_ChannelPause)(DWORD handle); BOOL BASSDEF(BASS_ChannelSetAttribute)(DWORD handle, DWORD attrib, float value); -BOOL BASSDEF(BASS_ChannelGetAttribute)(DWORD handle, DWORD attrib, float *value); -BOOL BASSDEF(BASS_ChannelSlideAttribute)(DWORD handle, DWORD attrib, float value, DWORD time); +BOOL BASSDEF(BASS_ChannelGetAttribute)(DWORD handle, DWORD attrib, + float *value); +BOOL BASSDEF(BASS_ChannelSlideAttribute)(DWORD handle, DWORD attrib, + float value, DWORD time); BOOL BASSDEF(BASS_ChannelIsSliding)(DWORD handle, DWORD attrib); -BOOL BASSDEF(BASS_ChannelSetAttributeEx)(DWORD handle, DWORD attrib, void *value, DWORD size); -DWORD BASSDEF(BASS_ChannelGetAttributeEx)(DWORD handle, DWORD attrib, void *value, DWORD size); -BOOL BASSDEF(BASS_ChannelSet3DAttributes)(DWORD handle, int mode, float min, float max, int iangle, int oangle, float outvol); -BOOL BASSDEF(BASS_ChannelGet3DAttributes)(DWORD handle, DWORD *mode, float *min, float *max, DWORD *iangle, DWORD *oangle, float *outvol); -BOOL BASSDEF(BASS_ChannelSet3DPosition)(DWORD handle, const BASS_3DVECTOR *pos, const BASS_3DVECTOR *orient, const BASS_3DVECTOR *vel); -BOOL BASSDEF(BASS_ChannelGet3DPosition)(DWORD handle, BASS_3DVECTOR *pos, BASS_3DVECTOR *orient, BASS_3DVECTOR *vel); +BOOL BASSDEF(BASS_ChannelSetAttributeEx)(DWORD handle, DWORD attrib, + void *value, DWORD size); +DWORD BASSDEF(BASS_ChannelGetAttributeEx)(DWORD handle, DWORD attrib, + void *value, DWORD size); +BOOL BASSDEF(BASS_ChannelSet3DAttributes)(DWORD handle, int mode, float min, + float max, int iangle, int oangle, + float outvol); +BOOL BASSDEF(BASS_ChannelGet3DAttributes)(DWORD handle, DWORD *mode, float *min, + float *max, DWORD *iangle, + DWORD *oangle, float *outvol); +BOOL BASSDEF(BASS_ChannelSet3DPosition)(DWORD handle, const BASS_3DVECTOR *pos, + const BASS_3DVECTOR *orient, + const BASS_3DVECTOR *vel); +BOOL BASSDEF(BASS_ChannelGet3DPosition)(DWORD handle, BASS_3DVECTOR *pos, + BASS_3DVECTOR *orient, + BASS_3DVECTOR *vel); QWORD BASSDEF(BASS_ChannelGetLength)(DWORD handle, DWORD mode); BOOL BASSDEF(BASS_ChannelSetPosition)(DWORD handle, QWORD pos, DWORD mode); QWORD BASSDEF(BASS_ChannelGetPosition)(DWORD handle, DWORD mode); DWORD BASSDEF(BASS_ChannelGetLevel)(DWORD handle); -BOOL BASSDEF(BASS_ChannelGetLevelEx)(DWORD handle, float *levels, float length, DWORD flags); +BOOL BASSDEF(BASS_ChannelGetLevelEx)(DWORD handle, float *levels, float length, + DWORD flags); DWORD BASSDEF(BASS_ChannelGetData)(DWORD handle, void *buffer, DWORD length); -HSYNC BASSDEF(BASS_ChannelSetSync)(DWORD handle, DWORD type, QWORD param, SYNCPROC *proc, void *user); +HSYNC BASSDEF(BASS_ChannelSetSync)(DWORD handle, DWORD type, QWORD param, + SYNCPROC *proc, void *user); BOOL BASSDEF(BASS_ChannelRemoveSync)(DWORD handle, HSYNC sync); -HDSP BASSDEF(BASS_ChannelSetDSP)(DWORD handle, DSPPROC *proc, void *user, int priority); +HDSP BASSDEF(BASS_ChannelSetDSP)(DWORD handle, DSPPROC *proc, void *user, + int priority); BOOL BASSDEF(BASS_ChannelRemoveDSP)(DWORD handle, HDSP dsp); BOOL BASSDEF(BASS_ChannelSetLink)(DWORD handle, DWORD chan); BOOL BASSDEF(BASS_ChannelRemoveLink)(DWORD handle, DWORD chan); @@ -1011,41 +1079,51 @@ BOOL BASSDEF(BASS_FXSetParameters)(HFX handle, const void *params); BOOL BASSDEF(BASS_FXGetParameters)(HFX handle, void *params); BOOL BASSDEF(BASS_FXReset)(HFX handle); BOOL BASSDEF(BASS_FXSetPriority)(HFX handle, int priority); - + #ifdef __cplusplus } - -#if defined(_WIN32) && !defined(NOBASSOVERLOADS) -static inline HPLUGIN BASS_PluginLoad(const WCHAR *file, DWORD flags) -{ - return BASS_PluginLoad((const char*)file, flags|BASS_UNICODE); -} - -static inline HMUSIC BASS_MusicLoad(BOOL mem, const WCHAR *file, QWORD offset, DWORD length, DWORD flags, DWORD freq) -{ - return BASS_MusicLoad(mem, (const void*)file, offset, length, flags|BASS_UNICODE, freq); -} - -static inline HSAMPLE BASS_SampleLoad(BOOL mem, const WCHAR *file, QWORD offset, DWORD length, DWORD max, DWORD flags) -{ - return BASS_SampleLoad(mem, (const void*)file, offset, length, max, flags|BASS_UNICODE); -} - -static inline HSTREAM BASS_StreamCreateFile(BOOL mem, const WCHAR *file, QWORD offset, QWORD length, DWORD flags) -{ - return BASS_StreamCreateFile(mem, (const void*)file, offset, length, flags|BASS_UNICODE); -} - -static inline HSTREAM BASS_StreamCreateURL(const WCHAR *url, DWORD offset, DWORD flags, DOWNLOADPROC *proc, void *user) -{ - return BASS_StreamCreateURL((const char*)url, offset, flags|BASS_UNICODE, proc, user); -} - -static inline BOOL BASS_SetConfigPtr(DWORD option, const WCHAR *value) -{ - return BASS_SetConfigPtr(option|BASS_UNICODE, (const void*)value); -} -#endif + +#if defined(_WIN32) && !defined(NOBASSOVERLOADS) +static inline HPLUGIN BASS_PluginLoad(const WCHAR *file, DWORD flags) +{ + return BASS_PluginLoad((const char *)file, flags | BASS_UNICODE); +} + +static inline HMUSIC BASS_MusicLoad(BOOL mem, const WCHAR *file, QWORD offset, + DWORD length, DWORD flags, DWORD freq) +{ + return BASS_MusicLoad(mem, (const void *)file, offset, length, + flags | BASS_UNICODE, freq); +} + +static inline HSAMPLE BASS_SampleLoad(BOOL mem, const WCHAR *file, QWORD offset, + DWORD length, DWORD max, DWORD flags) +{ + return BASS_SampleLoad(mem, (const void *)file, offset, length, max, + flags | BASS_UNICODE); +} + +static inline HSTREAM BASS_StreamCreateFile(BOOL mem, const WCHAR *file, + QWORD offset, QWORD length, + DWORD flags) +{ + return BASS_StreamCreateFile(mem, (const void *)file, offset, length, + flags | BASS_UNICODE); +} + +static inline HSTREAM BASS_StreamCreateURL(const WCHAR *url, DWORD offset, + DWORD flags, DOWNLOADPROC *proc, + void *user) +{ + return BASS_StreamCreateURL((const char *)url, offset, flags | BASS_UNICODE, + proc, user); +} + +static inline BOOL BASS_SetConfigPtr(DWORD option, const WCHAR *value) +{ + return BASS_SetConfigPtr(option | BASS_UNICODE, (const void *)value); +} +#endif #endif #endif diff --git a/charselect.cpp b/charselect.cpp index 5e887b9b6..f138caf9e 100644 --- a/charselect.cpp +++ b/charselect.cpp @@ -7,195 +7,197 @@ void Courtroom::construct_char_select() { - ui_char_select_background = new AOImage(this, ao_app); + ui_char_select_background = new AOImage(this, ao_app); - ui_char_buttons = new QWidget(ui_char_select_background); + ui_char_buttons = new QWidget(ui_char_select_background); - ui_char_button_selector = new AOImage(ui_char_buttons, ao_app); - ui_char_button_selector->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_char_button_selector->resize(62, 62); + ui_char_button_selector = new AOImage(ui_char_buttons, ao_app); + ui_char_button_selector->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_char_button_selector->resize(62, 62); - ui_back_to_lobby = new AOButton(ui_char_select_background, ao_app); + ui_back_to_lobby = new AOButton(ui_char_select_background, ao_app); - ui_char_select_left = new AOButton(ui_char_select_background, ao_app); - ui_char_select_right = new AOButton(ui_char_select_background, ao_app); + ui_char_select_left = new AOButton(ui_char_select_background, ao_app); + ui_char_select_right = new AOButton(ui_char_select_background, ao_app); - ui_spectator = new AOButton(ui_char_select_background, ao_app); - ui_spectator->setText("Spectator"); + ui_spectator = new AOButton(ui_char_select_background, ao_app); + ui_spectator->setText("Spectator"); - connect(char_button_mapper, SIGNAL(mapped(int)), this, SLOT(char_clicked(int))); - connect(ui_back_to_lobby, SIGNAL(clicked()), this, SLOT(on_back_to_lobby_clicked())); + connect(char_button_mapper, SIGNAL(mapped(int)), this, + SLOT(char_clicked(int))); + connect(ui_back_to_lobby, SIGNAL(clicked()), this, + SLOT(on_back_to_lobby_clicked())); - connect(ui_char_select_left, SIGNAL(clicked()), this, SLOT(on_char_select_left_clicked())); - connect(ui_char_select_right, SIGNAL(clicked()), this, SLOT(on_char_select_right_clicked())); + connect(ui_char_select_left, SIGNAL(clicked()), this, + SLOT(on_char_select_left_clicked())); + connect(ui_char_select_right, SIGNAL(clicked()), this, + SLOT(on_char_select_right_clicked())); - connect(ui_spectator, SIGNAL(clicked()), this, SLOT(on_spectator_clicked())); + connect(ui_spectator, SIGNAL(clicked()), this, SLOT(on_spectator_clicked())); - reconstruct_char_select(); + reconstruct_char_select(); } void Courtroom::reconstruct_char_select() { - while (!ui_char_button_list.isEmpty()) - delete ui_char_button_list.takeLast(); + while (!ui_char_button_list.isEmpty()) + delete ui_char_button_list.takeLast(); - QPoint f_spacing = ao_app->get_button_spacing("char_button_spacing", "courtroom_design.ini"); + QPoint f_spacing = + ao_app->get_button_spacing("char_button_spacing", "courtroom_design.ini"); - const int button_width = 60; - int x_spacing = f_spacing.x(); - int x_mod_count = 0; + const int button_width = 60; + int x_spacing = f_spacing.x(); + int x_mod_count = 0; - const int button_height = 60; - int y_spacing = f_spacing.y(); - int y_mod_count = 0; + const int button_height = 60; + int y_spacing = f_spacing.y(); + int y_mod_count = 0; - set_size_and_pos(ui_char_buttons, "char_buttons"); + set_size_and_pos(ui_char_buttons, "char_buttons"); - char_columns = ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + 1; - char_rows = ((ui_char_buttons->height() - button_height) / (y_spacing + button_height)) + 1; + char_columns = + ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + + 1; + char_rows = ((ui_char_buttons->height() - button_height) / + (y_spacing + button_height)) + + 1; - max_chars_on_page = char_columns * char_rows; + max_chars_on_page = char_columns * char_rows; - for (int n = 0; n < max_chars_on_page; ++n) - { - int x_pos = (button_width + x_spacing) * x_mod_count; - int y_pos = (button_height + y_spacing) * y_mod_count; + for (int n = 0; n < max_chars_on_page; ++n) { + int x_pos = (button_width + x_spacing) * x_mod_count; + int y_pos = (button_height + y_spacing) * y_mod_count; - AOCharButton *button = new AOCharButton(ui_char_buttons, ao_app, x_pos, y_pos); - ui_char_button_list.append(button); + AOCharButton *button = + new AOCharButton(ui_char_buttons, ao_app, x_pos, y_pos); + ui_char_button_list.append(button); - connect(button, SIGNAL(clicked()), char_button_mapper, SLOT(map())); - char_button_mapper->setMapping(button, n); + connect(button, SIGNAL(clicked()), char_button_mapper, SLOT(map())); + char_button_mapper->setMapping(button, n); - // mouse events - connect(button, SIGNAL(mouse_entered(AOCharButton *)), this, SLOT(char_mouse_entered(AOCharButton *))); - connect(button, SIGNAL(mouse_left()), this, SLOT(char_mouse_left())); + // mouse events + connect(button, SIGNAL(mouse_entered(AOCharButton *)), this, + SLOT(char_mouse_entered(AOCharButton *))); + connect(button, SIGNAL(mouse_left()), this, SLOT(char_mouse_left())); - ++x_mod_count; + ++x_mod_count; - if (x_mod_count == char_columns) - { - ++y_mod_count; - x_mod_count = 0; - } + if (x_mod_count == char_columns) { + ++y_mod_count; + x_mod_count = 0; } + } - reset_char_select(); + reset_char_select(); } void Courtroom::reset_char_select() { - current_char_page = 0; + current_char_page = 0; - set_char_select(); - set_char_select_page(); + set_char_select(); + set_char_select_page(); } void Courtroom::set_char_select() { - QString filename = "courtroom_design.ini"; + QString filename = "courtroom_design.ini"; - pos_size_type f_charselect = ao_app->get_element_dimensions("char_select", filename); + pos_size_type f_charselect = + ao_app->get_element_dimensions("char_select", filename); - if (f_charselect.width < 0 || f_charselect.height < 0) - { - qDebug() << "W: did not find courtroom width or height in courtroom_design.ini!"; - this->resize(714, 668); - } - else - this->resize(f_charselect.width, f_charselect.height); + if (f_charselect.width < 0 || f_charselect.height < 0) { + qDebug() + << "W: did not find courtroom width or height in courtroom_design.ini!"; + this->resize(714, 668); + } + else + this->resize(f_charselect.width, f_charselect.height); - ui_char_select_background->resize(f_charselect.width, f_charselect.height); - ui_char_select_background->set_image("charselect_background.png"); + ui_char_select_background->resize(f_charselect.width, f_charselect.height); + ui_char_select_background->set_image("charselect_background.png"); } void Courtroom::set_char_select_page() { - ui_char_select_background->show(); + ui_char_select_background->show(); - ui_char_select_left->hide(); - ui_char_select_right->hide(); + ui_char_select_left->hide(); + ui_char_select_right->hide(); - for (AOCharButton *button : ui_char_button_list) - { - button->reset(); - button->hide(); - } + for (AOCharButton *button : ui_char_button_list) { + button->reset(); + button->hide(); + } - int total_pages = char_list.size() / max_chars_on_page; - int chars_on_page = 0; - - if (char_list.size() % max_chars_on_page != 0) - { - ++total_pages; - //i. e. not on the last page - if (total_pages > current_char_page + 1) - chars_on_page = max_chars_on_page; - else - chars_on_page = char_list.size() % max_chars_on_page; - } - else - chars_on_page = max_chars_on_page; + int total_pages = char_list.size() / max_chars_on_page; + int chars_on_page = 0; + if (char_list.size() % max_chars_on_page != 0) { + ++total_pages; + // i. e. not on the last page if (total_pages > current_char_page + 1) - ui_char_select_right->show(); + chars_on_page = max_chars_on_page; + else + chars_on_page = char_list.size() % max_chars_on_page; + } + else + chars_on_page = max_chars_on_page; - if (current_char_page > 0) - ui_char_select_left->show(); + if (total_pages > current_char_page + 1) + ui_char_select_right->show(); - // show all buttons for this page - for (int n_button = 0; n_button < chars_on_page; ++n_button) - { - if (char_list.length() <= n_button) - continue; + if (current_char_page > 0) + ui_char_select_left->show(); - int n_real_char = n_button + current_char_page * max_chars_on_page; - AOCharButton *f_button = ui_char_button_list.at(n_button); + // show all buttons for this page + for (int n_button = 0; n_button < chars_on_page; ++n_button) { + if (char_list.length() <= n_button) + continue; - f_button->set_image(char_list.at(n_real_char).name); - f_button->show(); + int n_real_char = n_button + current_char_page * max_chars_on_page; + AOCharButton *f_button = ui_char_button_list.at(n_button); - if (char_list.at(n_real_char).taken) - f_button->set_taken(); - } + f_button->set_image(char_list.at(n_real_char).name); + f_button->show(); + + if (char_list.at(n_real_char).taken) + f_button->set_taken(); + } } void Courtroom::char_clicked(int n_char) { - int n_real_char = n_char + current_char_page * max_chars_on_page; - - QString char_ini_path = ao_app->get_character_path(char_list.at(n_real_char).name) + "char.ini"; - qDebug() << "char_ini_path" << char_ini_path; - - if (!file_exists(char_ini_path)) - { - qDebug() << "did not find " << char_ini_path; - call_notice("Could not find " + char_ini_path); - return; - } - - if (n_real_char == m_cid) - { - enter_courtroom(m_cid); - } - else - { - QString content = "CC#" + QString::number(ao_app->s_pv) + "#" + - QString::number(n_real_char) + "#" + get_hdid() + "#%"; - ao_app->send_server_packet(new AOPacket(content)); - } + int n_real_char = n_char + current_char_page * max_chars_on_page; + + QString char_ini_path = + ao_app->get_character_path(char_list.at(n_real_char).name) + "char.ini"; + qDebug() << "char_ini_path" << char_ini_path; + + if (!file_exists(char_ini_path)) { + qDebug() << "did not find " << char_ini_path; + call_notice("Could not find " + char_ini_path); + return; + } + + if (n_real_char == m_cid) { + enter_courtroom(m_cid); + } + else { + QString content = "CC#" + QString::number(ao_app->s_pv) + "#" + + QString::number(n_real_char) + "#" + get_hdid() + "#%"; + ao_app->send_server_packet(new AOPacket(content)); + } } void Courtroom::char_mouse_entered(AOCharButton *p_caller) { - if (p_caller == nullptr) - return; - ui_char_button_selector->move(p_caller->x() - 1, p_caller->y() - 1); - ui_char_button_selector->raise(); - ui_char_button_selector->show(); + if (p_caller == nullptr) + return; + ui_char_button_selector->move(p_caller->x() - 1, p_caller->y() - 1); + ui_char_button_selector->raise(); + ui_char_button_selector->show(); } -void Courtroom::char_mouse_left() -{ - ui_char_button_selector->hide(); -} +void Courtroom::char_mouse_left() { ui_char_button_selector->hide(); } diff --git a/courtroom.cpp b/courtroom.cpp index cea930d20..3f68dad59 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1,32 +1,32 @@ #include "courtroom.h" #include "aoapplication.h" -#include "lobby.h" -#include "hardware_functions.h" -#include "file_functions.h" #include "datatypes.h" #include "debug_functions.h" +#include "file_functions.h" +#include "hardware_functions.h" +#include "lobby.h" -#include -#include -#include #include -#include -#include -#include -#include -#include +#include #include -#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; ao_config = new AOConfig(this); - //initializing sound device + // initializing sound device BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, 0, NULL); BASS_PluginLoad("bassopus.dll", BASS_UNICODE); @@ -47,25 +47,22 @@ void Courtroom::enter_courtroom(int p_cid) set_char_rpc(); - if (m_cid == -1) - { + if (m_cid == -1) { ao_app->discord->state_spectate(); f_char = ""; } - else - { + else { f_char = ao_app->get_char_name(char_list.at(m_cid).name); QString r_char = f_char; // regex for removing non letter (except _) characters - QRegularExpression re(QString::fromUtf8("[-`~!@#$%^&*()—+=|:;<>«»,.?/{}\'\"\\[\\]]")); + QRegularExpression re( + QString::fromUtf8("[-`~!@#$%^&*()—+=|:;<>«»,.?/{}\'\"\\[\\]]")); r_char.remove(re); - if(!rpc_char_list.contains(f_char.toLower())) - { + if (!rpc_char_list.contains(f_char.toLower())) { ao_app->discord->toggle(1); } - else - { + else { ao_app->discord->toggle(0); } @@ -101,9 +98,9 @@ void Courtroom::enter_courtroom(int p_cid) set_character_position(ao_app->get_char_side(f_char), changed_character); // Update widgets first, then check if everything is valid - // This will also handle showing the correct shouts, effects and wtce buttons, and cycling - // through them if the buttons that are supposed to be displayed do not exist - // It will also take care of free blocks + // This will also handle showing the correct shouts, effects and wtce buttons, + // and cycling through them if the buttons that are supposed to be displayed + // do not exist It will also take care of free blocks set_widgets(); check_shouts(); @@ -129,14 +126,15 @@ void Courtroom::enter_courtroom(int p_cid) list_areas(); list_sfx(); - ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors + ui_sfx_list->setCurrentItem( + ui_sfx_list->item(0)); // prevents undefined errors // unmute audio set_audio_mute_enabled(false); testimony_in_progress = false; - //ui_server_chatlog->setHtml(ui_server_chatlog->toHtml()); + // ui_server_chatlog->setHtml(ui_server_chatlog->toHtml()); ui_char_select_background->hide(); @@ -144,7 +142,7 @@ void Courtroom::enter_courtroom(int p_cid) ui_ic_chat_message->setFocus(); for (int i = 0; i < ui_timers.length(); ++i) - ui_timers[i]->redraw(); + ui_timers[i]->redraw(); set_widget_names(); set_widget_layers(); @@ -172,22 +170,19 @@ void Courtroom::set_window_title(QString p_title) this->setWindowTitle(p_title); } - - void Courtroom::set_scene() { if (testimony_in_progress) show_testimony(); - //witness is default if pos is invalid + // witness is default if pos is invalid QString f_background = "witnessempty"; QString f_desk_image = "stand"; QString f_desk_mod = m_chatmessage[DESK_MOD]; QString f_side = m_chatmessage[SIDE]; QString ini_path = ao_app->get_background_path() + "backgrounds.ini"; - if (file_exists(ini_path)) - { + if (file_exists(ini_path)) { f_background = ao_app->read_design_ini(f_side, ini_path); f_desk_image = ao_app->read_design_ini(f_side + "_desk", ini_path); @@ -196,35 +191,28 @@ void Courtroom::set_scene() ui_vp_desk->hide(); } } - else - { - if (f_side == "def") - { + else { + if (f_side == "def") { f_background = "defenseempty"; f_desk_image = "defensedesk"; } - else if (f_side == "pro") - { + else if (f_side == "pro") { f_background = "prosecutorempty"; f_desk_image = "prosecutiondesk"; } - else if (f_side == "jud") - { + else if (f_side == "jud") { f_background = "judgestand"; f_desk_image = "judgedesk"; } - else if (f_side == "hld") - { + else if (f_side == "hld") { f_background = "helperstand"; f_desk_image = "helperdesk"; } - else if (f_side == "hlp") - { + else if (f_side == "hlp") { f_background = "prohelperstand"; f_desk_image = "prohelperdesk"; } - else - { + else { f_desk_image = "stand"; } @@ -242,9 +230,8 @@ void Courtroom::set_scene() has_all_desks = true; if (f_desk_mod == "0" || - (f_desk_mod != "1" && (f_side == "jud" || - f_side == "hld" || - f_side == "hlp"))) + (f_desk_mod != "1" && + (f_side == "jud" || f_side == "hld" || f_side == "hlp"))) ui_vp_desk->hide(); else if (!has_all_desks) ui_vp_desk->hide(); @@ -261,13 +248,14 @@ void Courtroom::set_char_rpc() rpc_char_list.clear(); QFile config_file(ao_app->get_base_path() + rpc_ini); - if (!config_file.open(QIODevice::ReadOnly)) - { qDebug() << "Error reading" << ao_app->get_base_path() + rpc_ini; return; } + if (!config_file.open(QIODevice::ReadOnly)) { + qDebug() << "Error reading" << ao_app->get_base_path() + rpc_ini; + return; + } QTextStream in(&config_file); - while(!in.atEnd()) - { + while (!in.atEnd()) { QString f_line = in.readLine().trimmed(); QStringList line_elements = f_line.split("-"); @@ -280,9 +268,9 @@ void Courtroom::set_char_rpc() void Courtroom::set_taken(int n_char, bool p_taken) { - if (n_char >= char_list.size()) - { - qDebug() << "W: set_taken attempted to set an index bigger than char_list size"; + if (n_char >= char_list.size()) { + qDebug() + << "W: set_taken attempted to set an index bigger than char_list size"; return; } @@ -307,19 +295,24 @@ void Courtroom::handle_music_anim() QString file_b = fonts_ini; pos_size_type res_a = ao_app->get_element_dimensions("music_name", file_a); pos_size_type res_b = ao_app->get_element_dimensions("music_area", file_a); - float speed = static_cast(ao_app->get_font_property("music_name_speed", file_b)); + float speed = + static_cast(ao_app->get_font_property("music_name_speed", file_b)); QFont f_font = ui_vp_music_name->font(); QFontMetrics fm(f_font); int dist; - if ( ao_app->read_theme_ini( "enable_const_music_speed", cc_config_ini ) == "true") + if (ao_app->read_theme_ini("enable_const_music_speed", cc_config_ini) == + "true") dist = res_b.width; - else dist = fm.width(ui_vp_music_name->toPlainText()); - int time = static_cast(1000000*dist/speed); + else + dist = fm.width(ui_vp_music_name->toPlainText()); + int time = static_cast(1000000 * dist / speed); music_anim->setLoopCount(-1); music_anim->setDuration(time); - music_anim->setStartValue(QRect(res_b.width + res_b.x, res_a.y, res_a.width, res_a.height)); - music_anim->setEndValue(QRect(-dist + res_a.x, res_a.y, res_a.width, res_a.height)); + music_anim->setStartValue( + QRect(res_b.width + res_b.x, res_a.y, res_a.width, res_a.height)); + music_anim->setEndValue( + QRect(-dist + res_a.x, res_a.y, res_a.width, res_a.height)); music_anim->start(); } @@ -348,24 +341,21 @@ void Courtroom::list_music() int n_listed_songs = 0; - for (int n_song = 0 ; n_song < music_list.size() ; ++n_song) - { + for (int n_song = 0; n_song < music_list.size(); ++n_song) { QString i_song = music_list.at(n_song); bool found = false; - for (auto& ext : QStringList { "", ".wav", ".ogg", ".mp3" }) - { + for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) { QString r_song = i_song + ext; - QString song_path = ao_app->get_base_path() + "sounds/music/" + r_song.toLower(); - if (file_exists(song_path)) - { + QString song_path = + ao_app->get_base_path() + "sounds/music/" + r_song.toLower(); + if (file_exists(song_path)) { found = true; break; } } - if (i_song.toLower().contains(ui_music_search->text().toLower())) - { + if (i_song.toLower().contains(ui_music_search->text().toLower())) { ui_music_list->addItem(i_song); if (found) @@ -381,7 +371,7 @@ void Courtroom::list_music() void Courtroom::list_areas() { ui_area_list->clear(); -// area_names.clear(); + // area_names.clear(); QString f_file = "courtroom_design.ini"; @@ -395,17 +385,14 @@ void Courtroom::list_areas() int n_listed_areas = 0; - for (int n_area = 0 ; n_area < area_list.size() ; ++n_area) - { + for (int n_area = 0; n_area < area_list.size(); ++n_area) { QString i_area = ""; i_area.append(area_list.at(n_area)); - if (i_area.toLower().contains(ui_music_search->text().toLower())) - { + if (i_area.toLower().contains(ui_music_search->text().toLower())) { ui_area_list->addItem(i_area); -// area_names.append(i_); - + // area_names.append(i_); ui_area_list->item(n_listed_areas)->setBackground(free_brush); @@ -434,24 +421,25 @@ void Courtroom::list_sfx() int n_listed_sfxs = 0; - for (int n_sfx = 0 ; n_sfx < sfx_list.size() ; ++n_sfx) - { + for (int n_sfx = 0; n_sfx < sfx_list.size(); ++n_sfx) { QStringList sfx = sfx_list.at(n_sfx).split("="); QString i_sfx = sfx.at(0).trimmed(); QString d_sfx = ""; sfx_names.append(i_sfx); - if(sfx_list.at(n_sfx).split("=").size() < 2) + if (sfx_list.at(n_sfx).split("=").size() < 2) d_sfx = i_sfx; - else d_sfx = sfx.at(1).trimmed(); + else + d_sfx = sfx.at(1).trimmed(); // qDebug() << "isfx=" << i_sfx << "dsfx=" << d_sfx << "sfx=" << sfx; - if (i_sfx.toLower().contains(ui_sfx_search->text().toLower())) - { + if (i_sfx.toLower().contains(ui_sfx_search->text().toLower())) { ui_sfx_list->addItem(d_sfx); - ui_sfx_list->item(ui_sfx_list->count()-1)->setStatusTip(QString::number(n_sfx+2)); + ui_sfx_list->item(ui_sfx_list->count() - 1) + ->setStatusTip(QString::number(n_sfx + 2)); - QString sfx_path = ao_app->get_base_path() + "sounds/general/" + i_sfx.toLower(); + QString sfx_path = + ao_app->get_base_path() + "sounds/general/" + i_sfx.toLower(); if (file_exists(sfx_path)) ui_sfx_list->item(n_listed_sfxs)->setBackground(found_brush); @@ -467,8 +455,10 @@ void Courtroom::list_note_files() { QString f_config = ao_app->get_base_path() + file_select_ini; QFile f_file(f_config); - if(!f_file.open(QIODevice::ReadOnly)) - { qDebug() << "Couldn't open" << f_config; return; } + if (!f_file.open(QIODevice::ReadOnly)) { + qDebug() << "Couldn't open" << f_config; + return; + } note_list.clear(); @@ -479,24 +469,24 @@ void Courtroom::list_note_files() QVBoxLayout *f_layout = ui_note_area->m_layout; - while(!in.atEnd()) - { + while (!in.atEnd()) { QString line = in.readLine().trimmed(); QStringList f_contents = line.split("="); - if(f_contents.size() < 2) + if (f_contents.size() < 2) continue; int f_index = f_contents.at(0).toInt(); f_filestring = f_filename = f_contents.at(1).trimmed(); - if(f_contents.size() > 2) + if (f_contents.size() > 2) f_filename = f_contents.at(2).trimmed(); - while(f_index >= f_layout->count()) + while (f_index >= f_layout->count()) on_add_button_clicked(); - AONotePicker *f_notepicker = static_cast(f_layout->itemAt(f_index)->widget()); + AONotePicker *f_notepicker = + static_cast(f_layout->itemAt(f_index)->widget()); f_notepicker->m_line->setText(f_filename); f_notepicker->real_file = f_filestring; } @@ -504,12 +494,12 @@ void Courtroom::list_note_files() void Courtroom::load_note() { - //Do not attempt to load anything if no file was chosen. This makes it so that notepad text - //is kept in client if the user has decided not to choose a file to save to. Of course, this is - //ephimeral storage, it will be erased after the client closes or when the user decides to load - //a file. + // Do not attempt to load anything if no file was chosen. This makes it so + // that notepad text is kept in client if the user has decided not to choose a + // file to save to. Of course, this is ephimeral storage, it will be erased + // after the client closes or when the user decides to load a file. if (current_file.isEmpty()) - return; + return; QString f_text = ao_app->read_note(current_file); ui_vp_notepad->setText(f_text); } @@ -532,8 +522,9 @@ void Courtroom::save_textlog(QString p_text) void Courtroom::append_server_chatmessage(QString p_name, QString p_message) { ui_server_chatlog->append_chatmessage(p_name, p_message); - if(ao_config->log_is_recording_enabled()) - save_textlog("(OOC)[" + QTime::currentTime().toString() + "] " + p_name + ": " + p_message); + if (ao_config->log_is_recording_enabled()) + save_textlog("(OOC)[" + QTime::currentTime().toString() + "] " + p_name + + ": " + p_message); } void Courtroom::on_chat_return_pressed() @@ -541,29 +532,29 @@ void Courtroom::on_chat_return_pressed() if (ui_ic_chat_message->text() == "" || is_client_muted) return; - if ((anim_state < 3 || text_state < 2) && - m_objection_state == 0) + if ((anim_state < 3 || text_state < 2) && m_objection_state == 0) return; - // qDebug() << "prev_emote = " << prev_emote << "current_emote = " << current_emote; + // qDebug() << "prev_emote = " << prev_emote << "current_emote = " << + // current_emote; // qDebug() << "same_emote = " << same_emote; - //MS# - //deskmod# - //pre-emote# - //character# - //emote# - //message# - //side# - //sfx-name# - //emote_modifier# - //char_id# - //sfx_delay# - //objection_modifier# - //evidence# - //placeholder# - //realization# - //text_color#% + // MS# + // deskmod# + // pre-emote# + // character# + // emote# + // message# + // side# + // sfx-name# + // emote_modifier# + // char_id# + // sfx_delay# + // objection_modifier# + // evidence# + // placeholder# + // realization# + // text_color#% QStringList packet_contents; @@ -571,9 +562,9 @@ void Courtroom::on_chat_return_pressed() QString f_desk_mod = "chat"; - if (ao_app->desk_mod_enabled) - { - f_desk_mod = QString::number(ao_app->get_desk_mod(current_char, current_emote)); + if (ao_app->desk_mod_enabled) { + f_desk_mod = + QString::number(ao_app->get_desk_mod(current_char, current_emote)); if (f_desk_mod == "-1") f_desk_mod = "chat"; } @@ -584,13 +575,11 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(current_char); - if(ui_hidden->isChecked()) + if (ui_hidden->isChecked()) packet_contents.append("../../misc/blank"); else packet_contents.append(ao_app->get_emote(current_char, current_emote)); - - packet_contents.append(ui_ic_chat_message->text()); packet_contents.append(f_side); @@ -612,23 +601,20 @@ void Courtroom::on_chat_return_pressed() int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); - //needed or else legacy won't understand what we're saying - if (m_objection_state > 0) - { + // needed or else legacy won't understand what we're saying + if (m_objection_state > 0) { if (f_emote_mod == 5) f_emote_mod = 6; else f_emote_mod = 2; } - else if (ui_pre->isChecked()) - { + else if (ui_pre->isChecked()) { if (f_emote_mod == 0) f_emote_mod = 1; else if (f_emote_mod == 5 && ao_app->prezoom_enabled) f_emote_mod = 4; } - else - { + else { if (f_emote_mod == 1) f_emote_mod = 0; else if (f_emote_mod == 4) @@ -638,11 +624,13 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(QString::number(f_emote_mod)); packet_contents.append(QString::number(m_cid)); - packet_contents.append(QString::number(ao_app->get_sfx_delay(current_char, current_emote))); + packet_contents.append( + QString::number(ao_app->get_sfx_delay(current_char, current_emote))); QString f_obj_state; - if (m_objection_state < 0 || (!ao_app->custom_objection_enabled && m_objection_state > 3)) + if (m_objection_state < 0 || + (!ao_app->custom_objection_enabled && m_objection_state > 3)) f_obj_state = "0"; else f_obj_state = QString::number(m_objection_state); @@ -650,16 +638,15 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(f_obj_state); if (is_presenting_evidence) - //the evidence index is shifted by 1 because 0 is no evidence per legacy standards - //besides, older clients crash if we pass -1 + // the evidence index is shifted by 1 because 0 is no evidence per legacy + // standards besides, older clients crash if we pass -1 packet_contents.append(QString::number(current_evidence + 1)); else packet_contents.append("0"); QString f_flip; - if (ao_app->flipping_enabled) - { + if (ao_app->flipping_enabled) { if (ui_flip->isChecked()) f_flip = "1"; else @@ -695,21 +682,21 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) else if (p_contents->size() == 15) p_contents->append(""); - for (int n_string = 0 ; n_string < chatmessage_size ; ++n_string) - { + for (int n_string = 0; n_string < chatmessage_size; ++n_string) { m_chatmessage[n_string] = p_contents->at(n_string); - // qDebug() << "m_chatmessage[" << n_string << "] = " << m_chatmessage[n_string]; + // qDebug() << "m_chatmessage[" << n_string << "] = " << + // m_chatmessage[n_string]; } int f_char_id = m_chatmessage[CHAR_ID].toInt(); - if (f_char_id == -1) - { + if (f_char_id == -1) { is_system_speaking = true; m_chatmessage[CHAR_ID] = "0"; f_char_id = 0; } - else is_system_speaking = false; + else + is_system_speaking = false; if (f_char_id < 0 || f_char_id >= char_list.size()) return; @@ -717,40 +704,39 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) if (mute_map.value(m_chatmessage[CHAR_ID].toInt())) return; - chatmessage_is_empty = m_chatmessage[MESSAGE] == " " || m_chatmessage[MESSAGE] == ""; + chatmessage_is_empty = + m_chatmessage[MESSAGE] == " " || m_chatmessage[MESSAGE] == ""; m_msg_is_first_person = false; // reset our ui state - if (m_cid == f_char_id && !is_system_speaking) - { - ui_ic_chat_message->clear(); - ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); + if (m_cid == f_char_id && !is_system_speaking) { + ui_ic_chat_message->clear(); + ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); - m_objection_state = 0; - reset_shout_buttons(); + m_objection_state = 0; + reset_shout_buttons(); - m_effect_state = 0; - reset_effect_buttons(); + m_effect_state = 0; + reset_effect_buttons(); - m_wtce_current = 0; - reset_judge_wtce_buttons(); + m_wtce_current = 0; + reset_judge_wtce_buttons(); - is_presenting_evidence = false; - ui_evidence_present->set_image("present_disabled.png"); + is_presenting_evidence = false; + ui_evidence_present->set_image("present_disabled.png"); - m_msg_is_first_person = ao_app->get_first_person_enabled(); + m_msg_is_first_person = ao_app->get_first_person_enabled(); } QString f_showname; qDebug() << "handle_chatmessage"; - qDebug() << m_chatmessage[SHOWNAME] << ao_app->get_showname(char_list.at(f_char_id).name); + qDebug() << m_chatmessage[SHOWNAME] + << ao_app->get_showname(char_list.at(f_char_id).name); - if(m_chatmessage[SHOWNAME].isEmpty()) - { + if (m_chatmessage[SHOWNAME].isEmpty()) { f_showname = ao_app->get_showname(char_list.at(f_char_id).name); } - else - { + else { f_showname = m_chatmessage[SHOWNAME]; } @@ -776,38 +762,36 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) else append_ic_text(f_showname, m_chatmessage[MESSAGE], false, false); - if(ao_config->log_is_recording_enabled()) - save_textlog("[" + QTime::currentTime().toString() + "] " + f_showname + ": " + m_chatmessage[MESSAGE]); + if (ao_config->log_is_recording_enabled()) + save_textlog("[" + QTime::currentTime().toString() + "] " + f_showname + + ": " + m_chatmessage[MESSAGE]); int 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 an objection is used - if (objection_mod > 0) - { + // if an objection is used + if (objection_mod > 0) { int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); if (emote_mod == 0) m_chatmessage[EMOTE_MOD] = 1; - //handles cases 1-8 (5-8 are DRO only) - if(objection_mod >= 1 && objection_mod <= ui_shouts.size() && ui_shouts.size() > 0) // check to prevent crashing + // handles cases 1-8 (5-8 are DRO only) + if (objection_mod >= 1 && objection_mod <= ui_shouts.size() && + ui_shouts.size() > 0) // check to prevent crashing { - ui_vp_objection->play(shout_names.at(objection_mod-1), f_char, f_custom_theme); - m_shouts_player->play(shout_names.at(objection_mod-1) + ".wav", f_char); + ui_vp_objection->play(shout_names.at(objection_mod - 1), f_char, + f_custom_theme); + m_shouts_player->play(shout_names.at(objection_mod - 1) + ".wav", f_char); } else qDebug() << "W: Shout identifier unknown" << objection_mod; - } else handle_chatmessage_2(); } -void Courtroom::objection_done() -{ - handle_chatmessage_2(); -} +void Courtroom::objection_done() { handle_chatmessage_2(); } void Courtroom::handle_chatmessage_2() // handles IC { @@ -820,17 +804,17 @@ void Courtroom::handle_chatmessage_2() // handles IC QString f_showname; - if(m_chatmessage[SHOWNAME].isEmpty()) - { + if (m_chatmessage[SHOWNAME].isEmpty()) { f_showname = ao_app->get_showname(real_name); } - else - { + else { f_showname = m_chatmessage[SHOWNAME]; } - // Check if char.ini has color property, which overrides the theme's default showname color - QString f_color = ao_app->read_char_ini(real_name, "color", "[Options]", "[Time]"); + // Check if char.ini has color property, which overrides the theme's default + // showname color + QString f_color = + ao_app->read_char_ini(real_name, "color", "[Options]", "[Time]"); set_qtextedit_font(ui_vp_showname, "showname", f_color); ui_vp_showname->setText(f_showname); @@ -843,8 +827,7 @@ void Courtroom::handle_chatmessage_2() // handles IC QString chatbox = ao_app->get_chat(m_chatmessage[CHAR_NAME]); if (chatbox == "") - if (ao_app->read_theme_ini("daynight_theme", cc_config_ini) == "true") - { + if (ao_app->read_theme_ini("daynight_theme", cc_config_ini) == "true") { if (current_clock < 0) ui_vp_chatbox->set_image("chatmed.png"); else if (current_clock >= 7 && current_clock < 22) @@ -854,15 +837,13 @@ void Courtroom::handle_chatmessage_2() // handles IC } else ui_vp_chatbox->set_image("chatmed.png"); - else - { + else { QString chatbox_path = ao_app->get_base_path() + "misc/" + chatbox + ".png"; ui_vp_chatbox->set_image_from_path(chatbox_path); } - if (!m_msg_is_first_person) - { - set_scene(); + if (!m_msg_is_first_person) { + set_scene(); } set_text_color(); @@ -874,63 +855,60 @@ void Courtroom::handle_chatmessage_2() // handles IC else ui_vp_player_char->set_mirror_enabled(false); - switch (emote_mod) - { - case 1: case 2: case 6: + switch (emote_mod) { + case 1: + case 2: + case 6: play_preanim(); break; default: qDebug() << "W: invalid emote mod: " << QString::number(emote_mod); - //intentional fallthru - case 0: case 5: + // intentional fallthru + case 0: + case 5: handle_chatmessage_3(); } } void Courtroom::handle_chatmessage_3() { - qDebug() << "handle_chatmessage_3"; + qDebug() << "handle_chatmessage_3"; start_chat_ticking(); int f_evi_id = m_chatmessage[EVIDENCE_ID].toInt(); QString f_side = m_chatmessage[SIDE]; - if (f_evi_id > 0 && f_evi_id <= local_evidence_list.size()) - { - //shifted by 1 because 0 is no evidence per legacy standards + 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; - //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"); + // 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"); ui_vp_evidence_display->show_evidence(f_image, is_left_side); } int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); - if (emote_mod == 5 || - emote_mod == 6) - { + if (emote_mod == 5 || emote_mod == 6) { QString side = m_chatmessage[SIDE]; ui_vp_desk->hide(); - if (side == "pro" || - side == "hlp" || - side == "wit") + if (side == "pro" || side == "hlp" || side == "wit") ui_vp_speedlines->play("prosecution_speedlines"); else ui_vp_speedlines->play("defense_speedlines"); - } int f_anim_state = 0; - //BLUE is from an enum in datatypes.h + // BLUE is from an enum in datatypes.h bool text_is_blue = m_chatmessage[TEXT_COLOR].toInt() == BLUE; if (!text_is_blue && text_state == 1) - //talking + // talking f_anim_state = 2; else - //idle + // idle f_anim_state = 3; if (f_anim_state <= anim_state) @@ -943,53 +921,51 @@ void Courtroom::handle_chatmessage_3() ui_vp_showname_image->show(); QVector exts = {".png", ".jpg", ".bmp"}; - QString ext = file_exists(ao_app->get_character_path(f_char) + "showname", exts); - if(ext != "" && !chatmessage_is_empty && ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") - { + QString ext = + file_exists(ao_app->get_character_path(f_char) + "showname", exts); + if (ext != "" && !chatmessage_is_empty && + ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == + "true") { ui_vp_showname->hide(); QString path = ao_app->get_character_path(f_char) + "showname" + ext; ui_vp_showname_image->set_image_from_path(path); ui_vp_showname_image->show(); } - else - { + else { ui_vp_showname->show(); ui_vp_showname_image->hide(); } - - switch (f_anim_state) - { + switch (f_anim_state) { case 2: - if (!m_msg_is_first_person) - { - ui_vp_player_char->play_talking(f_char, f_emote, true); - } + if (!m_msg_is_first_person) { + ui_vp_player_char->play_talking(f_char, f_emote, true); + } anim_state = 2; break; default: qDebug() << "W: invalid anim_state: " << f_anim_state; case 3: - if (!m_msg_is_first_person) - { - ui_vp_player_char->play_idle(f_char, f_emote, true); - } + if (!m_msg_is_first_person) { + ui_vp_player_char->play_idle(f_char, f_emote, true); + } anim_state = 3; } int effect = m_chatmessage[EFFECT_STATE].toInt(); QStringList offset = ao_app->get_effect_offset(f_char, effect); - ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), ui_viewport->y() + offset.at(1).toInt()); + ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), + ui_viewport->y() + offset.at(1).toInt()); QStringList overlay = ao_app->get_overlay(f_char, effect); QString overlay_name = overlay.at(0); QString overlay_sfx = overlay.at(1); - bool do_it = ao_app->read_theme_ini("non_vanilla_effects", cc_config_ini) == "true"; + bool do_it = + ao_app->read_theme_ini("non_vanilla_effects", cc_config_ini) == "true"; - if (effect == 1 && !do_it) - { + if (effect == 1 && !do_it) { if (overlay_sfx == "") overlay_sfx = ao_app->get_sfx("effect_flash"); m_effects_player->play(overlay_sfx); @@ -999,22 +975,23 @@ void Courtroom::handle_chatmessage_3() ui_vp_effect->play(overlay_name, f_char); realization_timer->start(60); } -// else if (effect == 2) -// { -// if (overlay_sfx == "") -// overlay_sfx = ao_app->get_sfx("effect_gloom"); -// m_sfx_player->play(overlay_sfx); -// ui_vp_effect->set_play_once(false); -// if (overlay_name == "") -// overlay_name = "effect_gloom"; -// ui_vp_effect->play(overlay_name, f_char); -// } - else if (do_it && effect > 0 && effect <= ui_effects.size() && effect_names.size() > 0) // check to prevent crashing + // else if (effect == 2) + // { + // if (overlay_sfx == "") + // overlay_sfx = ao_app->get_sfx("effect_gloom"); + // m_sfx_player->play(overlay_sfx); + // ui_vp_effect->set_play_once(false); + // if (overlay_name == "") + // overlay_name = "effect_gloom"; + // ui_vp_effect->play(overlay_name, f_char); + // } + else if (do_it && effect > 0 && effect <= ui_effects.size() && + effect_names.size() > 0) // check to prevent crashing { QString s_eff = effect_names.at(effect - 1); QStringList f_eff = ao_app->get_effect(effect); -// QString s_eff = f_eff.at(0).trimmed(); + // QString s_eff = f_eff.at(0).trimmed(); bool once = f_eff.at(1).trimmed().toInt(); if (overlay_sfx == "") @@ -1030,209 +1007,219 @@ void Courtroom::handle_chatmessage_3() QString f_message = m_chatmessage[MESSAGE]; QStringList callwords = ao_app->get_callwords(); - for (QString word : callwords) - { - if (f_message.contains(word, Qt::CaseInsensitive)) - { + for (QString word : callwords) { + if (f_message.contains(word, Qt::CaseInsensitive)) { m_system_player->play(ao_app->get_sfx("word_call")); ao_app->alert(this); ui_server_chatlog->append_chatmessage( - "CLIENT", - "[" + QTime::currentTime().toString("HH:mm") + "] " + - ui_vp_showname->toPlainText() + " has called you via your callword \"" + word + - "\": \"" + f_message + "\""); + "CLIENT", "[" + QTime::currentTime().toString("HH:mm") + "] " + + ui_vp_showname->toPlainText() + + " has called you via your callword \"" + word + + "\": \"" + f_message + "\""); break; } } - } void Courtroom::on_chat_config_changed() { - // forward declaration for a possible update of the chatlog - bool chatlog_changed = false; + // forward declaration for a possible update of the chatlog + bool chatlog_changed = false; - int chatlog_limit = ao_config->log_max_lines(); - // default chatlog_limit? - chatlog_limit = chatlog_limit <= 0 ? 200 : chatlog_limit; // TODO declare the default somewhere so it's not a magic number - if (chatlog_limit < m_chatlog_limit) // only update if we need to chop away records - chatlog_changed = true; - m_chatlog_limit = chatlog_limit; + int chatlog_limit = ao_config->log_max_lines(); + // default chatlog_limit? + chatlog_limit = chatlog_limit <= 0 + ? 200 + : chatlog_limit; // TODO declare the default somewhere so + // it's not a magic number + if (chatlog_limit < + m_chatlog_limit) // only update if we need to chop away records + chatlog_changed = true; + m_chatlog_limit = chatlog_limit; - bool chatlog_scrolldown = ao_config->log_goes_downward_enabled(); - if (m_chatlog_scrolldown != chatlog_scrolldown) - chatlog_changed = true; - m_chatlog_scrolldown = chatlog_scrolldown; + bool chatlog_scrolldown = ao_config->log_goes_downward_enabled(); + if (m_chatlog_scrolldown != chatlog_scrolldown) + chatlog_changed = true; + m_chatlog_scrolldown = chatlog_scrolldown; - bool chatlog_newline = ao_config->log_uses_newline_enabled(); - if (m_chatlog_newline != chatlog_newline) - chatlog_changed = true; - m_chatlog_newline = chatlog_newline; + bool chatlog_newline = ao_config->log_uses_newline_enabled(); + if (m_chatlog_newline != chatlog_newline) + chatlog_changed = true; + m_chatlog_newline = chatlog_newline; - // refresh the log if needed - if (chatlog_changed) - update_ic_log(chatlog_changed); + // refresh the log if needed + if (chatlog_changed) + update_ic_log(chatlog_changed); } void Courtroom::update_ic_log(bool p_reset_log) { - // resize if needed - int len = m_ic_records.length(); - if (len > m_chatlog_limit) - m_ic_records = m_ic_records.mid(len - m_chatlog_limit); - - /* - * first, we figure out whatever we append the last message or if we reset - * the entire log - * */ - record_type_array records_to_add; - // populate - if (p_reset_log) - { - // we need the entire recordings - records_to_add = m_ic_records; + // resize if needed + int len = m_ic_records.length(); + if (len > m_chatlog_limit) + m_ic_records = m_ic_records.mid(len - m_chatlog_limit); - // clear log - ui_ic_chatlog->clear(); + /* + * first, we figure out whatever we append the last message or if we reset + * the entire log + * */ + record_type_array records_to_add; + // populate + if (p_reset_log) { + // we need the entire recordings + records_to_add = m_ic_records; + + // clear log + ui_ic_chatlog->clear(); + } + else { + records_to_add.append(m_ic_records.last()); + } + + // prepare the formats we need + // default color + QColor default_color = ao_app->get_color("ic_chatlog_color", fonts_ini); + QColor not_found_color = QColor(255, 255, 255); + + QTextCharFormat name_format = ui_ic_chatlog->currentCharFormat(); + name_format.setFontWeight(QFont::Bold); + QColor showname_color = + ao_app->get_color("ic_chatlog_showname_color", fonts_ini); + if (showname_color == not_found_color) + showname_color = default_color; + name_format.setForeground(showname_color); + + QTextCharFormat line_format = ui_ic_chatlog->currentCharFormat(); + line_format.setFontWeight(QFont::Normal); + QColor message_color = + ao_app->get_color("ic_chatlog_message_color", fonts_ini); + if (message_color == not_found_color) + message_color = default_color; + line_format.setForeground(message_color); + + QTextCharFormat system_format = ui_ic_chatlog->currentCharFormat(); + system_format.setFontWeight(QFont::Normal); + QColor system_color = ao_app->get_color("ic_chatlog_system_color", fonts_ini); + if (system_color == not_found_color) + system_color = not_found_color; + system_format.setForeground(system_color); + + // need vscroll bar for cache + QScrollBar *vscrollbar = ui_ic_chatlog->verticalScrollBar(); + + // cache previous values + const QTextCursor prev_cursor = ui_ic_chatlog->textCursor(); + const int scroll_pos = vscrollbar->value(); + const bool is_scrolled = m_chatlog_scrolldown + ? scroll_pos == vscrollbar->maximum() + : scroll_pos == vscrollbar->minimum(); + + // recover cursor + QTextCursor cursor = ui_ic_chatlog->textCursor(); + // figure out if we need to move up or down + const QTextCursor::MoveOperation move_type = + m_chatlog_scrolldown ? QTextCursor::End : QTextCursor::Start; + + for (record_type_ptr record : records_to_add) { + // move cursor + cursor.movePosition(move_type); + + if (record->system) { + cursor.insertText(record->line + QChar::LineFeed, system_format); } - else - { - records_to_add.append(m_ic_records.last()); + else { + QString separator; + if (m_chatlog_newline) + separator = QString(QChar::LineFeed); + else if (!record->music) + separator = ": "; + else + separator = " "; + cursor.insertText(record->name + separator, name_format); + cursor.insertText(record->line + QChar::LineFeed + + (m_chatlog_newline ? QChar::LineFeed : QChar()), + line_format); } + } - // prepare the formats we need - // default color - QColor default_color = ao_app->get_color("ic_chatlog_color", fonts_ini); - QColor not_found_color = QColor(255, 255, 255); - - QTextCharFormat name_format = ui_ic_chatlog->currentCharFormat(); - name_format.setFontWeight(QFont::Bold); - QColor showname_color = ao_app->get_color("ic_chatlog_showname_color", fonts_ini); - if (showname_color == not_found_color) - showname_color = default_color; - name_format.setForeground(showname_color); - - QTextCharFormat line_format = ui_ic_chatlog->currentCharFormat(); - line_format.setFontWeight(QFont::Normal); - QColor message_color = ao_app->get_color("ic_chatlog_message_color", fonts_ini); - if (message_color == not_found_color) - message_color = default_color; - line_format.setForeground(message_color); - - QTextCharFormat system_format = ui_ic_chatlog->currentCharFormat(); - system_format.setFontWeight(QFont::Normal); - QColor system_color = ao_app->get_color("ic_chatlog_system_color", fonts_ini); - if (system_color == not_found_color) - system_color = not_found_color; - system_format.setForeground(system_color); - - // need vscroll bar for cache - QScrollBar *vscrollbar = ui_ic_chatlog->verticalScrollBar(); - - // cache previous values - const QTextCursor prev_cursor = ui_ic_chatlog->textCursor(); - const int scroll_pos = vscrollbar->value(); - const bool is_scrolled = m_chatlog_scrolldown ? scroll_pos == vscrollbar->maximum() : scroll_pos == vscrollbar->minimum(); - - // recover cursor - QTextCursor cursor = ui_ic_chatlog->textCursor(); - // figure out if we need to move up or down - const QTextCursor::MoveOperation move_type = m_chatlog_scrolldown ? QTextCursor::End : QTextCursor::Start; - - for (record_type_ptr record : records_to_add) - { - // move cursor - cursor.movePosition(move_type); + // figure out the number of blocks we need overall + // this is always going to amount to at least the current length of records + int block_count = m_ic_records.length() + 1; // there's always one extra block + // to do that, we need to go through the records + for (record_type_ptr record : m_ic_records) + if (!record->system) + if (m_chatlog_newline) + block_count += 2; // if newline is actived, it always inserts two extra + // newlines; therefor two paragraphs + + // there's always one extra block count, so deduce one from block_count + int blocks_to_delete = ui_ic_chatlog->document()->blockCount() - block_count; + + // the orientation at which we need to delete from + const QTextCursor::MoveOperation start_location = + m_chatlog_scrolldown ? QTextCursor::Start : QTextCursor::End; + const QTextCursor::MoveOperation block_orientation = + m_chatlog_scrolldown ? QTextCursor::NextBlock + : QTextCursor::PreviousBlock; + + /* Blocks appear like this + * textQChar(0x2029) + * additionaltextQChar(0x2029) + * moretextQChar(0x2029) + * where QChar(0x2029) is the paragraph break block. + * Do note that the above example has FOUR blocks: text, additionaltext, + * moretext, and an empty block. That is because QTextCursor separates blocks + * by paragraph break block (which is why the above code has a -1) and does + * not consider this break character as part of the block (which is why we + * move Left in the loop, to 'be in the block'). Finally, BlockUnderCursor + * does NOT select the break character, so we deleteChar after removing the + * selection to remove the straggling newline. + * */ + + // move our cursor at the start + cursor.movePosition(start_location); + + // move the cursor around, depending on the orientation we need + for (int i = 0; i < blocks_to_delete; ++i) + cursor.movePosition(block_orientation, QTextCursor::KeepAnchor); + + // now that everything is selected, delete it + cursor.removeSelectedText(); - if (record->system) - { - cursor.insertText(record->line + QChar::LineFeed, system_format); - } - else - { - QString separator; - if (m_chatlog_newline) - separator = QString(QChar::LineFeed); - else if (!record->music) - separator = ": "; - else - separator = " "; - cursor.insertText(record->name + separator, name_format); - cursor.insertText(record->line + QChar::LineFeed + (m_chatlog_newline ? QChar::LineFeed : QChar()), line_format); - } - } + /* + * However, if we do this, we also remove the last newline of the last block + * that remains, which will make it difficult to append new blocks to + * it/figure out the amount of blocks if we have a scroll up log, so we add it + * again if we removed any break characters at all + * */ + if (!m_chatlog_scrolldown && blocks_to_delete > 0) + cursor.insertBlock(); - // figure out the number of blocks we need overall - // this is always going to amount to at least the current length of records - int block_count = m_ic_records.length() + 1; // there's always one extra block - // to do that, we need to go through the records - for (record_type_ptr record : m_ic_records) - if (!record->system) - if (m_chatlog_newline) - block_count += 2; // if newline is actived, it always inserts two extra newlines; therefor two paragraphs - - // there's always one extra block count, so deduce one from block_count - int blocks_to_delete = ui_ic_chatlog->document()->blockCount() - block_count; - - // the orientation at which we need to delete from - const QTextCursor::MoveOperation start_location = m_chatlog_scrolldown ? QTextCursor::Start : QTextCursor::End; - const QTextCursor::MoveOperation block_orientation = m_chatlog_scrolldown ? QTextCursor::NextBlock : QTextCursor::PreviousBlock; - - /* Blocks appear like this - * textQChar(0x2029) - * additionaltextQChar(0x2029) - * moretextQChar(0x2029) - * where QChar(0x2029) is the paragraph break block. - * Do note that the above example has FOUR blocks: text, additionaltext, moretext, and - * an empty block. That is because QTextCursor separates blocks by paragraph break block - * (which is why the above code has a -1) and does not consider this break character as - * part of the block (which is why we move Left in the loop, to 'be in the block'). - * Finally, BlockUnderCursor does NOT select the break character, so we deleteChar after - * removing the selection to remove the straggling newline. - * */ - - // move our cursor at the start - cursor.movePosition(start_location); - - // move the cursor around, depending on the orientation we need - for (int i = 0; i < blocks_to_delete; ++i) - cursor.movePosition(block_orientation, QTextCursor::KeepAnchor); - - // now that everything is selected, delete it - cursor.removeSelectedText(); - - /* - * However, if we do this, we also remove the last newline of the last block that remains, - * which will make it difficult to append new blocks to it/figure out the amount of blocks - * if we have a scroll up log, so we add it again if we removed any break characters at all - * */ - if (!m_chatlog_scrolldown && blocks_to_delete > 0) - cursor.insertBlock(); - - /* - * Unfortunately, the simplest alternative, that is, move cursor to the last block, remove - * the block under it and delete the last char does not work, as this also removes the last - * character of the block that remains. That's why we have to do this whole complicated - * process. - * */ - if (prev_cursor.hasSelection() || !is_scrolled) - { - // restore previous selection and vscrollbar - ui_ic_chatlog->setTextCursor(prev_cursor); - vscrollbar->setValue(scroll_pos); - } - // scroll up/down depending on context - else - { - ui_ic_chatlog->moveCursor(move_type); - vscrollbar->setValue(m_chatlog_scrolldown ? vscrollbar->maximum() : vscrollbar->minimum()); - } + /* + * Unfortunately, the simplest alternative, that is, move cursor to the last + * block, remove the block under it and delete the last char does not work, as + * this also removes the last character of the block that remains. That's why + * we have to do this whole complicated process. + * */ + if (prev_cursor.hasSelection() || !is_scrolled) { + // restore previous selection and vscrollbar + ui_ic_chatlog->setTextCursor(prev_cursor); + vscrollbar->setValue(scroll_pos); + } + // scroll up/down depending on context + else { + ui_ic_chatlog->moveCursor(move_type); + vscrollbar->setValue(m_chatlog_scrolldown ? vscrollbar->maximum() + : vscrollbar->minimum()); + } } -void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music) +void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, + bool p_music) { // record new entry - m_ic_records.append(std::make_shared(p_name, p_line, "", p_system, p_music)); + m_ic_records.append( + std::make_shared(p_name, p_line, "", p_system, p_music)); // update update_ic_log(false); @@ -1250,7 +1237,8 @@ void Courtroom::play_preanim() 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 + // all time values in char.inis are multiplied by a constant(time_mod) to get + // the actual time int text_delay = ao_app->get_text_delay(f_char, f_preanim) * time_mod; int sfx_delay = m_chatmessage[SFX_DELAY].toInt() * 60; @@ -1259,36 +1247,28 @@ void Courtroom::play_preanim() // set state anim_state = 1; - if (!m_msg_is_first_person) - { - QString f_anim_path = ao_app->get_character_path(f_char) + f_preanim.toLower(); - if (ui_vp_player_char->play_pre(f_char, f_preanim, true)) - { - if (text_delay >= 0) - text_delay_timer->start(text_delay); - - // finished - return; - } - else - { - qDebug() << "could not find " + f_anim_path; - } + if (!m_msg_is_first_person) { + QString f_anim_path = + ao_app->get_character_path(f_char) + f_preanim.toLower(); + if (ui_vp_player_char->play_pre(f_char, f_preanim, true)) { + if (text_delay >= 0) + text_delay_timer->start(text_delay); + + // finished + return; + } + else { + qDebug() << "could not find " + f_anim_path; + } } // no animation, continue preanim_done(); } -void Courtroom::preanim_done() -{ - handle_chatmessage_3(); -} +void Courtroom::preanim_done() { handle_chatmessage_3(); } -void Courtroom::realization_done() -{ - ui_vp_effect->stop(); -} +void Courtroom::realization_done() { ui_vp_effect->stop(); } void Courtroom::start_chat_ticking() { @@ -1296,13 +1276,13 @@ void Courtroom::start_chat_ticking() set_text_color(); rainbow_counter = 0; - //we need to ensure that the text isn't already ticking because this function can be called by two logic paths + // we need to ensure that the text isn't already ticking because this function + // can be called by two logic paths if (text_state != 0) return; - if (chatmessage_is_empty) - { - //since the message is empty, it's technically done ticking + if (chatmessage_is_empty) { + // since the message is empty, it's technically done ticking text_state = 2; return; } @@ -1318,14 +1298,14 @@ void Courtroom::start_chat_ticking() // m_blip_player->set_file(f_gender); m_blips_player->set_blips("sfx-blip" + f_gender + ".wav"); - //means text is currently ticking + // means text is currently ticking text_state = 1; } void Courtroom::chat_tick() { - //note: this is called fairly often(every 60 ms when char is talking) - //do not perform heavy operations here + // note: this is called fairly often(every 60 ms when char is talking) + // do not perform heavy operations here QTextCharFormat vp_message_format = ui_vp_message->currentCharFormat(); if (ao_app->get_font_property("message_outline", fonts_ini) == 1) vp_message_format.setTextOutline(QPen(Qt::black, 1)); @@ -1334,33 +1314,29 @@ void Courtroom::chat_tick() QString f_message = m_chatmessage[MESSAGE]; - if (tick_pos >= f_message.size()) - { + if (tick_pos >= f_message.size()) { text_state = 2; chat_tick_timer->stop(); anim_state = 3; - if (!m_msg_is_first_person) - { - ui_vp_player_char->play_idle(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE], true); + if (!m_msg_is_first_person) { + ui_vp_player_char->play_idle(m_chatmessage[CHAR_NAME], + m_chatmessage[EMOTE], true); } m_string_color = ""; m_color_stack.clear(); } - else - { + else { QString f_character = f_message.at(tick_pos); if (f_character == " ") ui_vp_message->insertPlainText(" "); - else if (m_chatmessage[TEXT_COLOR].toInt() == RAINBOW) - { + else if (m_chatmessage[TEXT_COLOR].toInt() == RAINBOW) { QString html_color; - switch (rainbow_counter) - { + switch (rainbow_counter) { case 0: html_color = "#BA1518"; break; @@ -1386,19 +1362,19 @@ void Courtroom::chat_tick() ui_vp_message->textCursor().insertText(f_character, vp_message_format); } - else if (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "true") - { + else if (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == + "true") { bool highlight_found = false; bool render_character = true; - // render_character should only be false if the character is a highlight character - // specifically marked as a character that should not be rendered. + // render_character should only be false if the character is a highlight + // character specifically marked as a character that should not be + // rendered. QVector f_vec = ao_app->get_highlight_color(); - if(m_color_stack.isEmpty()) m_color_stack.push(""); + if (m_color_stack.isEmpty()) + m_color_stack.push(""); - for(const auto& col : f_vec) - { - if(f_character == col[0][0] && m_string_color != col[1]) - { + for (const auto &col : f_vec) { + if (f_character == col[0][0] && m_string_color != col[1]) { m_color_stack.push(col[1]); m_string_color = m_color_stack.top(); highlight_found = true; @@ -1410,8 +1386,7 @@ void Courtroom::chat_tick() // Apply color to the next character if (m_string_color.isEmpty()) vp_message_format.setForeground(m_base_string_color); - else - { + else { QColor textColor; textColor.setNamedColor(m_string_color); vp_message_format.setForeground(textColor); @@ -1419,11 +1394,10 @@ void Courtroom::chat_tick() QString m_future_string_color = m_string_color; - for(const auto& col : f_vec) - { - if(f_character == col[0][1] && !highlight_found) - { - if(m_color_stack.size() > 1) m_color_stack.pop(); + for (const auto &col : f_vec) { + if (f_character == col[0][1] && !highlight_found) { + if (m_color_stack.size() > 1) + m_color_stack.pop(); m_future_string_color = m_color_stack.top(); highlight_found = true; render_character = (col[2] != "0"); @@ -1436,19 +1410,16 @@ void Courtroom::chat_tick() m_string_color = m_future_string_color; } - else - { + else { ui_vp_message->textCursor().insertText(f_character, vp_message_format); } QScrollBar *scroll = ui_vp_message->verticalScrollBar(); scroll->setValue(scroll->maximum()); - if ((f_message.at(tick_pos) != ' ' || ao_config->blank_blips_enabled())) - { + if ((f_message.at(tick_pos) != ' ' || ao_config->blank_blips_enabled())) { - if (blip_pos % ao_app->read_blip_rate() == 0) - { + if (blip_pos % ao_app->read_blip_rate() == 0) { blip_pos = 0; // play blip @@ -1489,7 +1460,7 @@ void Courtroom::play_sfx() if (sfx_name == "1") return; - QVector extensions{ "", ".ogg", ".wav", ".mp3"}; + QVector extensions{"", ".ogg", ".wav", ".mp3"}; QString general_path = ao_app->get_base_path() + "/sounds/general/"; QString f_ext = file_exists(general_path + sfx_name, extensions); @@ -1500,8 +1471,7 @@ void Courtroom::play_sfx() void Courtroom::set_text_color() { - switch (m_chatmessage[TEXT_COLOR].toInt()) - { + switch (m_chatmessage[TEXT_COLOR].toInt()) { case GREEN: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setRgb(101, 200, 86); @@ -1554,8 +1524,7 @@ void Courtroom::set_mute(bool p_muted, int p_cid) if (p_muted) ui_muted->show(); - else - { + else { ui_muted->hide(); ui_ic_chat_message->setFocus(); } @@ -1589,53 +1558,44 @@ void Courtroom::handle_song(QStringList *p_contents) QString f_song = f_contents.at(0); int n_char = f_contents.at(1).toInt(); - for (auto& ext : QStringList { "", ".wav", ".ogg", ".mp3" }) - { + for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) { QString r_song = f_song + ext; - QString song_path = ao_app->get_base_path() + "sounds/music/" + r_song.toLower(); - if (file_exists(song_path)) - { + QString song_path = + ao_app->get_base_path() + "sounds/music/" + r_song.toLower(); + if (file_exists(song_path)) { f_song = r_song; break; } } - if (n_char < 0 || n_char >= char_list.size()) - { + if (n_char < 0 || n_char >= char_list.size()) { m_music_player->play(f_song); } - else - { + else { // This 2th argument corresponds to the showname to use when displaying the // music change message in IC - // Backwards compatibility is explicitly kept for older versions of tsuserver - // that do not send such an argument by assuming an empty showname + // Backwards compatibility is explicitly kept for older versions of + // tsuserver that do not send such an argument by assuming an empty showname // If there is an empty showname, the client will use instead the default // showname of the character. QString f_showname; - if (f_contents.size() == 3) - { + if (f_contents.size() == 3) { f_showname = f_contents.at(2); } - else - { + else { f_showname = ""; } QString str_char; - if (f_showname.isEmpty()) - { + if (f_showname.isEmpty()) { str_char = ao_app->get_showname(char_list.at(n_char).name); } - else - { + else { str_char = f_showname; } - if (!mute_map.value(n_char)) - { - if (ao_app->get_music_change_log_enabled()) - { + if (!mute_map.value(n_char)) { + if (ao_app->get_music_change_log_enabled()) { append_ic_text(str_char, "has played a song: " + f_song, false, true); } m_music_player->play(f_song); @@ -1655,15 +1615,14 @@ void Courtroom::handle_wtce(QString p_wtce) QString sfx_file = cc_sounds_ini; int index = p_wtce.at(p_wtce.size() - 1).digitValue(); - if (index > 0 && index < wtce_names.size() + 1 && wtce_names.size() > 0) // check to prevent crash + if (index > 0 && index < wtce_names.size() + 1 && + wtce_names.size() > 0) // check to prevent crash { p_wtce.chop(1); // looking for the 'testimony' part - if (p_wtce == "testimony") - { - m_effects_player->play(ao_app->get_sfx(wtce_names[index-1])); - ui_vp_wtce->play(wtce_names[index-1]); - if (index == 1) - { + if (p_wtce == "testimony") { + m_effects_player->play(ao_app->get_sfx(wtce_names[index - 1])); + ui_vp_wtce->play(wtce_names[index - 1]); + if (index == 1) { testimony_in_progress = true; } else if (index == 2) @@ -1677,14 +1636,13 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) if (p_state < 0 || p_state > 10) return; - if (p_bar == 1) - { + if (p_bar == 1) { ui_defense_bar->set_image("defensebar" + QString::number(p_state) + ".png"); defense_bar_state = p_state; } - else if (p_bar == 2) - { - ui_prosecution_bar->set_image("prosecutionbar" + QString::number(p_state) + ".png"); + else if (p_bar == 2) { + ui_prosecution_bar->set_image("prosecutionbar" + QString::number(p_state) + + ".png"); prosecution_bar_state = p_state; } } @@ -1702,8 +1660,7 @@ void Courtroom::set_character_position(QString p_pos, bool refresh_dropdown) void Courtroom::mod_called(QString p_ip) { ui_server_chatlog->append(p_ip); - if (ao_app->get_server_alerts_enabled()) - { + if (ao_app->get_server_alerts_enabled()) { m_system_player->play(ao_app->get_sfx("mod_call")); ao_app->alert(this); } @@ -1714,56 +1671,46 @@ void Courtroom::on_ooc_return_pressed() QString ooc_name = ui_ooc_chat_name->text(); QString ooc_message = ui_ooc_chat_message->text(); - if (ooc_message.isEmpty()) - { - append_server_chatmessage( - "CLIENT", "You cannot send an empty message."); + if (ooc_message.isEmpty()) { + append_server_chatmessage("CLIENT", "You cannot send an empty message."); return; } - if (ooc_name.isEmpty()) - { + if (ooc_name.isEmpty()) { bool ok; QString name; - do - { - ooc_name = QInputDialog::getText(this, - "Enter a name", - "You must have a name to talk in OOC chat. Enter a name: ", - QLineEdit::Normal, - "user", &ok); + do { + ooc_name = QInputDialog::getText( + this, "Enter a name", + "You must have a name to talk in OOC chat. Enter a name: ", + QLineEdit::Normal, "user", &ok); } while (ok && ooc_name.isEmpty()); if (!ok) return; ao_config->set_username(ooc_name); } - else if (ooc_message.startsWith("/rainbow") && ao_app->yellow_text_enabled && !rainbow_appended) - { + else if (ooc_message.startsWith("/rainbow") && ao_app->yellow_text_enabled && + !rainbow_appended) { ui_text_color->addItem("Rainbow"); ui_ooc_chat_message->clear(); rainbow_appended = true; return; } - else if (ooc_message.startsWith("/switch_am")) - { - on_switch_area_music_clicked(); - ui_ooc_chat_message->clear(); - return; + else if (ooc_message.startsWith("/switch_am")) { + on_switch_area_music_clicked(); + ui_ooc_chat_message->clear(); + return; } - else if (ooc_message.startsWith("/rollp")) - { - m_effects_player->play(ao_app->get_sfx("dice")); + else if (ooc_message.startsWith("/rollp")) { + m_effects_player->play(ao_app->get_sfx("dice")); } - else if (ooc_message.startsWith("/roll")) - { - m_effects_player->play(ao_app->get_sfx("dice")); + else if (ooc_message.startsWith("/roll")) { + m_effects_player->play(ao_app->get_sfx("dice")); } - else if (ooc_message.startsWith("/coinflip")) - { - m_effects_player->play(ao_app->get_sfx("coinflip")); + else if (ooc_message.startsWith("/coinflip")) { + m_effects_player->play(ao_app->get_sfx("coinflip")); } - else if (ooc_message.startsWith("/tr ")) - { + else if (ooc_message.startsWith("/tr ")) { // Timer resume int space_location = ooc_message.indexOf(" "); @@ -1771,29 +1718,28 @@ void Courtroom::on_ooc_return_pressed() if (space_location == -1) timer_id = 0; else - timer_id = ooc_message.mid(space_location+1).toInt(); + timer_id = ooc_message.mid(space_location + 1).toInt(); resume_timer(timer_id); } - else if (ooc_message.startsWith("/ts ")) - { + else if (ooc_message.startsWith("/ts ")) { // Timer set QStringList arguments = ooc_message.split(" "); int size = arguments.size(); - // Note arguments[0] == "/ts", so every index (and thus length) is off by one. + // Note arguments[0] == "/ts", so every index (and thus length) is off by + // one. if (size > 5) return; int timer_id = (size > 1 ? arguments[1].toInt() : 0); - int new_time = (size > 2 ? arguments[2].toInt() : 300)*1000; - int timestep_length = (size > 3 ? arguments[3].toDouble() : -.016)*1000; - int firing_interval = (size > 4 ? arguments[4].toDouble() : .016)*1000; + int new_time = (size > 2 ? arguments[2].toInt() : 300) * 1000; + int timestep_length = (size > 3 ? arguments[3].toDouble() : -.016) * 1000; + int firing_interval = (size > 4 ? arguments[4].toDouble() : .016) * 1000; set_timer_time(timer_id, new_time); set_timer_timestep(timer_id, timestep_length); set_timer_firing(timer_id, firing_interval); } - else if (ooc_message.startsWith("/tp ")) - { + else if (ooc_message.startsWith("/tp ")) { // Timer pause int space_location = ooc_message.indexOf(" "); @@ -1801,7 +1747,7 @@ void Courtroom::on_ooc_return_pressed() if (space_location == -1) timer_id = 0; else - timer_id = ooc_message.mid(space_location+1).toInt(); + timer_id = ooc_message.mid(space_location + 1).toInt(); pause_timer(timer_id); } QStringList packet_contents; @@ -1819,7 +1765,7 @@ void Courtroom::on_ooc_return_pressed() void Courtroom::on_music_search_edited(QString p_text) { - //preventing compiler warnings + // preventing compiler warnings p_text += "a"; list_music(); list_areas(); @@ -1827,7 +1773,7 @@ void Courtroom::on_music_search_edited(QString p_text) void Courtroom::on_sfx_search_edited(QString p_text) { - //preventing compiler warnings + // preventing compiler warnings p_text += "a"; list_sfx(); } @@ -1841,8 +1787,7 @@ void Courtroom::on_pos_dropdown_changed(int p_index) QString f_pos; - switch (p_index) - { + switch (p_index) { case 0: f_pos = "wit"; break; @@ -1870,10 +1815,11 @@ void Courtroom::on_pos_dropdown_changed(int p_index) set_judge_enabled(f_pos == "jud"); - ao_app->send_server_packet(new AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); + ao_app->send_server_packet( + new AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); // Uncomment later and remove above // Will only work in TSDR 4.3+ servers - //ao_app->send_server_packet(new AOPacket("SP#" + f_pos + "#%")); + // ao_app->send_server_packet(new AOPacket("SP#" + f_pos + "#%")); } void Courtroom::on_mute_list_clicked(QModelIndex p_index) @@ -1889,31 +1835,25 @@ void Courtroom::on_mute_list_clicked(QModelIndex p_index) int f_cid = -1; - for (int n_char = 0 ; n_char < char_list.size() ; n_char++) - { + for (int n_char = 0; n_char < char_list.size(); n_char++) { if (char_list.at(n_char).name == real_char) f_cid = n_char; } - if (f_cid < 0 || f_cid >= char_list.size()) - { + if (f_cid < 0 || f_cid >= char_list.size()) { qDebug() << "W: " << real_char << " not present in char_list"; return; } - if (mute_map.value(f_cid)) - { + if (mute_map.value(f_cid)) { mute_map.insert(f_cid, false); f_item->setText(real_char); } - else - { + else { mute_map.insert(f_cid, true); f_item->setText(real_char + " [x]"); } - - /* if (f_char.endsWith(" [x]")) { @@ -1932,15 +1872,9 @@ void Courtroom::on_mute_list_clicked(QModelIndex p_index) */ } -void Courtroom::on_music_list_clicked() -{ - ui_ic_chat_message->setFocus(); -} +void Courtroom::on_music_list_clicked() { ui_ic_chat_message->setFocus(); } -void Courtroom::on_area_list_clicked() -{ - ui_ic_chat_message->setFocus(); -} +void Courtroom::on_area_list_clicked() { ui_ic_chat_message->setFocus(); } void Courtroom::on_music_list_double_clicked(QModelIndex p_model) { @@ -1949,7 +1883,9 @@ void Courtroom::on_music_list_double_clicked(QModelIndex p_model) QString p_song = ui_music_list->item(p_model.row())->text(); - ao_app->send_server_packet(new AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%"), false); + ao_app->send_server_packet( + new AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%"), + false); ui_ic_chat_message->setFocus(); } @@ -1958,23 +1894,27 @@ void Courtroom::on_area_list_double_clicked(QModelIndex p_model) { QString p_area = ui_area_list->item(p_model.row())->text(); - ao_app->send_server_packet(new AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%"), false); + ao_app->send_server_packet( + new AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%"), + false); ui_ic_chat_message->setFocus(); } void Courtroom::reset_shout_buttons() { - for(int i = 0; i < ui_shouts.size(); ++i) + for (int i = 0; i < ui_shouts.size(); ++i) ui_shouts[i]->set_image(shout_names.at(i) + ".png"); - if(m_objection_state != 0 && ui_shouts.size() > 0) // check to prevent crashing - ui_shouts.at(m_objection_state-1)->set_image(shout_names.at(m_objection_state-1) + "_selected.png"); + if (m_objection_state != 0 && + ui_shouts.size() > 0) // check to prevent crashing + ui_shouts.at(m_objection_state - 1) + ->set_image(shout_names.at(m_objection_state - 1) + "_selected.png"); } void Courtroom::on_shout_clicked() { - AOButton *f_shout_button = static_cast(sender()); + AOButton *f_shout_button = static_cast(sender()); int f_shout_id = f_shout_button->property("shout_id").toInt(); // update based on current button selected @@ -1990,11 +1930,10 @@ void Courtroom::on_shout_clicked() void Courtroom::on_cycle_clicked() { - AOButton *f_cycle_button = static_cast(sender()); + AOButton *f_cycle_button = static_cast(sender()); int f_cycle_id = f_cycle_button->property("cycle_id").toInt(); - switch(f_cycle_id) - { + switch (f_cycle_id) { case 5: cycle_wtce(-1); break; @@ -2027,7 +1966,9 @@ void Courtroom::on_cycle_clicked() void Courtroom::cycle_shout(int p_index) { int n = ui_shouts.size(); - do { m_shout_state = (m_shout_state - p_index + n) % n; } while( !shouts_enabled[m_shout_state] ); + do { + m_shout_state = (m_shout_state - p_index + n) % n; + } while (!shouts_enabled[m_shout_state]); set_shouts(); } @@ -2035,7 +1976,9 @@ void Courtroom::cycle_shout(int p_index) void Courtroom::cycle_effect(int p_index) { int n = ui_effects.size(); - do { m_effect_current = (m_effect_current - p_index + n) % n; } while( !effects_enabled[m_effect_current] ); + do { + m_effect_current = (m_effect_current - p_index + n) % n; + } while (!effects_enabled[m_effect_current]); set_effects(); } @@ -2043,7 +1986,9 @@ void Courtroom::cycle_effect(int p_index) void Courtroom::cycle_wtce(int p_index) { int n = ui_wtce.size(); - do { m_wtce_current = (m_wtce_current - p_index + n) % n; } while ( !wtce_enabled[m_wtce_current] ); + do { + m_wtce_current = (m_wtce_current - p_index + n) % n; + } while (!wtce_enabled[m_wtce_current]); set_judge_wtce(); } @@ -2051,19 +1996,19 @@ void Courtroom::cycle_wtce(int p_index) void Courtroom::reset_effect_buttons() { // effect names does not necessarily have the same size as ui effects - for(int i = 0; i < effect_names.size(); ++i) - { + for (int i = 0; i < effect_names.size(); ++i) { // qDebug() << effect_names << i; ui_effects[i]->set_image(effect_names.at(i) + ".png"); } - if(m_effect_state != 0 && ui_effects.size() > 0) // check to prevent crashing - ui_effects[m_effect_state-1]->set_image(effect_names.at(m_effect_state-1) + "_pressed.png"); + if (m_effect_state != 0 && ui_effects.size() > 0) // check to prevent crashing + ui_effects[m_effect_state - 1]->set_image( + effect_names.at(m_effect_state - 1) + "_pressed.png"); } void Courtroom::on_effect_button_clicked() { - AOButton *f_button = static_cast(this->sender()); + AOButton *f_button = static_cast(this->sender()); int f_effect_id = f_button->property("effect_id").toInt(); if (m_effect_state == f_effect_id) @@ -2078,13 +2023,11 @@ void Courtroom::on_effect_button_clicked() void Courtroom::on_mute_clicked() { - if (ui_mute_list->isHidden()) - { + if (ui_mute_list->isHidden()) { ui_mute_list->show(); ui_mute->set_image("mute_pressed.png"); } - else - { + else { ui_mute_list->hide(); ui_mute->set_image("mute.png"); } @@ -2095,7 +2038,8 @@ void Courtroom::on_defense_minus_clicked() int f_state = defense_bar_state - 1; if (f_state >= 0) - ao_app->send_server_packet(new AOPacket("HP#1#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet( + new AOPacket("HP#1#" + QString::number(f_state) + "#%")); } void Courtroom::on_defense_plus_clicked() @@ -2103,7 +2047,8 @@ void Courtroom::on_defense_plus_clicked() int f_state = defense_bar_state + 1; if (f_state <= 10) - ao_app->send_server_packet(new AOPacket("HP#1#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet( + new AOPacket("HP#1#" + QString::number(f_state) + "#%")); } void Courtroom::on_prosecution_minus_clicked() @@ -2111,7 +2056,8 @@ void Courtroom::on_prosecution_minus_clicked() int f_state = prosecution_bar_state - 1; if (f_state >= 0) - ao_app->send_server_packet(new AOPacket("HP#2#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet( + new AOPacket("HP#2#" + QString::number(f_state) + "#%")); } void Courtroom::on_prosecution_plus_clicked() @@ -2119,7 +2065,8 @@ void Courtroom::on_prosecution_plus_clicked() int f_state = prosecution_bar_state + 1; if (f_state <= 10) - ao_app->send_server_packet(new AOPacket("HP#2#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet( + new AOPacket("HP#2#" + QString::number(f_state) + "#%")); } void Courtroom::on_text_color_changed(int p_color) @@ -2148,9 +2095,12 @@ void Courtroom::on_cross_examination_clicked() ui_ic_chat_message->setFocus(); } -void Courtroom::reset_judge_wtce_buttons() // kind of an unnecessary function, but I added it just in case +void Courtroom::reset_judge_wtce_buttons() // kind of an unnecessary function, + // but I added it just in case { - for(int i = 0; i < wtce_names.size(); ++i) // effect names does not necessarily have the same size as ui effects + for (int i = 0; i < wtce_names.size(); + ++i) // effect names does not necessarily have the same size as ui + // effects { ui_wtce[i]->set_image(wtce_names.at(i) + ".png"); } @@ -2158,11 +2108,11 @@ void Courtroom::reset_judge_wtce_buttons() // kind of an unnecessary function, b void Courtroom::on_wtce_clicked() { -// qDebug() << "AA: wtce clicked!"; + // qDebug() << "AA: wtce clicked!"; if (is_client_muted) return; - AOButton* f_sig = static_cast(sender()); + AOButton *f_sig = static_cast(sender()); QString id = f_sig->property("wtce_id").toString(); QString packet = QString("RT#testimony%1#%").arg(id); @@ -2184,27 +2134,27 @@ void Courtroom::on_change_character_clicked() void Courtroom::on_app_reload_theme_requested() { - load_shouts(); - load_effects(); - load_wtce(); - load_free_blocks(); + load_shouts(); + load_effects(); + load_wtce(); + load_free_blocks(); - //to update status on the background - set_background(current_background); - enter_courtroom(m_cid); - anim_state = 4; - text_state = 3; + // to update status on the background + set_background(current_background); + enter_courtroom(m_cid); + anim_state = 4; + text_state = 3; } void Courtroom::on_back_to_lobby_clicked() { - // hide so we don't get the 'disconnected from server' prompt - hide(); + // hide so we don't get the 'disconnected from server' prompt + hide(); - ao_app->construct_lobby(); - ao_app->w_lobby->list_servers(); - ao_app->w_lobby->set_choose_a_server(); - ao_app->destruct_courtroom(); + ao_app->construct_lobby(); + ao_app->w_lobby->list_servers(); + ao_app->w_lobby->set_choose_a_server(); + ao_app->destruct_courtroom(); } void Courtroom::on_char_select_left_clicked() @@ -2221,7 +2171,8 @@ void Courtroom::on_char_select_right_clicked() void Courtroom::on_spectator_clicked() { - QString content = "CC#" + QString::number(ao_app->s_pv) + "#-1#" + get_hdid() + "#%"; + QString content = + "CC#" + QString::number(ao_app->s_pv) + "#-1#" + get_hdid() + "#%"; ao_app->send_server_packet(new AOPacket(content)); enter_courtroom(-1); @@ -2233,11 +2184,13 @@ void Courtroom::on_spectator_clicked() void Courtroom::on_call_mod_clicked() { QMessageBox::StandardButton reply; - QString warning = "Are you sure you want to call a mod? They will get angry at you if the reason is no good."; - reply = QMessageBox::warning(this, "Warning", warning, QMessageBox::Yes|QMessageBox::No, QMessageBox::No); + QString warning = "Are you sure you want to call a mod? They will get angry " + "at you if the reason is no good."; + reply = + QMessageBox::warning(this, "Warning", warning, + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - if(reply == QMessageBox::Yes) - { + if (reply == QMessageBox::Yes) { ao_app->send_server_packet(new AOPacket("ZZ#%")); qDebug() << "Called mod"; } @@ -2249,65 +2202,49 @@ void Courtroom::on_call_mod_clicked() void Courtroom::on_switch_area_music_clicked() { - if (ui_area_list->isHidden()) - { - ui_area_list->show(); - ui_music_list->hide(); - } - else - { - ui_area_list->hide(); - ui_music_list->show(); - } + if (ui_area_list->isHidden()) { + ui_area_list->show(); + ui_music_list->hide(); + } + else { + ui_area_list->hide(); + ui_music_list->show(); + } } +void Courtroom::on_pre_clicked() { ui_ic_chat_message->setFocus(); } -void Courtroom::on_pre_clicked() -{ - ui_ic_chat_message->setFocus(); -} +void Courtroom::on_flip_clicked() { ui_ic_chat_message->setFocus(); } -void Courtroom::on_flip_clicked() -{ - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_hidden_clicked() -{ - ui_ic_chat_message->setFocus(); -} +void Courtroom::on_hidden_clicked() { ui_ic_chat_message->setFocus(); } void Courtroom::on_evidence_button_clicked() { - if (ui_evidence->isHidden()) - { + if (ui_evidence->isHidden()) { ui_evidence->show(); ui_evidence_overlay->hide(); } - else - { + else { ui_evidence->hide(); } } void Courtroom::on_config_panel_clicked() { - ao_app->toggle_config_panel(); - ui_ic_chat_message->setFocus(); + ao_app->toggle_config_panel(); + ui_ic_chat_message->setFocus(); } void Courtroom::on_note_button_clicked() { - if(!note_shown) - { + if (!note_shown) { load_note(); ui_vp_notepad_image->show(); ui_vp_notepad->show(); ui_vp_notepad->setFocus(); note_shown = true; } - else - { + else { save_note(); ui_vp_notepad_image->hide(); ui_vp_notepad->hide(); @@ -2323,23 +2260,21 @@ void Courtroom::on_note_text_changed() void Courtroom::ping_server() { - ao_app->send_server_packet(new AOPacket("CH#" + QString::number(m_cid) + "#%")); + ao_app->send_server_packet( + new AOPacket("CH#" + QString::number(m_cid) + "#%")); } void Courtroom::closeEvent(QCloseEvent *event) { - emit closing(); - QMainWindow::closeEvent(event); + emit closing(); + QMainWindow::closeEvent(event); } -void Courtroom::on_sfx_list_clicked() -{ - ui_ic_chat_message->setFocus(); -} +void Courtroom::on_sfx_list_clicked() { ui_ic_chat_message->setFocus(); } void Courtroom::on_set_notes_clicked() { - if(note_scroll_area->isHidden()) + if (note_scroll_area->isHidden()) note_scroll_area->show(); else note_scroll_area->hide(); diff --git a/courtroom.h b/courtroom.h index d27b98bf3..3cf02d2eb 100644 --- a/courtroom.h +++ b/courtroom.h @@ -10,6 +10,7 @@ #include "aoconfigpanel.h" #include "aoemotebutton.h" #include "aoevidencebutton.h" +#include "aoevidencedescription.h" #include "aoevidencedisplay.h" #include "aoimage.h" #include "aolabel.h" @@ -23,7 +24,6 @@ #include "aosfxplayer.h" #include "aoshoutplayer.h" #include "aotextarea.h" -#include "aoevidencedescription.h" #include "aotimer.h" #include "datatypes.h" @@ -46,734 +46,752 @@ class AOApplication; -class Courtroom : public QMainWindow -{ - Q_OBJECT +class Courtroom : public QMainWindow { + Q_OBJECT public: - explicit Courtroom(AOApplication *p_ao_app); - - void append_char(char_type p_char) { char_list.append(p_char); } - void append_evidence(evi_type p_evi) { evidence_list.append(p_evi); } - void append_music(QString f_music) { music_list.append(f_music); } - void append_area(QString f_area) { area_list.append(f_area); } - void clear_music() { music_list.clear(); } - void clear_areas() { area_list.clear(); } - - void fix_last_area() - { - if (area_list.size() > 0) - { - QString malplaced = area_list.last(); - area_list.removeLast(); - append_music(malplaced); - // qDebug() << "what" << malplaced; - } + explicit Courtroom(AOApplication *p_ao_app); + + void append_char(char_type p_char) { char_list.append(p_char); } + void append_evidence(evi_type p_evi) { evidence_list.append(p_evi); } + void append_music(QString f_music) { music_list.append(f_music); } + void append_area(QString f_area) { area_list.append(f_area); } + void clear_music() { music_list.clear(); } + void clear_areas() { area_list.clear(); } + + void fix_last_area() + { + if (area_list.size() > 0) { + QString malplaced = area_list.last(); + area_list.removeLast(); + append_music(malplaced); + // qDebug() << "what" << malplaced; } + } - //sets position of widgets based on theme ini files - void set_widgets(); - //sets font properties based on theme ini files - void set_font(QWidget *widget, QString p_identifier); - //same as above, but use override color as color if it is not an empty string, otherwise use - //normal logic for color of set_font - void set_font(QWidget *widget, QString p_identifier, QString override_color); - //sets font properties for QTextEdit (same as above but also text outline) - void set_qtextedit_font(QTextEdit *widget, QString p_identifier); - //same as second set_font but for qtextedit - void set_qtextedit_font(QTextEdit *widget, QString p_identifier, QString override_color); - //helper function that calls above function on the relevant widgets - void set_fonts(); + // sets position of widgets based on theme ini files + void set_widgets(); + // sets font properties based on theme ini files + void set_font(QWidget *widget, QString p_identifier); + // same as above, but use override color as color if it is not an empty + // string, otherwise use normal logic for color of set_font + void set_font(QWidget *widget, QString p_identifier, QString override_color); + // sets font properties for QTextEdit (same as above but also text outline) + void set_qtextedit_font(QTextEdit *widget, QString p_identifier); + // same as second set_font but for qtextedit + void set_qtextedit_font(QTextEdit *widget, QString p_identifier, + QString override_color); + // helper function that calls above function on the relevant widgets + void set_fonts(); - //sets dropdown menu stylesheet - void set_dropdown(QWidget *widget, QString target_tag); + // sets dropdown menu stylesheet + void set_dropdown(QWidget *widget, QString target_tag); - //helper funciton that call above function on the relevant widgets - void set_dropdowns(); + // helper funciton that call above function on the relevant widgets + void set_dropdowns(); - void set_window_title(QString p_title); + void set_window_title(QString p_title); - //reads theme inis and sets size and pos based on the identifier - void set_size_and_pos(QWidget *p_widget, QString p_identifier); + // reads theme inis and sets size and pos based on the identifier + void set_size_and_pos(QWidget *p_widget, QString p_identifier); - //sets status as taken on character with cid n_char and places proper shading on charselect - void set_taken(int n_char, bool p_taken); + // sets status as taken on character with cid n_char and places proper shading + // on charselect + void set_taken(int n_char, bool p_taken); - //sets the current background to argument. also does some checks to see if it's a legacy bg - void set_background(QString p_background); + // sets the current background to argument. also does some checks to see if + // it's a legacy bg + void set_background(QString p_background); - //sets the evidence list member variable to argument - void set_evidence_list(QVector &p_evi_list); + // sets the evidence list member variable to argument + void set_evidence_list(QVector &p_evi_list); - //sets the character position - void set_character_position(QString p_pos, bool refresh_dropdown); + // sets the character position + void set_character_position(QString p_pos, bool refresh_dropdown); - //called when a DONE#% from the server was received - void done_received(); + // called when a DONE#% from the server was received + void done_received(); - //sets the local mute list based on characters available on the server - void set_mute_list(); + // sets the local mute list based on characters available on the server + void set_mute_list(); - //sets desk and bg based on pos in chatmessage - void set_scene(); + // sets desk and bg based on pos in chatmessage + void set_scene(); - //sets text color based on text color in chatmessage - void set_text_color(); + // sets text color based on text color in chatmessage + void set_text_color(); - //takes in serverD-formatted IP list as prints a converted version to server OOC - //admittedly poorly named - void set_ip_list(QString p_list); + // takes in serverD-formatted IP list as prints a converted version to server + // OOC admittedly poorly named + void set_ip_list(QString p_list); - //disables chat if current cid matches second argument - //enables if p_muted is false - void set_mute(bool p_muted, int p_cid); + // disables chat if current cid matches second argument + // enables if p_muted is false + void set_mute(bool p_muted, int p_cid); - //send a message that the player is banned and quits the server - void set_ban(int p_cid); + // send a message that the player is banned and quits the server + void set_ban(int p_cid); - //implementations in path_functions.cpp - QString get_background_path(); - QString get_default_background_path(); + // implementations in path_functions.cpp + QString get_background_path(); + QString get_default_background_path(); - //cid = character id, returns the cid of the currently selected character - int get_cid() { return m_cid; } - QString get_current_char() { return current_char; } + // cid = character id, returns the cid of the currently selected character + int get_cid() { return m_cid; } + QString get_current_char() { return current_char; } - //properly sets up some varibles: resets user state - void enter_courtroom(int p_cid); + // properly sets up some varibles: resets user state + void enter_courtroom(int p_cid); - //helper function that populates ui_music_list with the contents of music_list - void list_music(); + // helper function that populates ui_music_list with the contents of + // music_list + void list_music(); - void list_areas(); + void list_areas(); - void list_sfx(); + void list_sfx(); - void list_note_files(); + void list_note_files(); - void set_note_files(); + void set_note_files(); - void move_widget(QWidget *p_widget, QString p_identifier); + void move_widget(QWidget *p_widget, QString p_identifier); - void set_shouts(); - void set_effects(); - void set_judge_enabled(bool p_enabled); - void set_judge_wtce(); - void set_free_blocks(); + void set_shouts(); + void set_effects(); + void set_judge_enabled(bool p_enabled); + void set_judge_wtce(); + void set_free_blocks(); - //these are for OOC chat - void append_server_chatmessage(QString p_name, QString p_message); + // these are for OOC chat + void append_server_chatmessage(QString p_name, QString p_message); - //these functions handle chatmessages sequentially. - //The process itself is very convoluted and merits separate documentation - //But the general idea is objection animation->pre animation->talking->idle - void handle_chatmessage(QStringList *p_contents); - void handle_chatmessage_2(); - void handle_chatmessage_3(); + // these functions handle chatmessages sequentially. + // The process itself is very convoluted and merits separate documentation + // But the general idea is objection animation->pre animation->talking->idle + void handle_chatmessage(QStringList *p_contents); + void handle_chatmessage_2(); + void handle_chatmessage_3(); - //adds text to the IC chatlog. p_name first as bold then p_text then a newlin - //this function keeps the chatlog scrolled to the top unless there's text selected - // or the user isn't already scrolled to the top - void update_ic_log(bool p_reset_log); - void append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music); - void append_system_text(QString p_line); + // adds text to the IC chatlog. p_name first as bold then p_text then a newlin + // this function keeps the chatlog scrolled to the top unless there's text + // selected + // or the user isn't already scrolled to the top + void update_ic_log(bool p_reset_log); + void append_ic_text(QString p_name, QString p_line, bool p_system, + bool p_music); + void append_system_text(QString p_line); - //prints who played the song to IC chat and plays said song(if found on local filesystem) - //takes in a list where the first element is the song name and the second is the char id of who played it - void handle_song(QStringList *p_contents); + // prints who played the song to IC chat and plays said song(if found on local + // filesystem) takes in a list where the first element is the song name and the + // second is the char id of who played it + void handle_song(QStringList *p_contents); - //animates music text - void handle_music_anim(); + // animates music text + void handle_music_anim(); - //handle server-side clock animation and display - void handle_clock(QString time); + // handle server-side clock animation and display + void handle_clock(QString time); - //handle request to change theme variant - void handle_theme_variant(QString theme_variant); + // handle request to change theme variant + void handle_theme_variant(QString theme_variant); - void play_preanim(); + void play_preanim(); - //plays the witness testimony or cross examination animation based on argument - void handle_wtce(QString p_wtce); + // plays the witness testimony or cross examination animation based on + // argument + void handle_wtce(QString p_wtce); - //sets the hp bar of defense(p_bar 1) or pro(p_bar 2) - //state is an number between 0 and 10 inclusive - void set_hp_bar(int p_bar, int p_state); + // sets the hp bar of defense(p_bar 1) or pro(p_bar 2) + // state is an number between 0 and 10 inclusive + void set_hp_bar(int p_bar, int p_state); - void check_connection_received(); + void check_connection_received(); - //checks whether shout/effect/wtce/free block files are found - void check_shouts(); - void check_effects(); - void check_wtce(); - void check_free_blocks(); + // checks whether shout/effect/wtce/free block files are found + void check_shouts(); + void check_effects(); + void check_wtce(); + void check_free_blocks(); - void resume_timer(int timer_id); - void set_timer_time(int timer_id, int new_time); - void set_timer_timestep(int timer_id, int timestep_length); - void set_timer_firing(int timer_id, int firing_interval); - void pause_timer(int timer_id); + void resume_timer(int timer_id); + void set_timer_time(int timer_id, int new_time); + void set_timer_timestep(int timer_id, int timestep_length); + void set_timer_firing(int timer_id, int firing_interval); + void pause_timer(int timer_id); - template - int adapt_numbered_items(QVector &item_vector, QString config_item_number, QString item_name); + template + int adapt_numbered_items(QVector &item_vector, + QString config_item_number, QString item_name); signals: - void closing(); + void closing(); private: - AOApplication *ao_app = nullptr; - AOConfig *ao_config = nullptr; - - int m_courtroom_width = 714; - int m_courtroom_height = 668; - - int m_viewport_x = 0; - int m_viewport_y = 0; - - int m_viewport_width = 256; - int m_viewport_height = 192; - - QVector char_list; - QVector evidence_list; - QVector music_list; - QVector area_list; - QVector sfx_names; - QVector area_names; - QVector note_list; - - QSignalMapper *char_button_mapper; - - //triggers ping_server() every 60 seconds - QTimer *keepalive_timer; + AOApplication *ao_app = nullptr; + AOConfig *ao_config = nullptr; - //maintains a timer for how fast messages tick onto screen - QTimer *chat_tick_timer; - //which tick position(character in chat message) we are at - int tick_pos = 0; - //used to determine how often blips sound - int blip_pos = 0; - int rainbow_counter = 0; - bool rainbow_appended = false; - bool note_shown = false; - bool contains_add_button = false; + int m_courtroom_width = 714; + int m_courtroom_height = 668; - ////////////// - QScrollArea *note_scroll_area; + int m_viewport_x = 0; + int m_viewport_y = 0; - //delay before chat messages starts ticking - QTimer *text_delay_timer; + int m_viewport_width = 256; + int m_viewport_height = 192; - //delay before sfx plays - QTimer *sfx_delay_timer; + QVector char_list; + QVector evidence_list; + QVector music_list; + QVector area_list; + QVector sfx_names; + QVector area_names; + QVector note_list; - //keeps track of how long realization is visible(it's just a white square and should be visible less than a second) - QTimer *realization_timer; + QSignalMapper *char_button_mapper; - //times how long the blinking testimony should be shown(green one in the corner) - QTimer *testimony_show_timer; - //times how long the blinking testimony should be hidden - QTimer *testimony_hide_timer; + // triggers ping_server() every 60 seconds + QTimer *keepalive_timer; - //Generate a File Name based on the time you launched the client - QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); + // maintains a timer for how fast messages tick onto screen + QTimer *chat_tick_timer; + // which tick position(character in chat message) we are at + int tick_pos = 0; + // used to determine how often blips sound + int blip_pos = 0; + int rainbow_counter = 0; + bool rainbow_appended = false; + bool note_shown = false; + bool contains_add_button = false; - //configuration files locations - QString rpc_ini = "configs/rpccharlist.ini"; - QString file_select_ini = "configs/filesabstract.ini"; - QString shownames_ini = "configs/shownames.ini"; + ////////////// + QScrollArea *note_scroll_area; - //theme files locations - QString design_ini = "courtroom_design.ini"; - QString fonts_ini = "courtroom_fonts.ini"; - QString cc_config_ini = "courtroom_config.ini"; - QString cc_sounds_ini = "courtroom_sounds.ini"; + // delay before chat messages starts ticking + QTimer *text_delay_timer; - //every time point in char.inis times this equals the final time - const int time_mod = 40; + // delay before sfx plays + QTimer *sfx_delay_timer; - static const int chatmessage_size = 16; - QString m_chatmessage[chatmessage_size]; - bool chatmessage_is_empty = false; + // keeps track of how long realization is visible(it's just a white square and + // should be visible less than a second) + QTimer *realization_timer; - QString previous_ic_message = ""; + // times how long the blinking testimony should be shown(green one in the + // corner) + QTimer *testimony_show_timer; + // times how long the blinking testimony should be hidden + QTimer *testimony_hide_timer; - QColor m_base_string_color; - QString m_string_color = ""; + // Generate a File Name based on the time you launched the client + QString icchatlogsfilename = QDateTime::currentDateTime().toString( + "'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); - QStack m_color_stack; + // configuration files locations + QString rpc_ini = "configs/rpccharlist.ini"; + QString file_select_ini = "configs/filesabstract.ini"; + QString shownames_ini = "configs/shownames.ini"; - bool testimony_in_progress = false; + // theme files locations + QString design_ini = "courtroom_design.ini"; + QString fonts_ini = "courtroom_fonts.ini"; + QString cc_config_ini = "courtroom_config.ini"; + QString cc_sounds_ini = "courtroom_sounds.ini"; - //in milliseconds - const int testimony_show_time = 1500; + // every time point in char.inis times this equals the final time + const int time_mod = 40; - //in milliseconds - const int testimony_hide_time = 500; + static const int chatmessage_size = 16; + QString m_chatmessage[chatmessage_size]; + bool chatmessage_is_empty = false; - //char id, muted or not - QMap mute_map; + QString previous_ic_message = ""; - //QVector muted_cids; + QColor m_base_string_color; + QString m_string_color = ""; - bool is_client_muted = false; + QStack m_color_stack; - // state of animation, 0 = objecting, 1 = preanim, 2 = talking, 3 = idle - int anim_state = 3; + bool testimony_in_progress = false; - // state of text ticking, 0 = not yet ticking, 1 = ticking in progress, 2 = ticking done - int text_state = 2; + // in milliseconds + const int testimony_show_time = 1500; - // character id, which index of the char_list the player is - int m_cid = -1; + // in milliseconds + const int testimony_hide_time = 500; - // if enabled, disable showing our own sprites when we talk in ic - bool m_msg_is_first_person = false; + // char id, muted or not + QMap mute_map; - //cid and this may differ in cases of ini-editing - QString current_char = ""; + // QVector muted_cids; - QString current_file = ""; + bool is_client_muted = false; - int m_objection_state = 0; - int m_effect_state = 0; - int m_text_color = 0; - int m_shout_state = 0; - int m_effect_current = 0; - int m_wtce_current = 0; - bool is_presenting_evidence = false; - bool is_judge = false; - bool is_system_speaking = false; + // state of animation, 0 = objecting, 1 = preanim, 2 = talking, 3 = idle + int anim_state = 3; - int defense_bar_state = 0; - int prosecution_bar_state = 0; + // state of text ticking, 0 = not yet ticking, 1 = ticking in progress, 2 = + // ticking done + int text_state = 2; - int current_char_page = 0; - int char_columns = 10; - int char_rows = 9; - int max_chars_on_page = 90; + // character id, which index of the char_list the player is + int m_cid = -1; - int current_emote_page = 0; - int current_emote = 0; - int prev_emote = 0; - int emote_columns = 5; - int emote_rows = 2; - int max_emotes_on_page = 10; + // if enabled, disable showing our own sprites when we talk in ic + bool m_msg_is_first_person = false; - int m_chatlog_limit = 200; - bool m_chatlog_newline = false; - bool m_chatlog_scrolldown = false; + // cid and this may differ in cases of ini-editing + QString current_char = ""; - // inmchatlog_changed; + QString current_file = ""; - QVector local_evidence_list; + int m_objection_state = 0; + int m_effect_state = 0; + int m_text_color = 0; + int m_shout_state = 0; + int m_effect_current = 0; + int m_wtce_current = 0; + bool is_presenting_evidence = false; + bool is_judge = false; + bool is_system_speaking = false; - int current_evidence_page = 0; - int current_evidence = 0; - int evidence_columns = 6; - int evidence_rows = 3; - int max_evidence_on_page = 18; + int defense_bar_state = 0; + int prosecution_bar_state = 0; - int current_clock = -1; - int timer_number = 0; + int current_char_page = 0; + int char_columns = 10; + int char_rows = 9; + int max_chars_on_page = 90; - QString current_background = "gs4"; + int current_emote_page = 0; + int current_emote = 0; + int prev_emote = 0; + int emote_columns = 5; + int emote_rows = 2; + int max_emotes_on_page = 10; - AOImage *ui_background; + int m_chatlog_limit = 200; + bool m_chatlog_newline = false; + bool m_chatlog_scrolldown = false; - QWidget *ui_viewport; - AOScene *ui_vp_background; - AOMovie *ui_vp_speedlines; - AOCharMovie *ui_vp_player_char; - AOScene *ui_vp_desk; - AOEvidenceDisplay *ui_vp_evidence_display; + // inmchatlog_changed; - AONoteArea *ui_note_area; + QVector local_evidence_list; - // AONotepad *ui_vp_notepad; - // list of characters that require a second application ID - // note that since it's hardcoded, it won't be of much use in other servers - QVector rpc_char_list; + int current_evidence_page = 0; + int current_evidence = 0; + int evidence_columns = 6; + int evidence_rows = 3; + int max_evidence_on_page = 18; - AOImage *ui_vp_notepad_image; - QTextEdit *ui_vp_notepad; + int current_clock = -1; + int timer_number = 0; - AOImage *ui_vp_chatbox = nullptr; - QTextEdit *ui_vp_showname = nullptr; - QTextEdit *ui_vp_message = nullptr; - AOImage *ui_vp_testimony = nullptr; - AOMovie *ui_vp_effect = nullptr; - AOMovie *ui_vp_wtce = nullptr; - AOMovie *ui_vp_objection = nullptr; + QString current_background = "gs4"; - AOImage *ui_vp_music_display_a = nullptr; - AOImage *ui_vp_music_display_b = nullptr; + AOImage *ui_background; - AOImage *ui_vp_showname_image = nullptr; + QWidget *ui_viewport; + AOScene *ui_vp_background; + AOMovie *ui_vp_speedlines; + AOCharMovie *ui_vp_player_char; + AOScene *ui_vp_desk; + AOEvidenceDisplay *ui_vp_evidence_display; - QTextEdit *ui_vp_music_name = nullptr; - QPropertyAnimation *music_anim = nullptr; + AONoteArea *ui_note_area; - QWidget *ui_vp_music_area; + // AONotepad *ui_vp_notepad; + // list of characters that require a second application ID + // note that since it's hardcoded, it won't be of much use in other servers + QVector rpc_char_list; - AOMovie *ui_vp_clock; - QVector ui_timers; + AOImage *ui_vp_notepad_image; + QTextEdit *ui_vp_notepad; - QTextEdit *ui_ic_chatlog = nullptr; - record_type_array m_ic_records; + AOImage *ui_vp_chatbox = nullptr; + QTextEdit *ui_vp_showname = nullptr; + QTextEdit *ui_vp_message = nullptr; + AOImage *ui_vp_testimony = nullptr; + AOMovie *ui_vp_effect = nullptr; + AOMovie *ui_vp_wtce = nullptr; + AOMovie *ui_vp_objection = nullptr; - AOTextArea *ui_server_chatlog; + AOImage *ui_vp_music_display_a = nullptr; + AOImage *ui_vp_music_display_b = nullptr; - QListWidget *ui_mute_list; - QListWidget *ui_area_list; - QListWidget *ui_music_list; - QListWidget *ui_sfx_list; + AOImage *ui_vp_showname_image = nullptr; - QLineEdit *ui_ic_chat_message; + QTextEdit *ui_vp_music_name = nullptr; + QPropertyAnimation *music_anim = nullptr; - QLineEdit *ui_ooc_chat_message; - QLineEdit *ui_ooc_chat_name; + QWidget *ui_vp_music_area; - QLineEdit *ui_music_search; + AOMovie *ui_vp_clock; + QVector ui_timers; - QLineEdit *ui_sfx_search; + QTextEdit *ui_ic_chatlog = nullptr; + record_type_array m_ic_records; + + AOTextArea *ui_server_chatlog; - QWidget *ui_emotes = nullptr; - QVector ui_emote_list; - AOButton *ui_emote_left; - AOButton *ui_emote_right; + QListWidget *ui_mute_list; + QListWidget *ui_area_list; + QListWidget *ui_music_list; + QListWidget *ui_sfx_list; - QComboBox *ui_emote_dropdown; - QComboBox *ui_pos_dropdown; + QLineEdit *ui_ic_chat_message; - AOImage *ui_defense_bar; - AOImage *ui_prosecution_bar; + QLineEdit *ui_ooc_chat_message; + QLineEdit *ui_ooc_chat_name; - //buttons to cycle through shouts - AOButton *ui_shout_up = nullptr; - AOButton *ui_shout_down = nullptr; - //buttons to cycle through effects - AOButton *ui_effect_up = nullptr; - AOButton *ui_effect_down = nullptr; - //buttons to cycle through wtce - AOButton *ui_wtce_up = nullptr; - AOButton *ui_wtce_down = nullptr; + QLineEdit *ui_music_search; - //holds all the shout button objects - QVector ui_shouts; - //holds all the effect button objects - QVector ui_effects; - //holds all the shout buttons objects - QVector ui_wtce; - //holds all the free block objects - QVector ui_free_blocks; + QLineEdit *ui_sfx_search; - //holds all the names for sound files for the shouts - // QVector shout_names = {"holdit", "objection", "takethat", "custom", "gotit", "crossswords", "counteralt"}; - QVector shout_names; + QWidget *ui_emotes = nullptr; + QVector ui_emote_list; + AOButton *ui_emote_left; + AOButton *ui_emote_right; - //holds all the names for sound/anim files for the effects - //QVector effect_names = {"effect_flash", "effect_gloom", "effect_question", "effect_pow"}; - QVector effect_names; + QComboBox *ui_emote_dropdown; + QComboBox *ui_pos_dropdown; - //holds all the names for sound/anim files for the shouts - //QVector shout_names = {"witnesstestimony", "crossexamination", "investigation", "nonstop"}; - QVector wtce_names; + AOImage *ui_defense_bar; + AOImage *ui_prosecution_bar; - //holds all the names for free blocks - QVector free_block_names; + // buttons to cycle through shouts + AOButton *ui_shout_up = nullptr; + AOButton *ui_shout_down = nullptr; + // buttons to cycle through effects + AOButton *ui_effect_up = nullptr; + AOButton *ui_effect_down = nullptr; + // buttons to cycle through wtce + AOButton *ui_wtce_up = nullptr; + AOButton *ui_wtce_down = nullptr; - //holds whether the animation file exists for a determined shout/effect - QVector shouts_enabled; - QVector effects_enabled; - QVector wtce_enabled; - QVector free_blocks_enabled; + // holds all the shout button objects + QVector ui_shouts; + // holds all the effect button objects + QVector ui_effects; + // holds all the shout buttons objects + QVector ui_wtce; + // holds all the free block objects + QVector ui_free_blocks; - // AOButton* ui_shout_hold_it = nullptr; // 1 - // AOButton* ui_shout_objection = nullptr; // 2 - // AOButton* ui_shout_take_that = nullptr; // 3 - // AOButton* ui_shout_custom = nullptr; // 4 - // AOButton* ui_shout_got_it = nullptr; // 5 - // AOButton* ui_shout_cross_swords = nullptr; // 6 - // AOButton* ui_shout_counter_alt = nullptr; // 7 + // holds all the names for sound files for the shouts + // QVector shout_names = {"holdit", "objection", "takethat", + // "custom", "gotit", "crossswords", "counteralt"}; + QVector shout_names; - AOButton *ui_witness_testimony; - AOButton *ui_cross_examination; - AOButton *ui_investigation; - AOButton *ui_nonstop; + // holds all the names for sound/anim files for the effects + // QVector effect_names = {"effect_flash", "effect_gloom", + // "effect_question", "effect_pow"}; + QVector effect_names; - AOButton *ui_change_character; - AOButton *ui_call_mod; - AOButton *ui_switch_area_music; + // holds all the names for sound/anim files for the shouts + // QVector shout_names = {"witnesstestimony", "crossexamination", + // "investigation", "nonstop"}; + QVector wtce_names; + + // holds all the names for free blocks + QVector free_block_names; + + // holds whether the animation file exists for a determined shout/effect + QVector shouts_enabled; + QVector effects_enabled; + QVector wtce_enabled; + QVector free_blocks_enabled; + + // AOButton* ui_shout_hold_it = nullptr; // 1 + // AOButton* ui_shout_objection = nullptr; // 2 + // AOButton* ui_shout_take_that = nullptr; // 3 + // AOButton* ui_shout_custom = nullptr; // 4 + // AOButton* ui_shout_got_it = nullptr; // 5 + // AOButton* ui_shout_cross_swords = nullptr; // 6 + // AOButton* ui_shout_counter_alt = nullptr; // 7 + + AOButton *ui_witness_testimony; + AOButton *ui_cross_examination; + AOButton *ui_investigation; + AOButton *ui_nonstop; - AOButton *ui_config_panel; + AOButton *ui_change_character; + AOButton *ui_call_mod; + AOButton *ui_switch_area_music; - AOButton *ui_set_notes; + AOButton *ui_config_panel; - QCheckBox *ui_pre; - QCheckBox *ui_flip; - QCheckBox *ui_hidden; + AOButton *ui_set_notes; + + QCheckBox *ui_pre; + QCheckBox *ui_flip; + QCheckBox *ui_hidden; + + QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden + QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip + QVector ui_label_images; + QVector label_images = {"Pre", "Flip", "Hidden", + "Music", "SFX", "Blip"}; + + AOButton *ui_effect_flash = nullptr; + AOButton *ui_effect_gloom = nullptr; + + AOButton *ui_mute = nullptr; + + AOButton *ui_defense_plus; + AOButton *ui_defense_minus; + + AOButton *ui_prosecution_plus; + AOButton *ui_prosecution_minus; + + QComboBox *ui_text_color; + + AOImage *ui_muted; + + AOButton *ui_note_button; + + AOButton *ui_evidence_button; + AOImage *ui_evidence; + AOLineEdit *ui_evidence_name; + QWidget *ui_evidence_buttons; + QVector ui_evidence_list; + AOButton *ui_evidence_left; + AOButton *ui_evidence_right; + AOButton *ui_evidence_present; + AOImage *ui_evidence_overlay; + AOButton *ui_evidence_delete; + AOLineEdit *ui_evidence_image_name; + AOButton *ui_evidence_image_button; + AOButton *ui_evidence_x; + AOEvidenceDescription *ui_evidence_description; + + AOImage *ui_char_select_background; + + // abstract widget to hold char buttons + QWidget *ui_char_buttons = nullptr; + AOImage *ui_char_button_selector = nullptr; + QVector ui_char_button_list; + + AOButton *ui_back_to_lobby; + + AOButton *ui_char_select_left; + AOButton *ui_char_select_right; + + AOButton *ui_spectator; + + QHash widget_names; + + void create_widgets(); + void connect_widgets(); + void set_widget_names(); + void reset_widget_names(); + void insert_widget_name(QString p_widget_name, QWidget *p_widget); + void insert_widget_names(QVector &p_widget_names, + QVector &p_widgets); + template + void insert_widget_names(QVector &p_widget_names, + QVector &p_widgets); + void set_widget_layers(); - QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden - QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip - QVector ui_label_images; - QVector label_images = {"Pre", "Flip", "Hidden", "Music", "SFX", "Blip"}; + void construct_char_select(); + void reconstruct_char_select(); + void reset_char_select(); + void set_char_select(); + void set_char_select_page(); + + void construct_emotes(); + void reconstruct_emotes(); + void reset_emote_page(); + void set_emote_page(); + void set_emote_dropdown(); - AOButton *ui_effect_flash = nullptr; - AOButton *ui_effect_gloom = nullptr; + void construct_evidence(); + void set_evidence_page(); - AOButton *ui_mute = nullptr; + void load_note(); + void save_note(); + void save_textlog(QString p_text); - AOButton *ui_defense_plus; - AOButton *ui_defense_minus; - - AOButton *ui_prosecution_plus; - AOButton *ui_prosecution_minus; - - QComboBox *ui_text_color; - - AOImage *ui_muted; - - AOButton *ui_note_button; - - AOButton *ui_evidence_button; - AOImage *ui_evidence; - AOLineEdit *ui_evidence_name; - QWidget *ui_evidence_buttons; - QVector ui_evidence_list; - AOButton *ui_evidence_left; - AOButton *ui_evidence_right; - AOButton *ui_evidence_present; - AOImage *ui_evidence_overlay; - AOButton *ui_evidence_delete; - AOLineEdit *ui_evidence_image_name; - AOButton *ui_evidence_image_button; - AOButton *ui_evidence_x; - AOEvidenceDescription *ui_evidence_description; - - AOImage *ui_char_select_background; - - //abstract widget to hold char buttons - QWidget *ui_char_buttons = nullptr; - AOImage *ui_char_button_selector = nullptr; - QVector ui_char_button_list; - - AOButton *ui_back_to_lobby; - - AOButton *ui_char_select_left; - AOButton *ui_char_select_right; - - AOButton *ui_spectator; - - QHash widget_names; - - void create_widgets(); - void connect_widgets(); - void set_widget_names(); - void reset_widget_names(); - void insert_widget_name(QString p_widget_name, QWidget *p_widget); - void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); - template - void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); - void set_widget_layers(); - - void construct_char_select(); - void reconstruct_char_select(); - void reset_char_select(); - void set_char_select(); - void set_char_select_page(); - - void construct_emotes(); - void reconstruct_emotes(); - void reset_emote_page(); - void set_emote_page(); - void set_emote_dropdown(); - - void construct_evidence(); - void set_evidence_page(); - - void load_note(); - void save_note(); - void save_textlog(QString p_text); - - void set_char_rpc(); + void set_char_rpc(); public slots: - void objection_done(); - void preanim_done(); + void objection_done(); + void preanim_done(); - void realization_done(); + void realization_done(); - void show_testimony(); - void hide_testimony(); + void show_testimony(); + void hide_testimony(); - void mod_called(QString p_ip); + void mod_called(QString p_ip); private slots: - void start_chat_ticking(); - void play_sfx(); + void start_chat_ticking(); + void play_sfx(); - void chat_tick(); + void chat_tick(); - void on_mute_list_clicked(QModelIndex p_index); + void on_mute_list_clicked(QModelIndex p_index); - void on_chat_return_pressed(); - void on_chat_config_changed(); + void on_chat_return_pressed(); + void on_chat_config_changed(); - void on_ooc_return_pressed(); + void on_ooc_return_pressed(); - void on_music_search_edited(QString p_text); - void on_music_list_clicked(); - void on_area_list_clicked(); - void on_music_list_double_clicked(QModelIndex p_model); - void on_area_list_double_clicked(QModelIndex p_model); + void on_music_search_edited(QString p_text); + void on_music_list_clicked(); + void on_area_list_clicked(); + void on_music_list_double_clicked(QModelIndex p_model); + void on_area_list_double_clicked(QModelIndex p_model); - void on_sfx_search_edited(QString p_text); + void on_sfx_search_edited(QString p_text); - void select_emote(int p_id); + void select_emote(int p_id); - void on_emote_clicked(int p_id); + void on_emote_clicked(int p_id); - void on_emote_left_clicked(); - void on_emote_right_clicked(); + void on_emote_left_clicked(); + void on_emote_right_clicked(); - void on_emote_dropdown_changed(int p_index); - void on_pos_dropdown_changed(int p_index); + void on_emote_dropdown_changed(int p_index); + void on_pos_dropdown_changed(int p_index); - void on_evidence_name_edited(); - void on_evidence_image_name_edited(); - void on_evidence_image_button_clicked(); - void on_evidence_clicked(int p_id); - void on_evidence_double_clicked(int p_id); + void on_evidence_name_edited(); + void on_evidence_image_name_edited(); + void on_evidence_image_button_clicked(); + void on_evidence_clicked(int p_id); + void on_evidence_double_clicked(int p_id); - void on_evidence_hover(int p_id, bool p_state); + void on_evidence_hover(int p_id, bool p_state); - void on_evidence_left_clicked(); - void on_evidence_right_clicked(); - void on_evidence_present_clicked(); + void on_evidence_left_clicked(); + void on_evidence_right_clicked(); + void on_evidence_present_clicked(); - void on_cycle_clicked(); + void on_cycle_clicked(); - void cycle_shout(int p_index); - void cycle_effect(int p_index); - void cycle_wtce(int p_index); + void cycle_shout(int p_index); + void cycle_effect(int p_index); + void cycle_wtce(int p_index); - void on_add_button_clicked(); - void on_delete_button_clicked(); + void on_add_button_clicked(); + void on_delete_button_clicked(); - void on_set_file_button_clicked(); - void on_file_selected(); + void on_set_file_button_clicked(); + void on_file_selected(); - void delete_widget(QWidget *p_widget); - void load_shouts(); - void load_effects(); - void load_wtce(); - void load_free_blocks(); - /** + void delete_widget(QWidget *p_widget); + void load_shouts(); + void load_effects(); + void load_wtce(); + void load_free_blocks(); + /** * @brief reset the shout button's texture to default * DOES NOT MODIFY OBJECTION_STATE */ - void reset_shout_buttons(); + void reset_shout_buttons(); - /** + /** * @brief a general purpose function to toggle button selection */ - void on_shout_clicked(); + void on_shout_clicked(); - void reset_effect_buttons(); - void on_effect_button_clicked(); + void reset_effect_buttons(); + void on_effect_button_clicked(); - void on_mute_clicked(); + void on_mute_clicked(); - void on_defense_minus_clicked(); - void on_defense_plus_clicked(); - void on_prosecution_minus_clicked(); - void on_prosecution_plus_clicked(); + void on_defense_minus_clicked(); + void on_defense_plus_clicked(); + void on_prosecution_minus_clicked(); + void on_prosecution_plus_clicked(); - void on_text_color_changed(int p_color); + void on_text_color_changed(int p_color); - void on_witness_testimony_clicked(); - void on_cross_examination_clicked(); - void reset_judge_wtce_buttons(); - void on_wtce_clicked(); + void on_witness_testimony_clicked(); + void on_cross_examination_clicked(); + void reset_judge_wtce_buttons(); + void on_wtce_clicked(); - void on_change_character_clicked(); - void on_app_reload_theme_requested(); - void on_call_mod_clicked(); + void on_change_character_clicked(); + void on_app_reload_theme_requested(); + void on_call_mod_clicked(); - void on_switch_area_music_clicked(); + void on_switch_area_music_clicked(); - void on_config_panel_clicked(); - void on_note_button_clicked(); + void on_config_panel_clicked(); + void on_note_button_clicked(); - void on_set_notes_clicked(); + void on_set_notes_clicked(); - void on_note_text_changed(); + void on_note_text_changed(); - void on_pre_clicked(); - void on_flip_clicked(); - void on_hidden_clicked(); + void on_pre_clicked(); + void on_flip_clicked(); + void on_hidden_clicked(); - void on_sfx_list_clicked(); + void on_sfx_list_clicked(); - void on_evidence_button_clicked(); + void on_evidence_button_clicked(); - void on_evidence_delete_clicked(); - void on_evidence_x_clicked(); + void on_evidence_delete_clicked(); + void on_evidence_x_clicked(); - void on_back_to_lobby_clicked(); + void on_back_to_lobby_clicked(); - void on_char_select_left_clicked(); - void on_char_select_right_clicked(); - void char_clicked(int n_char); - void char_mouse_entered(AOCharButton *p_caller); - void char_mouse_left(); + void on_char_select_left_clicked(); + void on_char_select_right_clicked(); + void char_clicked(int n_char); + void char_mouse_entered(AOCharButton *p_caller); + void char_mouse_left(); - void on_spectator_clicked(); + void on_spectator_clicked(); - void ping_server(); + void ping_server(); -/*! - * ============================================================================= - * AUDIO SYSTEM - */ + /*! + * ============================================================================= + * AUDIO SYSTEM + */ public: - bool is_audio_muted(); + bool is_audio_muted(); public slots: - void set_audio_mute_enabled(bool p_enabled); - void set_effects_volume(int p_volume); - void set_system_volume(int p_volume); - void set_music_volume(int p_volume); - void set_blips_volume(int p_volume); + void set_audio_mute_enabled(bool p_enabled); + void set_effects_volume(int p_volume); + void set_system_volume(int p_volume); + void set_music_volume(int p_volume); + void set_blips_volume(int p_volume); private: - bool m_audio_mute = false; - AOSfxPlayer *m_effects_player = nullptr; - AOShoutPlayer *m_shouts_player = nullptr; - AOSfxPlayer *m_system_player = nullptr; - AOMusicPlayer *m_music_player = nullptr; - AOBlipPlayer *m_blips_player = nullptr; + bool m_audio_mute = false; + AOSfxPlayer *m_effects_player = nullptr; + AOShoutPlayer *m_shouts_player = nullptr; + AOSfxPlayer *m_system_player = nullptr; + AOMusicPlayer *m_music_player = nullptr; + AOBlipPlayer *m_blips_player = nullptr; private slots: - void on_config_effects_volume_changed(int p_volume); - void on_config_system_volume_changed(int p_volume); - void on_config_music_volume_changed(int p_volume); - void on_config_blips_volume_changed(int p_volume); + void on_config_effects_volume_changed(int p_volume); + void on_config_system_volume_changed(int p_volume); + void on_config_music_volume_changed(int p_volume); + void on_config_blips_volume_changed(int p_volume); - // QWidget interface + // QWidget interface protected: - void closeEvent(QCloseEvent *event) override; + void closeEvent(QCloseEvent *event) override; }; template -void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) +void Courtroom::insert_widget_names(QVector &p_widget_names, + QVector &p_widgets) { - QVector widgets; + QVector widgets; - for (QWidget *widget : p_widgets) - widgets.append(widget); + for (QWidget *widget : p_widgets) + widgets.append(widget); - insert_widget_names(p_widget_names, widgets); + insert_widget_names(p_widget_names, widgets); } #endif // COURTROOM_H diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 804b8d86b..d630557ba 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -1,24 +1,24 @@ #include "courtroom.h" #include "aoapplication.h" -#include "lobby.h" -#include "hardware_functions.h" -#include "file_functions.h" #include "datatypes.h" #include "debug_functions.h" +#include "file_functions.h" +#include "hardware_functions.h" +#include "lobby.h" -#include -#include -#include #include -#include +#include +#include +#include #include -#include -#include #include -#include #include -#include +#include +#include +#include +#include +#include void Courtroom::create_widgets() { @@ -48,16 +48,20 @@ void Courtroom::create_widgets() m_effects_player->set_volume(ao_config->effects_volume()); m_shouts_player = new AOShoutPlayer(this, ao_app); m_shouts_player->set_volume(ao_config->effects_volume()); - connect(ao_config, SIGNAL(effects_volume_changed(int)), this, SLOT(on_config_effects_volume_changed(int))); + connect(ao_config, SIGNAL(effects_volume_changed(int)), this, + SLOT(on_config_effects_volume_changed(int))); m_system_player = new AOSfxPlayer(this, ao_app); m_system_player->set_volume(ao_config->system_volume()); - connect(ao_config, SIGNAL(system_volume_changed(int)), this, SLOT(on_config_system_volume_changed(int))); + connect(ao_config, SIGNAL(system_volume_changed(int)), this, + SLOT(on_config_system_volume_changed(int))); m_music_player = new AOMusicPlayer(this, ao_app); m_music_player->set_volume(ao_config->music_volume()); - connect(ao_config, SIGNAL(music_volume_changed(int)), this, SLOT(on_config_music_volume_changed(int))); + connect(ao_config, SIGNAL(music_volume_changed(int)), this, + SLOT(on_config_music_volume_changed(int))); m_blips_player = new AOBlipPlayer(this, ao_app); m_blips_player->set_volume(ao_config->blips_volume()); - connect(ao_config, SIGNAL(blips_volume_changed(int)), this, SLOT(on_config_blips_volume_changed(int))); + connect(ao_config, SIGNAL(blips_volume_changed(int)), this, + SLOT(on_config_blips_volume_changed(int))); ui_background = new AOImage(this, ao_app); @@ -149,9 +153,10 @@ void Courtroom::create_widgets() construct_emotes(); ui_defense_bar = new AOImage(this, ao_app); - ui_prosecution_bar = new AOImage(this, ao_app); + ui_prosecution_bar = new AOImage(this, ao_app); - load_shouts(); // Readds from theme, deletes old shouts if needed and creates new ones + load_shouts(); // Readds from theme, deletes old shouts if needed and creates + // new ones ui_shout_up = new AOButton(this, ao_app); ui_shout_up->setProperty("cycle_id", 1); @@ -180,8 +185,7 @@ void Courtroom::create_widgets() ui_note_button = new AOButton(this, ao_app); ui_label_images.resize(label_images.size()); - for(int i = 0; i < ui_label_images.size(); ++i) - { + for (int i = 0; i < ui_label_images.size(); ++i) { ui_label_images[i] = new AOImage(this, ao_app); } @@ -212,8 +216,7 @@ void Courtroom::create_widgets() ui_text_color->addItem("Red"); ui_text_color->addItem("Orange"); ui_text_color->addItem("Blue"); - if (ao_app->yellow_text_enabled) - { + if (ao_app->yellow_text_enabled) { ui_text_color->addItem("Yellow"); ui_text_color->addItem("Purple"); ui_text_color->addItem("Pink"); @@ -239,320 +242,350 @@ void Courtroom::connect_widgets() { connect(keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); - connect(ao_app, SIGNAL(reload_theme()), this, SLOT(on_app_reload_theme_requested())); + connect(ao_app, SIGNAL(reload_theme()), this, + SLOT(on_app_reload_theme_requested())); connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); - connect(text_delay_timer, SIGNAL(timeout()), this, SLOT(start_chat_ticking())); + connect(text_delay_timer, SIGNAL(timeout()), this, + SLOT(start_chat_ticking())); connect(sfx_delay_timer, SIGNAL(timeout()), this, SLOT(play_sfx())); connect(chat_tick_timer, SIGNAL(timeout()), this, SLOT(chat_tick())); connect(realization_timer, SIGNAL(timeout()), this, SLOT(realization_done())); - connect(testimony_show_timer, SIGNAL(timeout()), this, SLOT(hide_testimony())); - connect(testimony_hide_timer, SIGNAL(timeout()), this, SLOT(show_testimony())); - - connect(ui_emote_left, SIGNAL(clicked()), this, SLOT(on_emote_left_clicked())); - connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); - - connect(ui_emote_dropdown, SIGNAL(activated(int)), this, SLOT(on_emote_dropdown_changed(int))); - connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); - - connect(ui_mute_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_mute_list_clicked(QModelIndex))); - - connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_chat_return_pressed())); - - connect(ui_ooc_chat_name, SIGNAL(textEdited(QString)), ao_config, SLOT(set_username(QString))); - connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); - connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); - - connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_music_list_clicked())); - connect(ui_area_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_area_list_clicked())); - connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_music_list_double_clicked(QModelIndex))); - connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_area_list_double_clicked(QModelIndex))); - - // connect events for shout/effect/wtce buttons happen in load_shouts(), load_effects(), load_wtce() + connect(testimony_show_timer, SIGNAL(timeout()), this, + SLOT(hide_testimony())); + connect(testimony_hide_timer, SIGNAL(timeout()), this, + SLOT(show_testimony())); + + connect(ui_emote_left, SIGNAL(clicked()), this, + SLOT(on_emote_left_clicked())); + connect(ui_emote_right, SIGNAL(clicked()), this, + SLOT(on_emote_right_clicked())); + + connect(ui_emote_dropdown, SIGNAL(activated(int)), this, + SLOT(on_emote_dropdown_changed(int))); + connect(ui_pos_dropdown, SIGNAL(activated(int)), this, + SLOT(on_pos_dropdown_changed(int))); + + connect(ui_mute_list, SIGNAL(clicked(QModelIndex)), this, + SLOT(on_mute_list_clicked(QModelIndex))); + + connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, + SLOT(on_chat_return_pressed())); + + connect(ui_ooc_chat_name, SIGNAL(textEdited(QString)), ao_config, + SLOT(set_username(QString))); + connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, + SLOT(setText(QString))); + connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, + SLOT(on_ooc_return_pressed())); + + connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, + SLOT(on_music_list_clicked())); + connect(ui_area_list, SIGNAL(clicked(QModelIndex)), this, + SLOT(on_area_list_clicked())); + connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, + SLOT(on_music_list_double_clicked(QModelIndex))); + connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, + SLOT(on_area_list_double_clicked(QModelIndex))); + + // connect events for shout/effect/wtce buttons happen in load_shouts(), + // load_effects(), load_wtce() connect(ui_shout_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_shout_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_effect_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - connect(ui_effect_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + connect(ui_effect_down, SIGNAL(clicked(bool)), this, + SLOT(on_cycle_clicked())); connect(ui_wtce_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_wtce_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_mute, SIGNAL(clicked()), this, SLOT(on_mute_clicked())); - connect(ui_defense_minus, SIGNAL(clicked()), this, SLOT(on_defense_minus_clicked())); - connect(ui_defense_plus, SIGNAL(clicked()), this, SLOT(on_defense_plus_clicked())); - connect(ui_prosecution_minus, SIGNAL(clicked()), this, SLOT(on_prosecution_minus_clicked())); - connect(ui_prosecution_plus, SIGNAL(clicked()), this, SLOT(on_prosecution_plus_clicked())); - - connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, SLOT(on_text_color_changed(int))); - - connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, SLOT(on_chat_config_changed())); - connect(ao_config, SIGNAL(log_goes_downward_changed(bool)), this, SLOT(on_chat_config_changed())); - connect(ao_config, SIGNAL(log_uses_newline_changed(bool)), this, SLOT(on_chat_config_changed())); - - connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); - connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited(QString))); - - connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); + connect(ui_defense_minus, SIGNAL(clicked()), this, + SLOT(on_defense_minus_clicked())); + connect(ui_defense_plus, SIGNAL(clicked()), this, + SLOT(on_defense_plus_clicked())); + connect(ui_prosecution_minus, SIGNAL(clicked()), this, + SLOT(on_prosecution_minus_clicked())); + connect(ui_prosecution_plus, SIGNAL(clicked()), this, + SLOT(on_prosecution_plus_clicked())); + + connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, + SLOT(on_text_color_changed(int))); + + connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, + SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_goes_downward_changed(bool)), this, + SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_uses_newline_changed(bool)), this, + SLOT(on_chat_config_changed())); + + connect(ui_music_search, SIGNAL(textChanged(QString)), this, + SLOT(on_music_search_edited(QString))); + connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, + SLOT(on_sfx_search_edited(QString))); + + connect(ui_change_character, SIGNAL(clicked()), this, + SLOT(on_change_character_clicked())); connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); - connect(ui_switch_area_music, SIGNAL(clicked()), this, SLOT(on_switch_area_music_clicked())); + connect(ui_switch_area_music, SIGNAL(clicked()), this, + SLOT(on_switch_area_music_clicked())); - connect(ui_config_panel, SIGNAL(clicked()), this, SLOT(on_config_panel_clicked())); - connect(ui_note_button, SIGNAL(clicked()), this, SLOT(on_note_button_clicked())); + connect(ui_config_panel, SIGNAL(clicked()), this, + SLOT(on_config_panel_clicked())); + connect(ui_note_button, SIGNAL(clicked()), this, + SLOT(on_note_button_clicked())); - connect(ui_vp_notepad, SIGNAL(textChanged()), this, SLOT(on_note_text_changed())); + connect(ui_vp_notepad, SIGNAL(textChanged()), this, + SLOT(on_note_text_changed())); connect(ui_pre, SIGNAL(clicked()), this, SLOT(on_pre_clicked())); connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); - connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_sfx_list_clicked())); + connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, + SLOT(on_sfx_list_clicked())); - connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); + connect(ui_evidence_button, SIGNAL(clicked()), this, + SLOT(on_evidence_button_clicked())); - connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, SLOT(on_add_button_clicked())); - connect(ui_set_notes, SIGNAL(clicked(bool)), this, SLOT(on_set_notes_clicked())); + connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, + SLOT(on_add_button_clicked())); + connect(ui_set_notes, SIGNAL(clicked(bool)), this, + SLOT(on_set_notes_clicked())); } void Courtroom::reset_widget_names() { - // Assign names to the default widgets - widget_names = { - {"courtroom", this}, - {"viewport", ui_viewport}, - {"background", ui_vp_background}, //* - {"speedlines", ui_vp_speedlines}, //* - {"player_char", ui_vp_player_char}, //* - {"desk", ui_vp_desk}, //* - {"music_display_a", ui_vp_music_display_a}, - {"music_display_b", ui_vp_music_display_b}, - {"music_area", ui_vp_music_area}, - {"music_name", ui_vp_music_name}, - // music_anim - {"clock", ui_vp_clock}, - // ui_vp_evidence_display - {"ao2_chatbox", ui_vp_chatbox}, - {"showname", ui_vp_showname}, - {"message", ui_vp_message}, - {"showname_image", ui_vp_showname_image}, - {"vp_testimony", ui_vp_testimony}, - {"vp_effect", ui_vp_effect}, - {"vp_wtce", ui_vp_wtce}, - {"vp_objection", ui_vp_objection}, - {"ic_chatlog", ui_ic_chatlog}, - {"server_chatlog", ui_server_chatlog}, - {"mute_list", ui_mute_list}, - {"area_list", ui_area_list}, - {"music_list", ui_music_list}, - {"sfx_list", ui_sfx_list}, - {"ao2_ic_chat_message", ui_ic_chat_message}, - // ui_muted - {"ooc_chat_message", ui_ooc_chat_message}, - {"ooc_chat_name", ui_ooc_chat_name}, - {"music_search", ui_music_search}, - {"sfx_search", ui_sfx_search}, - {"note_scroll_area", note_scroll_area}, - {"note_area", ui_note_area}, - // add_button - // m_layout - {"set_notes_button", ui_set_notes}, - {"emotes", ui_emotes}, - {"emote_left", ui_emote_left}, - {"emote_right", ui_emote_right}, - {"emote_dropdown", ui_emote_dropdown}, - {"pos_dropdown", ui_pos_dropdown}, - {"defense_bar", ui_defense_bar}, - {"prosecution_bar", ui_prosecution_bar}, - // Each ui_shouts[i] - {"shout_up", ui_shout_up}, - {"shout_down", ui_shout_down}, - // Each ui_effects[i] - {"effect_down", ui_effect_down}, - {"effect_up", ui_effect_up}, - // Each ui_wtce[i] - {"wtce_up", ui_wtce_up}, - {"wtce_down", ui_wtce_down}, - {"change_character", ui_change_character}, - {"call_mod", ui_call_mod}, - {"switch_area_music", ui_switch_area_music}, - {"config_panel", ui_config_panel}, - {"note_button", ui_note_button}, - // Each ui_label_images[i] - {"pre", ui_pre}, - {"flip", ui_flip}, - {"hidden", ui_hidden}, - {"mute_button", ui_mute}, - {"defense_plus", ui_defense_plus}, - {"defense_minus", ui_defense_minus}, - {"prosecution_plus", ui_prosecution_plus}, - {"prosecution_minus", ui_prosecution_minus}, - {"text_color", ui_text_color}, - {"evidence_button", ui_evidence_button}, - {"notepad_image", ui_vp_notepad_image}, - {"notepad", ui_vp_notepad}, - // Each ui_timers[i] - {"evidence_background", ui_evidence}, - {"evidence_buttons", ui_evidence_buttons}, - {"char_select", ui_char_select_background}, - {"back_to_lobby", ui_back_to_lobby}, - {"char_buttons", ui_char_buttons}, - {"char_select_left", ui_char_select_left}, - {"char_select_right", ui_char_select_right}, - {"spectator", ui_spectator}, - }; + // Assign names to the default widgets + widget_names = { + {"courtroom", this}, + {"viewport", ui_viewport}, + {"background", ui_vp_background}, //* + {"speedlines", ui_vp_speedlines}, //* + {"player_char", ui_vp_player_char}, //* + {"desk", ui_vp_desk}, //* + {"music_display_a", ui_vp_music_display_a}, + {"music_display_b", ui_vp_music_display_b}, + {"music_area", ui_vp_music_area}, + {"music_name", ui_vp_music_name}, + // music_anim + {"clock", ui_vp_clock}, + // ui_vp_evidence_display + {"ao2_chatbox", ui_vp_chatbox}, + {"showname", ui_vp_showname}, + {"message", ui_vp_message}, + {"showname_image", ui_vp_showname_image}, + {"vp_testimony", ui_vp_testimony}, + {"vp_effect", ui_vp_effect}, + {"vp_wtce", ui_vp_wtce}, + {"vp_objection", ui_vp_objection}, + {"ic_chatlog", ui_ic_chatlog}, + {"server_chatlog", ui_server_chatlog}, + {"mute_list", ui_mute_list}, + {"area_list", ui_area_list}, + {"music_list", ui_music_list}, + {"sfx_list", ui_sfx_list}, + {"ao2_ic_chat_message", ui_ic_chat_message}, + // ui_muted + {"ooc_chat_message", ui_ooc_chat_message}, + {"ooc_chat_name", ui_ooc_chat_name}, + {"music_search", ui_music_search}, + {"sfx_search", ui_sfx_search}, + {"note_scroll_area", note_scroll_area}, + {"note_area", ui_note_area}, + // add_button + // m_layout + {"set_notes_button", ui_set_notes}, + {"emotes", ui_emotes}, + {"emote_left", ui_emote_left}, + {"emote_right", ui_emote_right}, + {"emote_dropdown", ui_emote_dropdown}, + {"pos_dropdown", ui_pos_dropdown}, + {"defense_bar", ui_defense_bar}, + {"prosecution_bar", ui_prosecution_bar}, + // Each ui_shouts[i] + {"shout_up", ui_shout_up}, + {"shout_down", ui_shout_down}, + // Each ui_effects[i] + {"effect_down", ui_effect_down}, + {"effect_up", ui_effect_up}, + // Each ui_wtce[i] + {"wtce_up", ui_wtce_up}, + {"wtce_down", ui_wtce_down}, + {"change_character", ui_change_character}, + {"call_mod", ui_call_mod}, + {"switch_area_music", ui_switch_area_music}, + {"config_panel", ui_config_panel}, + {"note_button", ui_note_button}, + // Each ui_label_images[i] + {"pre", ui_pre}, + {"flip", ui_flip}, + {"hidden", ui_hidden}, + {"mute_button", ui_mute}, + {"defense_plus", ui_defense_plus}, + {"defense_minus", ui_defense_minus}, + {"prosecution_plus", ui_prosecution_plus}, + {"prosecution_minus", ui_prosecution_minus}, + {"text_color", ui_text_color}, + {"evidence_button", ui_evidence_button}, + {"notepad_image", ui_vp_notepad_image}, + {"notepad", ui_vp_notepad}, + // Each ui_timers[i] + {"evidence_background", ui_evidence}, + {"evidence_buttons", ui_evidence_buttons}, + {"char_select", ui_char_select_background}, + {"back_to_lobby", ui_back_to_lobby}, + {"char_buttons", ui_char_buttons}, + {"char_select_left", ui_char_select_left}, + {"char_select_right", ui_char_select_right}, + {"spectator", ui_spectator}, + }; } void Courtroom::insert_widget_name(QString p_widget_name, QWidget *p_widget) { - // insert entry - widget_names[p_widget_name] = p_widget; - // set name - p_widget->setObjectName(p_widget_name); + // insert entry + widget_names[p_widget_name] = p_widget; + // set name + p_widget->setObjectName(p_widget_name); } - -void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) +void Courtroom::insert_widget_names(QVector &p_widget_names, + QVector &p_widgets) { - for (int i = 0; i < p_widgets.length(); ++i) - insert_widget_name(p_widget_names[i], p_widgets[i]); + for (int i = 0; i < p_widgets.length(); ++i) + insert_widget_name(p_widget_names[i], p_widgets[i]); } void Courtroom::set_widget_names() { - // Assign names to the default widgets - reset_widget_names(); - - // set existing widget names - for (QString widget_name : widget_names.keys()) - widget_names[widget_name]->setObjectName(widget_name); - - // setup table of widgets and names - insert_widget_names(effect_names, ui_effects); - insert_widget_names(free_block_names, ui_free_blocks); - insert_widget_names(shout_names, ui_shouts); - insert_widget_names(wtce_names, ui_wtce); - - // timers are special children - QVector timer_names; - for (int i = 0; i < ui_timers.length(); ++i) - timer_names.append("timer_" + QString::number(i)); - insert_widget_names(timer_names, ui_timers); + // Assign names to the default widgets + reset_widget_names(); + + // set existing widget names + for (QString widget_name : widget_names.keys()) + widget_names[widget_name]->setObjectName(widget_name); + + // setup table of widgets and names + insert_widget_names(effect_names, ui_effects); + insert_widget_names(free_block_names, ui_free_blocks); + insert_widget_names(shout_names, ui_shouts); + insert_widget_names(wtce_names, ui_wtce); + + // timers are special children + QVector timer_names; + for (int i = 0; i < ui_timers.length(); ++i) + timer_names.append("timer_" + QString::number(i)); + insert_widget_names(timer_names, ui_timers); } void Courtroom::set_widget_layers() { - QStringList paths{ - ao_app->get_theme_variant_path() + "courtroom_layers.ini", - ao_app->get_theme_path() + "courtroom_layers.ini", - ao_app->get_default_theme_path() + "courtroom_layers.ini", - }; + QStringList paths{ + ao_app->get_theme_variant_path() + "courtroom_layers.ini", + ao_app->get_theme_path() + "courtroom_layers.ini", + ao_app->get_default_theme_path() + "courtroom_layers.ini", + }; - // needed to avoid cyclic parenting - QStringList recorded_widgets; + // needed to avoid cyclic parenting + QStringList recorded_widgets; - // read the entire thing - for (QString path : paths) - { - QFile layer_ini(path); - - if (layer_ini.open(QFile::ReadOnly)) - { - QTextStream in(&layer_ini); - - // current parent's name - QString parent_name = "courtroom"; - // the courtroom is ALWAYS going to be recorded - recorded_widgets.append(parent_name); - - while (!in.atEnd()) - { - QString line = in.readLine().trimmed(); - - // skip if line is empty - if (line.isEmpty()) - continue; - - // revert to default parent if we encounter an end scope - if (line.startsWith("[\\")) - { - parent_name = "courtroom"; - } - // is this a parent? - else if (line.startsWith("[")) - { - // update the current parent - parent_name = line.remove(0, 1).chopped(1).trimmed(); - } - // if this is not a parent, it's a child - else - { - // if the child is already known, skip - if (recorded_widgets.contains(line)) - continue; - // make the child known - recorded_widgets.append(line); - - // attach the children to the parents' - QWidget *child = widget_names[line]; - // if child is null, then it does not exist - if (!child) - continue; - - QWidget *parent = widget_names[parent_name]; - // if parent is null, attach main parent - if (!parent) - parent = widget_names["courtroom"]; - - // set child to parent - bool was_visible = child->isVisible(); - child->setParent(parent); - child->raise(); - - // Readjust visibility in case this changed after the widget changed parent - // I don't know why, I don't want to know why, I shouldn't - // have to wonder why, but for whatever reason these stupid - // panels aren't laying out correctly unless we do this terribleness - if (child->isVisible() != was_visible) - child->setVisible(was_visible); - } - } - - // break the loop, we have found a proper file - break; + // read the entire thing + for (QString path : paths) { + QFile layer_ini(path); + + if (layer_ini.open(QFile::ReadOnly)) { + QTextStream in(&layer_ini); + + // current parent's name + QString parent_name = "courtroom"; + // the courtroom is ALWAYS going to be recorded + recorded_widgets.append(parent_name); + + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + + // skip if line is empty + if (line.isEmpty()) + continue; + + // revert to default parent if we encounter an end scope + if (line.startsWith("[\\")) { + parent_name = "courtroom"; } + // is this a parent? + else if (line.startsWith("[")) { + // update the current parent + parent_name = line.remove(0, 1).chopped(1).trimmed(); + } + // if this is not a parent, it's a child + else { + // if the child is already known, skip + if (recorded_widgets.contains(line)) + continue; + // make the child known + recorded_widgets.append(line); + + // attach the children to the parents' + QWidget *child = widget_names[line]; + // if child is null, then it does not exist + if (!child) + continue; + + QWidget *parent = widget_names[parent_name]; + // if parent is null, attach main parent + if (!parent) + parent = widget_names["courtroom"]; + + // set child to parent + bool was_visible = child->isVisible(); + child->setParent(parent); + child->raise(); + + // Readjust visibility in case this changed after the widget changed + // parent I don't know why, I don't want to know why, I shouldn't have + // to wonder why, but for whatever reason these stupid panels aren't + // laying out correctly unless we do this terribleness + if (child->isVisible() != was_visible) + child->setVisible(was_visible); + } + } + + // break the loop, we have found a proper file + break; } - // do special logic if config panel was not found in courtroom_layers. In particular, make it - // visible and raise it in front of all assets. This can help assist a theme designer who - // accidentally missed config_panel and would have become unable to reload themes had they - // closed the config panel - if (!recorded_widgets.contains("config_panel")) - { - ui_config_panel->setParent(this); - ui_config_panel->raise(); - ui_config_panel->setVisible(true); - } + } + // do special logic if config panel was not found in courtroom_layers. In + // particular, make it visible and raise it in front of all assets. This can + // help assist a theme designer who accidentally missed config_panel and would + // have become unable to reload themes had they closed the config panel + if (!recorded_widgets.contains("config_panel")) { + ui_config_panel->setParent(this); + ui_config_panel->raise(); + ui_config_panel->setVisible(true); + } } void Courtroom::set_widgets() { QString filename = design_ini; - pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", filename); + pos_size_type f_courtroom = + ao_app->get_element_dimensions("courtroom", filename); - if (f_courtroom.width < 0 || f_courtroom.height < 0) - { + if (f_courtroom.width < 0 || f_courtroom.height < 0) { qDebug() << "W: did not find courtroom width or height in " << filename; this->resize(714, 668); } - else - { + else { m_courtroom_width = f_courtroom.width; m_courtroom_height = f_courtroom.height; @@ -574,7 +607,7 @@ void Courtroom::set_widgets() ui_vp_player_char->move(0, 0); ui_vp_player_char->combo_resize(ui_viewport->size()); - //the AO2 desk element + // the AO2 desk element ui_vp_desk->move(0, 0); ui_vp_desk->combo_resize(ui_viewport->size()); @@ -622,7 +655,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_area_list, "area_list"); ui_area_list->hide(); -// ui_area_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); + // ui_area_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); set_size_and_pos(ui_music_list, "music_list"); @@ -646,7 +679,8 @@ void Courtroom::set_widgets() set_size_and_pos(ui_vp_clock, "clock"); ui_vp_clock->show(); - ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); + ui_ic_chat_message->setStyleSheet( + "QLineEdit{background-color: rgba(100, 100, 100, 255);}"); ui_vp_chatbox->set_image("chatmed.png"); ui_vp_chatbox->hide(); @@ -682,28 +716,29 @@ void Courtroom::set_widgets() set_size_and_pos(ui_pos_dropdown, "pos_dropdown"); set_size_and_pos(ui_defense_bar, "defense_bar"); - ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + ".png"); + ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + + ".png"); set_size_and_pos(ui_prosecution_bar, "prosecution_bar"); - ui_prosecution_bar->set_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); - -// set_size_and_pos(ui_shouts[0], "hold_it"); -// ui_shouts[0]->show(); -// set_size_and_pos(ui_shouts[1], "objection"); -// ui_shouts[1]->show(); -// set_size_and_pos(ui_shouts[2], "take_that"); -// ui_shouts[2]->show(); -// set_size_and_pos(ui_shouts[3], "custom_objection"); -// set_size_and_pos(ui_shouts[4], "got_it"); -// set_size_and_pos(ui_shouts[5], "cross_swords"); -// set_size_and_pos(ui_shouts[6], "counter_alt"); - for(int i = 0; i < shout_names.size(); ++i) - { + ui_prosecution_bar->set_image( + "prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); + + // set_size_and_pos(ui_shouts[0], "hold_it"); + // ui_shouts[0]->show(); + // set_size_and_pos(ui_shouts[1], "objection"); + // ui_shouts[1]->show(); + // set_size_and_pos(ui_shouts[2], "take_that"); + // ui_shouts[2]->show(); + // set_size_and_pos(ui_shouts[3], "custom_objection"); + // set_size_and_pos(ui_shouts[4], "got_it"); + // set_size_and_pos(ui_shouts[5], "cross_swords"); + // set_size_and_pos(ui_shouts[6], "counter_alt"); + for (int i = 0; i < shout_names.size(); ++i) { set_size_and_pos(ui_shouts[i], shout_names[i]); } -// ui_shouts[0]->show(); -// ui_shouts[1]->show(); -// ui_shouts[2]->show(); + // ui_shouts[0]->show(); + // ui_shouts[1]->show(); + // ui_shouts[2]->show(); reset_shout_buttons(); set_size_and_pos(ui_shout_up, "shout_up"); @@ -714,9 +749,10 @@ void Courtroom::set_widgets() ui_shout_down->hide(); // courtroom_config.ini necessary + check for crash - if (ao_app->read_theme_ini("enable_single_shout", cc_config_ini) == "true" && ui_shouts.size() > 0) - { - for(auto & shout : ui_shouts) move_widget(shout, "bullet"); + if (ao_app->read_theme_ini("enable_single_shout", cc_config_ini) == "true" && + ui_shouts.size() > 0) { + for (auto &shout : ui_shouts) + move_widget(shout, "bullet"); set_shouts(); @@ -724,12 +760,11 @@ void Courtroom::set_widgets() ui_shout_down->show(); } -// set_size_and_pos(ui_effects[0], "effect_flash"); -// set_size_and_pos(ui_effects[1], "effect_gloom"); -// set_size_and_pos(ui_effects[2], "effect_question"); -// set_size_and_pos(ui_effects[3], "effect_pow"); - for(int i = 0; i < effect_names.size(); ++i) - { + // set_size_and_pos(ui_effects[0], "effect_flash"); + // set_size_and_pos(ui_effects[1], "effect_gloom"); + // set_size_and_pos(ui_effects[2], "effect_question"); + // set_size_and_pos(ui_effects[3], "effect_pow"); + for (int i = 0; i < effect_names.size(); ++i) { set_size_and_pos(ui_effects[i], effect_names[i]); } reset_effect_buttons(); @@ -741,9 +776,11 @@ void Courtroom::set_widgets() ui_effect_down->set_image("effectdown.png"); ui_effect_down->hide(); - if (ao_app->read_theme_ini("enable_single_effect", cc_config_ini) == "true" && ui_effects.size() > 0) // check to prevent crashing + if (ao_app->read_theme_ini("enable_single_effect", cc_config_ini) == "true" && + ui_effects.size() > 0) // check to prevent crashing { - for(auto & effect : ui_effects) move_widget(effect, "effect"); + for (auto &effect : ui_effects) + move_widget(effect, "effect"); set_effects(); @@ -758,15 +795,15 @@ void Courtroom::set_widgets() ui_wtce_down->set_image("wtcedown.png"); ui_wtce_down->hide(); - for(int i = 0; i < wtce_names.size(); ++i) - { - set_size_and_pos(ui_wtce[i], wtce_names[i]); + for (int i = 0; i < wtce_names.size(); ++i) { + set_size_and_pos(ui_wtce[i], wtce_names[i]); } - if (ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true" ) // courtroom_config.ini necessary + if (ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == + "true") // courtroom_config.ini necessary { - for(auto & wtce : ui_wtce) - move_widget(wtce, "wtce"); + for (auto &wtce : ui_wtce) + move_widget(wtce, "wtce"); qDebug() << "AA: single wtce"; } set_judge_wtce(); @@ -774,13 +811,13 @@ void Courtroom::set_widgets() // this will reset the image reset_judge_wtce_buttons(); - for(int i = 0; i < free_block_names.size(); ++i) - { + for (int i = 0; i < free_block_names.size(); ++i) { set_size_and_pos(ui_free_blocks[i], free_block_names[i]); } set_free_blocks(); - // Set the default values for the buttons, then determine if they should be replaced by images + // Set the default values for the buttons, then determine if they should be + // replaced by images set_size_and_pos(ui_change_character, "change_character"); set_size_and_pos(ui_call_mod, "call_mod"); set_size_and_pos(ui_note_button, "note_button"); @@ -799,11 +836,10 @@ void Courtroom::set_widgets() ui_config_panel->setStyleSheet(""); ui_note_button->setStyleSheet(""); - if (ao_app->read_theme_ini("enable_button_images", cc_config_ini) == "true") - { + if (ao_app->read_theme_ini("enable_button_images", cc_config_ini) == "true") { // Set files, ask questions later - // set_image first tries the theme variant folder, then the theme folder, then falls back to - // the default theme + // set_image first tries the theme variant folder, then the theme folder, + // then falls back to the default theme ui_change_character->set_image("changecharacter.png"); if (ui_change_character->image_path.isEmpty()) ui_change_character->setText("Change Character"); @@ -818,25 +854,24 @@ void Courtroom::set_widgets() ui_config_panel->set_image("config_panel.png"); if (ui_config_panel->image_path.isEmpty()) - ui_config_panel->setText("Config"); + ui_config_panel->setText("Config"); ui_note_button->set_image("notebutton.png"); if (ui_note_button->image_path.isEmpty()) - ui_note_button->setText("Notes"); + ui_note_button->setText("Notes"); } - // The config panel has a special property. If it is displayed beyond the right or lower limit - // of the window, it will be moved to 0, 0 - // A similar behavior will occur if the button is hidden due to 'config_panel' not being found - // in courtroom_design.ini - // This is to assist with people who switch to incompatible and/or smaller themes and have the - // button disappear + // The config panel has a special property. If it is displayed beyond the + // right or lower limit of the window, it will be moved to 0, 0 A similar + // behavior will occur if the button is hidden due to 'config_panel' not being + // found in courtroom_design.ini This is to assist with people who switch to + // incompatible and/or smaller themes and have the button disappear if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || - !ui_config_panel->isVisible()) - { + !ui_config_panel->isVisible()) { ui_config_panel->setVisible(true); ui_config_panel->move(0, 0); - // Moreover, if the width or height is invalid, change it to some fixed values + // Moreover, if the width or height is invalid, change it to some fixed + // values if (ui_config_panel->width() <= 0 || ui_config_panel->height() <= 0) ui_config_panel->resize(64, 64); } @@ -848,14 +883,12 @@ void Courtroom::set_widgets() set_size_and_pos(ui_hidden, "hidden"); - for(int i = 0; i < ui_label_images.size(); ++i) - { + for (int i = 0; i < ui_label_images.size(); ++i) { set_size_and_pos(ui_label_images[i], label_images[i].toLower() + "_image"); } - if (ao_app->read_theme_ini("enable_label_images", cc_config_ini) == "true") - { - for (int i = 0 ; i < ui_checks.size(); ++i) // loop through checks + if (ao_app->read_theme_ini("enable_label_images", cc_config_ini) == "true") { + for (int i = 0; i < ui_checks.size(); ++i) // loop through checks { QString image = label_images[i].toLower() + ".png"; ui_label_images[i]->set_image(image); @@ -866,7 +899,7 @@ void Courtroom::set_widgets() ui_checks[i]->setText(label_images[i]); } - for(int i = 0 ; i < ui_labels.size(); ++i) // now through labels.......... + for (int i = 0; i < ui_labels.size(); ++i) // now through labels.......... { int j = i + ui_checks.size(); QString image = label_images[j].toLower() + ".png"; @@ -878,15 +911,14 @@ void Courtroom::set_widgets() ui_labels[i]->setText(label_images[j]); } } - else - { - for(int i = 0; i < ui_checks.size(); ++i) //same thing + else { + for (int i = 0; i < ui_checks.size(); ++i) // same thing { ui_checks[i]->setText(label_images[i]); ui_label_images[i]->set_image(""); } - for(int i = 0; i < ui_labels.size(); ++i) //same thing + for (int i = 0; i < ui_labels.size(); ++i) // same thing { int j = i + ui_checks.size(); ui_labels[i]->setText(label_images[j]); @@ -971,15 +1003,15 @@ void Courtroom::set_widgets() note_scroll_area->setWidget(ui_note_area); ui_note_area->set_image("note_area.png"); ui_note_area->add_button->set_image("add_button.png"); - ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, + QSizePolicy::Fixed); ui_note_area->setLayout(ui_note_area->m_layout); ui_note_area->show(); note_scroll_area->hide(); list_note_files(); - if(!contains_add_button) - { + if (!contains_add_button) { ui_note_area->m_layout->addWidget(ui_note_area->add_button); contains_add_button = true; } @@ -993,16 +1025,14 @@ void Courtroom::set_size_and_pos(QWidget *p_widget, QString p_identifier) { QString filename = design_ini; + pos_size_type design_ini_result = + ao_app->get_element_dimensions(p_identifier, filename); - pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - - if (design_ini_result.width < 0 || design_ini_result.height < 0) - { + if (design_ini_result.width < 0 || design_ini_result.height < 0) { qDebug() << "W: could not find \"" << p_identifier << "\" in " << filename; p_widget->hide(); } - else - { + else { p_widget->move(design_ini_result.x, design_ini_result.y); p_widget->resize(design_ini_result.width, design_ini_result.height); } @@ -1012,63 +1042,61 @@ void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) { QString filename = design_ini; - pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); + pos_size_type design_ini_result = + ao_app->get_element_dimensions(p_identifier, filename); - if (design_ini_result.width < 0 || design_ini_result.height < 0) - { + if (design_ini_result.width < 0 || design_ini_result.height < 0) { qDebug() << "W: could not find \"" << p_identifier << "\" in " << filename; p_widget->hide(); } - else - { + else { p_widget->move(design_ini_result.x, design_ini_result.y); } } -template -int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_item_number, +template +int Courtroom::adapt_numbered_items(QVector &item_vector, + QString config_item_number, QString item_name) { // &item_vector must be a vector of size at least 1! // Redraw the new correct number of items. - int new_item_number = ao_app->read_theme_ini(config_item_number, cc_config_ini).toInt(); + int new_item_number = + ao_app->read_theme_ini(config_item_number, cc_config_ini).toInt(); int current_item_number = item_vector.size(); - // Note we use the fact that, if config_item_number is not there, read_theme_ini returns an - // empty string, which .toInt() would fail to convert and by documentation transform to 0. + // Note we use the fact that, if config_item_number is not there, + // read_theme_ini returns an empty string, which .toInt() would fail to + // convert and by documentation transform to 0. - // Decide what to do if the new theme has a different amount of items than the old one - if (new_item_number < current_item_number) - { + // Decide what to do if the new theme has a different amount of items than the + // old one + if (new_item_number < current_item_number) { // Hide old items if there are any. - for (int i=new_item_number; ihide(); } } - else if (current_item_number < new_item_number) - { + else if (current_item_number < new_item_number) { // Create new items item_vector.resize(new_item_number); - for (int i=current_item_number; istackUnder(item_vector[i-1]); + item_vector[i]->stackUnder(item_vector[i - 1]); // index i-1 exists as i >= current_item_number == item_vector.size() >= 1 } } - // Note that by design we do *not* destroy items (or similarly, the size of item_vector - // is non-decreasing. This is because we want to allow for items to, say, run in the - // background as invisible. - // With that said, we can now properly format our new items - for (int i=0; ishow(); - set_size_and_pos(item_vector[i], item_name+"_"+QString::number(i)); + set_size_and_pos(item_vector[i], item_name + "_" + QString::number(i)); // Note that show is deliberately placed before set_size_and_pos - // That is because we want to retain the functionality set_size_and_pos includes where - // hides a widget if it is unable to find a position for it, and does not change its - // visibility otherwise. + // That is because we want to retain the functionality set_size_and_pos + // includes where hides a widget if it is unable to find a position for it, + // and does not change its visibility otherwise. } return new_item_number; } @@ -1078,25 +1106,20 @@ void Courtroom::check_effects() QString char_path = ao_app->get_character_path(current_char); QString theme_variant_path = ao_app->get_theme_variant_path(); QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < ui_effects.size(); ++i) - { - QStringList paths{ - char_path + effect_names.at(i) + ".webp", - char_path + effect_names.at(i) + ".gif", - theme_variant_path + effect_names.at(i) + ".webp", - theme_variant_path + effect_names.at(i) + ".gif", - theme_variant_path + effect_names.at(i) + ".apng", - theme_path + effect_names.at(i) + ".webp", - theme_path + effect_names.at(i) + ".gif", - theme_path + effect_names.at(i) + ".apng" - }; + for (int i = 0; i < ui_effects.size(); ++i) { + QStringList paths{char_path + effect_names.at(i) + ".webp", + char_path + effect_names.at(i) + ".gif", + theme_variant_path + effect_names.at(i) + ".webp", + theme_variant_path + effect_names.at(i) + ".gif", + theme_variant_path + effect_names.at(i) + ".apng", + theme_path + effect_names.at(i) + ".webp", + theme_path + effect_names.at(i) + ".gif", + theme_path + effect_names.at(i) + ".apng"}; // Assume the effect does not exist until a matching file is found effects_enabled[i] = false; - for (QString path : paths) - { - if (file_exists(path)) - { + for (QString path : paths) { + if (file_exists(path)) { effects_enabled[i] = true; break; } @@ -1109,25 +1132,20 @@ void Courtroom::check_free_blocks() QString char_path = ao_app->get_character_path(current_char); QString theme_variant_path = ao_app->get_theme_variant_path(); QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < ui_free_blocks.size(); ++i) - { - QStringList paths{ - char_path + free_block_names.at(i) + ".webp", - char_path + free_block_names.at(i) + ".gif", - theme_variant_path + free_block_names.at(i) + ".webp", - theme_variant_path + free_block_names.at(i) + ".gif", - theme_variant_path + free_block_names.at(i) + ".apng", - theme_path + free_block_names.at(i) + ".webp", - theme_path + free_block_names.at(i) + ".gif", - theme_path + free_block_names.at(i) + ".apng" - }; + for (int i = 0; i < ui_free_blocks.size(); ++i) { + QStringList paths{char_path + free_block_names.at(i) + ".webp", + char_path + free_block_names.at(i) + ".gif", + theme_variant_path + free_block_names.at(i) + ".webp", + theme_variant_path + free_block_names.at(i) + ".gif", + theme_variant_path + free_block_names.at(i) + ".apng", + theme_path + free_block_names.at(i) + ".webp", + theme_path + free_block_names.at(i) + ".gif", + theme_path + free_block_names.at(i) + ".apng"}; // Assume the free block does not exist until a matching file is found free_blocks_enabled[i] = false; - for (QString path : paths) - { - if (file_exists(path)) - { + for (QString path : paths) { + if (file_exists(path)) { free_blocks_enabled[i] = true; break; } @@ -1140,25 +1158,20 @@ void Courtroom::check_shouts() QString char_path = ao_app->get_character_path(current_char); QString theme_variant_path = ao_app->get_theme_variant_path(); QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < ui_shouts.size(); ++i) - { - QStringList paths{ - char_path + shout_names.at(i) + ".webp", - char_path + shout_names.at(i) + ".gif", - theme_variant_path + shout_names.at(i) + ".webp", - theme_variant_path + shout_names.at(i) + ".gif", - theme_variant_path + shout_names.at(i) + ".apng", - theme_path + shout_names.at(i) + ".webp", - theme_path + shout_names.at(i) + ".gif", - theme_path + shout_names.at(i) + ".apng" - }; + for (int i = 0; i < ui_shouts.size(); ++i) { + QStringList paths{char_path + shout_names.at(i) + ".webp", + char_path + shout_names.at(i) + ".gif", + theme_variant_path + shout_names.at(i) + ".webp", + theme_variant_path + shout_names.at(i) + ".gif", + theme_variant_path + shout_names.at(i) + ".apng", + theme_path + shout_names.at(i) + ".webp", + theme_path + shout_names.at(i) + ".gif", + theme_path + shout_names.at(i) + ".apng"}; // Assume the shout does not exist until a matching file is found shouts_enabled[i] = false; - for (QString path : paths) - { - if (file_exists(path)) - { + for (QString path : paths) { + if (file_exists(path)) { shouts_enabled[i] = true; break; } @@ -1171,25 +1184,20 @@ void Courtroom::check_wtce() QString char_path = ao_app->get_character_path(current_char); QString theme_variant_path = ao_app->get_theme_variant_path(); QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < ui_wtce.size(); ++i) - { - QStringList paths{ - char_path + wtce_names.at(i) + ".webp", - char_path + wtce_names.at(i) + ".gif", - theme_variant_path + wtce_names.at(i) + ".webp", - theme_variant_path + wtce_names.at(i) + ".gif", - theme_variant_path + wtce_names.at(i) + ".apng", - theme_path + wtce_names.at(i) + ".webp", - theme_path + wtce_names.at(i) + ".gif", - theme_path + wtce_names.at(i) + ".apng" - }; + for (int i = 0; i < ui_wtce.size(); ++i) { + QStringList paths{char_path + wtce_names.at(i) + ".webp", + char_path + wtce_names.at(i) + ".gif", + theme_variant_path + wtce_names.at(i) + ".webp", + theme_variant_path + wtce_names.at(i) + ".gif", + theme_variant_path + wtce_names.at(i) + ".apng", + theme_path + wtce_names.at(i) + ".webp", + theme_path + wtce_names.at(i) + ".gif", + theme_path + wtce_names.at(i) + ".apng"}; // Assume the judge button does not exist until a matching file is found wtce_enabled[i] = false; - for (QString path : paths) - { - if (file_exists(path)) - { + for (QString path : paths) { + if (file_exists(path)) { wtce_enabled[i] = true; break; } @@ -1199,54 +1207,54 @@ void Courtroom::check_wtce() void Courtroom::delete_widget(QWidget *p_widget) { - // remove the widget from recorded names - widget_names.remove(p_widget->objectName()); - - // transfer the children to our grandparent since our parent is - // about to commit suicide - QWidget *grand_parent = p_widget->parentWidget(); - // if we don't have a grand parent, attach ourselves to courtroom - if (!grand_parent) - grand_parent = this; - - // set new parent - for (QWidget *child : p_widget->findChildren(QString(), Qt::FindDirectChildrenOnly)) - child->setParent(grand_parent); - - // delete widget - delete p_widget; + // remove the widget from recorded names + widget_names.remove(p_widget->objectName()); + + // transfer the children to our grandparent since our parent is + // about to commit suicide + QWidget *grand_parent = p_widget->parentWidget(); + // if we don't have a grand parent, attach ourselves to courtroom + if (!grand_parent) + grand_parent = this; + + // set new parent + for (QWidget *child : + p_widget->findChildren(QString(), Qt::FindDirectChildrenOnly)) + child->setParent(grand_parent); + + // delete widget + delete p_widget; } void Courtroom::load_effects() { // Close any existing effects to prevent memory leaks for (QWidget *widget : ui_effects) - delete_widget(widget); + delete_widget(widget); // And create new effects - int effect_number = ao_app->get_design_ini_value("effect_number", cc_config_ini); + int effect_number = + ao_app->get_design_ini_value("effect_number", cc_config_ini); effects_enabled.resize(effect_number); ui_effects.resize(effect_number); - for (int i=0; isetProperty("effect_id", i+1); + ui_effects[i]->setProperty("effect_id", i + 1); ui_effects[i]->stackUnder(ui_effect_up); ui_effects[i]->stackUnder(ui_effect_down); } // And connect their actions - for (auto & effect : ui_effects) - connect(effect, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked())); + for (auto &effect : ui_effects) + connect(effect, SIGNAL(clicked(bool)), this, + SLOT(on_effect_button_clicked())); // And add names effect_names.clear(); - for (int i=1; i<=ui_effects.size(); ++i) - { + for (int i = 1; i <= ui_effects.size(); ++i) { QStringList names = ao_app->get_effect(i); - if (!names.isEmpty()) - { + if (!names.isEmpty()) { QString name = names.at(0).trimmed(); effect_names.append(name); } @@ -1255,32 +1263,31 @@ void Courtroom::load_effects() void Courtroom::load_free_blocks() { - for (QWidget *widget : ui_free_blocks) - delete_widget(widget); + for (QWidget *widget : ui_free_blocks) + delete_widget(widget); // And create new free block buttons - int free_block_number = ao_app->get_design_ini_value("free_block_number", cc_config_ini); + int free_block_number = + ao_app->get_design_ini_value("free_block_number", cc_config_ini); free_blocks_enabled.resize(free_block_number); ui_free_blocks.resize(free_block_number); - for (int i=0; isetProperty("free_block_id", i+1); + // ui_free_blocks[i]->setProperty("free_block_id", i+1); ui_free_blocks[i]->set_play_once(false); ui_free_blocks[i]->stackUnder(ui_vp_player_char); } // And add names free_block_names.clear(); - for (int i=1; i<=ui_free_blocks.size(); ++i) - { - QString name = "free_block_" + ao_app->get_spbutton("[FREE BLOCKS]", i).trimmed(); - if (!name.isEmpty()) - { + for (int i = 1; i <= ui_free_blocks.size(); ++i) { + QString name = + "free_block_" + ao_app->get_spbutton("[FREE BLOCKS]", i).trimmed(); + if (!name.isEmpty()) { free_block_names.append(name); - widget_names[name] = ui_free_blocks[i-1]; - ui_free_blocks[i-1]->setObjectName(name); + widget_names[name] = ui_free_blocks[i - 1]; + ui_free_blocks[i - 1]->setObjectName(name); } } qDebug() << "FREE BLOCKS HERE " << free_block_names; @@ -1288,37 +1295,35 @@ void Courtroom::load_free_blocks() void Courtroom::load_shouts() { - for (QWidget *widget : ui_shouts) - delete_widget(widget); + for (QWidget *widget : ui_shouts) + delete_widget(widget); // And create new shouts - int shout_number = ao_app->get_design_ini_value("shout_number", cc_config_ini); + int shout_number = + ao_app->get_design_ini_value("shout_number", cc_config_ini); shouts_enabled.resize(shout_number); ui_shouts.resize(shout_number); - for (int i=0; isetProperty("shout_id", i+1); + ui_shouts[i]->setProperty("shout_id", i + 1); ui_shouts[i]->stackUnder(ui_shout_up); ui_shouts[i]->stackUnder(ui_shout_down); } // And connect their actions - for (auto & shout : ui_shouts) + for (auto &shout : ui_shouts) connect(shout, SIGNAL(clicked(bool)), this, SLOT(on_shout_clicked())); // And add names shout_names.clear(); - for (int i=1; i<=ui_shouts.size(); ++i) - { + for (int i = 1; i <= ui_shouts.size(); ++i) { QString name = ao_app->get_spbutton("[SHOUTS]", i).trimmed(); - if (!name.isEmpty()) - { - qDebug() << "SHOUT " << name << " " << ui_shouts[i-1]; + if (!name.isEmpty()) { + qDebug() << "SHOUT " << name << " " << ui_shouts[i - 1]; shout_names.append(name); - widget_names[name] = ui_shouts[i-1]; - ui_shouts[i-1]->setObjectName(name); + widget_names[name] = ui_shouts[i - 1]; + ui_shouts[i - 1]->setObjectName(name); } } qDebug() << widget_names; @@ -1326,104 +1331,100 @@ void Courtroom::load_shouts() void Courtroom::load_wtce() { - for (QWidget *widget : ui_wtce) - delete_widget(widget); + for (QWidget *widget : ui_wtce) + delete_widget(widget); // And create new wtce buttons int wtce_number = ao_app->get_design_ini_value("wtce_number", cc_config_ini); wtce_enabled.resize(wtce_number); ui_wtce.resize(wtce_number); - for (int i=0; isetProperty("wtce_id", i+1); + ui_wtce[i]->setProperty("wtce_id", i + 1); ui_wtce[i]->stackUnder(ui_wtce_up); ui_wtce[i]->stackUnder(ui_wtce_down); } // And connect their actions - for (auto & wtce : ui_wtce) + for (auto &wtce : ui_wtce) connect(wtce, SIGNAL(clicked(bool)), this, SLOT(on_wtce_clicked())); // And add names wtce_names.clear(); - for (int i=1; i<=ui_wtce.size(); ++i) - { + for (int i = 1; i <= ui_wtce.size(); ++i) { QString name = ao_app->get_spbutton("[WTCE]", i).trimmed(); - if (!name.isEmpty()) - { + if (!name.isEmpty()) { wtce_names.append(name); - widget_names[name] = ui_wtce[i-1]; - ui_wtce[i-1]->setObjectName(name); + widget_names[name] = ui_wtce[i - 1]; + ui_wtce[i - 1]->setObjectName(name); } } } void Courtroom::set_shouts() { - for(auto & shout : ui_shouts) shout->hide(); - if (ui_shouts.size() > 0) ui_shouts[m_shout_state]->show(); // check to prevent crashing + for (auto &shout : ui_shouts) + shout->hide(); + if (ui_shouts.size() > 0) + ui_shouts[m_shout_state]->show(); // check to prevent crashing } void Courtroom::set_effects() { - for(auto & effect : ui_effects) - effect->hide(); + for (auto &effect : ui_effects) + effect->hide(); // check to prevent crashing if (ui_effects.size() > 0) - ui_effects[m_effect_current]->show(); + ui_effects[m_effect_current]->show(); } void Courtroom::set_judge_enabled(bool p_enabled) { - is_judge = p_enabled; + is_judge = p_enabled; - // set judge button visibility - ui_defense_plus->setVisible(is_judge); - ui_defense_minus->setVisible(is_judge); - ui_prosecution_plus->setVisible(is_judge); - ui_prosecution_minus->setVisible(is_judge); + // set judge button visibility + ui_defense_plus->setVisible(is_judge); + ui_defense_minus->setVisible(is_judge); + ui_prosecution_plus->setVisible(is_judge); + ui_prosecution_minus->setVisible(is_judge); - set_judge_wtce(); + set_judge_wtce(); } void Courtroom::set_judge_wtce() { - // hide all wtce before enabling visibility - for(auto & wtce : ui_wtce) - wtce->hide(); - - // check if we use a single wtce or multiple - const bool is_single_wtce = ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true"; + // hide all wtce before enabling visibility + for (auto &wtce : ui_wtce) + wtce->hide(); - // update visibility for next/previous - ui_wtce_up->setVisible(is_judge && is_single_wtce); - ui_wtce_down->setVisible(is_judge && is_single_wtce); + // check if we use a single wtce or multiple + const bool is_single_wtce = + ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true"; - // prevent going ahead if we have no wtce - if (!is_judge || ui_wtce.length() == 0) - return; + // update visibility for next/previous + ui_wtce_up->setVisible(is_judge && is_single_wtce); + ui_wtce_down->setVisible(is_judge && is_single_wtce); + // prevent going ahead if we have no wtce + if (!is_judge || ui_wtce.length() == 0) + return; - // set visibility based off parameter - if (is_single_wtce == true) - { - ui_wtce[m_wtce_current]->show(); - } - else - { - for (AOButton *i_wtce : ui_wtce) - i_wtce->show(); - } + // set visibility based off parameter + if (is_single_wtce == true) { + ui_wtce[m_wtce_current]->show(); + } + else { + for (AOButton *i_wtce : ui_wtce) + i_wtce->show(); + } } void Courtroom::set_free_blocks() { - for (int i=0; iplay(free_block_names[i]); } } @@ -1446,44 +1447,49 @@ void Courtroom::set_dropdowns() set_dropdown(ui_ooc_chat_message, "[OOC LINE]"); } - void Courtroom::set_font(QWidget *widget, QString p_identifier) { - set_font(widget, p_identifier, ""); + set_font(widget, p_identifier, ""); } -void Courtroom::set_font(QWidget *widget, QString p_identifier, QString override_color) +void Courtroom::set_font(QWidget *widget, QString p_identifier, + QString override_color) { - QString design_file = fonts_ini; - QString class_name = widget->metaObject()->className(); - - int f_weight = ao_app->get_font_property(p_identifier, design_file); - QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); - widget->setFont(QFont(font_name, f_weight)); - - if (override_color.isEmpty()) - { - QString color = ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); - if (color.isEmpty()) - color = "255, 255, 255"; - override_color = "rgba(" + color + ", 255)"; - } + QString design_file = fonts_ini; + QString class_name = widget->metaObject()->className(); + + int f_weight = ao_app->get_font_property(p_identifier, design_file); + QString font_name = + ao_app->get_font_name("font_" + p_identifier, design_file); + widget->setFont(QFont(font_name, f_weight)); + + if (override_color.isEmpty()) { + QString color = + ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); + if (color.isEmpty()) + color = "255, 255, 255"; + override_color = "rgba(" + color + ", 255)"; + } - int bold = ao_app->get_font_property(p_identifier + "_bold", design_file); - QString is_bold = (bold == 1 ? "bold" : ""); + int bold = ao_app->get_font_property(p_identifier + "_bold", design_file); + QString is_bold = (bold == 1 ? "bold" : ""); - QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + - "color: " + override_color + ";\n" - "font: " + is_bold + "; }"; - widget->setStyleSheet(style_sheet_string); + QString style_sheet_string = class_name + + " { background-color: rgba(0, 0, 0, 0);\n" + + "color: " + override_color + + ";\n" + "font: " + + is_bold + "; }"; + widget->setStyleSheet(style_sheet_string); } void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier) { - set_qtextedit_font(widget, p_identifier, ""); + set_qtextedit_font(widget, p_identifier, ""); } -void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier, QString override_color) +void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier, + QString override_color) { set_font(widget, p_identifier, override_color); @@ -1501,26 +1507,24 @@ void Courtroom::set_fonts() set_qtextedit_font(ui_vp_showname, "showname"); set_qtextedit_font(ui_vp_message, "message"); set_qtextedit_font(ui_ic_chatlog, "ic_chatlog"); - set_font(ui_server_chatlog, "server_chatlog"); // Chatlog does not support qtextedit because html + set_font(ui_server_chatlog, + "server_chatlog"); // Chatlog does not support qtextedit because html set_font(ui_music_list, "music_list"); set_font(ui_area_list, "area_list"); set_font(ui_sfx_list, "sfx_list"); set_qtextedit_font(ui_vp_music_name, "music_name"); set_qtextedit_font(ui_vp_notepad, "notepad"); - for (int i=0; iaddItem(i_name); } } diff --git a/datatypes.h b/datatypes.h index 5406a643f..41212b1cb 100644 --- a/datatypes.h +++ b/datatypes.h @@ -1,11 +1,10 @@ #ifndef DATATYPES_H #define DATATYPES_H -#include #include +#include -struct record_type -{ +struct record_type { QString name; QString line; QString color; @@ -25,109 +24,99 @@ typedef std::shared_ptr record_type_ptr; typedef QVector record_type_array; -struct server_type -{ - QString name; - QString desc; - QString ip; - int port; +struct server_type { + QString name; + QString desc; + QString ip; + int port; }; -struct emote_type -{ - QString comment; - QString preanim; - QString anim; - int mod; - QString sfx_name; - int sfx_delay; - int sfx_duration; +struct emote_type { + QString comment; + QString preanim; + QString anim; + int mod; + QString sfx_name; + int sfx_delay; + int sfx_duration; }; -struct char_type -{ - QString name; - QString description; - QString evidence_string; - bool taken; +struct char_type { + QString name; + QString description; + QString evidence_string; + bool taken; }; -struct evi_type -{ - QString name; - QString description; - QString image; +struct evi_type { + QString name; + QString description; + QString image; }; -struct chatmessage_type -{ - QString message; - QString character; - QString side; - QString sfx_name; - QString pre_emote; - QString emote; - int emote_modifier; - int objection_modifier; - int realization; - int text_color; - int evidence; - int cid; - int sfx_delay; - int flip; +struct chatmessage_type { + QString message; + QString character; + QString side; + QString sfx_name; + QString pre_emote; + QString emote; + int emote_modifier; + int objection_modifier; + int realization; + int text_color; + int evidence; + int cid; + int sfx_delay; + int flip; }; -struct area_type -{ - QString name; - QString background; +struct area_type { + QString name; + QString background; }; -struct pos_type -{ - int x; - int y; +struct pos_type { + int x; + int y; }; -struct pos_size_type -{ - int x = 0; - int y = 0; - int width = 0; - int height = 0; +struct pos_size_type { + int x = 0; + int y = 0; + int width = 0; + int height = 0; }; -enum CHAT_MESSAGE -{ - DESK_MOD = 0, - PRE_EMOTE, - CHAR_NAME, - EMOTE, - MESSAGE, - SIDE, - SFX_NAME, - EMOTE_MOD, - CHAR_ID, - SFX_DELAY, - OBJECTION_MOD, - EVIDENCE_ID, - FLIP, - EFFECT_STATE, - TEXT_COLOR, - SHOWNAME +enum CHAT_MESSAGE { + DESK_MOD = 0, + PRE_EMOTE, + CHAR_NAME, + EMOTE, + MESSAGE, + SIDE, + SFX_NAME, + EMOTE_MOD, + CHAR_ID, + SFX_DELAY, + OBJECTION_MOD, + EVIDENCE_ID, + FLIP, + EFFECT_STATE, + TEXT_COLOR, + SHOWNAME }; -enum COLOR -{ - WHITE = 0, - GREEN, - RED, - ORANGE, - BLUE, - YELLOW, - PURPLE, - PINK, - RAINBOW +enum COLOR { + WHITE = 0, + GREEN, + RED, + ORANGE, + BLUE, + YELLOW, + PURPLE, + PINK, + RAINBOW }; #endif // DATATYPES_H diff --git a/debug_functions.cpp b/debug_functions.cpp index f1dc345c4..0da26fc3f 100644 --- a/debug_functions.cpp +++ b/debug_functions.cpp @@ -1,7 +1,7 @@ #include "debug_functions.h" -#include #include +#include void call_error(QString p_message) { @@ -11,7 +11,7 @@ void call_error(QString p_message) f_box->setWindowTitle("Error"); qDebug() << f_box->text(); - //msgBox->setWindowModality(Qt::NonModal); + // msgBox->setWindowModality(Qt::NonModal); f_box->exec(); delete f_box; } @@ -24,7 +24,7 @@ void call_notice(QString p_message) f_box->setWindowTitle("Notice"); qDebug() << f_box->text(); - //msgBox->setWindowModality(Qt::NonModal); + // msgBox->setWindowModality(Qt::NonModal); f_box->exec(); delete f_box; } diff --git a/discord-rpc.h b/discord-rpc.h index feb874b20..455f62aa4 100644 --- a/discord-rpc.h +++ b/discord-rpc.h @@ -24,61 +24,63 @@ extern "C" { #endif typedef struct DiscordRichPresence { - const char* state; /* max 128 bytes */ - const char* details; /* max 128 bytes */ - int64_t startTimestamp; - int64_t endTimestamp; - const char* largeImageKey; /* max 32 bytes */ - const char* largeImageText; /* max 128 bytes */ - const char* smallImageKey; /* max 32 bytes */ - const char* smallImageText; /* max 128 bytes */ - const char* partyId; /* max 128 bytes */ - int partySize; - int partyMax; - const char* matchSecret; /* max 128 bytes */ - const char* joinSecret; /* max 128 bytes */ - const char* spectateSecret; /* max 128 bytes */ - int8_t instance; + const char *state; /* max 128 bytes */ + const char *details; /* max 128 bytes */ + int64_t startTimestamp; + int64_t endTimestamp; + const char *largeImageKey; /* max 32 bytes */ + const char *largeImageText; /* max 128 bytes */ + const char *smallImageKey; /* max 32 bytes */ + const char *smallImageText; /* max 128 bytes */ + const char *partyId; /* max 128 bytes */ + int partySize; + int partyMax; + const char *matchSecret; /* max 128 bytes */ + const char *joinSecret; /* max 128 bytes */ + const char *spectateSecret; /* max 128 bytes */ + int8_t instance; } DiscordRichPresence; typedef struct DiscordJoinRequest { - const char* userId; - const char* username; - const char* discriminator; - const char* avatar; + const char *userId; + const char *username; + const char *discriminator; + const char *avatar; } DiscordJoinRequest; typedef struct DiscordEventHandlers { - void (*ready)(void); - void (*disconnected)(int errorCode, const char* message); - void (*errored)(int errorCode, const char* message); - void (*joinGame)(const char* joinSecret); - void (*spectateGame)(const char* spectateSecret); - void (*joinRequest)(const DiscordJoinRequest* request); + void (*ready)(void); + void (*disconnected)(int errorCode, const char *message); + void (*errored)(int errorCode, const char *message); + void (*joinGame)(const char *joinSecret); + void (*spectateGame)(const char *spectateSecret); + void (*joinRequest)(const DiscordJoinRequest *request); } DiscordEventHandlers; #define DISCORD_REPLY_NO 0 #define DISCORD_REPLY_YES 1 #define DISCORD_REPLY_IGNORE 2 -DISCORD_EXPORT void Discord_Initialize(const char* applicationId, - DiscordEventHandlers* handlers, +DISCORD_EXPORT void Discord_Initialize(const char *applicationId, + DiscordEventHandlers *handlers, int autoRegister, - const char* optionalSteamId); + const char *optionalSteamId); DISCORD_EXPORT void Discord_Shutdown(void); /* checks for incoming messages, dispatches callbacks */ DISCORD_EXPORT void Discord_RunCallbacks(void); -/* If you disable the lib starting its own io thread, you'll need to call this from your own */ +/* If you disable the lib starting its own io thread, you'll need to call this + * from your own */ #ifdef DISCORD_DISABLE_IO_THREAD DISCORD_EXPORT void Discord_UpdateConnection(void); #endif -DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); +DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence *presence); DISCORD_EXPORT void Discord_ClearPresence(void); -DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); +DISCORD_EXPORT void Discord_Respond(const char *userid, + /* DISCORD_REPLY_ */ int reply); #ifdef __cplusplus } /* extern "C" */ diff --git a/discord_register.h b/discord_register.h index 2058cc4c6..3dc89071b 100644 --- a/discord_register.h +++ b/discord_register.h @@ -1,25 +1,27 @@ #pragma once #if defined(DISCORD_DYNAMIC_LIB) -# if defined(_WIN32) -# if defined(DISCORD_BUILDING_SDK) -# define DISCORD_EXPORT __declspec(dllexport) -# else -# define DISCORD_EXPORT __declspec(dllimport) -# endif -# else -# define DISCORD_EXPORT __attribute__((visibility("default"))) -# endif +#if defined(_WIN32) +#if defined(DISCORD_BUILDING_SDK) +#define DISCORD_EXPORT __declspec(dllexport) #else -# define DISCORD_EXPORT +#define DISCORD_EXPORT __declspec(dllimport) +#endif +#else +#define DISCORD_EXPORT __attribute__((visibility("default"))) +#endif +#else +#define DISCORD_EXPORT #endif #ifdef __cplusplus extern "C" { #endif -DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); -DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); +DISCORD_EXPORT void Discord_Register(const char *applicationId, + const char *command); +DISCORD_EXPORT void Discord_RegisterSteamGame(const char *applicationId, + const char *steamId); #ifdef __cplusplus } diff --git a/discord_rich_presence.cpp b/discord_rich_presence.cpp index ab9772628..a7466ba26 100644 --- a/discord_rich_presence.cpp +++ b/discord_rich_presence.cpp @@ -9,20 +9,20 @@ namespace AttorneyOnline { Discord::Discord() { -// DiscordEventHandlers handlers; -// std::memset(&handlers, 0, sizeof(handlers)); -// handlers = {}; -// handlers.ready = [] { -// qInfo() << "Discord RPC ready"; -// }; -// handlers.disconnected = [](int errorCode, const char* message) { -// qInfo() << "Discord RPC disconnected! " << message; -// }; -// handlers.errored = [](int errorCode, const char* message) { -// qWarning() << "Discord RPC errored out! " << message; -// }; -// qInfo() << "Initializing Discord RPC"; -// Discord_Initialize(APPLICATION_ID1, &handlers, 1, nullptr); + // DiscordEventHandlers handlers; + // std::memset(&handlers, 0, sizeof(handlers)); + // handlers = {}; + // handlers.ready = [] { + // qInfo() << "Discord RPC ready"; + // }; + // handlers.disconnected = [](int errorCode, const char* message) { + // qInfo() << "Discord RPC disconnected! " << message; + // }; + // handlers.errored = [](int errorCode, const char* message) { + // qWarning() << "Discord RPC errored out! " << message; + // }; + // qInfo() << "Initializing Discord RPC"; + // Discord_Initialize(APPLICATION_ID1, &handlers, 1, nullptr); start(APPLICATION_ID[0]); } @@ -32,23 +32,18 @@ void Discord::start(const char *APPLICATION_ID) DiscordEventHandlers handlers; std::memset(&handlers, 0, sizeof(handlers)); handlers = {}; - handlers.ready = [] { - qInfo() << "Discord RPC ready"; - }; - handlers.disconnected = [](int errorCode, const char* message) { + handlers.ready = [] { qInfo() << "Discord RPC ready"; }; + handlers.disconnected = [](int errorCode, const char *message) { qInfo() << "Discord RPC disconnected! " << message; }; - handlers.errored = [](int errorCode, const char* message) { + handlers.errored = [](int errorCode, const char *message) { qWarning() << "Discord RPC errored out! " << message; }; qInfo() << "Initializing Discord RPC"; Discord_Initialize(APPLICATION_ID, &handlers, 1, nullptr); } -Discord::~Discord() -{ - Discord_Shutdown(); -} +Discord::~Discord() { Discord_Shutdown(); } void Discord::restart(const char *APPLICATION_ID) { @@ -58,11 +53,14 @@ void Discord::restart(const char *APPLICATION_ID) void Discord::toggle(int p_index) { - if(p_index >=0 && p_index < 2) - { - if(p_index!=m_index) { restart(APPLICATION_ID[p_index]); m_index = p_index; } + if (p_index >= 0 && p_index < 2) { + if (p_index != m_index) { + restart(APPLICATION_ID[p_index]); + m_index = p_index; } - else qDebug() << p_index << "is not a valid APPLICATION_ID Index"; + } + else + qDebug() << p_index << "is not a valid APPLICATION_ID Index"; } void Discord::state_lobby() @@ -103,10 +101,12 @@ void Discord::state_server(std::string name, std::string server_id) void Discord::state_character(std::string name) { - auto name_internal = QString(name.c_str()).toLower().replace(' ', '_').toStdString(); + auto name_internal = + QString(name.c_str()).toLower().replace(' ', '_').toStdString(); auto name_friendly = QString(name.c_str()).replace('_', ' ').toStdString(); const std::string playing_as = "Playing as " + name_friendly; - qDebug() << "Discord RPC: Setting character state (" << playing_as.c_str() << ")"; + qDebug() << "Discord RPC: Setting character state (" << playing_as.c_str() + << ")"; DiscordRichPresence presence; std::memset(&presence, 0, sizeof(presence)); @@ -117,7 +117,7 @@ void Discord::state_character(std::string name) presence.startTimestamp = this->timestamp; presence.state = playing_as.c_str(); -// presence.smallImageKey = "danganronpa_online"; + // presence.smallImageKey = "danganronpa_online"; presence.smallImageKey = "danganronpa_online"; presence.smallImageText = "Danganronpa Online"; Discord_UpdatePresence(&presence); @@ -140,4 +140,4 @@ void Discord::state_spectate() Discord_UpdatePresence(&presence); } -} +} // namespace AttorneyOnline diff --git a/discord_rich_presence.h b/discord_rich_presence.h index 3e8ec47d6..94dac1808 100644 --- a/discord_rich_presence.h +++ b/discord_rich_presence.h @@ -1,18 +1,21 @@ #ifndef DISCORD_RICH_PRESENCE_H #define DISCORD_RICH_PRESENCE_H -#include #include +#include namespace AttorneyOnline { -class Discord -{ +class Discord { private: - const char* APPLICATION_ID[2] = { "538080629535801347" , "538080629535801347", }; // insert second one here blah blah + const char *APPLICATION_ID[2] = { + "538080629535801347", + "538080629535801347", + }; // insert second one here blah blah int m_index = 0; std::string server_name, server_id; int64_t timestamp; + public: Discord(); ~Discord(); @@ -26,5 +29,5 @@ class Discord void toggle(int p_index); }; -} +} // namespace AttorneyOnline #endif // DISCORD_RICH_PRESENCE_H diff --git a/discord_rpc.h b/discord_rpc.h index 97a6e9ec8..72d2e7f2a 100644 --- a/discord_rpc.h +++ b/discord_rpc.h @@ -24,61 +24,63 @@ extern "C" { #endif typedef struct DiscordRichPresence { - const char* state; /* max 128 bytes */ - const char* details; /* max 128 bytes */ - int64_t startTimestamp; - int64_t endTimestamp; - const char* largeImageKey; /* max 32 bytes */ - const char* largeImageText; /* max 128 bytes */ - const char* smallImageKey; /* max 32 bytes */ - const char* smallImageText; /* max 128 bytes */ - const char* partyId; /* max 128 bytes */ - int partySize; - int partyMax; - const char* matchSecret; /* max 128 bytes */ - const char* joinSecret; /* max 128 bytes */ - const char* spectateSecret; /* max 128 bytes */ - int8_t instance; + const char *state; /* max 128 bytes */ + const char *details; /* max 128 bytes */ + int64_t startTimestamp; + int64_t endTimestamp; + const char *largeImageKey; /* max 32 bytes */ + const char *largeImageText; /* max 128 bytes */ + const char *smallImageKey; /* max 32 bytes */ + const char *smallImageText; /* max 128 bytes */ + const char *partyId; /* max 128 bytes */ + int partySize; + int partyMax; + const char *matchSecret; /* max 128 bytes */ + const char *joinSecret; /* max 128 bytes */ + const char *spectateSecret; /* max 128 bytes */ + int8_t instance; } DiscordRichPresence; typedef struct DiscordJoinRequest { - const char* userId; - const char* username; - const char* discriminator; - const char* avatar; + const char *userId; + const char *username; + const char *discriminator; + const char *avatar; } DiscordJoinRequest; typedef struct DiscordEventHandlers { - void (*ready)(void); - void (*disconnected)(int errorCode, const char* message); - void (*errored)(int errorCode, const char* message); - void (*joinGame)(const char* joinSecret); - void (*spectateGame)(const char* spectateSecret); - void (*joinRequest)(const DiscordJoinRequest* request); + void (*ready)(void); + void (*disconnected)(int errorCode, const char *message); + void (*errored)(int errorCode, const char *message); + void (*joinGame)(const char *joinSecret); + void (*spectateGame)(const char *spectateSecret); + void (*joinRequest)(const DiscordJoinRequest *request); } DiscordEventHandlers; #define DISCORD_REPLY_NO 0 #define DISCORD_REPLY_YES 1 #define DISCORD_REPLY_IGNORE 2 -DISCORD_EXPORT void Discord_Initialize(const char* applicationId, - DiscordEventHandlers* handlers, +DISCORD_EXPORT void Discord_Initialize(const char *applicationId, + DiscordEventHandlers *handlers, int autoRegister, - const char* optionalSteamId); + const char *optionalSteamId); DISCORD_EXPORT void Discord_Shutdown(void); /* checks for incoming messages, dispatches callbacks */ DISCORD_EXPORT void Discord_RunCallbacks(void); -/* If you disable the lib starting its own io thread, you'll need to call this from your own */ +/* If you disable the lib starting its own io thread, you'll need to call this + * from your own */ #ifdef DISCORD_DISABLE_IO_THREAD DISCORD_EXPORT void Discord_UpdateConnection(void); #endif -DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); +DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence *presence); DISCORD_EXPORT void Discord_ClearPresence(void); -DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); +DISCORD_EXPORT void Discord_Respond(const char *userid, + /* DISCORD_REPLY_ */ int reply); #ifdef __cplusplus } /* extern "C" */ diff --git a/emotes.cpp b/emotes.cpp index 35a69dbdd..7f8a50b3a 100644 --- a/emotes.cpp +++ b/emotes.cpp @@ -6,84 +6,86 @@ void Courtroom::construct_emotes() { - ui_emotes = new QWidget(this); + ui_emotes = new QWidget(this); - ui_emote_left = new AOButton(this, ao_app); - ui_emote_right = new AOButton(this, ao_app); + ui_emote_left = new AOButton(this, ao_app); + ui_emote_right = new AOButton(this, ao_app); - ui_emote_dropdown = new QComboBox(this); - ui_pos_dropdown = new QComboBox(this); - ui_pos_dropdown->addItem("wit", "wit"); - ui_pos_dropdown->addItem("def", "def"); - ui_pos_dropdown->addItem("pro", "pro"); - ui_pos_dropdown->addItem("jud", "jud"); - ui_pos_dropdown->addItem("hld", "hld"); - ui_pos_dropdown->addItem("hlp", "hlp"); + ui_emote_dropdown = new QComboBox(this); + ui_pos_dropdown = new QComboBox(this); + ui_pos_dropdown->addItem("wit", "wit"); + ui_pos_dropdown->addItem("def", "def"); + ui_pos_dropdown->addItem("pro", "pro"); + ui_pos_dropdown->addItem("jud", "jud"); + ui_pos_dropdown->addItem("hld", "hld"); + ui_pos_dropdown->addItem("hlp", "hlp"); - reconstruct_emotes(); + reconstruct_emotes(); } void Courtroom::reconstruct_emotes() { - // delete previous buttons - while (!ui_emote_list.isEmpty()) - delete ui_emote_list.takeLast(); + // delete previous buttons + while (!ui_emote_list.isEmpty()) + delete ui_emote_list.takeLast(); - // resize and move - set_size_and_pos(ui_emotes, "emotes"); + // resize and move + set_size_and_pos(ui_emotes, "emotes"); - QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", "courtroom_design.ini"); + QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", + "courtroom_design.ini"); - const int button_width = 40; - int x_spacing = f_spacing.x(); - int x_mod_count = 0; + const int button_width = 40; + int x_spacing = f_spacing.x(); + int x_mod_count = 0; - const int button_height = 40; - int y_spacing = f_spacing.y(); - int y_mod_count = 0; + const int button_height = 40; + int y_spacing = f_spacing.y(); + int y_mod_count = 0; - emote_columns = ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; - emote_rows = ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; + emote_columns = + ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; + emote_rows = + ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; - max_emotes_on_page = emote_columns * emote_rows; + max_emotes_on_page = emote_columns * emote_rows; - for (int n = 0 ; n < max_emotes_on_page ; ++n) - { - int x_pos = (button_width + x_spacing) * x_mod_count; - int y_pos = (button_height + y_spacing) * y_mod_count; + for (int n = 0; n < max_emotes_on_page; ++n) { + int x_pos = (button_width + x_spacing) * x_mod_count; + int y_pos = (button_height + y_spacing) * y_mod_count; - AOEmoteButton *f_emote = new AOEmoteButton(ui_emotes, ao_app, x_pos, y_pos); + AOEmoteButton *f_emote = new AOEmoteButton(ui_emotes, ao_app, x_pos, y_pos); - ui_emote_list.append(f_emote); + ui_emote_list.append(f_emote); - f_emote->set_id(n); + f_emote->set_id(n); - connect(f_emote, SIGNAL(emote_clicked(int)), this, SLOT(on_emote_clicked(int))); + connect(f_emote, SIGNAL(emote_clicked(int)), this, + SLOT(on_emote_clicked(int))); - ++x_mod_count; + ++x_mod_count; - if (x_mod_count == emote_columns) - { - ++y_mod_count; - x_mod_count = 0; - } + if (x_mod_count == emote_columns) { + ++y_mod_count; + x_mod_count = 0; } + } - reset_emote_page(); + reset_emote_page(); } void Courtroom::reset_emote_page() { - current_emote_page = 0; - current_emote = 0; + current_emote_page = 0; + current_emote = 0; - if (m_cid == -1) - ui_emotes->hide(); - else - ui_emotes->show(); + if (m_cid == -1) + ui_emotes->hide(); + else + ui_emotes->show(); - set_emote_page(); - set_emote_dropdown(); + set_emote_page(); + set_emote_dropdown(); } void Courtroom::set_emote_page() @@ -96,23 +98,20 @@ void Courtroom::set_emote_page() ui_emote_left->hide(); ui_emote_right->hide(); - for (AOEmoteButton *i_button : ui_emote_list) - { + for (AOEmoteButton *i_button : ui_emote_list) { i_button->hide(); } int total_pages = total_emotes / max_emotes_on_page; int emotes_on_page = 0; - if (total_emotes % max_emotes_on_page != 0) - { + if (total_emotes % max_emotes_on_page != 0) { ++total_pages; - //i. e. not on the last page + // i. e. not on the last page if (total_pages > current_emote_page + 1) emotes_on_page = max_emotes_on_page; else emotes_on_page = total_emotes % max_emotes_on_page; - } else emotes_on_page = max_emotes_on_page; @@ -123,8 +122,7 @@ void Courtroom::set_emote_page() if (current_emote_page > 0) ui_emote_left->show(); - for (int n_emote = 0 ; n_emote < emotes_on_page ; ++n_emote) - { + for (int n_emote = 0; n_emote < emotes_on_page; ++n_emote) { int n_real_emote = n_emote + current_emote_page * max_emotes_on_page; AOEmoteButton *f_emote = ui_emote_list.at(n_emote); @@ -135,7 +133,6 @@ void Courtroom::set_emote_page() f_emote->show(); } - } void Courtroom::set_emote_dropdown() @@ -145,8 +142,7 @@ void Courtroom::set_emote_dropdown() int total_emotes = ao_app->get_emote_number(current_char); QStringList emote_list; - for (int n = 0 ; n < total_emotes ; ++n) - { + for (int n = 0; n < total_emotes; ++n) { emote_list.append(ao_app->get_emote_comment(current_char, n)); } @@ -159,14 +155,16 @@ void Courtroom::select_emote(int p_id) int max = (max_emotes_on_page - 1) + current_emote_page * max_emotes_on_page; if (current_emote >= min && current_emote <= max) - ui_emote_list.at(current_emote % max_emotes_on_page)->set_image(current_char, current_emote, "_off.png"); + ui_emote_list.at(current_emote % max_emotes_on_page) + ->set_image(current_char, current_emote, "_off.png"); int old_emote = current_emote; current_emote = p_id; if (current_emote >= min && current_emote <= max) - ui_emote_list.at(current_emote % max_emotes_on_page)->set_image(current_char, current_emote, "_on.png"); + ui_emote_list.at(current_emote % max_emotes_on_page) + ->set_image(current_char, current_emote, "_on.png"); int emote_mod = ao_app->get_emote_mod(current_char, current_emote); diff --git a/encryption_functions.cpp b/encryption_functions.cpp index 56b6e34cd..49bb99d08 100644 --- a/encryption_functions.cpp +++ b/encryption_functions.cpp @@ -2,16 +2,16 @@ #include "hex_functions.h" +#include #include -#include -#include #include -#include +#include +#include QString fanta_encrypt(QString temp_input, unsigned int p_key) { - //using standard stdlib types is actually easier here because of implicit char<->int conversion - //which in turn makes encryption arithmetic easier + // using standard stdlib types is actually easier here because of implicit + // char<->int conversion which in turn makes encryption arithmetic easier unsigned int key = p_key; unsigned int C1 = 53761; @@ -20,8 +20,7 @@ QString fanta_encrypt(QString temp_input, unsigned int p_key) QVector temp_result; std::string input = temp_input.toUtf8().constData(); - for (unsigned int pos = 0 ; pos < input.size() ; ++pos) - { + for (unsigned int pos = 0; pos < input.size(); ++pos) { uint_fast8_t output = input.at(pos) ^ (key >> 8) % 256; temp_result.append(output); key = (temp_result.at(pos) + key) * C1 + C2; @@ -29,8 +28,7 @@ QString fanta_encrypt(QString temp_input, unsigned int p_key) std::string result = ""; - for (uint_fast8_t i_int : temp_result) - { + for (uint_fast8_t i_int : temp_result) { result += omni::int_to_hex(i_int); } @@ -45,9 +43,8 @@ QString fanta_decrypt(QString temp_input, unsigned int key) QVector unhexed_vector; - for(unsigned int i=0; i< input.length(); i+=2) - { - std::string byte = input.substr(i,2); + for (unsigned int i = 0; i < input.length(); i += 2) { + std::string byte = input.substr(i, 2); unsigned int hex_int = strtoul(byte.c_str(), nullptr, 16); unhexed_vector.append(hex_int); } @@ -57,13 +54,11 @@ QString fanta_decrypt(QString temp_input, unsigned int key) std::string result = ""; - for (int pos = 0 ; pos < unhexed_vector.size() ; ++pos) - { + for (int pos = 0; pos < unhexed_vector.size(); ++pos) { unsigned char output = unhexed_vector.at(pos) ^ (key >> 8) % 256; result += output; key = (unhexed_vector.at(pos) + key) * C1 + C2; } return QString::fromStdString(result); - } diff --git a/evidence.cpp b/evidence.cpp index c8a486d77..e107cbf13 100644 --- a/evidence.cpp +++ b/evidence.cpp @@ -7,7 +7,7 @@ void Courtroom::construct_evidence() { ui_evidence = new AOImage(this, ao_app); - //ui_evidence_name = new QLabel(ui_evidence); + // ui_evidence_name = new QLabel(ui_evidence); ui_evidence_name = new AOLineEdit(ui_evidence); ui_evidence_name->setAlignment(Qt::AlignCenter); ui_evidence_name->setFont(QFont("Arial", 14, QFont::Bold)); @@ -35,7 +35,8 @@ void Courtroom::construct_evidence() set_size_and_pos(ui_evidence, "evidence_background"); set_size_and_pos(ui_evidence_buttons, "evidence_buttons"); - QPoint f_spacing = ao_app->get_button_spacing("evidence_button_spacing", "courtroom_design.ini"); + QPoint f_spacing = ao_app->get_button_spacing("evidence_button_spacing", + "courtroom_design.ini"); const int button_width = 70; int x_spacing = f_spacing.x(); @@ -45,43 +46,57 @@ void Courtroom::construct_evidence() int y_spacing = f_spacing.y(); int y_mod_count = 0; - evidence_columns = ((ui_evidence_buttons->width() - button_width) / (x_spacing + button_width)) + 1; - evidence_rows = ((ui_evidence_buttons->height() - button_height) / (y_spacing + button_height)) + 1; + evidence_columns = ((ui_evidence_buttons->width() - button_width) / + (x_spacing + button_width)) + + 1; + evidence_rows = ((ui_evidence_buttons->height() - button_height) / + (y_spacing + button_height)) + + 1; max_evidence_on_page = evidence_columns * evidence_rows; - for (int n = 0 ; n < max_evidence_on_page ; ++n) - { + for (int n = 0; n < max_evidence_on_page; ++n) { int x_pos = (button_width + x_spacing) * x_mod_count; int y_pos = (button_height + y_spacing) * y_mod_count; - AOEvidenceButton *f_evidence = new AOEvidenceButton(ui_evidence_buttons, ao_app, x_pos, y_pos); + AOEvidenceButton *f_evidence = + new AOEvidenceButton(ui_evidence_buttons, ao_app, x_pos, y_pos); ui_evidence_list.append(f_evidence); f_evidence->set_id(n); - connect(f_evidence, SIGNAL(evidence_clicked(int)), this, SLOT(on_evidence_clicked(int))); - connect(f_evidence, SIGNAL(evidence_double_clicked(int)), this, SLOT(on_evidence_double_clicked(int))); - connect(f_evidence, SIGNAL(on_hover(int, bool)), this, SLOT(on_evidence_hover(int, bool))); + connect(f_evidence, SIGNAL(evidence_clicked(int)), this, + SLOT(on_evidence_clicked(int))); + connect(f_evidence, SIGNAL(evidence_double_clicked(int)), this, + SLOT(on_evidence_double_clicked(int))); + connect(f_evidence, SIGNAL(on_hover(int, bool)), this, + SLOT(on_evidence_hover(int, bool))); ++x_mod_count; - if (x_mod_count == evidence_columns) - { + if (x_mod_count == evidence_columns) { ++y_mod_count; x_mod_count = 0; } } - connect(ui_evidence_name, SIGNAL(returnPressed()), this, SLOT(on_evidence_name_edited())); - connect(ui_evidence_left, SIGNAL(clicked()), this, SLOT(on_evidence_left_clicked())); - connect(ui_evidence_right, SIGNAL(clicked()), this, SLOT(on_evidence_right_clicked())); - connect(ui_evidence_present, SIGNAL(clicked()), this, SLOT(on_evidence_present_clicked())); - connect(ui_evidence_delete, SIGNAL(clicked()), this, SLOT(on_evidence_delete_clicked())); - connect(ui_evidence_image_name, SIGNAL(returnPressed()), this, SLOT(on_evidence_image_name_edited())); - connect(ui_evidence_image_button, SIGNAL(clicked()), this, SLOT(on_evidence_image_button_clicked())); - connect(ui_evidence_x, SIGNAL(clicked()), this, SLOT(on_evidence_x_clicked())); + connect(ui_evidence_name, SIGNAL(returnPressed()), this, + SLOT(on_evidence_name_edited())); + connect(ui_evidence_left, SIGNAL(clicked()), this, + SLOT(on_evidence_left_clicked())); + connect(ui_evidence_right, SIGNAL(clicked()), this, + SLOT(on_evidence_right_clicked())); + connect(ui_evidence_present, SIGNAL(clicked()), this, + SLOT(on_evidence_present_clicked())); + connect(ui_evidence_delete, SIGNAL(clicked()), this, + SLOT(on_evidence_delete_clicked())); + connect(ui_evidence_image_name, SIGNAL(returnPressed()), this, + SLOT(on_evidence_image_name_edited())); + connect(ui_evidence_image_button, SIGNAL(clicked()), this, + SLOT(on_evidence_image_button_clicked())); + connect(ui_evidence_x, SIGNAL(clicked()), this, + SLOT(on_evidence_x_clicked())); ui_evidence->hide(); } @@ -101,26 +116,23 @@ void Courtroom::set_evidence_page() ui_evidence_left->hide(); ui_evidence_right->hide(); - for (AOEvidenceButton *i_button : ui_evidence_list) - { + for (AOEvidenceButton *i_button : ui_evidence_list) { i_button->reset(); } - //to account for the "add evidence" button + // to account for the "add evidence" button ++total_evidence; int total_pages = total_evidence / max_evidence_on_page; int evidence_on_page = 0; - if ((total_evidence % max_evidence_on_page) != 0) - { + if ((total_evidence % max_evidence_on_page) != 0) { ++total_pages; - //i. e. not on the last page + // i. e. not on the last page if (total_pages > current_evidence_page + 1) evidence_on_page = max_evidence_on_page; else evidence_on_page = total_evidence % max_evidence_on_page; - } else evidence_on_page = max_evidence_on_page; @@ -131,17 +143,19 @@ void Courtroom::set_evidence_page() if (current_evidence_page > 0) ui_evidence_left->show(); - for (int n_evidence_button = 0 ; n_evidence_button < evidence_on_page ; ++n_evidence_button) - { - int n_real_evidence = n_evidence_button + current_evidence_page * max_evidence_on_page; - AOEvidenceButton *f_evidence_button = ui_evidence_list.at(n_evidence_button); + for (int n_evidence_button = 0; n_evidence_button < evidence_on_page; + ++n_evidence_button) { + int n_real_evidence = + n_evidence_button + current_evidence_page * max_evidence_on_page; + AOEvidenceButton *f_evidence_button = + ui_evidence_list.at(n_evidence_button); - //ie. the add evidence button + // ie. the add evidence button if (n_real_evidence == (total_evidence - 1)) f_evidence_button->set_theme_image("addevidence.png"); - else if (n_real_evidence < (total_evidence - 1)) - { - f_evidence_button->set_image(local_evidence_list.at(n_real_evidence).image); + else if (n_real_evidence < (total_evidence - 1)) { + f_evidence_button->set_image( + local_evidence_list.at(n_real_evidence).image); if (n_real_evidence == current_evidence) f_evidence_button->set_selected(true); @@ -200,7 +214,7 @@ void Courtroom::on_evidence_image_button_clicked() QStringList filenames; if (dialog.exec()) - filenames = dialog.selectedFiles(); + filenames = dialog.selectedFiles(); if (filenames.size() != 1) return; @@ -222,9 +236,9 @@ void Courtroom::on_evidence_clicked(int p_id) int f_real_id = p_id + max_evidence_on_page * current_evidence_page; - if (f_real_id == local_evidence_list.size()) - { - ao_app->send_server_packet(new AOPacket("PE###empty.png#%")); + if (f_real_id == local_evidence_list.size()) { + ao_app->send_server_packet( + new AOPacket("PE###empty.png#%")); return; } else if (f_real_id > local_evidence_list.size()) @@ -240,7 +254,6 @@ void Courtroom::on_evidence_clicked(int p_id) current_evidence = f_real_id; ui_ic_chat_message->setFocus(); - } void Courtroom::on_evidence_double_clicked(int p_id) @@ -269,8 +282,7 @@ void Courtroom::on_evidence_hover(int p_id, bool p_state) ui_evidence_name->setReadOnly(true); int final_id = p_id + max_evidence_on_page * current_evidence_page; - if (p_state) - { + if (p_state) { if (final_id == local_evidence_list.size()) ui_evidence_name->setText("Add new evidence..."); else if (final_id < local_evidence_list.size()) @@ -317,7 +329,8 @@ void Courtroom::on_evidence_delete_clicked() ui_evidence_description->setReadOnly(true); ui_evidence_overlay->hide(); - ao_app->send_server_packet(new AOPacket("DE#" + QString::number(current_evidence) + "#%")); + ao_app->send_server_packet( + new AOPacket("DE#" + QString::number(current_evidence) + "#%")); current_evidence = 0; @@ -345,4 +358,3 @@ void Courtroom::on_evidence_x_clicked() ui_ic_chat_message->setFocus(); } - diff --git a/file_functions.cpp b/file_functions.cpp index f3a634fa5..50a91345e 100644 --- a/file_functions.cpp +++ b/file_functions.cpp @@ -1,5 +1,5 @@ -#include #include +#include #include "file_functions.h" @@ -12,9 +12,8 @@ bool file_exists(QString file_path) QString file_exists(QString file_path, QVector p_exts) { - for(auto &ext : p_exts) - { - if(file_exists(file_path + ext)) + for (auto &ext : p_exts) { + if (file_exists(file_path + ext)) return ext; } return ""; diff --git a/hardware_functions.cpp b/hardware_functions.cpp index 29823cdfb..1a252a55e 100644 --- a/hardware_functions.cpp +++ b/hardware_functions.cpp @@ -2,7 +2,7 @@ #include -#if (defined (_WIN32) || defined (_WIN64)) +#if (defined(_WIN32) || defined(_WIN64)) #include DWORD dwVolSerial; @@ -10,18 +10,18 @@ BOOL bIsRetrieved; QString get_hdid() { - bIsRetrieved = GetVolumeInformation(TEXT("C:\\"), NULL, NULL, &dwVolSerial, NULL, NULL, NULL, NULL); + bIsRetrieved = GetVolumeInformation(TEXT("C:\\"), NULL, NULL, &dwVolSerial, + NULL, NULL, NULL, NULL); if (bIsRetrieved) return QString::number(dwVolSerial, 16); else - //a totally random string - //what could possibly go wrong + // a totally random string + // what could possibly go wrong return "gxsps32sa9fnwic92mfbs0"; - } -#elif (defined (LINUX) || defined (__linux__)) +#elif (defined(LINUX) || defined(__linux__)) #include #include @@ -34,12 +34,10 @@ QString get_hdid() QTextStream in(&fstab_file); - while(!in.atEnd()) - { + while (!in.atEnd()) { QString line = in.readLine(); - if (line.startsWith("UUID")) - { + if (line.startsWith("UUID")) { QStringList line_elements = line.split("="); if (line_elements.size() > 1) diff --git a/hex_functions.cpp b/hex_functions.cpp index 9db2b0a73..d7d8f4e8b 100644 --- a/hex_functions.cpp +++ b/hex_functions.cpp @@ -1,83 +1,83 @@ -//WELCOME TO THE EXTREMELY GHETTO HEX CONVERSION KLUDGE BECAUSE I COULDNT MAKE ANYTHING ELSE WORK +// WELCOME TO THE EXTREMELY GHETTO HEX CONVERSION KLUDGE BECAUSE I COULDNT MAKE +// ANYTHING ELSE WORK #include "hex_functions.h" -namespace omni +namespace omni { +char halfword_to_hex_char(unsigned int input) { - char halfword_to_hex_char(unsigned int input) - { - if (input > 127) - return 'F'; + if (input > 127) + return 'F'; - switch (input) - { - case 0: - return '0'; - case 1: - return '1'; - case 2: - return '2'; - case 3: - return '3'; - case 4: - return '4'; - case 5: - return '5'; - case 6: - return '6'; - case 7: - return '7'; - case 8: - return '8'; - case 9: - return '9'; - case 10: - return 'A'; - case 11: - return 'B'; - case 12: - return 'C'; - case 13: - return 'D'; - case 14: - return 'E'; - case 15: - return 'F'; - default: - return 'F'; - } + switch (input) { + case 0: + return '0'; + case 1: + return '1'; + case 2: + return '2'; + case 3: + return '3'; + case 4: + return '4'; + case 5: + return '5'; + case 6: + return '6'; + case 7: + return '7'; + case 8: + return '8'; + case 9: + return '9'; + case 10: + return 'A'; + case 11: + return 'B'; + case 12: + return 'C'; + case 13: + return 'D'; + case 14: + return 'E'; + case 15: + return 'F'; + default: + return 'F'; } +} - std::string int_to_hex(unsigned int input) - { - if (input > 255) - return "FF"; +std::string int_to_hex(unsigned int input) +{ + if (input > 255) + return "FF"; - std::bitset<8> whole_byte(input); - //240 represents 11110000, our needed bitmask - uint8_t left_mask_int = 240; - std::bitset<8> left_mask(left_mask_int); - std::bitset<8> left_halfword((whole_byte & left_mask) >> 4); - //likewise, 15 represents 00001111 - uint8_t right_mask_int = 15; - std::bitset<8> right_mask(right_mask_int); - std::bitset<8> right_halfword((whole_byte & right_mask)); + std::bitset<8> whole_byte(input); + // 240 represents 11110000, our needed bitmask + uint8_t left_mask_int = 240; + std::bitset<8> left_mask(left_mask_int); + std::bitset<8> left_halfword((whole_byte & left_mask) >> 4); + // likewise, 15 represents 00001111 + uint8_t right_mask_int = 15; + std::bitset<8> right_mask(right_mask_int); + std::bitset<8> right_halfword((whole_byte & right_mask)); - unsigned int left = left_halfword.to_ulong(); - unsigned int right = right_halfword.to_ulong(); + unsigned int left = left_halfword.to_ulong(); + unsigned int right = right_halfword.to_ulong(); - //std::cout << "now have have " << left << " and " << right << '\n'; + // std::cout << "now have have " << left << " and " << right << '\n'; - char a = halfword_to_hex_char(left); - char b = halfword_to_hex_char(right); + char a = halfword_to_hex_char(left); + char b = halfword_to_hex_char(right); - std::string left_string(1, a); - std::string right_string(1, b); + std::string left_string(1, a); + std::string right_string(1, b); - std::string final_byte = left_string + right_string; + std::string final_byte = left_string + right_string; - //std::string final_byte = halfword_to_hex_char(left) + "" + halfword_to_hex_char(right); + // std::string final_byte = halfword_to_hex_char(left) + "" + + // halfword_to_hex_char(right); - return final_byte; - } -} //namespace omni + return final_byte; +} +} // namespace omni diff --git a/hex_functions.h b/hex_functions.h index 47d9466b7..c37e7997a 100644 --- a/hex_functions.h +++ b/hex_functions.h @@ -5,10 +5,9 @@ #include #include -namespace omni -{ - char halfword_to_hex_char(unsigned int input); - std::string int_to_hex(unsigned int input); -} +namespace omni { +char halfword_to_hex_char(unsigned int input); +std::string int_to_hex(unsigned int input); +} // namespace omni -#endif //HEX_OPERATIONS_H +#endif // HEX_OPERATIONS_H diff --git a/lobby.cpp b/lobby.cpp index 900451f64..e001d7a75 100644 --- a/lobby.cpp +++ b/lobby.cpp @@ -12,236 +12,245 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() { - ao_app = p_ao_app; - - this->setWindowTitle("Danganronpa Online"); - - ui_background = new AOImage(this, ao_app); - ui_public_servers = new AOButton(this, ao_app); - ui_favorites = new AOButton(this, ao_app); - ui_refresh = new AOButton(this, ao_app); - ui_add_to_fav = new AOButton(this, ao_app); - ui_connect = new AOButton(this, ao_app); - ui_version = new QTextEdit(this); - ui_version->setFrameStyle(QFrame::NoFrame); - ui_version->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_version->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_version->setReadOnly(true); - ui_about = new AOButton(this, ao_app); - ui_server_list = new QListWidget(this); - ui_player_count = new QTextEdit(this); - ui_player_count->setFrameStyle(QFrame::NoFrame); - ui_player_count->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_player_count->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_player_count->setReadOnly(true); - ui_description = new AOTextArea(this); - ui_chatbox = new AOTextArea(this); - ui_chatbox->setOpenExternalLinks(true); - ui_chatname = new QLineEdit(this); - ui_chatname->setPlaceholderText("Name"); - ui_chatmessage = new QLineEdit(this); - ui_loading_background = new AOImage(this, ao_app); - ui_loading_text = new QTextEdit(ui_loading_background); - ui_progress_bar = new QProgressBar(ui_loading_background); - ui_progress_bar->setMinimum(0); - ui_progress_bar->setMaximum(100); - ui_progress_bar->setStyleSheet("QProgressBar{ color: white; }"); - ui_cancel = new AOButton(ui_loading_background, ao_app); - - connect(ui_public_servers, SIGNAL(clicked()), this, SLOT(on_public_servers_clicked())); - connect(ui_favorites, SIGNAL(clicked()), this, SLOT(on_favorites_clicked())); - connect(ui_refresh, SIGNAL(pressed()), this, SLOT(on_refresh_pressed())); - connect(ui_refresh, SIGNAL(released()), this, SLOT(on_refresh_released())); - connect(ui_add_to_fav, SIGNAL(pressed()), this, SLOT(on_add_to_fav_pressed())); - connect(ui_add_to_fav, SIGNAL(released()), this, SLOT(on_add_to_fav_released())); - connect(ui_connect, SIGNAL(pressed()), this, SLOT(on_connect_pressed())); - connect(ui_connect, SIGNAL(released()), this, SLOT(on_connect_released())); - connect(ui_about, SIGNAL(clicked()), this, SLOT(on_about_clicked())); - connect(ui_server_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_server_list_clicked(QModelIndex))); - connect(ui_chatmessage, SIGNAL(returnPressed()), this, SLOT(on_chatfield_return_pressed())); - connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); - - set_widgets(); + ao_app = p_ao_app; + + this->setWindowTitle("Danganronpa Online"); + + ui_background = new AOImage(this, ao_app); + ui_public_servers = new AOButton(this, ao_app); + ui_favorites = new AOButton(this, ao_app); + ui_refresh = new AOButton(this, ao_app); + ui_add_to_fav = new AOButton(this, ao_app); + ui_connect = new AOButton(this, ao_app); + ui_version = new QTextEdit(this); + ui_version->setFrameStyle(QFrame::NoFrame); + ui_version->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_version->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_version->setReadOnly(true); + ui_about = new AOButton(this, ao_app); + ui_server_list = new QListWidget(this); + ui_player_count = new QTextEdit(this); + ui_player_count->setFrameStyle(QFrame::NoFrame); + ui_player_count->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_player_count->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_player_count->setReadOnly(true); + ui_description = new AOTextArea(this); + ui_chatbox = new AOTextArea(this); + ui_chatbox->setOpenExternalLinks(true); + ui_chatname = new QLineEdit(this); + ui_chatname->setPlaceholderText("Name"); + ui_chatmessage = new QLineEdit(this); + ui_loading_background = new AOImage(this, ao_app); + ui_loading_text = new QTextEdit(ui_loading_background); + ui_progress_bar = new QProgressBar(ui_loading_background); + ui_progress_bar->setMinimum(0); + ui_progress_bar->setMaximum(100); + ui_progress_bar->setStyleSheet("QProgressBar{ color: white; }"); + ui_cancel = new AOButton(ui_loading_background, ao_app); + + connect(ui_public_servers, SIGNAL(clicked()), this, + SLOT(on_public_servers_clicked())); + connect(ui_favorites, SIGNAL(clicked()), this, SLOT(on_favorites_clicked())); + connect(ui_refresh, SIGNAL(pressed()), this, SLOT(on_refresh_pressed())); + connect(ui_refresh, SIGNAL(released()), this, SLOT(on_refresh_released())); + connect(ui_add_to_fav, SIGNAL(pressed()), this, + SLOT(on_add_to_fav_pressed())); + connect(ui_add_to_fav, SIGNAL(released()), this, + SLOT(on_add_to_fav_released())); + connect(ui_connect, SIGNAL(pressed()), this, SLOT(on_connect_pressed())); + connect(ui_connect, SIGNAL(released()), this, SLOT(on_connect_released())); + connect(ui_about, SIGNAL(clicked()), this, SLOT(on_about_clicked())); + connect(ui_server_list, SIGNAL(clicked(QModelIndex)), this, + SLOT(on_server_list_clicked(QModelIndex))); + connect(ui_chatmessage, SIGNAL(returnPressed()), this, + SLOT(on_chatfield_return_pressed())); + connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); + + set_widgets(); } -//sets images, position and size +// sets images, position and size void Lobby::set_widgets() { - QString filename = "lobby_design.ini"; + QString filename = "lobby_design.ini"; - pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", filename); + pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", filename); - if (f_lobby.width < 0 || f_lobby.height < 0) - { - qDebug() << "W: did not find lobby width or height in " << filename; + if (f_lobby.width < 0 || f_lobby.height < 0) { + qDebug() << "W: did not find lobby width or height in " << filename; - // Most common symptom of bad config files and missing assets. - call_notice("It doesn't look like your client is set up correctly " - "at " + QDir::currentPath() + "\n" - "Did you download all resources correctly from the DRO Discord " - "including the large 'base' folder?"); + // Most common symptom of bad config files and missing assets. + call_notice("It doesn't look like your client is set up correctly " + "at " + + QDir::currentPath() + + "\n" + "Did you download all resources correctly from the DRO Discord " + "including the large 'base' folder?"); - this->resize(517, 666); - } - else - { - this->resize(f_lobby.width, f_lobby.height); - } + this->resize(517, 666); + } + else { + this->resize(f_lobby.width, f_lobby.height); + } - set_size_and_pos(ui_background, "lobby"); - ui_background->set_image("lobbybackground.png"); + set_size_and_pos(ui_background, "lobby"); + ui_background->set_image("lobbybackground.png"); - set_size_and_pos(ui_public_servers, "public_servers"); - ui_public_servers->set_image("publicservers_selected.png"); + set_size_and_pos(ui_public_servers, "public_servers"); + ui_public_servers->set_image("publicservers_selected.png"); - set_size_and_pos(ui_favorites, "favorites"); - ui_favorites->set_image("favorites.png"); + set_size_and_pos(ui_favorites, "favorites"); + ui_favorites->set_image("favorites.png"); - set_size_and_pos(ui_refresh, "refresh"); - ui_refresh->set_image("refresh.png"); + set_size_and_pos(ui_refresh, "refresh"); + ui_refresh->set_image("refresh.png"); - set_size_and_pos(ui_add_to_fav, "add_to_fav"); - ui_add_to_fav->set_image("addtofav.png"); + set_size_and_pos(ui_add_to_fav, "add_to_fav"); + ui_add_to_fav->set_image("addtofav.png"); - set_size_and_pos(ui_connect, "connect"); - ui_connect->set_image("connect.png"); + set_size_and_pos(ui_connect, "connect"); + ui_connect->set_image("connect.png"); - set_size_and_pos(ui_version, "version"); - ui_version->setText("Version: " + ao_app->get_version_string()); + set_size_and_pos(ui_version, "version"); + ui_version->setText("Version: " + ao_app->get_version_string()); - set_size_and_pos(ui_about, "about"); - ui_about->set_image("about.png"); + set_size_and_pos(ui_about, "about"); + ui_about->set_image("about.png"); - set_size_and_pos(ui_server_list, "server_list"); - ui_server_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "font: bold;"); + set_size_and_pos(ui_server_list, "server_list"); + ui_server_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "font: bold;"); - set_size_and_pos(ui_player_count, "player_count"); - ui_player_count->setStyleSheet("font: bold;" - "color: white;" - "qproperty-alignment: AlignCenter;"); + set_size_and_pos(ui_player_count, "player_count"); + ui_player_count->setStyleSheet("font: bold;" + "color: white;" + "qproperty-alignment: AlignCenter;"); - set_size_and_pos(ui_description, "description"); - ui_description->setReadOnly(true); - ui_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: white;"); + set_size_and_pos(ui_description, "description"); + ui_description->setReadOnly(true); + ui_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "color: white;"); - set_size_and_pos(ui_chatbox, "chatbox"); - ui_chatbox->setReadOnly(true); - ui_chatbox->setStyleSheet("QTextBrowser{background-color: rgba(0, 0, 0, 0);}"); + set_size_and_pos(ui_chatbox, "chatbox"); + ui_chatbox->setReadOnly(true); + ui_chatbox->setStyleSheet( + "QTextBrowser{background-color: rgba(0, 0, 0, 0);}"); - set_size_and_pos(ui_chatname, "chatname"); - ui_chatname->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "selection-background-color: rgba(0, 0, 0, 0);"); + set_size_and_pos(ui_chatname, "chatname"); + ui_chatname->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "selection-background-color: rgba(0, 0, 0, 0);"); - set_size_and_pos(ui_chatmessage, "chatmessage"); - ui_chatmessage->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "selection-background-color: rgba(0, 0, 0, 0);"); + set_size_and_pos(ui_chatmessage, "chatmessage"); + ui_chatmessage->setStyleSheet( + "background-color: rgba(0, 0, 0, 0);" + "selection-background-color: rgba(0, 0, 0, 0);"); - ui_loading_background->resize(this->width(), this->height()); - ui_loading_background->set_image("loadingbackground.png"); + ui_loading_background->resize(this->width(), this->height()); + ui_loading_background->set_image("loadingbackground.png"); - set_size_and_pos(ui_loading_text, "loading_label"); - ui_loading_text->setFont(QFont("Arial", 20, QFont::Bold)); - ui_loading_text->setReadOnly(true); - ui_loading_text->setAlignment(Qt::AlignCenter); - ui_loading_text->setFrameStyle(QFrame::NoFrame); - ui_loading_text->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: rgba(255, 128, 0, 255);"); - ui_loading_text->append("Loading"); + set_size_and_pos(ui_loading_text, "loading_label"); + ui_loading_text->setFont(QFont("Arial", 20, QFont::Bold)); + ui_loading_text->setReadOnly(true); + ui_loading_text->setAlignment(Qt::AlignCenter); + ui_loading_text->setFrameStyle(QFrame::NoFrame); + ui_loading_text->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "color: rgba(255, 128, 0, 255);"); + ui_loading_text->append("Loading"); - set_size_and_pos(ui_progress_bar, "progress_bar"); - set_size_and_pos(ui_cancel, "cancel"); - ui_cancel->setText("Cancel"); + set_size_and_pos(ui_progress_bar, "progress_bar"); + set_size_and_pos(ui_cancel, "cancel"); + ui_cancel->setText("Cancel"); - ui_loading_background->hide(); + ui_loading_background->hide(); - set_fonts(); - set_stylesheets(); - set_choose_a_server(); + set_fonts(); + set_stylesheets(); + set_choose_a_server(); } void Lobby::set_size_and_pos(QWidget *p_widget, QString p_identifier) { - QString filename = "lobby_design.ini"; - - pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - - 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); - } + QString filename = "lobby_design.ini"; + + pos_size_type design_ini_result = + ao_app->get_element_dimensions(p_identifier, filename); + + 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 Lobby::set_fonts() { - set_qtextedit_font(ui_player_count, "player_count"); - set_qtextedit_font(ui_description, "description"); - set_font(ui_chatbox, "chatbox"); - set_font(ui_chatname, "chatname"); - set_font(ui_chatmessage, "chatmessage"); - set_qtextedit_font(ui_loading_text, "loading_text"); - set_font(ui_server_list, "server_list"); + set_qtextedit_font(ui_player_count, "player_count"); + set_qtextedit_font(ui_description, "description"); + set_font(ui_chatbox, "chatbox"); + set_font(ui_chatname, "chatname"); + set_font(ui_chatmessage, "chatmessage"); + set_qtextedit_font(ui_loading_text, "loading_text"); + set_font(ui_server_list, "server_list"); } void Lobby::set_stylesheet(QWidget *widget, QString target_tag) { - QString f_file = "lobby_stylesheets.css"; - QString style_sheet_string = ao_app->get_stylesheet(target_tag, f_file); - if (style_sheet_string != "") - widget->setStyleSheet(style_sheet_string); + QString f_file = "lobby_stylesheets.css"; + QString style_sheet_string = ao_app->get_stylesheet(target_tag, f_file); + if (style_sheet_string != "") + widget->setStyleSheet(style_sheet_string); } void Lobby::set_stylesheets() { - set_stylesheet(ui_player_count, "[PLAYER COUNT]"); - set_stylesheet(ui_description, "[DESCRIPTION]"); - set_stylesheet(ui_chatbox, "[CHAT BOX]"); - set_stylesheet(ui_chatname, "[CHAT NAME]"); - set_stylesheet(ui_chatmessage, "[CHAT MESSAGE]"); - set_stylesheet(ui_loading_text, "[LOADING TEXT]"); - set_stylesheet(ui_server_list, "[SERVER LIST]"); + set_stylesheet(ui_player_count, "[PLAYER COUNT]"); + set_stylesheet(ui_description, "[DESCRIPTION]"); + set_stylesheet(ui_chatbox, "[CHAT BOX]"); + set_stylesheet(ui_chatname, "[CHAT NAME]"); + set_stylesheet(ui_chatmessage, "[CHAT MESSAGE]"); + set_stylesheet(ui_loading_text, "[LOADING TEXT]"); + set_stylesheet(ui_server_list, "[SERVER LIST]"); } void Lobby::set_font(QWidget *widget, QString p_identifier) { - QString design_file = "lobby_fonts.ini"; + QString design_file = "lobby_fonts.ini"; - if (!(bool)ao_app->get_font_property("use_custom_fonts", design_file)) - return; + if (!(bool)ao_app->get_font_property("use_custom_fonts", design_file)) + return; - int f_weight = ao_app->get_font_property(p_identifier, design_file); - QString class_name = widget->metaObject()->className(); + int f_weight = ao_app->get_font_property(p_identifier, design_file); + QString class_name = widget->metaObject()->className(); - QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); - QFont font(font_name, f_weight); - widget->setFont(font); + QString font_name = + ao_app->get_font_name("font_" + p_identifier, design_file); + QFont font(font_name, f_weight); + widget->setFont(font); - QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); + QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); - bool bold = (bool)ao_app->get_font_property(p_identifier + "_bold", design_file); - QString is_bold = ""; - if (bold) is_bold = "bold"; + bool bold = + (bool)ao_app->get_font_property(p_identifier + "_bold", design_file); + QString is_bold = ""; + if (bold) + is_bold = "bold"; - bool center = (bool)ao_app->get_font_property(p_identifier + "_center", design_file); - QString is_center = ""; - if (center) is_center = "qproperty-alignment: AlignCenter;"; + bool center = + (bool)ao_app->get_font_property(p_identifier + "_center", design_file); + QString is_center = ""; + if (center) + is_center = "qproperty-alignment: AlignCenter;"; - QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + - "color: rgba(" + - QString::number(f_color.red()) + ", " + - QString::number(f_color.green()) + ", " + - QString::number(f_color.blue()) + ", 255);\n" + - is_center + "\n" + - "font: " + is_bold + "; }"; + QString style_sheet_string = + class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: rgba(" + + QString::number(f_color.red()) + ", " + QString::number(f_color.green()) + + ", " + QString::number(f_color.blue()) + ", 255);\n" + is_center + "\n" + + "font: " + is_bold + "; }"; - widget->setStyleSheet(style_sheet_string); + widget->setStyleSheet(style_sheet_string); } void Lobby::set_qtextedit_font(QTextEdit *widget, QString p_identifier) @@ -259,212 +268,206 @@ void Lobby::set_qtextedit_font(QTextEdit *widget, QString p_identifier) void Lobby::set_loading_text(QString p_text) { - ui_loading_text->clear(); - ui_loading_text->setAlignment(Qt::AlignCenter); - ui_loading_text->append(p_text); + ui_loading_text->clear(); + ui_loading_text->setAlignment(Qt::AlignCenter); + ui_loading_text->append(p_text); } QString Lobby::get_chatlog() { - QString return_value = ui_chatbox->toPlainText(); + QString return_value = ui_chatbox->toPlainText(); - return return_value; + return return_value; } -int Lobby::get_selected_server() -{ - return ui_server_list->currentRow(); -} +int Lobby::get_selected_server() { return ui_server_list->currentRow(); } void Lobby::set_loading_value(int p_value) { - ui_progress_bar->setValue(p_value); + ui_progress_bar->setValue(p_value); } void Lobby::on_public_servers_clicked() { - ui_public_servers->set_image("publicservers_selected.png"); - ui_favorites->set_image("favorites.png"); + ui_public_servers->set_image("publicservers_selected.png"); + ui_favorites->set_image("favorites.png"); - list_servers(); + list_servers(); - public_servers_selected = true; + public_servers_selected = true; } void Lobby::on_favorites_clicked() { - ui_favorites->set_image("favorites_selected.png"); - ui_public_servers->set_image("publicservers.png"); + ui_favorites->set_image("favorites_selected.png"); + ui_public_servers->set_image("publicservers.png"); - ao_app->set_favorite_list(); - //ao_app->favorite_list = read_serverlist_txt(); + ao_app->set_favorite_list(); + // ao_app->favorite_list = read_serverlist_txt(); - list_favorites(); + list_favorites(); - public_servers_selected = false; + public_servers_selected = false; } void Lobby::on_refresh_pressed() { - ui_refresh->set_image("refresh_pressed.png"); + ui_refresh->set_image("refresh_pressed.png"); } void Lobby::on_refresh_released() { - ui_refresh->set_image("refresh.png"); + ui_refresh->set_image("refresh.png"); - AOPacket *f_packet = new AOPacket("ALL#%"); + AOPacket *f_packet = new AOPacket("ALL#%"); - ao_app->send_ms_packet(f_packet); + ao_app->send_ms_packet(f_packet); } void Lobby::on_add_to_fav_pressed() { - ui_add_to_fav->set_image("addtofav_pressed.png"); + ui_add_to_fav->set_image("addtofav_pressed.png"); } void Lobby::on_add_to_fav_released() { - ui_add_to_fav->set_image("addtofav.png"); + ui_add_to_fav->set_image("addtofav.png"); - //you cant add favorites from favorites m8 - if (!public_servers_selected) - return; + // you cant add favorites from favorites m8 + if (!public_servers_selected) + return; - ao_app->add_favorite_server(ui_server_list->currentRow()); + ao_app->add_favorite_server(ui_server_list->currentRow()); } void Lobby::on_connect_pressed() { - ui_connect->set_image("connect_pressed.png"); + ui_connect->set_image("connect_pressed.png"); } void Lobby::on_connect_released() { - ui_connect->set_image("connect.png"); + ui_connect->set_image("connect.png"); - AOPacket *f_packet; + AOPacket *f_packet; - f_packet = new AOPacket("askchaa#%"); + f_packet = new AOPacket("askchaa#%"); - ao_app->send_server_packet(f_packet); + ao_app->send_server_packet(f_packet); } void Lobby::on_about_clicked() { - call_notice("Attorney Online 2 is built using Qt.\n\n" - "Lead development:\n" - "OmniTroid\n\n" - "stonedDiscord\n" - "longbyte1\n" - "Supporting development:\n" - "Fiercy\n\n" - "UI design:\n" - "Ruekasu\n" - "Draxirch\n\n" - "Special thanks:\n" - "Unishred\n" - "Argoneus\n" - "Noevain\n" - "Cronnicossy"); + call_notice("Attorney Online 2 is built using Qt.\n\n" + "Lead development:\n" + "OmniTroid\n\n" + "stonedDiscord\n" + "longbyte1\n" + "Supporting development:\n" + "Fiercy\n\n" + "UI design:\n" + "Ruekasu\n" + "Draxirch\n\n" + "Special thanks:\n" + "Unishred\n" + "Argoneus\n" + "Noevain\n" + "Cronnicossy"); } void Lobby::on_server_list_clicked(QModelIndex p_model) { - int n_server = p_model.row(); + int n_server = p_model.row(); - if (n_server < 0) - return; + if (n_server < 0) + return; - if (public_servers_selected) - { - QVector f_server_list = ao_app->get_server_list(); + if (public_servers_selected) { + QVector f_server_list = ao_app->get_server_list(); - if (n_server >= f_server_list.size()) - return; + if (n_server >= f_server_list.size()) + return; - f_last_server = f_server_list.at(p_model.row()); - } - else - { - if (n_server >= ao_app->get_favorite_list().size()) - return; + f_last_server = f_server_list.at(p_model.row()); + } + else { + if (n_server >= ao_app->get_favorite_list().size()) + return; - f_last_server = ao_app->get_favorite_list().at(p_model.row()); - } + f_last_server = ao_app->get_favorite_list().at(p_model.row()); + } - ui_player_count->setText(nullptr); - ui_description->moveCursor(QTextCursor::Start); - ui_description->setText("Connecting to " + f_last_server.name + "...\n\n"); - ui_description->append(f_last_server.desc); - ui_description->ensureCursorVisible(); + ui_player_count->setText(nullptr); + ui_description->moveCursor(QTextCursor::Start); + ui_description->setText("Connecting to " + f_last_server.name + "...\n\n"); + ui_description->append(f_last_server.desc); + ui_description->ensureCursorVisible(); - ao_app->net_manager->connect_to_server(f_last_server); + ao_app->net_manager->connect_to_server(f_last_server); } void Lobby::on_chatfield_return_pressed() { - //no you can't send empty messages - if (ui_chatname->text() == "" || ui_chatmessage->text() == "") - return; + // no you can't send empty messages + if (ui_chatname->text() == "" || ui_chatmessage->text() == "") + return; - QString f_header = "CT"; - QStringList f_contents{ui_chatname->text(), ui_chatmessage->text()}; + QString f_header = "CT"; + QStringList f_contents{ui_chatname->text(), ui_chatmessage->text()}; - AOPacket *f_packet = new AOPacket(f_header, f_contents); + AOPacket *f_packet = new AOPacket(f_header, f_contents); - ao_app->send_ms_packet(f_packet); + ao_app->send_ms_packet(f_packet); - ui_chatmessage->clear(); + ui_chatmessage->clear(); } void Lobby::list_servers() { - public_servers_selected = true; - ui_favorites->set_image("favorites.png"); - ui_public_servers->set_image("publicservers_selected.png"); + public_servers_selected = true; + ui_favorites->set_image("favorites.png"); + ui_public_servers->set_image("publicservers_selected.png"); - ui_server_list->clear(); + ui_server_list->clear(); - for (server_type i_server : ao_app->get_server_list()) - { - ui_server_list->addItem(i_server.name); - } + for (server_type i_server : ao_app->get_server_list()) { + ui_server_list->addItem(i_server.name); + } } void Lobby::list_favorites() { - ui_server_list->clear(); + ui_server_list->clear(); - for (server_type i_server : ao_app->get_favorite_list()) - { - ui_server_list->addItem(i_server.name); - } + for (server_type i_server : ao_app->get_favorite_list()) { + ui_server_list->addItem(i_server.name); + } } void Lobby::append_chatmessage(QString f_name, QString f_message) { - ui_chatbox->append_chatmessage(f_name, f_message); + ui_chatbox->append_chatmessage(f_name, f_message); } void Lobby::append_error(QString f_message) { - ui_chatbox->append_error(f_message); + ui_chatbox->append_error(f_message); } void Lobby::set_choose_a_server() { - ui_player_count->setText(nullptr); - ui_description->setText(tr("Choose a server.")); + ui_player_count->setText(nullptr); + ui_description->setText(tr("Choose a server.")); } void Lobby::set_player_count(int players_online, int max_players) { - QString f_string = "Online: " + QString::number(players_online) + "/" + QString::number(max_players); - ui_player_count->setText(f_string); - ui_player_count->setAlignment(Qt::AlignHCenter); - - ui_description->setText("Connected to " + f_last_server.name + "\n\n"); - ui_description->append(f_last_server.desc); - ui_description->ensureCursorVisible(); + QString f_string = "Online: " + QString::number(players_online) + "/" + + QString::number(max_players); + ui_player_count->setText(f_string); + ui_player_count->setAlignment(Qt::AlignHCenter); + + ui_description->setText("Connected to " + f_last_server.name + "\n\n"); + ui_description->append(f_last_server.desc); + ui_description->ensureCursorVisible(); } diff --git a/lobby.h b/lobby.h index ff342a660..219ba70df 100644 --- a/lobby.h +++ b/lobby.h @@ -16,82 +16,81 @@ class AOApplication; -class Lobby : public QMainWindow -{ - Q_OBJECT +class Lobby : public QMainWindow { + Q_OBJECT public: - Lobby(AOApplication *p_ao_app); - - void set_widgets(); - void list_servers(); - void list_favorites(); - void append_chatmessage(QString f_name, QString f_message); - void append_error(QString f_message); - void set_choose_a_server(); - void set_player_count(int players_online, int max_players); - void set_loading_text(QString p_text); - void set_stylesheet(QWidget *widget, QString target_tag); - void set_stylesheets(); - void set_fonts(); - void set_font(QWidget *widget, QString p_identifier); - void set_qtextedit_font(QTextEdit *widget, QString p_identifier); - void show_loading_overlay() { ui_loading_background->show(); } - void hide_loading_overlay() { ui_loading_background->hide(); } - QString get_chatlog(); - int get_selected_server(); - - void set_loading_value(int p_value); - - bool public_servers_selected = true; + Lobby(AOApplication *p_ao_app); + + void set_widgets(); + void list_servers(); + void list_favorites(); + void append_chatmessage(QString f_name, QString f_message); + void append_error(QString f_message); + void set_choose_a_server(); + void set_player_count(int players_online, int max_players); + void set_loading_text(QString p_text); + void set_stylesheet(QWidget *widget, QString target_tag); + void set_stylesheets(); + void set_fonts(); + void set_font(QWidget *widget, QString p_identifier); + void set_qtextedit_font(QTextEdit *widget, QString p_identifier); + void show_loading_overlay() { ui_loading_background->show(); } + void hide_loading_overlay() { ui_loading_background->hide(); } + QString get_chatlog(); + int get_selected_server(); + + void set_loading_value(int p_value); + + bool public_servers_selected = true; private: - AOApplication *ao_app = nullptr; + AOApplication *ao_app = nullptr; - AOImage *ui_background; + AOImage *ui_background; - AOButton *ui_public_servers; - AOButton *ui_favorites; + AOButton *ui_public_servers; + AOButton *ui_favorites; - AOButton *ui_refresh; - AOButton *ui_add_to_fav; - AOButton *ui_connect; + AOButton *ui_refresh; + AOButton *ui_add_to_fav; + AOButton *ui_connect; - QTextEdit *ui_version; - AOButton *ui_about; + QTextEdit *ui_version; + AOButton *ui_about; - QListWidget *ui_server_list; + QListWidget *ui_server_list; - QTextEdit *ui_player_count; - AOTextArea *ui_description; + QTextEdit *ui_player_count; + AOTextArea *ui_description; - AOTextArea *ui_chatbox; + AOTextArea *ui_chatbox; - QLineEdit *ui_chatname; - QLineEdit *ui_chatmessage; + QLineEdit *ui_chatname; + QLineEdit *ui_chatmessage; - AOImage *ui_loading_background; - QTextEdit *ui_loading_text; - QProgressBar *ui_progress_bar; - AOButton *ui_cancel; + AOImage *ui_loading_background; + QTextEdit *ui_loading_text; + QProgressBar *ui_progress_bar; + AOButton *ui_cancel; - server_type f_last_server; + server_type f_last_server; - void set_size_and_pos(QWidget *p_widget, QString p_identifier); + void set_size_and_pos(QWidget *p_widget, QString p_identifier); private slots: - void on_public_servers_clicked(); - void on_favorites_clicked(); - - void on_refresh_pressed(); - void on_refresh_released(); - void on_add_to_fav_pressed(); - void on_add_to_fav_released(); - void on_connect_pressed(); - void on_connect_released(); - void on_about_clicked(); - void on_server_list_clicked(QModelIndex p_model); - void on_chatfield_return_pressed(); + void on_public_servers_clicked(); + void on_favorites_clicked(); + + void on_refresh_pressed(); + void on_refresh_released(); + void on_add_to_fav_pressed(); + void on_add_to_fav_released(); + void on_connect_pressed(); + void on_connect_released(); + void on_about_clicked(); + void on_server_list_clicked(QModelIndex p_model); + void on_chatfield_return_pressed(); }; #endif // LOBBY_H diff --git a/main.cpp b/main.cpp index cd6f7f893..3cff1c383 100644 --- a/main.cpp +++ b/main.cpp @@ -14,26 +14,25 @@ int main(int argc, char *argv[]) { #if QT_VERSION > QT_VERSION_CHECK(5, 6, 0) - // High-DPI support is for Qt version >=5.6. - // However, many Linux distros still haven't brought their stable/LTS - // packages up to Qt 5.6, so this is conditional. - AOApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + // High-DPI support is for Qt version >=5.6. + // However, many Linux distros still haven't brought their stable/LTS + // packages up to Qt 5.6, so this is conditional. + AOApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif - AOApplication app(argc, argv); + AOApplication app(argc, argv); - QPluginLoader apng("qapng"); - if (!apng.load()) - { + QPluginLoader apng("qapng"); + if (!apng.load()) { #ifdef QT_NO_DEBUG - call_error("APNG plugin could not be loaded."); + call_error("APNG plugin could not be loaded."); #endif - } + } - app.construct_lobby(); + app.construct_lobby(); #ifdef QT_NO_DEBUG - app.net_manager->connect_to_master(); + app.net_manager->connect_to_master(); #endif - app.w_lobby->show(); + app.w_lobby->show(); - return app.exec(); + return app.exec(); } diff --git a/misc_functions.cpp b/misc_functions.cpp index e767b2ef1..288af4ecf 100644 --- a/misc_functions.cpp +++ b/misc_functions.cpp @@ -1,12 +1,12 @@ #include "misc_functions.h" -#include #include +#include void delay(int p_milliseconds) { QTime dieTime = QTime::currentTime().addMSecs(p_milliseconds); - while(QTime::currentTime() < dieTime) + while (QTime::currentTime() < dieTime) QCoreApplication::processEvents(QEventLoop::AllEvents, 100); } diff --git a/networkmanager.cpp b/networkmanager.cpp index 6aebb1e78..c4b2aa59f 100644 --- a/networkmanager.cpp +++ b/networkmanager.cpp @@ -15,17 +15,18 @@ NetworkManager::NetworkManager(AOApplication *parent) : QObject(parent) ms_reconnect_timer = new QTimer(this); ms_reconnect_timer->setSingleShot(true); - QObject::connect(ms_reconnect_timer, SIGNAL(timeout()), this, SLOT(retry_ms_connect())); - - QObject::connect(ms_socket, SIGNAL(readyRead()), this, SLOT(handle_ms_packet())); - QObject::connect(server_socket, SIGNAL(readyRead()), this, SLOT(handle_server_packet())); - QObject::connect(server_socket, SIGNAL(disconnected()), ao_app, SLOT(server_disconnected())); + QObject::connect(ms_reconnect_timer, SIGNAL(timeout()), this, + SLOT(retry_ms_connect())); + + QObject::connect(ms_socket, SIGNAL(readyRead()), this, + SLOT(handle_ms_packet())); + QObject::connect(server_socket, SIGNAL(readyRead()), this, + SLOT(handle_server_packet())); + QObject::connect(server_socket, SIGNAL(disconnected()), ao_app, + SLOT(server_disconnected())); } -NetworkManager::~NetworkManager() -{ - -} +NetworkManager::~NetworkManager() {} void NetworkManager::connect_to_master() { @@ -41,34 +42,32 @@ void NetworkManager::connect_to_master() void NetworkManager::connect_to_master_nosrv() { - QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); + QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, + SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); - QObject::connect(ms_socket, SIGNAL(connected()), - this, SLOT(on_ms_nosrv_connect_success())); + QObject::connect(ms_socket, SIGNAL(connected()), this, + SLOT(on_ms_nosrv_connect_success())); ms_socket->connectToHost(ms_nosrv_hostname, ms_port); } void NetworkManager::connect_to_server(server_type p_server) { - disconnect_from_server(); - server_socket->connectToHost(p_server.ip, p_server.port); + disconnect_from_server(); + server_socket->connectToHost(p_server.ip, p_server.port); } void NetworkManager::disconnect_from_server() { - server_socket->close(); - server_socket->abort(); + server_socket->close(); + server_socket->abort(); } void NetworkManager::ship_ms_packet(QString p_packet) { - if (!ms_socket->isOpen()) - { + if (!ms_socket->isOpen()) { retry_ms_connect(); } - else - { + else { ms_socket->write(p_packet.toUtf8()); } } @@ -83,91 +82,84 @@ void NetworkManager::handle_ms_packet() QByteArray buffer = ms_socket->readAll(); QString in_data = QString::fromUtf8(buffer, buffer.size()); - if (!in_data.endsWith("%")) - { + if (!in_data.endsWith("%")) { ms_partial_packet = true; ms_temp_packet += in_data; return; } - else - { - if (ms_partial_packet) - { + else { + if (ms_partial_packet) { in_data = ms_temp_packet + in_data; ms_temp_packet = ""; ms_partial_packet = false; } } - QStringList packet_list = in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); + QStringList packet_list = + in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); - for (QString packet : packet_list) - { + for (QString packet : packet_list) { AOPacket *f_packet = new AOPacket(packet); ao_app->ms_packet_received(f_packet); } } - void NetworkManager::perform_srv_lookup() { - #ifdef MS_FAILOVER_SUPPORTED +#ifdef MS_FAILOVER_SUPPORTED ms_dns = new QDnsLookup(QDnsLookup::SRV, ms_srv_hostname, this); connect(ms_dns, SIGNAL(finished()), this, SLOT(on_srv_lookup())); ms_dns->lookup(); - #endif +#endif } void NetworkManager::on_srv_lookup() { - #ifdef MS_FAILOVER_SUPPORTED +#ifdef MS_FAILOVER_SUPPORTED bool connected = false; - if (ms_dns->error() != QDnsLookup::NoError) - { + if (ms_dns->error() != QDnsLookup::NoError) { qWarning("SRV lookup of the master server DNS failed."); ms_dns->deleteLater(); } - else - { + else { const auto srv_records = ms_dns->serviceRecords(); - for (const QDnsServiceRecord &record : srv_records) - { + for (const QDnsServiceRecord &record : srv_records) { qDebug() << "Connecting to " << record.target() << ":" << record.port(); ms_socket->connectToHost(record.target(), record.port()); QTime timer; timer.start(); - do - { + do { ao_app->processEvents(); - if (ms_socket->state() == QAbstractSocket::ConnectedState) - { + if (ms_socket->state() == QAbstractSocket::ConnectedState) { connected = true; break; } - else if (ms_socket->state() != QAbstractSocket::ConnectingState - && ms_socket->state() != QAbstractSocket::HostLookupState - && ms_socket->error() != -1) - { + else if (ms_socket->state() != QAbstractSocket::ConnectingState && + ms_socket->state() != QAbstractSocket::HostLookupState && + ms_socket->error() != -1) { qDebug() << ms_socket->error(); - qWarning() << "Error connecting to master server:" << ms_socket->errorString(); + qWarning() << "Error connecting to master server:" + << ms_socket->errorString(); ms_socket->abort(); ms_socket->close(); break; } - } while (timer.elapsed() < timeout_milliseconds); // Very expensive spin-wait loop - it will bring CPU to 100%! - if (connected) - { - // Connect a one-shot signal in case the master server disconnects randomly - QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); + } while (timer.elapsed() < + timeout_milliseconds); // Very expensive spin-wait loop - it will + // bring CPU to 100%! + if (connected) { + // Connect a one-shot signal in case the master server disconnects + // randomly + QObject::connect( + ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, + SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); break; } - else - { + else { ms_socket->abort(); ms_socket->close(); } @@ -179,29 +171,30 @@ void NetworkManager::on_srv_lookup() connect_to_master_nosrv(); else emit ms_connect_finished(connected, false); - #endif +#endif } void NetworkManager::on_ms_nosrv_connect_success() { emit ms_connect_finished(true, false); - QObject::disconnect(ms_socket, SIGNAL(connected()), - this, SLOT(on_ms_nosrv_connect_success())); + QObject::disconnect(ms_socket, SIGNAL(connected()), this, + SLOT(on_ms_nosrv_connect_success())); - QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); + QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, + SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); } void NetworkManager::on_ms_socket_error(QAbstractSocket::SocketError error) { - qWarning() << "Master server socket error:" << ms_socket->errorString() - << "(" << error << ")"; + qWarning() << "Master server socket error:" << ms_socket->errorString() << "(" + << error << ")"; // Disconnect the one-shot signal - this way, failover connect attempts // don't trigger a full retry QObject::disconnect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); + this, + SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); emit ms_connect_finished(false, true); @@ -210,7 +203,8 @@ void NetworkManager::on_ms_socket_error(QAbstractSocket::SocketError error) void NetworkManager::retry_ms_connect() { - if (!ms_reconnect_timer->isActive() && ms_socket->state() != QAbstractSocket::ConnectingState) + if (!ms_reconnect_timer->isActive() && + ms_socket->state() != QAbstractSocket::ConnectingState) connect_to_master(); } @@ -219,30 +213,26 @@ void NetworkManager::handle_server_packet() QByteArray buffer = server_socket->readAll(); QString in_data = QString::fromUtf8(buffer, buffer.size()); - if (!in_data.endsWith("%")) - { + if (!in_data.endsWith("%")) { partial_packet = true; temp_packet += in_data; return; } - else - { - if (partial_packet) - { + 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)); + QStringList packet_list = + in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); - for (QString packet : packet_list) - { + for (QString packet : packet_list) { AOPacket *f_packet = new AOPacket(packet); ao_app->server_packet_received(f_packet); } } - diff --git a/networkmanager.h b/networkmanager.h index 2bd4ba285..5a4122709 100644 --- a/networkmanager.h +++ b/networkmanager.h @@ -1,8 +1,8 @@ #ifndef NETWORKMANAGER_H #define NETWORKMANAGER_H -// Qt for Android has stubbed QDnsLookup. This is not documented in any part of their wiki. -// This prevents SRV lookup/failover behavior from functioning. +// Qt for Android has stubbed QDnsLookup. This is not documented in any part of +// their wiki. This prevents SRV lookup/failover behavior from functioning. // https://bugreports.qt.io/browse/QTBUG-56143 #ifndef ANDROID #define MS_FAILOVER_SUPPORTED @@ -14,16 +14,15 @@ #undef MS_FAILOVER_SUPPORTED #endif -#include "aopacket.h" #include "aoapplication.h" +#include "aopacket.h" -#include #include +#include #include #include -class NetworkManager : public QObject -{ +class NetworkManager : public QObject { Q_OBJECT public: diff --git a/packet_distribution.cpp b/packet_distribution.cpp index 311686f69..ec1ac8ecd 100644 --- a/packet_distribution.cpp +++ b/packet_distribution.cpp @@ -1,14 +1,14 @@ #include "aoapplication.h" -#include "lobby.h" #include "courtroom.h" -#include "networkmanager.h" +#include "debug_functions.h" #include "encryption_functions.h" #include "hardware_functions.h" -#include "debug_functions.h" +#include "lobby.h" +#include "networkmanager.h" -#include #include +#include void AOApplication::ms_packet_received(AOPacket *p_packet) { @@ -20,17 +20,14 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) if (header != "CHECK") qDebug() << "R(ms):" << p_packet->to_string(); - if (header == "ALL") - { + if (header == "ALL") { server_list.clear(); - for (QString i_string : p_packet->get_contents()) - { + for (QString i_string : p_packet->get_contents()) { server_type f_server; QStringList sub_contents = i_string.split("&"); - if (sub_contents.size() < 4) - { + if (sub_contents.size() < 4) { qDebug() << "W: malformed packet"; continue; } @@ -43,35 +40,29 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) server_list.append(f_server); } - if (lobby_constructed) - { + if (lobby_constructed) { w_lobby->list_servers(); } } - else if (header == "CT") - { + else if (header == "CT") { QString f_name, f_message; - if (f_contents.size() == 1) - { + if (f_contents.size() == 1) { f_name = ""; f_message = f_contents.at(0); } - else if (f_contents.size() >= 2) - { + else if (f_contents.size() >= 2) { f_name = f_contents.at(0); f_message = f_contents.at(1); } else goto end; - if (lobby_constructed) - { + if (lobby_constructed) { w_lobby->append_chatmessage(f_name, f_message); } } - else if (header == "AO2CHECK") - { + else if (header == "AO2CHECK") { send_ms_packet(new AOPacket("ID#AO2#" + get_version_string() + "#%")); send_ms_packet(new AOPacket("HI#" + get_hdid() + "#%")); @@ -89,31 +80,28 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) if (get_release() > f_release) goto end; - else if (get_release() == f_release) - { + else if (get_release() == f_release) { if (get_major_version() > f_major) goto end; - else if (get_major_version() == f_major) - { + else if (get_major_version() == f_major) { if (get_minor_version() >= f_minor) goto end; } } - call_notice("Outdated version! Your version: " + get_version_string() - + "\nPlease go to aceattorneyonline.com to update."); + call_notice("Outdated version! Your version: " + get_version_string() + + "\nPlease go to aceattorneyonline.com to update."); destruct_courtroom(); destruct_lobby(); } - else if (header == "DOOM") - { + else if (header == "DOOM") { call_notice("You have been exiled from AO." "Have a nice day."); destruct_courtroom(); destruct_lobby(); } - end: +end: delete p_packet; } @@ -129,15 +117,14 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (header != "checkconnection") qDebug() << "R:" << f_packet; - if (header == "decryptor") - { + if (header == "decryptor") { if (f_contents.size() == 0) goto end; - //you may ask where 322 comes from. that would be a good question. + // you may ask where 322 comes from. that would be a good question. s_decryptor = fanta_decrypt(f_contents.at(0), 322).toUInt(); - //default(legacy) values + // default(legacy) values encryption_needed = true; yellow_text_enabled = false; prezoom_enabled = false; @@ -147,7 +134,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) desk_mod_enabled = false; evidence_enabled = false; - //workaround for tsuserver4 + // workaround for tsuserver4 if (f_contents.at(0) == "NOENCRYPT") encryption_needed = false; @@ -157,8 +144,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) AOPacket *hi_packet = new AOPacket("HI#" + f_hdid + "#%"); send_server_packet(hi_packet); } - else if (header == "ID") - { + else if (header == "ID") { if (f_contents.size() < 2) goto end; @@ -167,40 +153,38 @@ void AOApplication::server_packet_received(AOPacket *p_packet) send_server_packet(new AOPacket("ID#AO2#" + get_version_string() + "#%")); } - else if (header == "CT") - { + else if (header == "CT") { if (f_contents.size() < 2) goto end; if (courtroom_constructed) - w_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1)); + w_courtroom->append_server_chatmessage(f_contents.at(0), + f_contents.at(1)); } - else if (header == "FL") - { - if (f_packet.contains("yellowtext",Qt::CaseInsensitive)) + else if (header == "FL") { + if (f_packet.contains("yellowtext", Qt::CaseInsensitive)) yellow_text_enabled = true; - if (f_packet.contains("flipping",Qt::CaseInsensitive)) + if (f_packet.contains("flipping", Qt::CaseInsensitive)) flipping_enabled = true; - if (f_packet.contains("customobjections",Qt::CaseInsensitive)) + if (f_packet.contains("customobjections", Qt::CaseInsensitive)) custom_objection_enabled = true; - if (f_packet.contains("fastloading",Qt::CaseInsensitive)) + if (f_packet.contains("fastloading", Qt::CaseInsensitive)) improved_loading_enabled = true; - if (f_packet.contains("noencryption",Qt::CaseInsensitive)) + if (f_packet.contains("noencryption", Qt::CaseInsensitive)) encryption_needed = false; - if (f_packet.contains("deskmod",Qt::CaseInsensitive)) + if (f_packet.contains("deskmod", Qt::CaseInsensitive)) desk_mod_enabled = true; - if (f_packet.contains("evidence",Qt::CaseInsensitive)) + if (f_packet.contains("evidence", Qt::CaseInsensitive)) evidence_enabled = true; } - else if (header == "PN") - { + else if (header == "PN") { if (f_contents.size() < 2) goto end; - w_lobby->set_player_count(f_contents.at(0).toInt(), f_contents.at(1).toInt()); + w_lobby->set_player_count(f_contents.at(0).toInt(), + f_contents.at(1).toInt()); } - else if (header == "SI") - { + else if (header == "SI") { if (f_contents.size() != 3) goto end; @@ -223,8 +207,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int selected_server = w_lobby->get_selected_server(); QString server_address = "", server_name = ""; - if (w_lobby->public_servers_selected) - { + if (w_lobby->public_servers_selected) { if (selected_server >= 0 && selected_server < server_list.size()) { auto info = server_list.at(selected_server); server_name = info.name; @@ -232,8 +215,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) window_title += ": " + server_name; } } - else - { + else { if (selected_server >= 0 && selected_server < favorite_list.size()) { auto info = favorite_list.at(selected_server); server_name = info.name; @@ -250,7 +232,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) AOPacket *f_packet; - if(improved_loading_enabled) + if (improved_loading_enabled) f_packet = new AOPacket("RC#%"); else f_packet = new AOPacket("askchar2#%"); @@ -259,19 +241,19 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QCryptographicHash hash(QCryptographicHash::Algorithm::Sha256); hash.addData(server_address.toUtf8()); - discord->state_server(server_name.toStdString(), hash.result().toBase64().toStdString()); + discord->state_server(server_name.toStdString(), + hash.result().toBase64().toStdString()); } - else if (header == "CI") - { + else if (header == "CI") { if (!courtroom_constructed) goto end; - for (int n_element = 0 ; n_element < f_contents.size() ; n_element += 2) - { + for (int n_element = 0; n_element < f_contents.size(); n_element += 2) { if (f_contents.at(n_element).toInt() != loaded_chars) break; - //this means we are on the last element and checking n + 1 element will be game over so + // this means we are on the last element and checking n + 1 element will + // be game over so if (n_element == f_contents.size() - 1) break; @@ -283,37 +265,38 @@ void AOApplication::server_packet_received(AOPacket *p_packet) f_char.name = sub_elements.at(0); f_char.description = sub_elements.at(1); f_char.evidence_string = sub_elements.at(3); - //temporary. the CharsCheck packet sets this properly + // temporary. the CharsCheck packet sets this properly f_char.taken = false; ++loaded_chars; - w_lobby->set_loading_text("Loading chars:\n" + QString::number(loaded_chars) + "/" + QString::number(char_list_size)); + w_lobby->set_loading_text("Loading chars:\n" + + QString::number(loaded_chars) + "/" + + QString::number(char_list_size)); w_courtroom->append_char(f_char); } - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = (loaded_chars / static_cast(total_loading_size)) * 100; + int total_loading_size = + char_list_size + evidence_list_size + music_list_size; + int loading_value = + (loaded_chars / static_cast(total_loading_size)) * 100; w_lobby->set_loading_value(loading_value); if (improved_loading_enabled) send_server_packet(new AOPacket("RE#%")); - else - { - QString next_packet_number = QString::number(((loaded_chars - 1) / 10) + 1); + else { + QString next_packet_number = + QString::number(((loaded_chars - 1) / 10) + 1); send_server_packet(new AOPacket("AN#" + next_packet_number + "#%")); } - } - else if (header == "EI") - { + else if (header == "EI") { if (!courtroom_constructed) goto end; - // +1 because evidence starts at 1 rather than 0 for whatever reason - //enjoy fanta + // enjoy fanta if (f_contents.at(0).toInt() != loaded_evidence + 1) goto end; @@ -327,33 +310,35 @@ void AOApplication::server_packet_received(AOPacket *p_packet) evi_type f_evi; f_evi.name = sub_elements.at(0); f_evi.description = sub_elements.at(1); - //no idea what the number at position 2 is. probably an identifier? + // no idea what the number at position 2 is. probably an identifier? f_evi.image = sub_elements.at(3); ++loaded_evidence; - w_lobby->set_loading_text("Loading evidence:\n" + QString::number(loaded_evidence) + "/" + QString::number(evidence_list_size)); + w_lobby->set_loading_text("Loading evidence:\n" + + QString::number(loaded_evidence) + "/" + + QString::number(evidence_list_size)); w_courtroom->append_evidence(f_evi); - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = ((loaded_chars + loaded_evidence) / static_cast(total_loading_size)) * 100; + int total_loading_size = + char_list_size + evidence_list_size + music_list_size; + int loading_value = ((loaded_chars + loaded_evidence) / + static_cast(total_loading_size)) * + 100; w_lobby->set_loading_value(loading_value); QString next_packet_number = QString::number(loaded_evidence); send_server_packet(new AOPacket("AE#" + next_packet_number + "#%")); - } - else if (header == "EM") - { + else if (header == "EM") { if (!courtroom_constructed) goto end; bool music_turn = false; int areas = 0; - for (int n_element = 0 ; n_element < f_contents.size() ; n_element += 2) - { + for (int n_element = 0; n_element < f_contents.size(); n_element += 2) { if (f_contents.at(n_element).toInt() != loaded_music) break; @@ -364,49 +349,45 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ++loaded_music; - w_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + QString::number(music_list_size)); + w_lobby->set_loading_text("Loading music:\n" + + QString::number(loaded_music) + "/" + + QString::number(music_list_size)); - if (music_turn) - { - w_courtroom->append_music(f_music); + if (music_turn) { + w_courtroom->append_music(f_music); } - else - { - if (f_music.endsWith(".wav") || - f_music.endsWith(".mp3") || - f_music.endsWith(".mp4") || - f_music.endsWith(".ogg") || - f_music.endsWith(".opus")) - { - music_turn = true; - areas--; - w_courtroom->fix_last_area(); - w_courtroom->append_music(f_music); - } - else - { - w_courtroom->append_area(f_music); - areas++; - } + else { + if (f_music.endsWith(".wav") || f_music.endsWith(".mp3") || + f_music.endsWith(".mp4") || f_music.endsWith(".ogg") || + f_music.endsWith(".opus")) { + music_turn = true; + areas--; + w_courtroom->fix_last_area(); + w_courtroom->append_music(f_music); + } + else { + w_courtroom->append_area(f_music); + areas++; + } } qDebug() << f_music; - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = ((loaded_chars + loaded_evidence + loaded_music) / static_cast(total_loading_size)) * 100; + int total_loading_size = + char_list_size + evidence_list_size + music_list_size; + int loading_value = ((loaded_chars + loaded_evidence + loaded_music) / + static_cast(total_loading_size)) * + 100; w_lobby->set_loading_value(loading_value); - } QString next_packet_number = QString::number(((loaded_music - 1) / 10) + 1); send_server_packet(new AOPacket("AM#" + next_packet_number + "#%")); } - else if (header == "CharsCheck") - { + else if (header == "CharsCheck") { if (!courtroom_constructed) goto end; - for (int n_char = 0 ; n_char < f_contents.size() ; ++n_char) - { + for (int n_char = 0; n_char < f_contents.size(); ++n_char) { if (f_contents.at(n_char) == "-1") w_courtroom->set_taken(n_char, true); else @@ -414,13 +395,11 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } } - else if (header == "SC") - { + else if (header == "SC") { if (!courtroom_constructed) goto end; - for (int n_element = 0 ; n_element < f_contents.size() ; ++n_element) - { + for (int n_element = 0; n_element < f_contents.size(); ++n_element) { QStringList sub_elements = f_contents.at(n_element).split("&"); char_type f_char; @@ -428,70 +407,70 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (sub_elements.size() >= 2) f_char.description = sub_elements.at(1); - //temporary. the CharsCheck packet sets this properly + // temporary. the CharsCheck packet sets this properly f_char.taken = false; ++loaded_chars; - w_lobby->set_loading_text("Loading chars:\n" + QString::number(loaded_chars) + "/" + QString::number(char_list_size)); + w_lobby->set_loading_text("Loading chars:\n" + + QString::number(loaded_chars) + "/" + + QString::number(char_list_size)); w_courtroom->append_char(f_char); } - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = (loaded_chars / static_cast(total_loading_size)) * 100; + int total_loading_size = + char_list_size + evidence_list_size + music_list_size; + int loading_value = + (loaded_chars / static_cast(total_loading_size)) * 100; w_lobby->set_loading_value(loading_value); send_server_packet(new AOPacket("RM#%")); } - else if (header == "SM") - { + else if (header == "SM") { if (!courtroom_constructed) goto end; bool musics_time = false; int areas = 0; - for (int n_element = 0 ; n_element < f_contents.size() ; ++n_element) - { + for (int n_element = 0; n_element < f_contents.size(); ++n_element) { ++loaded_music; - w_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + QString::number(music_list_size)); + w_lobby->set_loading_text("Loading music:\n" + + QString::number(loaded_music) + "/" + + QString::number(music_list_size)); - if (musics_time) - { - w_courtroom->append_music(f_contents.at(n_element)); + if (musics_time) { + w_courtroom->append_music(f_contents.at(n_element)); } - else - { - if (f_contents.at(n_element).endsWith(".wav") || - f_contents.at(n_element).endsWith(".mp3") || - f_contents.at(n_element).endsWith(".mp4") || - f_contents.at(n_element).endsWith(".ogg") || - f_contents.at(n_element).endsWith(".opus")) - { - musics_time = true; - w_courtroom->fix_last_area(); - w_courtroom->append_music(f_contents.at(n_element)); - areas--; - } - else - { - w_courtroom->append_area(f_contents.at(n_element)); - areas++; - } + else { + if (f_contents.at(n_element).endsWith(".wav") || + f_contents.at(n_element).endsWith(".mp3") || + f_contents.at(n_element).endsWith(".mp4") || + f_contents.at(n_element).endsWith(".ogg") || + f_contents.at(n_element).endsWith(".opus")) { + musics_time = true; + w_courtroom->fix_last_area(); + w_courtroom->append_music(f_contents.at(n_element)); + areas--; + } + else { + w_courtroom->append_area(f_contents.at(n_element)); + areas++; + } } - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = (loaded_chars / static_cast(total_loading_size)) * 100; + int total_loading_size = + char_list_size + evidence_list_size + music_list_size; + int loading_value = + (loaded_chars / static_cast(total_loading_size)) * 100; w_lobby->set_loading_value(loading_value); - } send_server_packet(new AOPacket("RD#%")); } - else if (header == "FM") - { + else if (header == "FM") { if (!courtroom_constructed) goto end; @@ -501,39 +480,33 @@ void AOApplication::server_packet_received(AOPacket *p_packet) bool musics_time = false; int areas = 0; - for (int n_element = 0 ; n_element < f_contents.size() ; ++n_element) - { - if (musics_time) - { - w_courtroom->append_music(f_contents.at(n_element)); + for (int n_element = 0; n_element < f_contents.size(); ++n_element) { + if (musics_time) { + w_courtroom->append_music(f_contents.at(n_element)); } - else - { - if (f_contents.at(n_element).endsWith(".wav") || - f_contents.at(n_element).endsWith(".mp3") || - f_contents.at(n_element).endsWith(".mp4") || - f_contents.at(n_element).endsWith(".ogg") || - f_contents.at(n_element).endsWith(".opus")) - { - musics_time = true; - w_courtroom->fix_last_area(); - w_courtroom->append_music(f_contents.at(n_element)); - areas--; -// qDebug() << "wtf!!" << f_contents.at(n_element); - } - else - { - w_courtroom->append_area(f_contents.at(n_element)); - areas++; - } + else { + if (f_contents.at(n_element).endsWith(".wav") || + f_contents.at(n_element).endsWith(".mp3") || + f_contents.at(n_element).endsWith(".mp4") || + f_contents.at(n_element).endsWith(".ogg") || + f_contents.at(n_element).endsWith(".opus")) { + musics_time = true; + w_courtroom->fix_last_area(); + w_courtroom->append_music(f_contents.at(n_element)); + areas--; + // qDebug() << "wtf!!" << f_contents.at(n_element); + } + else { + w_courtroom->append_area(f_contents.at(n_element)); + areas++; + } } } w_courtroom->list_music(); w_courtroom->list_areas(); } - else if (header == "DONE") - { + else if (header == "DONE") { if (!courtroom_constructed) goto end; @@ -543,8 +516,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) destruct_lobby(); } - else if (header == "BN") - { + else if (header == "BN") { if (f_contents.size() < 1) goto end; @@ -553,45 +525,38 @@ void AOApplication::server_packet_received(AOPacket *p_packet) w_courtroom->set_scene(); } } - //server accepting char request(CC) packet - else if (header == "PV") - { + // server accepting char request(CC) packet + else if (header == "PV") { if (f_contents.size() < 3) goto end; if (courtroom_constructed) w_courtroom->enter_courtroom(f_contents.at(2).toInt()); } - else if (header == "MS") - { + else if (header == "MS") { if (courtroom_constructed && courtroom_loaded) w_courtroom->handle_chatmessage(&p_packet->get_contents()); } - else if (header == "MC") - { + else if (header == "MC") { if (courtroom_constructed && courtroom_loaded) w_courtroom->handle_song(&p_packet->get_contents()); } - else if (header == "RT") - { + else if (header == "RT") { if (f_contents.size() < 1) goto end; if (courtroom_constructed) w_courtroom->handle_wtce(f_contents.at(0)); } - else if (header == "HP") - { + 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()); + w_courtroom->set_hp_bar(f_contents.at(0).toInt(), + f_contents.at(1).toInt()); } - else if (header == "LE") - { - if (courtroom_constructed) - { + else if (header == "LE") { + if (courtroom_constructed) { QVector f_evi_list; - for (QString f_string : f_contents) - { + for (QString f_string : f_contents) { QStringList sub_contents = f_string.split("&"); if (sub_contents.size() < 3) @@ -608,25 +573,20 @@ void AOApplication::server_packet_received(AOPacket *p_packet) w_courtroom->set_evidence_list(f_evi_list); } } - else if (header == "IL") - { + else if (header == "IL") { if (courtroom_constructed && f_contents.size() > 0) w_courtroom->set_ip_list(f_contents.at(0)); } - else if (header == "MU") - { + else if (header == "MU") { if (courtroom_constructed && f_contents.size() > 0) w_courtroom->set_mute(true, f_contents.at(0).toInt()); } - else if (header == "UM") - { + else if (header == "UM") { if (courtroom_constructed && f_contents.size() > 0) w_courtroom->set_mute(false, f_contents.at(0).toInt()); } - else if (header == "KK") - { - if (courtroom_constructed && f_contents.size() > 0) - { + else if (header == "KK") { + if (courtroom_constructed && f_contents.size() > 0) { int f_cid = w_courtroom->get_cid(); int remote_cid = f_contents.at(0).toInt(); @@ -637,45 +597,35 @@ void AOApplication::server_packet_received(AOPacket *p_packet) construct_lobby(); destruct_courtroom(); } - } - else if (header == "KB") - { + else if (header == "KB") { if (courtroom_constructed && f_contents.size() > 0) w_courtroom->set_ban(f_contents.at(0).toInt()); } - else if (header == "BD") - { + else if (header == "BD") { call_notice("You are banned on this server."); } - else if (header == "ZZ") - { + else if (header == "ZZ") { if (courtroom_constructed && f_contents.size() > 0) w_courtroom->mod_called(f_contents.at(0)); } - else if (header == "CL") - { + else if (header == "CL") { w_courtroom->handle_clock(f_contents.at(1)); } - else if (header == "VA") - { - if (courtroom_constructed) - { + else if (header == "VA") { + if (courtroom_constructed) { w_courtroom->handle_theme_variant(f_contents.at(0)); } } - else if (header == "TR") - { + else if (header == "TR") { // Timer resume if (f_contents.size() != 1) goto end; int timer_id = f_contents.at(0).toInt(); w_courtroom->resume_timer(timer_id); - } - else if (header == "TST") - { + else if (header == "TST") { // Timer set time if (f_contents.size() != 2) goto end; @@ -684,8 +634,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int new_time = f_contents.at(1).toInt(); w_courtroom->set_timer_time(timer_id, new_time); } - else if (header == "TSS") - { + else if (header == "TSS") { // Timer set timeStep length if (f_contents.size() != 2) goto end; @@ -694,8 +643,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int timestep_length = f_contents.at(1).toInt(); w_courtroom->set_timer_timestep(timer_id, timestep_length); } - else if (header == "TSF") - { + else if (header == "TSF") { // Timer set Firing interval if (f_contents.size() != 2) goto end; @@ -704,8 +652,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int firing_interval = f_contents.at(1).toInt(); w_courtroom->set_timer_firing(timer_id, firing_interval); } - else if (header == "TP") - { + else if (header == "TP") { // Timer pause if (f_contents.size() != 1) goto end; @@ -713,8 +660,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int timer_id = f_contents.at(0).toInt(); w_courtroom->pause_timer(timer_id); } - else if (header == "SP") - { + else if (header == "SP") { // Set position if (f_contents.size() != 1) goto end; @@ -722,7 +668,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) w_courtroom->set_character_position(f_contents.at(0), true); } - end: +end: delete p_packet; } @@ -747,15 +693,13 @@ void AOApplication::send_server_packet(AOPacket *p_packet, bool encoded) QString f_packet = p_packet->to_string(); - if (encryption_needed) - { + if (encryption_needed) { qDebug() << "S(e):" << f_packet; p_packet->encrypt_header(s_decryptor); f_packet = p_packet->to_string(); } - else - { + else { qDebug() << "S:" << f_packet; } diff --git a/path_functions.cpp b/path_functions.cpp index 37cf3b39f..e14af8672 100644 --- a/path_functions.cpp +++ b/path_functions.cpp @@ -1,8 +1,8 @@ #include "aoapplication.h" #include "courtroom.h" #include "file_functions.h" -#include #include +#include #include #ifdef BASE_OVERRIDE @@ -12,29 +12,26 @@ QString base_path = ""; QString AOApplication::get_base_path() { - if (base_path == "") - { + if (base_path == "") { #ifdef BASE_OVERRIDE - base_path = base_override; + base_path = base_override; #elif defined(ANDROID) - QString sdcard_storage = getenv("SECONDARY_STORAGE"); - if (dir_exists(sdcard_storage + "/AO2/")){ - base_path = sdcard_storage + "/AO2/"; - }else{ - QString external_storage = getenv("EXTERNAL_STORAGE"); - base_path = external_storage + "/AO2/"; - } + QString sdcard_storage = getenv("SECONDARY_STORAGE"); + if (dir_exists(sdcard_storage + "/AO2/")) { + base_path = sdcard_storage + "/AO2/"; + } + else { + QString external_storage = getenv("EXTERNAL_STORAGE"); + base_path = external_storage + "/AO2/"; + } #else - base_path = QDir::currentPath() + "/base/"; + base_path = QDir::currentPath() + "/base/"; #endif -} - return base_path; + } + return base_path; } -QString AOApplication::get_data_path() -{ - return get_base_path() + "data/"; -} +QString AOApplication::get_data_path() { return get_base_path() + "data/"; } QString AOApplication::get_theme_path() { @@ -43,8 +40,8 @@ QString AOApplication::get_theme_path() QString AOApplication::get_theme_variant_path() { - return get_base_path() + "themes/" + get_theme().toLower() + "/variants/" + get_theme_variant() - + "/"; + return get_base_path() + "themes/" + get_theme().toLower() + "/variants/" + + get_theme_variant() + "/"; } QString AOApplication::get_default_theme_path() @@ -81,7 +78,8 @@ QString AOApplication::get_background_path() { if (courtroom_constructed) return w_courtroom->get_background_path(); - //this function being called when the courtroom isn't constructed makes no sense + // this function being called when the courtroom isn't constructed makes no + // sense return ""; } @@ -92,19 +90,20 @@ QString AOApplication::get_default_background_path() QString AOApplication::get_evidence_path() { - QString default_path = "evidence/"; - QString alt_path = "items/"; - if (dir_exists(default_path)) - return get_base_path() + default_path; - else if (dir_exists(alt_path)) - return get_base_path() + alt_path; - else - return get_base_path() + default_path; + QString default_path = "evidence/"; + QString alt_path = "items/"; + if (dir_exists(default_path)) + return get_base_path() + default_path; + else if (dir_exists(alt_path)) + return get_base_path() + alt_path; + else + return get_base_path() + default_path; } QString Courtroom::get_background_path() { - return ao_app->get_base_path() + "background/" + current_background.toLower() + "/"; + return ao_app->get_base_path() + "background/" + + current_background.toLower() + "/"; } QString Courtroom::get_default_background_path() diff --git a/text_file_functions.cpp b/text_file_functions.cpp index 74c561f20..a77948911 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -4,98 +4,77 @@ #include "aoconfig.h" -#include +#include +#include #include +#include #include -#include -#include -QString AOApplication::get_theme() -{ - return config->theme(); -} +QString AOApplication::get_theme() { return config->theme(); } -QString AOApplication::get_theme_variant() -{ - return config->theme_variant(); -} +QString AOApplication::get_theme_variant() { return config->theme_variant(); } -int AOApplication::read_blip_rate() -{ - return config->blip_rate(); -} +int AOApplication::read_blip_rate() { return config->blip_rate(); } bool AOApplication::read_chatlog_newline() { - return config->log_uses_newline_enabled(); + return config->log_uses_newline_enabled(); } -int AOApplication::get_default_music() -{ - return config->music_volume(); -} +int AOApplication::get_default_music() { return config->music_volume(); } -int AOApplication::get_default_sfx() -{ - return config->effects_volume(); -} +int AOApplication::get_default_sfx() { return config->effects_volume(); } -int AOApplication::get_default_blip() -{ - return config->blips_volume(); -} +int AOApplication::get_default_blip() { return config->blips_volume(); } QStringList AOApplication::get_callwords() { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - return config->callwords().split(" ", QString::SkipEmptyParts); + return config->callwords().split(" ", QString::SkipEmptyParts); #else - return config->callwords().split(" ", Qt::SkipEmptyParts); + return config->callwords().split(" ", Qt::SkipEmptyParts); #endif } QString AOApplication::read_note(QString filename) { - QFile note_txt(filename); + QFile note_txt(filename); - if(!note_txt.open(QIODevice::ReadOnly | QFile::Text)) - { - qDebug() << "Couldn't open" << filename; - return ""; - } + if (!note_txt.open(QIODevice::ReadOnly | QFile::Text)) { + qDebug() << "Couldn't open" << filename; + return ""; + } - QTextStream in(¬e_txt); - QString text = in.readAll(); - note_txt.close(); - return text; + QTextStream in(¬e_txt); + QString text = in.readAll(); + note_txt.close(); + return text; } void AOApplication::write_note(QString p_text, QString p_file) { - QFile f_log(p_file); - if(f_log.open(QIODevice::WriteOnly | QFile::Text)) - { - QTextStream out(&f_log); + QFile f_log(p_file); + if (f_log.open(QIODevice::WriteOnly | QFile::Text)) { + QTextStream out(&f_log); - out << p_text; + out << p_text; - f_log.flush(); - f_log.close(); - } + f_log.flush(); + f_log.close(); + } } void AOApplication::append_note(QString p_line, QString p_file) { QFile f_log(p_file); - if(f_log.open(QIODevice::WriteOnly | QIODevice::Append)) - { - QTextStream out(&f_log); + if (f_log.open(QIODevice::WriteOnly | QIODevice::Append)) { + QTextStream out(&f_log); - out << p_line << "\r\n"; + out << p_line << "\r\n"; - f_log.flush(); - f_log.close(); - } + f_log.flush(); + f_log.close(); + } } void AOApplication::write_to_serverlist_txt(QString p_line) @@ -105,8 +84,7 @@ void AOApplication::write_to_serverlist_txt(QString p_line) serverlist_txt.setFileName(serverlist_txt_path); - if (!serverlist_txt.open(QIODevice::WriteOnly | QIODevice::Append)) - { + if (!serverlist_txt.open(QIODevice::WriteOnly | QIODevice::Append)) { return; } @@ -126,21 +104,19 @@ QVector AOApplication::read_serverlist_txt() serverlist_txt.setFileName(serverlist_txt_path); - if (!serverlist_txt.open(QIODevice::ReadOnly)) - { + if (!serverlist_txt.open(QIODevice::ReadOnly)) { return f_server_list; } QTextStream in(&serverlist_txt); - while(!in.atEnd()) - { + while (!in.atEnd()) { QString line = in.readLine(); server_type f_server; QStringList line_contents = line.split(":"); if (line_contents.size() < 3) - continue; + continue; f_server.ip = line_contents.at(0); f_server.port = line_contents.at(1).toInt(); @@ -153,22 +129,21 @@ QVector AOApplication::read_serverlist_txt() return f_server_list; } -QString AOApplication::read_design_ini(QString p_identifier, QString p_design_path) +QString AOApplication::read_design_ini(QString p_identifier, + QString p_design_path) { QFile design_ini; design_ini.setFileName(p_design_path); - if (!design_ini.open(QIODevice::ReadOnly)) - { + if (!design_ini.open(QIODevice::ReadOnly)) { return ""; } QTextStream in(&design_ini); QString result = ""; - while (!in.atEnd()) - { + while (!in.atEnd()) { QString f_line = in.readLine().trimmed(); if (!f_line.startsWith(p_identifier)) @@ -219,7 +194,8 @@ QPoint AOApplication::get_button_spacing(QString p_identifier, QString p_file) return return_value; } -pos_size_type AOApplication::get_element_dimensions(QString p_identifier, QString p_file) +pos_size_type AOApplication::get_element_dimensions(QString p_identifier, + QString p_file) { pos_size_type return_value; return_value.x = 0; @@ -290,10 +266,10 @@ QString AOApplication::get_sfx(QString p_identifier) QString AOApplication::get_stylesheet(QString target_tag, QString p_file) { - QStringList paths{get_theme_variant_path() + p_file, get_theme_path() + p_file}; + QStringList paths{get_theme_variant_path() + p_file, + get_theme_path() + p_file}; - for (QString path : paths) - { + for (QString path : paths) { QFile design_ini; design_ini.setFileName(path); if (!design_ini.open(QIODevice::ReadOnly)) @@ -303,17 +279,14 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) QString f_text; bool tag_found = false; - while (!in.atEnd()) - { + while (!in.atEnd()) { QString line = in.readLine(); - if (line.startsWith(target_tag, Qt::CaseInsensitive)) - { + if (line.startsWith(target_tag, Qt::CaseInsensitive)) { tag_found = true; continue; } - if (tag_found) - { + if (tag_found) { if ((line.startsWith("[") && line.endsWith("]"))) break; f_text.append(line); @@ -332,10 +305,10 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) QVector AOApplication::get_highlight_color() { QString p_file = "courtroom_config.ini"; - QStringList paths{get_theme_variant_path() + p_file, get_theme_path() + p_file}; + QStringList paths{get_theme_variant_path() + p_file, + get_theme_path() + p_file}; - for (QString path : paths) - { + for (QString path : paths) { QVector f_vec; QFile design_ini; @@ -346,28 +319,25 @@ QVector AOApplication::get_highlight_color() QTextStream in(&design_ini); bool tag_found = false; - while (!in.atEnd()) - { + while (!in.atEnd()) { QString line = in.readLine(); - if (line.startsWith("[HIGHLIGHTS]", Qt::CaseInsensitive)) - { + if (line.startsWith("[HIGHLIGHTS]", Qt::CaseInsensitive)) { tag_found = true; continue; } - if (tag_found) - { - if((line.startsWith("[") && line.endsWith("]"))) + if (tag_found) { + if ((line.startsWith("[") && line.endsWith("]"))) break; // Syntax // OpenercharCloserchar = Color, Shown - // Shown is 1 if the character should be displayed in IC, 0 otherwise. If not present, - // assume 1. + // Shown is 1 if the character should be displayed in IC, 0 otherwise. + // If not present, assume 1. QString chars = line.split("=")[0].trimmed(); - QString chars_parameters = line.mid(line.indexOf("=")+1); + QString chars_parameters = line.mid(line.indexOf("=") + 1); QStringList parameters = chars_parameters.split(","); - for (int i=0; i AOApplication::get_highlight_color() QString AOApplication::get_spbutton(QString p_tag, int index) { QString p_file = "courtroom_config.ini"; - QStringList paths{get_theme_variant_path() + p_file, get_theme_path() + p_file}; + QStringList paths{get_theme_variant_path() + p_file, + get_theme_path() + p_file}; - for (QString path : paths) - { + for (QString path : paths) { QString res = ""; QFile design_ini; @@ -402,20 +372,17 @@ QString AOApplication::get_spbutton(QString p_tag, int index) QTextStream in(&design_ini); bool tag_found = false; - while (!in.atEnd()) - { + while (!in.atEnd()) { QString line = in.readLine(); - if (line.startsWith(p_tag, Qt::CaseInsensitive)) - { + if (line.startsWith(p_tag, Qt::CaseInsensitive)) { tag_found = true; continue; } - if (tag_found) - { + if (tag_found) { if ((line.startsWith("[") && line.endsWith("]"))) - break; + break; QStringList line_contents = line.split("="); if (line_contents.at(0).trimmed() == QString::number(index)) @@ -435,10 +402,10 @@ QString AOApplication::get_spbutton(QString p_tag, int index) QStringList AOApplication::get_effect(int index) { QString p_file = "courtroom_config.ini"; - QStringList paths{get_theme_variant_path() + p_file, get_theme_path() + p_file}; + QStringList paths{get_theme_variant_path() + p_file, + get_theme_path() + p_file}; - for (QString path : paths) - { + for (QString path : paths) { QStringList res; QFile design_ini; @@ -449,18 +416,15 @@ QStringList AOApplication::get_effect(int index) QTextStream in(&design_ini); bool tag_found = false; - while (!in.atEnd()) - { + while (!in.atEnd()) { QString line = in.readLine(); - if (line.startsWith("[EFFECTS]", Qt::CaseInsensitive)) - { + if (line.startsWith("[EFFECTS]", Qt::CaseInsensitive)) { tag_found = true; continue; } - if (tag_found) - { + if (tag_found) { if ((line.startsWith("[") && line.endsWith("]"))) break; @@ -490,24 +454,23 @@ QStringList AOApplication::get_sfx_list() QFile char_sfx_list_ini; base_sfx_list_ini.setFileName(get_base_path() + "configs/sounds.ini"); - char_sfx_list_ini.setFileName(get_character_path(get_current_char()) + "sounds.ini"); + char_sfx_list_ini.setFileName(get_character_path(get_current_char()) + + "sounds.ini"); - if (!char_sfx_list_ini.open(QIODevice::ReadOnly) && !base_sfx_list_ini.open(QIODevice::ReadOnly)) - { + if (!char_sfx_list_ini.open(QIODevice::ReadOnly) && + !base_sfx_list_ini.open(QIODevice::ReadOnly)) { return return_value; } QTextStream in_a(&base_sfx_list_ini); QTextStream in_b(&char_sfx_list_ini); - while (!in_a.atEnd()) - { + while (!in_a.atEnd()) { QString line = in_a.readLine(); return_value.append(line); } - while (!in_b.atEnd()) - { + while (!in_b.atEnd()) { QString line = in_b.readLine(); return_value.append(line); } @@ -515,9 +478,11 @@ QStringList AOApplication::get_sfx_list() return return_value; } -//returns whatever is to the right of "search_line =" within target_tag and terminator_tag, trimmed -//returns the empty string if the search line couldnt be found -QString AOApplication::read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag) +// returns whatever is to the right of "search_line =" within target_tag and +// terminator_tag, trimmed returns the empty string if the search line couldnt be +// found +QString AOApplication::read_char_ini(QString p_char, QString p_search_line, + QString target_tag, QString terminator_tag) { QString char_ini_path = get_character_path(p_char) + "char.ini"; @@ -532,15 +497,13 @@ QString AOApplication::read_char_ini(QString p_char, QString p_search_line, QStr bool tag_found = false; - while(!in.atEnd()) - { + while (!in.atEnd()) { QString line = in.readLine(); if (QString::compare(line, terminator_tag, Qt::CaseInsensitive) == 0) break; - if (line.startsWith(target_tag, Qt::CaseInsensitive)) - { + if (line.startsWith(target_tag, Qt::CaseInsensitive)) { tag_found = true; continue; } @@ -550,14 +513,14 @@ QString AOApplication::read_char_ini(QString p_char, QString p_search_line, QStr QStringList line_elements = line.split("="); - if (QString::compare(line_elements.at(0).trimmed(), p_search_line, Qt::CaseInsensitive) != 0) + if (QString::compare(line_elements.at(0).trimmed(), p_search_line, + Qt::CaseInsensitive) != 0) continue; if (line_elements.size() < 2) continue; - if (tag_found) - { + if (tag_found) { char_ini.close(); return line_elements.at(1).trimmed(); } @@ -573,36 +536,39 @@ QString AOApplication::get_char_name(QString p_char) if (f_result == "") return p_char; - else return f_result; + else + return f_result; } QString AOApplication::get_showname(QString p_char) { QString f_result = read_showname(p_char); - if(f_result == "") + if (f_result == "") f_result = read_char_ini(p_char, "showname", "[Options]", "[Time]"); if (f_result == "") return p_char; - else return f_result; + else + return f_result; } QString AOApplication::read_showname(QString p_char) { QString f_filename = get_base_path() + "configs/shownames.ini"; QFile f_file(f_filename); - if(!f_file.open(QIODevice::ReadOnly)) - { qDebug() << "Error reading" << f_filename; return ""; } + if (!f_file.open(QIODevice::ReadOnly)) { + qDebug() << "Error reading" << f_filename; + return ""; + } QTextStream in(&f_file); - while(!in.atEnd()) - { + while (!in.atEnd()) { QString f_line = in.readLine(); - if(!f_line.startsWith(p_char)) + if (!f_line.startsWith(p_char)) continue; QStringList line_elements = f_line.split("="); - if(line_elements.at(0).trimmed() == p_char) + if (line_elements.at(0).trimmed() == p_char) return line_elements.at(1).trimmed(); } return ""; @@ -614,7 +580,8 @@ QString AOApplication::get_char_side(QString p_char) if (f_result == "") return "wit"; - else return f_result; + else + return f_result; } QString AOApplication::get_gender(QString p_char) @@ -623,14 +590,16 @@ QString AOApplication::get_gender(QString p_char) if (f_result == "") return "male"; - else return f_result; + else + return f_result; } QString AOApplication::get_chat(QString p_char) { QString f_result = read_char_ini(p_char, "chat", "[Options]", "[Time]"); - //handling the correct order of chat is a bit complicated, we let the caller do it + // handling the correct order of chat is a bit complicated, we let the caller + // do it return f_result.toLower(); } @@ -647,68 +616,75 @@ int AOApplication::get_emote_number(QString p_char) if (f_result == "") return 0; - else return f_result.toInt(); + else + return f_result.toInt(); } QString AOApplication::get_emote_comment(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), + "[Emotions]", "[Offsets]"); QStringList result_contents = f_result.split("#"); - if (result_contents.size() < 4) - { + if (result_contents.size() < 4) { qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; return "normal"; } - else return result_contents.at(0); + else + return result_contents.at(0); } QString AOApplication::get_pre_emote(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), + "[Emotions]", "[Offsets]"); QStringList result_contents = f_result.split("#"); - if (result_contents.size() < 4) - { + if (result_contents.size() < 4) { qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; return ""; } - else return result_contents.at(1); + else + return result_contents.at(1); } QString AOApplication::get_emote(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), + "[Emotions]", "[Offsets]"); QStringList result_contents = f_result.split("#"); - if (result_contents.size() < 4) - { + if (result_contents.size() < 4) { qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; return "normal"; } - else return result_contents.at(2); + else + return result_contents.at(2); } int AOApplication::get_emote_mod(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), + "[Emotions]", "[Offsets]"); QStringList result_contents = f_result.split("#"); - if (result_contents.size() < 4) - { - qDebug() << "W: misformatted char.ini: " << p_char << ", " << QString::number(p_emote); + if (result_contents.size() < 4) { + qDebug() << "W: misformatted char.ini: " << p_char << ", " + << QString::number(p_emote); return 0; } - else return result_contents.at(3).toInt(); + else + return result_contents.at(3).toInt(); } int AOApplication::get_desk_mod(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), + "[Emotions]", "[Offsets]"); QStringList result_contents = f_result.split("#"); @@ -719,22 +695,27 @@ int AOApplication::get_desk_mod(QString p_char, int p_emote) if (string_result == "") return -1; - else return string_result.toInt(); + else + return string_result.toInt(); } QStringList AOApplication::get_effect_offset(QString p_char, int p_effect) { - QStringList f_result = read_char_ini(p_char, QString::number(p_effect), "[Offsets]", "[Overlay]").split(","); + QStringList f_result = + read_char_ini(p_char, QString::number(p_effect), "[Offsets]", "[Overlay]") + .split(","); if (f_result.size() < 2) - return decltype(f_result) {0, 0}; + return decltype(f_result){0, 0}; return f_result; } QStringList AOApplication::get_overlay(QString p_char, int p_effect) { - QStringList f_result = read_char_ini(p_char, QString::number(p_effect), "[Overlay]", "[SoundN]").split("#"); + QStringList f_result = + read_char_ini(p_char, QString::number(p_effect), "[Overlay]", "[SoundN]") + .split("#"); if (f_result.size() < 2) f_result.push_back(""); @@ -744,48 +725,50 @@ QStringList AOApplication::get_overlay(QString p_char, int p_effect) QString AOApplication::get_sfx_name(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[SoundN]", "[SoundT]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), + "[SoundN]", "[SoundT]"); if (f_result == "") return "1"; - else return f_result; + else + return f_result; } int AOApplication::get_sfx_delay(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[SoundT]", "[TextDelay]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), + "[SoundT]", "[TextDelay]"); if (f_result == "") return 1; - else return f_result.toInt(); + else + return f_result.toInt(); } int AOApplication::get_text_delay(QString p_char, QString p_emote) { - QString f_result = read_char_ini(p_char, p_emote, "[TextDelay]", "END_OF_FILE"); + QString f_result = + read_char_ini(p_char, p_emote, "[TextDelay]", "END_OF_FILE"); if (f_result == "") return -1; - else return f_result.toInt(); + else + return f_result.toInt(); } -bool AOApplication::get_blank_blip() -{ - return config->blank_blips_enabled(); -} +bool AOApplication::get_blank_blip() { return config->blank_blips_enabled(); } QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) { // Try to obtain a theme ini from either the current theme variant folder, // the current theme folder or the default theme folder QStringList paths{ - get_theme_variant_path() + p_file, - get_theme_path() + p_file, - get_default_theme_path() + p_file, + get_theme_variant_path() + p_file, + get_theme_path() + p_file, + get_default_theme_path() + p_file, }; - for (QString path: paths) - { + for (QString path : paths) { QString f_result = read_design_ini(p_identifier, path); if (!f_result.isEmpty()) return f_result; From b3de3c2753819611fbb452bae5ed6c8721f93c4f Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 8 Sep 2020 00:06:28 +0200 Subject: [PATCH 083/842] Allman braces --- .clang-format | 2 +- aoabstractplayer.h | 3 +- aoapplication.cpp | 33 +- aoapplication.h | 3 +- aobasshandle.cpp | 12 +- aobasshandle.h | 3 +- aoblipplayer.cpp | 6 +- aoblipplayer.h | 3 +- aobutton.cpp | 6 +- aobutton.h | 3 +- aocharbutton.cpp | 6 +- aocharbutton.h | 3 +- aocharmovie.cpp | 31 +- aocharmovie.h | 3 +- aoconfig.cpp | 9 +- aoconfig.h | 3 +- aoconfigpanel.cpp | 6 +- aoconfigpanel.h | 3 +- aoemotebutton.cpp | 12 +- aoemotebutton.h | 3 +- aoevidencebutton.cpp | 6 +- aoevidencebutton.h | 3 +- aoevidencedescription.h | 3 +- aoevidencedisplay.cpp | 9 +- aoevidencedisplay.h | 3 +- aoexception.h | 3 +- aoguiloader.cpp | 6 +- aoguiloader.h | 3 +- aoimage.h | 3 +- aolabel.h | 3 +- aolineedit.h | 3 +- aomovie.cpp | 12 +- aomovie.h | 3 +- aomusicplayer.cpp | 6 +- aomusicplayer.h | 3 +- aonotearea.cpp | 12 +- aonotearea.h | 3 +- aonotepad.h | 3 +- aonotepicker.cpp | 6 +- aonotepicker.h | 3 +- aopacket.cpp | 12 +- aopacket.h | 3 +- aopixmap.cpp | 3 +- aopixmap.h | 3 +- aoscene.cpp | 6 +- aoscene.h | 3 +- aosfxplayer.cpp | 6 +- aosfxplayer.h | 3 +- aoshoutplayer.cpp | 6 +- aoshoutplayer.h | 3 +- aotextarea.cpp | 6 +- aotextarea.h | 3 +- aotimer.cpp | 12 +- aotimer.h | 6 +- bass.h | 992 ++++++++++++++++++++------------------ charselect.cpp | 27 +- courtroom.cpp | 373 +++++++++----- courtroom.h | 10 +- courtroom_widgets.cpp | 186 ++++--- datatypes.h | 33 +- discord-rpc.h | 93 ++-- discord_register.h | 11 +- discord_rich_presence.cpp | 9 +- discord_rich_presence.h | 6 +- discord_rpc.h | 93 ++-- emotes.cpp | 18 +- encryption_functions.cpp | 12 +- evidence.cpp | 24 +- file_functions.cpp | 3 +- hardware_functions.cpp | 6 +- hex_functions.cpp | 6 +- hex_functions.h | 3 +- lobby.cpp | 24 +- lobby.h | 3 +- main.cpp | 3 +- networkmanager.cpp | 54 ++- networkmanager.h | 3 +- packet_distribution.cpp | 234 ++++++--- path_functions.cpp | 9 +- text_file_functions.cpp | 115 +++-- 80 files changed, 1603 insertions(+), 1071 deletions(-) diff --git a/.clang-format b/.clang-format index ddf0a58f1..b91e86c94 100644 --- a/.clang-format +++ b/.clang-format @@ -1,2 +1,2 @@ BasedOnStyle : LLVM -BreakBeforeBraces : Stroustrup +BreakBeforeBraces : Allman diff --git a/aoabstractplayer.h b/aoabstractplayer.h index a9856ff1c..849c09a2c 100644 --- a/aoabstractplayer.h +++ b/aoabstractplayer.h @@ -7,7 +7,8 @@ #include "aoapplication.h" #include "aobasshandle.h" -class AOAbstractPlayer : public QObject { +class AOAbstractPlayer : public QObject +{ Q_OBJECT public: diff --git a/aoapplication.cpp b/aoapplication.cpp index f90eb59bf..1beacd310 100644 --- a/aoapplication.cpp +++ b/aoapplication.cpp @@ -44,7 +44,8 @@ AOApplication::~AOApplication() void AOApplication::construct_lobby() { - if (lobby_constructed) { + if (lobby_constructed) + { qDebug() << "W: lobby was attempted constructed when it already exists"; return; } @@ -64,7 +65,8 @@ void AOApplication::construct_lobby() void AOApplication::destruct_lobby() { - if (!lobby_constructed) { + if (!lobby_constructed) + { qDebug() << "W: lobby was attempted destructed when it did not exist"; return; } @@ -75,7 +77,8 @@ void AOApplication::destruct_lobby() void AOApplication::construct_courtroom() { - if (courtroom_constructed) { + if (courtroom_constructed) + { qDebug() << "W: courtroom was attempted constructed when it already exists"; return; } @@ -95,11 +98,13 @@ void AOApplication::construct_courtroom() void AOApplication::destruct_courtroom() { // destruct courtroom - if (courtroom_constructed) { + if (courtroom_constructed) + { delete w_courtroom; courtroom_constructed = false; } - else { + else + { qDebug() << "W: courtroom was attempted destructed when it did not exist"; } @@ -141,7 +146,8 @@ QString AOApplication::get_current_char() void AOApplication::toggle_config_panel() { config_panel->setVisible(!config_panel->isVisible()); - if (config_panel->isVisible()) { + if (config_panel->isVisible()) + { QRect screenGeometry = QApplication::desktop()->screenGeometry(); int x = (screenGeometry.width() - config_panel->width()) / 2; int y = (screenGeometry.height() - config_panel->height()) / 2; @@ -225,21 +231,26 @@ void AOApplication::loading_cancelled() void AOApplication::ms_connect_finished(bool connected, bool will_retry) { - if (connected) { + if (connected) + { AOPacket *f_packet = new AOPacket("ALL#%"); send_ms_packet(f_packet); } - else { - if (!lobby_constructed) { + else + { + if (!lobby_constructed) + { return; } - else if (will_retry) { + else if (will_retry) + { w_lobby->append_error( "Error connecting to master server. Will try again in " + QString::number(net_manager->ms_reconnect_delay_ms / 1000.f) + " seconds."); } - else { + else + { call_error("There was an error connecting to the master server.\n" "We deploy multiple master servers to mitigate any possible " "downtime, " diff --git a/aoapplication.h b/aoapplication.h index c455055cf..ea54aca7f 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -15,7 +15,8 @@ class Courtroom; class AOConfig; class AOConfigPanel; -class AOApplication : public QApplication { +class AOApplication : public QApplication +{ Q_OBJECT public: diff --git a/aobasshandle.cpp b/aobasshandle.cpp index db380867e..438312cfe 100644 --- a/aobasshandle.cpp +++ b/aobasshandle.cpp @@ -11,12 +11,14 @@ AOBassHandle::AOBassHandle(QString p_file, bool p_suicide, AOBassHandle::~AOBassHandle() { - if (m_handle && m_sync) { + if (m_handle && m_sync) + { BASS_ChannelRemoveSync(m_handle, m_sync); } // nothing will go wrong if the handle isn't initialized, I promise...! - if (m_handle) { + if (m_handle) + { BASS_StreamFree(m_handle); } } @@ -50,7 +52,8 @@ void AOBassHandle::set_file(QString p_file, bool p_suicide) noexcept(false) m_sync = BASS_ChannelSetSync(m_handle, BASS_SYNC_END, 0, &AOBassHandle::static_sync, this); - if (!m_sync) { + if (!m_sync) + { // free stream since we can't sync BASS_StreamFree(m_handle); @@ -74,7 +77,8 @@ void AOBassHandle::stop() { BASS_ChannelStop(m_handle); } void CALLBACK AOBassHandle::static_sync(HSYNC handle, DWORD channel, DWORD data, void *user) { - if (auto self = static_cast(user)) { + if (auto self = static_cast(user)) + { self->sync(data); } } diff --git a/aobasshandle.h b/aobasshandle.h index 4471e9a3a..554f9289a 100644 --- a/aobasshandle.h +++ b/aobasshandle.h @@ -13,7 +13,8 @@ * The class may destroy itself once the audio provided is done playing. */ -class AOBassHandle : public QObject { +class AOBassHandle : public QObject +{ Q_OBJECT public: diff --git a/aoblipplayer.cpp b/aoblipplayer.cpp index ebaa06461..0f7060cc8 100644 --- a/aoblipplayer.cpp +++ b/aoblipplayer.cpp @@ -10,7 +10,8 @@ void AOBlipPlayer::set_blips(QString p_sfx) { QString f_path = ao_app->get_sounds_path() + p_sfx.toLower(); - for (int n_stream = 0; n_stream < BLIP_COUNT; ++n_stream) { + for (int n_stream = 0; n_stream < BLIP_COUNT; ++n_stream) + { BASS_StreamFree(m_stream_list[n_stream]); m_stream_list[n_stream] = BASS_StreamCreateFile( @@ -38,7 +39,8 @@ void AOBlipPlayer::set_volume(int p_value) float volume = p_value / 100.0f; - for (int n_stream = 0; n_stream < BLIP_COUNT; ++n_stream) { + for (int n_stream = 0; n_stream < BLIP_COUNT; ++n_stream) + { BASS_ChannelSetAttribute(m_stream_list[n_stream], BASS_ATTRIB_VOL, volume); } } diff --git a/aoblipplayer.h b/aoblipplayer.h index 1547b1885..020c5c2a3 100644 --- a/aoblipplayer.h +++ b/aoblipplayer.h @@ -10,7 +10,8 @@ const int BLIP_COUNT = 5; -class AOBlipPlayer { +class AOBlipPlayer +{ public: AOBlipPlayer(QWidget *parent, AOApplication *p_ao_app); diff --git a/aobutton.cpp b/aobutton.cpp index 8d680259e..89c151055 100644 --- a/aobutton.cpp +++ b/aobutton.cpp @@ -18,7 +18,8 @@ void AOButton::set_image(QString p_image) QString image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); QString hover_image_path = ao_app->get_image_path(image_name + "_hover.png"); - if (file_exists(image_path)) { + if (file_exists(image_path)) + { if (file_exists(hover_image_path)) this->setStyleSheet("QPushButton {border-image:url(\"" + image_path + "\");}" @@ -27,7 +28,8 @@ void AOButton::set_image(QString p_image) else this->setStyleSheet("border-image:url(\"" + image_path + "\")"); } - else { + else + { image_path = ""; this->setStyleSheet("border-image:url(\"" + image_path + "\")"); } diff --git a/aobutton.h b/aobutton.h index c16d3f9e6..894e23447 100644 --- a/aobutton.h +++ b/aobutton.h @@ -5,7 +5,8 @@ #include -class AOButton : public QPushButton { +class AOButton : public QPushButton +{ Q_OBJECT public: diff --git a/aocharbutton.cpp b/aocharbutton.cpp index 30b5a8a05..9ad213a67 100644 --- a/aocharbutton.cpp +++ b/aocharbutton.cpp @@ -37,12 +37,14 @@ void AOCharButton::set_image(QString p_character) if (file_exists(image_path)) this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - else if (file_exists(legacy_path)) { + else if (file_exists(legacy_path)) + { this->setStyleSheet("border-image:url(\"" + legacy_path + "\")"); // ninja optimization QFile::copy(legacy_path, image_path); } - else { + else + { this->setStyleSheet("border-image:url()"); this->setText(p_character); } diff --git a/aocharbutton.h b/aocharbutton.h index c1ac9ea31..461af1283 100644 --- a/aocharbutton.h +++ b/aocharbutton.h @@ -8,7 +8,8 @@ #include #include -class AOCharButton : public QPushButton { +class AOCharButton : public QPushButton +{ Q_OBJECT public: diff --git a/aocharmovie.cpp b/aocharmovie.cpp index a79757272..38fa97a96 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -36,12 +36,15 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, ao_app->get_default_theme_path() + "placeholder" // .gif }; - for (auto &f_file : f_paths) { + for (auto &f_file : f_paths) + { bool found = false; - for (auto &ext : QStringList{".webp", ".apng", ".gif", ".png"}) { + for (auto &ext : QStringList{".webp", ".apng", ".gif", ".png"}) + { QString fullPath = f_file + ext; found = file_exists(fullPath); - if (found) { + if (found) + { target_path = fullPath; break; } @@ -57,8 +60,8 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, movie_frames.clear(); QImageReader *reader = new QImageReader(target_path); - for (int i = 0; i < reader->imageCount(); - ++i) { // optimize can be better, but I'll just keep it like that for now + for (int i = 0; i < reader->imageCount(); ++i) + { // optimize can be better, but I'll just keep it like that for now QImage f_image = reader->read(); if (m_mirror) f_image = f_image.mirrored(true, false); @@ -80,9 +83,11 @@ bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) { // figure out what extension the animation is using QString f_source_path = ao_app->get_character_path(p_char) + p_emote.toLower(); - for (QString &i_ext : QStringList{".webp", ".apng", ".gif", ".png"}) { + for (QString &i_ext : QStringList{".webp", ".apng", ".gif", ".png"}) + { QString f_target_path = f_source_path + i_ext; - if (file_exists(f_target_path)) { + if (file_exists(f_target_path)) + { f_file_path = f_target_path; f_file_exist = true; break; @@ -91,7 +96,8 @@ bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) } // play if it exist - if (f_file_exist) { + if (f_file_exist) + { m_reader->stop(); this->clear(); m_play_once = true; @@ -148,15 +154,18 @@ void AOCharMovie::combo_resize(QSize p_size) void AOCharMovie::on_frame_changed(int p_frame_num) { - if (movie_frames.size() > p_frame_num) { + if (movie_frames.size() > p_frame_num) + { AOPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(p_frame_num)); this->setPixmap(f_pixmap.scale_to_size(this->size())); } // pre-anim only - if (m_play_once) { + if (m_play_once) + { int f_frame_count = m_reader->frameCount(); - if (f_frame_count == 0 || p_frame_num == (f_frame_count - 1)) { + if (f_frame_count == 0 || p_frame_num == (f_frame_count - 1)) + { int f_frame_delay = m_reader->nextFrameDelay(); if (f_frame_delay < 0) f_frame_delay = 0; diff --git a/aocharmovie.h b/aocharmovie.h index 0dcd8d31b..24d4bea77 100644 --- a/aocharmovie.h +++ b/aocharmovie.h @@ -9,7 +9,8 @@ class AOApplication; -class AOCharMovie : public QLabel { +class AOCharMovie : public QLabel +{ Q_OBJECT public: diff --git a/aoconfig.cpp b/aoconfig.cpp index 8583d09f0..3d5423082 100644 --- a/aoconfig.cpp +++ b/aoconfig.cpp @@ -10,7 +10,8 @@ but hey, when has ao2 ever cared? Wait, am I using the term wrong? Nice. */ -class AOConfigPrivate : public QObject { +class AOConfigPrivate : public QObject +{ Q_OBJECT friend AOConfig; @@ -224,7 +225,8 @@ public slots: void invoke_parents(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)) { - for (QObject *i_parent : parents) { + for (QObject *i_parent : parents) + { QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), p_arg1); } @@ -239,7 +241,8 @@ static AOConfigPrivate *d = nullptr; AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) { // init if not created yet - if (d == nullptr) { + if (d == nullptr) + { d = new AOConfigPrivate; } diff --git a/aoconfig.h b/aoconfig.h index 9278ba22c..5210ffa67 100644 --- a/aoconfig.h +++ b/aoconfig.h @@ -3,7 +3,8 @@ // qt #include -class AOConfig : public QObject { +class AOConfig : public QObject +{ Q_OBJECT public: diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp index 3032907c0..0b5f217ba 100644 --- a/aoconfigpanel.cpp +++ b/aoconfigpanel.cpp @@ -169,7 +169,8 @@ void AOConfigPanel::refresh_theme_list() // themes for (QString i_folder : - QDir(QDir::currentPath() + "/base/themes").entryList(QDir::Dirs)) { + QDir(QDir::currentPath() + "/base/themes").entryList(QDir::Dirs)) + { if (i_folder == "." || i_folder == "..") continue; w_theme->addItem(i_folder); @@ -195,7 +196,8 @@ void AOConfigPanel::refresh_theme_variant_list() // themes for (QString i_folder : QDir(QDir::currentPath() + "/base/themes/" + m_config->theme() + "/variants/") - .entryList(QDir::Dirs)) { + .entryList(QDir::Dirs)) + { if (i_folder == "." || i_folder == "..") continue; w_theme_variant->addItem(i_folder, i_folder); diff --git a/aoconfigpanel.h b/aoconfigpanel.h index 7473bed9e..51310cee5 100644 --- a/aoconfigpanel.h +++ b/aoconfigpanel.h @@ -14,7 +14,8 @@ #include "aoconfig.h" #include "aoguiloader.h" -class AOConfigPanel : public QWidget { +class AOConfigPanel : public QWidget +{ Q_OBJECT AOConfig *m_config = nullptr; diff --git a/aoemotebutton.cpp b/aoemotebutton.cpp index d65111955..882021a97 100644 --- a/aoemotebutton.cpp +++ b/aoemotebutton.cpp @@ -26,13 +26,16 @@ void AOEmoteButton::set_image(QString p_char, int p_emote, QString suffix) "emotions/hovers/button" + emotion_number + "_hover" + suffix; - if (file_exists(image_path)) { + if (file_exists(image_path)) + { this->setText(""); this->setStyleSheet("border-image:url(\"" + image_path + "\")"); } - else if (file_exists(alt_path)) { + else if (file_exists(alt_path)) + { this->setText(""); - if (file_exists(hover_path)) { + if (file_exists(hover_path)) + { this->setStyleSheet("QPushButton {border-image:url(\"" + alt_path + "\");}" "QPushButton:hover {border-image:url(\"" + @@ -41,7 +44,8 @@ void AOEmoteButton::set_image(QString p_char, int p_emote, QString suffix) else this->setStyleSheet("border-image:url(\"" + alt_path + "\")"); } - else { + else + { this->setText(ao_app->get_emote_comment(p_char, p_emote)); this->setStyleSheet("border-image:url(\"\")"); } diff --git a/aoemotebutton.h b/aoemotebutton.h index b65e3fc83..5bb69fc99 100644 --- a/aoemotebutton.h +++ b/aoemotebutton.h @@ -5,7 +5,8 @@ #include "aoapplication.h" -class AOEmoteButton : public QPushButton { +class AOEmoteButton : public QPushButton +{ Q_OBJECT public: diff --git a/aoevidencebutton.cpp b/aoevidencebutton.cpp index adb983a37..077723a89 100644 --- a/aoevidencebutton.cpp +++ b/aoevidencebutton.cpp @@ -41,11 +41,13 @@ void AOEvidenceButton::set_image(QString p_image) { QString image_path = ao_app->get_evidence_path() + p_image; - if (file_exists(image_path)) { + if (file_exists(image_path)) + { this->setText(""); this->setStyleSheet("border-image:url(\"" + image_path + "\")"); } - else { + else + { this->setText(p_image); this->setStyleSheet(""); } diff --git a/aoevidencebutton.h b/aoevidencebutton.h index 20e3c275d..ece4f8d14 100644 --- a/aoevidencebutton.h +++ b/aoevidencebutton.h @@ -7,7 +7,8 @@ #include #include -class AOEvidenceButton : public QPushButton { +class AOEvidenceButton : public QPushButton +{ Q_OBJECT public: diff --git a/aoevidencedescription.h b/aoevidencedescription.h index e0c924821..b4d399e40 100644 --- a/aoevidencedescription.h +++ b/aoevidencedescription.h @@ -3,7 +3,8 @@ #include -class AOEvidenceDescription : public QPlainTextEdit { +class AOEvidenceDescription : public QPlainTextEdit +{ Q_OBJECT public: AOEvidenceDescription(QWidget *parent); diff --git a/aoevidencedisplay.cpp b/aoevidencedisplay.cpp index 77c4e508d..2d3e3d468 100644 --- a/aoevidencedisplay.cpp +++ b/aoevidencedisplay.cpp @@ -32,11 +32,13 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, QString gif_name; QString icon_identifier; - if (is_left_side) { + if (is_left_side) + { icon_identifier = "left_evidence_icon"; gif_name = "evidence_appear_left.gif"; } - else { + else + { icon_identifier = "right_evidence_icon"; gif_name = "evidence_appear_right.gif"; } @@ -61,7 +63,8 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, void AOEvidenceDisplay::frame_change(int p_frame) { - if (p_frame == (evidence_movie->frameCount() - 1)) { + if (p_frame == (evidence_movie->frameCount() - 1)) + { // we need this or else the last frame wont show delay(evidence_movie->nextFrameDelay()); diff --git a/aoevidencedisplay.h b/aoevidencedisplay.h index 8c31c1bb2..95d7e5b24 100644 --- a/aoevidencedisplay.h +++ b/aoevidencedisplay.h @@ -8,7 +8,8 @@ #include "aopixmap.h" #include "aosfxplayer.h" -class AOEvidenceDisplay : public QLabel { +class AOEvidenceDisplay : public QLabel +{ Q_OBJECT public: diff --git a/aoexception.h b/aoexception.h index d29046d15..173ccb5d4 100644 --- a/aoexception.h +++ b/aoexception.h @@ -5,7 +5,8 @@ #include -class AOException : public std::exception { +class AOException : public std::exception +{ public: AOException() = default; AOException(QString p_msg); diff --git a/aoguiloader.cpp b/aoguiloader.cpp index d99a486cc..fd4099696 100644 --- a/aoguiloader.cpp +++ b/aoguiloader.cpp @@ -13,11 +13,13 @@ QWidget *AOGuiLoader::load_from_file(QString p_file_path, QWidget *p_parent) QWidget *r_widget = nullptr; QFile f_file(p_file_path); - if (f_file.open(QIODevice::ReadOnly)) { + if (f_file.open(QIODevice::ReadOnly)) + { r_widget = load(&f_file, p_parent); // lazily replace the parent's layout with our own - if (p_parent != nullptr) { + if (p_parent != nullptr) + { QVBoxLayout *f_parent_layout = new QVBoxLayout(p_parent); f_parent_layout->addWidget(r_widget); p_parent->setLayout(f_parent_layout); diff --git a/aoguiloader.h b/aoguiloader.h index 4413cd05c..13bfe4e65 100644 --- a/aoguiloader.h +++ b/aoguiloader.h @@ -7,7 +7,8 @@ // we could make a smarter system but let's keep it simple ;) #define AO_GUI_WIDGET(p_type, p_name) findChild(p_name) -class AOGuiLoader : public QUiLoader { +class AOGuiLoader : public QUiLoader +{ Q_OBJECT public: diff --git a/aoimage.h b/aoimage.h index 97f903cff..0f91fc638 100644 --- a/aoimage.h +++ b/aoimage.h @@ -8,7 +8,8 @@ #include -class AOImage : public QLabel { +class AOImage : public QLabel +{ public: AOImage(QWidget *parent, AOApplication *p_ao_app); diff --git a/aolabel.h b/aolabel.h index b54e2a56f..02256977b 100644 --- a/aolabel.h +++ b/aolabel.h @@ -5,7 +5,8 @@ #include -class AOLabel : public QLabel { +class AOLabel : public QLabel +{ public: AOLabel(QWidget *parent, AOApplication *p_ao_app); diff --git a/aolineedit.h b/aolineedit.h index 70f1f978b..52e1d7966 100644 --- a/aolineedit.h +++ b/aolineedit.h @@ -4,7 +4,8 @@ #include #include -class AOLineEdit : public QLineEdit { +class AOLineEdit : public QLineEdit +{ Q_OBJECT public: diff --git a/aomovie.cpp b/aomovie.cpp index 42f8e2bc5..2ead87a77 100644 --- a/aomovie.cpp +++ b/aomovie.cpp @@ -48,12 +48,15 @@ void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) ao_app->get_theme_path() + "placeholder", ao_app->get_default_theme_path() + "placeholder"}; - for (auto &f_file : f_paths) { + for (auto &f_file : f_paths) + { bool found = false; - for (auto &ext : decltype(f_vec){".webp", ".apng", ".gif", ".png"}) { + for (auto &ext : decltype(f_vec){".webp", ".apng", ".gif", ".png"}) + { QString fullPath = f_file + ext; found = file_exists(fullPath); - if (found) { + if (found) + { file_path = fullPath; break; } @@ -79,7 +82,8 @@ void AOMovie::stop() void AOMovie::frame_change(int n_frame) { - if (n_frame == (m_movie->frameCount() - 1) && play_once) { + if (n_frame == (m_movie->frameCount() - 1) && play_once) + { // we need this or else the last frame wont show delay(m_movie->nextFrameDelay()); diff --git a/aomovie.h b/aomovie.h index 4f9b3e2e8..c890783ce 100644 --- a/aomovie.h +++ b/aomovie.h @@ -7,7 +7,8 @@ class Courtroom; class AOApplication; -class AOMovie : public QLabel { +class AOMovie : public QLabel +{ Q_OBJECT public: diff --git a/aomusicplayer.cpp b/aomusicplayer.cpp index 70682d186..e0801d8b1 100644 --- a/aomusicplayer.cpp +++ b/aomusicplayer.cpp @@ -17,7 +17,8 @@ void AOMusicPlayer::play(QString p_file) m_file = f_file; - try { // create new song + try + { // create new song AOBassHandle *handle = new AOBassHandle(m_file, false, this); connect(this, &AOMusicPlayer::new_volume, handle, &AOBassHandle::set_volume); @@ -31,7 +32,8 @@ void AOMusicPlayer::play(QString p_file) m_handle->set_volume(get_volume()); m_handle->play(); } - catch (const std::exception &e_exception) { + catch (const std::exception &e_exception) + { qDebug() << e_exception.what(); } } diff --git a/aomusicplayer.h b/aomusicplayer.h index 07b6fced1..b1c0a2614 100644 --- a/aomusicplayer.h +++ b/aomusicplayer.h @@ -3,7 +3,8 @@ #include "aoabstractplayer.h" -class AOMusicPlayer : public AOAbstractPlayer { +class AOMusicPlayer : public AOAbstractPlayer +{ Q_OBJECT public: diff --git a/aonotearea.cpp b/aonotearea.cpp index 2d3bf72dd..3abfd27a5 100644 --- a/aonotearea.cpp +++ b/aonotearea.cpp @@ -45,7 +45,8 @@ void Courtroom::on_add_button_clicked() f_notepicker->setLayout(f_layout); ui_note_area->m_layout->addWidget(f_notepicker); - if (contains_add_button) { + if (contains_add_button) + { ui_note_area->m_layout->removeWidget(ui_note_area->add_button); ui_note_area->m_layout->addWidget(ui_note_area->add_button); set_note_files(); @@ -65,7 +66,8 @@ void Courtroom::set_note_files() QString filename = ao_app->get_base_path() + "configs/filesabstract.ini"; QFile config_file(filename); - if (!config_file.open(QIODevice::ReadOnly | QIODevice::Text)) { + if (!config_file.open(QIODevice::ReadOnly | QIODevice::Text)) + { qDebug() << "Couldn't open" << filename; return; } @@ -74,7 +76,8 @@ void Courtroom::set_note_files() QByteArray t = ""; - for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) { + for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) + { AONotePicker *f_notepicker = static_cast( ui_note_area->m_layout->itemAt(i)->widget()); QString f_filestring = f_notepicker->real_file; @@ -87,7 +90,8 @@ void Courtroom::set_note_files() config_file.close(); QFile ex(filename); - if (!ex.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + if (!ex.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) + { qDebug() << "Couldn't open" << filename; return; } diff --git a/aonotearea.h b/aonotearea.h index cc57ca4ae..f2f6ec433 100644 --- a/aonotearea.h +++ b/aonotearea.h @@ -12,7 +12,8 @@ #include "aoapplication.h" -class AONoteArea : public AOImage { +class AONoteArea : public AOImage +{ Q_OBJECT public: diff --git a/aonotepad.h b/aonotepad.h index 306fff7d0..b9a74ee42 100644 --- a/aonotepad.h +++ b/aonotepad.h @@ -5,7 +5,8 @@ #include "aoapplication.h" -class AONotepad : public QTextEdit { +class AONotepad : public QTextEdit +{ Q_OBJECT public: diff --git a/aonotepicker.cpp b/aonotepicker.cpp index 635dafe74..05f625625 100644 --- a/aonotepicker.cpp +++ b/aonotepicker.cpp @@ -13,7 +13,8 @@ AONotePicker::AONotePicker(QWidget *p_parent, AOApplication *p_ao_app) void Courtroom::on_file_selected() { - for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) { + for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) + { AONotePicker *f_notepicker = static_cast( ui_note_area->m_layout->itemAt(i)->widget()); f_notepicker->m_hover->set_image("note_select.png"); @@ -31,7 +32,8 @@ void Courtroom::on_set_file_button_clicked() AOButton *f_button = static_cast(sender()); AONotePicker *f_notepicker = static_cast(f_button->parent()); QString f_filename = QFileDialog::getOpenFileName(this, "Open File"); - if (f_filename != "") { + if (f_filename != "") + { f_notepicker->m_line->setText(f_filename); f_notepicker->real_file = f_filename; diff --git a/aonotepicker.h b/aonotepicker.h index f2173ad42..393abf024 100644 --- a/aonotepicker.h +++ b/aonotepicker.h @@ -6,7 +6,8 @@ #include #include -class AONotePicker : public QLabel { +class AONotePicker : public QLabel +{ Q_OBJECT public: diff --git a/aopacket.cpp b/aopacket.cpp index 304523e50..5bb6bfec7 100644 --- a/aopacket.cpp +++ b/aopacket.cpp @@ -10,7 +10,8 @@ AOPacket::AOPacket(QString p_packet_string) m_header = packet_contents.at(0); - for (int n_string = 1; n_string < packet_contents.size() - 1; ++n_string) { + for (int n_string = 1; n_string < packet_contents.size() - 1; ++n_string) + { m_contents.append(packet_contents.at(n_string)); } } @@ -25,7 +26,8 @@ QString AOPacket::to_string() { QString f_string = m_header; - for (QString i_string : m_contents) { + for (QString i_string : m_contents) + { f_string += ("#" + i_string); } @@ -53,7 +55,8 @@ void AOPacket::decrypt_header(unsigned int p_key) void AOPacket::net_encode() { - for (int n_element = 0; n_element < m_contents.size(); ++n_element) { + for (int n_element = 0; n_element < m_contents.size(); ++n_element) + { QString f_element = m_contents.at(n_element); f_element.replace("#", "") .replace("%", "") @@ -67,7 +70,8 @@ void AOPacket::net_encode() void AOPacket::net_decode() { - for (int n_element = 0; n_element < m_contents.size(); ++n_element) { + for (int n_element = 0; n_element < m_contents.size(); ++n_element) + { QString f_element = m_contents.at(n_element); f_element.replace("", "#") .replace("", "%") diff --git a/aopacket.h b/aopacket.h index d1e8aad27..f97b15149 100644 --- a/aopacket.h +++ b/aopacket.h @@ -4,7 +4,8 @@ #include #include -class AOPacket { +class AOPacket +{ public: AOPacket(QString p_packet_string); AOPacket(QString header, QStringList &p_contents); diff --git a/aopixmap.cpp b/aopixmap.cpp index 69692bfbc..ab3ce79e6 100644 --- a/aopixmap.cpp +++ b/aopixmap.cpp @@ -2,7 +2,8 @@ AOPixmap::AOPixmap(QPixmap p_pixmap) : m_pixmap(p_pixmap) { - if (m_pixmap.isNull()) { + if (m_pixmap.isNull()) + { clear(); } } diff --git a/aopixmap.h b/aopixmap.h index 8d0bf543c..648405e25 100644 --- a/aopixmap.h +++ b/aopixmap.h @@ -3,7 +3,8 @@ // qt #include -class AOPixmap { +class AOPixmap +{ public: AOPixmap(QPixmap p_pixmap = QPixmap()); AOPixmap(QString p_file_path); diff --git a/aoscene.cpp b/aoscene.cpp index a12554192..95c8d7b83 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -20,10 +20,12 @@ void AOScene::set_image(QString p_image) // background specific path QString background_path = ao_app->get_background_path() + p_image; - for (auto &ext : QVector{".webp", ".apng", ".gif", ".png"}) { + for (auto &ext : QVector{".webp", ".apng", ".gif", ".png"}) + { QString full_background_path = background_path + ext; - if (file_exists(full_background_path)) { + if (file_exists(full_background_path)) + { target_path = full_background_path; break; } diff --git a/aoscene.h b/aoscene.h index ddf3f3e8f..ac726120d 100644 --- a/aoscene.h +++ b/aoscene.h @@ -6,7 +6,8 @@ class Courtroom; class AOApplication; -class AOScene : public QLabel { +class AOScene : public QLabel +{ Q_OBJECT public: diff --git a/aosfxplayer.cpp b/aosfxplayer.cpp index 5617772d9..6cf8e2c64 100644 --- a/aosfxplayer.cpp +++ b/aosfxplayer.cpp @@ -13,14 +13,16 @@ void AOSfxPlayer::play(QString p_name) { QString f_file = ao_app->get_sounds_path() + p_name.toLower(); - try { + try + { AOBassHandle *handle = new AOBassHandle(f_file, true, this); connect(this, &AOSfxPlayer::new_volume, handle, &AOBassHandle::set_volume); connect(this, &AOSfxPlayer::stopping, handle, &AOBassHandle::stop); handle->set_volume(get_volume()); handle->play(); } - catch (const std::exception &e_exception) { + catch (const std::exception &e_exception) + { qDebug() << e_exception.what(); } } diff --git a/aosfxplayer.h b/aosfxplayer.h index 21c26967e..b83345b3c 100644 --- a/aosfxplayer.h +++ b/aosfxplayer.h @@ -3,7 +3,8 @@ #include "aoabstractplayer.h" -class AOSfxPlayer : public AOAbstractPlayer { +class AOSfxPlayer : public AOAbstractPlayer +{ Q_OBJECT public: diff --git a/aoshoutplayer.cpp b/aoshoutplayer.cpp index ceae0a47c..4938ffd42 100644 --- a/aoshoutplayer.cpp +++ b/aoshoutplayer.cpp @@ -25,7 +25,8 @@ void AOShoutPlayer::play(QString p_name, QString p_char) else f_file = ""; - try { + try + { AOBassHandle *handle = new AOBassHandle(f_file, true, this); connect(this, &AOShoutPlayer::new_volume, handle, &AOBassHandle::set_volume); @@ -33,7 +34,8 @@ void AOShoutPlayer::play(QString p_name, QString p_char) handle->set_volume(get_volume()); handle->play(); } - catch (const std::exception &e_exception) { + catch (const std::exception &e_exception) + { qDebug() << e_exception.what(); } } diff --git a/aoshoutplayer.h b/aoshoutplayer.h index 534c8e23c..15f04b4d2 100644 --- a/aoshoutplayer.h +++ b/aoshoutplayer.h @@ -3,7 +3,8 @@ #include "aoabstractplayer.h" -class AOShoutPlayer : public AOAbstractPlayer { +class AOShoutPlayer : public AOAbstractPlayer +{ Q_OBJECT public: diff --git a/aotextarea.cpp b/aotextarea.cpp index 394328099..f6c5ec918 100644 --- a/aotextarea.cpp +++ b/aotextarea.cpp @@ -52,13 +52,15 @@ void AOTextArea::append_error(QString p_message) void AOTextArea::auto_scroll(QTextCursor old_cursor, int old_scrollbar_value, bool is_scrolled_down) { - if (old_cursor.hasSelection() || !is_scrolled_down) { + if (old_cursor.hasSelection() || !is_scrolled_down) + { // The user has selected text or scrolled away from the bottom: maintain // position. this->setTextCursor(old_cursor); this->verticalScrollBar()->setValue(old_scrollbar_value); } - else { + else + { // The user hasn't selected any text and the scrollbar is at the bottom: // scroll to the bottom. this->moveCursor(QTextCursor::End); diff --git a/aotextarea.h b/aotextarea.h index 237ede660..7fcd853a4 100644 --- a/aotextarea.h +++ b/aotextarea.h @@ -3,7 +3,8 @@ #include -class AOTextArea : public QTextBrowser { +class AOTextArea : public QTextBrowser +{ public: AOTextArea(QWidget *p_parent = nullptr); diff --git a/aotimer.cpp b/aotimer.cpp index eff2099e5..3083bc763 100644 --- a/aotimer.cpp +++ b/aotimer.cpp @@ -29,8 +29,10 @@ void AOTimer::update_time() // This one checks overflows, as the updated manual timer would appear to have // a past time compared to the manual timer a step ago (represented by // old_manual_timer) - if (manual_timer.get_timestep_length() > 0) { - if (manual_timer.get_time().operator<(old_manual_timer.get_time())) { + if (manual_timer.get_timestep_length() > 0) + { + if (manual_timer.get_time().operator<(old_manual_timer.get_time())) + { set_time(QTime(0, 0)); firing_timer.stop(); redraw(); @@ -40,8 +42,10 @@ void AOTimer::update_time() // This one checks underflows, as the updated manual timer would appear to // have a future time compared to the manual timer a step ago (represented by // old_manual_timer) - else if (manual_timer.get_timestep_length() < 0) { - if (manual_timer.get_time().operator>(old_manual_timer.get_time())) { + else if (manual_timer.get_timestep_length() < 0) + { + if (manual_timer.get_time().operator>(old_manual_timer.get_time())) + { set_time(QTime(0, 0)); firing_timer.stop(); redraw(); diff --git a/aotimer.h b/aotimer.h index 6487a748e..1ab051e52 100644 --- a/aotimer.h +++ b/aotimer.h @@ -9,7 +9,8 @@ #include #include -class ManualTimer { +class ManualTimer +{ QTime current_time; int timestep_length; @@ -28,7 +29,8 @@ class ManualTimer { } }; -class AOTimer : public QTextEdit { +class AOTimer : public QTextEdit +{ Q_OBJECT public: diff --git a/bass.h b/bass.h index 89e0a6316..77db5da97 100644 --- a/bass.h +++ b/bass.h @@ -35,7 +35,8 @@ typedef int BOOL; #endif #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif #define BASSVERSION 0x204 // API version @@ -47,15 +48,15 @@ extern "C" { #define NOBASSOVERLOADS #endif -typedef DWORD HMUSIC; // MOD music handle -typedef DWORD HSAMPLE; // sample handle -typedef DWORD HCHANNEL; // playing sample's channel handle -typedef DWORD HSTREAM; // sample stream handle -typedef DWORD HRECORD; // recording handle -typedef DWORD HSYNC; // synchronizer handle -typedef DWORD HDSP; // DSP handle -typedef DWORD HFX; // DX8 effect handle -typedef DWORD HPLUGIN; // Plugin handle + typedef DWORD HMUSIC; // MOD music handle + typedef DWORD HSAMPLE; // sample handle + typedef DWORD HCHANNEL; // playing sample's channel handle + typedef DWORD HSTREAM; // sample stream handle + typedef DWORD HRECORD; // recording handle + typedef DWORD HSYNC; // synchronizer handle + typedef DWORD HDSP; // DSP handle + typedef DWORD HFX; // DX8 effect handle + typedef DWORD HPLUGIN; // Plugin handle // Error codes returned by BASS_ErrorGetCode #define BASS_OK 0 // all is OK @@ -162,18 +163,19 @@ typedef DWORD HPLUGIN; // Plugin handle #define BASS_OBJECT_DS 1 // IDirectSound #define BASS_OBJECT_DS3DL 2 // IDirectSound3DListener -// Device info structure -typedef struct { + // Device info structure + typedef struct + { #if defined(_WIN32_WCE) || \ (WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - const wchar_t *name; // description - const wchar_t *driver; // driver + const wchar_t *name; // description + const wchar_t *driver; // driver #else const char *name; // description const char *driver; // driver #endif - DWORD flags; -} BASS_DEVICEINFO; + DWORD flags; + } BASS_DEVICEINFO; // BASS_DEVICEINFO flags #define BASS_DEVICE_ENABLED 1 @@ -196,25 +198,26 @@ typedef struct { // BASS_GetDeviceInfo flags #define BASS_DEVICES_AIRPLAY 0x1000000 -typedef struct { - DWORD flags; // device capabilities (DSCAPS_xxx flags) - DWORD hwsize; // size of total device hardware memory - DWORD hwfree; // size of free device hardware memory - DWORD freesam; // number of free sample slots in the hardware - DWORD free3d; // number of free 3D sample slots in the hardware - DWORD minrate; // min sample rate supported by the hardware - DWORD maxrate; // max sample rate supported by the hardware - BOOL - eax; // device supports EAX? (always FALSE if BASS_DEVICE_3D was not used) - DWORD minbuf; // recommended minimum buffer length in ms (requires - // BASS_DEVICE_LATENCY) - DWORD dsver; // DirectSound version - DWORD latency; // delay (in ms) before start of playback (requires - // BASS_DEVICE_LATENCY) - DWORD initflags; // BASS_Init "flags" parameter - DWORD speakers; // number of speakers available - DWORD freq; // current output rate -} BASS_INFO; + typedef struct + { + DWORD flags; // device capabilities (DSCAPS_xxx flags) + DWORD hwsize; // size of total device hardware memory + DWORD hwfree; // size of free device hardware memory + DWORD freesam; // number of free sample slots in the hardware + DWORD free3d; // number of free 3D sample slots in the hardware + DWORD minrate; // min sample rate supported by the hardware + DWORD maxrate; // max sample rate supported by the hardware + BOOL eax; // device supports EAX? (always FALSE if BASS_DEVICE_3D was not + // used) + DWORD minbuf; // recommended minimum buffer length in ms (requires + // BASS_DEVICE_LATENCY) + DWORD dsver; // DirectSound version + DWORD latency; // delay (in ms) before start of playback (requires + // BASS_DEVICE_LATENCY) + DWORD initflags; // BASS_Init "flags" parameter + DWORD speakers; // number of speakers available + DWORD freq; // current output rate + } BASS_INFO; // BASS_INFO flags (from DSOUND.H) #define DSCAPS_CONTINUOUSRATE \ @@ -228,14 +231,15 @@ typedef struct { #define DSCAPS_SECONDARY8BIT 0x00000400 // 8 bit #define DSCAPS_SECONDARY16BIT 0x00000800 // 16 bit -// Recording device info structure -typedef struct { - DWORD flags; // device capabilities (DSCCAPS_xxx flags) - DWORD formats; // supported standard formats (WAVE_FORMAT_xxx flags) - DWORD inputs; // number of inputs - BOOL singlein; // TRUE = only 1 input can be set at a time - DWORD freq; // current input rate -} BASS_RECORDINFO; + // Recording device info structure + typedef struct + { + DWORD flags; // device capabilities (DSCCAPS_xxx flags) + DWORD formats; // supported standard formats (WAVE_FORMAT_xxx flags) + DWORD inputs; // number of inputs + BOOL singlein; // TRUE = only 1 input can be set at a time + DWORD freq; // current input rate + } BASS_RECORDINFO; // BASS_RECORDINFO flags (from DSOUND.H) #define DSCCAPS_EMULDRIVER \ @@ -260,26 +264,27 @@ typedef struct { #define WAVE_FORMAT_4S16 0x00000800 /* 44.1 kHz, Stereo, 16-bit */ #endif -// Sample info structure -typedef struct { - DWORD freq; // default playback rate - float volume; // default volume (0-1) - float pan; // default pan (-1=left, 0=middle, 1=right) - DWORD flags; // BASS_SAMPLE_xxx flags - DWORD length; // length (in bytes) - DWORD max; // maximum simultaneous playbacks - DWORD origres; // original resolution bits - DWORD chans; // number of channels - DWORD mingap; // minimum gap (ms) between creating channels - DWORD mode3d; // BASS_3DMODE_xxx mode - float mindist; // minimum distance - float maxdist; // maximum distance - DWORD iangle; // angle of inside projection cone - DWORD oangle; // angle of outside projection cone - float outvol; // delta-volume outside the projection cone - DWORD vam; // voice allocation/management flags (BASS_VAM_xxx) - DWORD priority; // priority (0=lowest, 0xffffffff=highest) -} BASS_SAMPLE; + // Sample info structure + typedef struct + { + DWORD freq; // default playback rate + float volume; // default volume (0-1) + float pan; // default pan (-1=left, 0=middle, 1=right) + DWORD flags; // BASS_SAMPLE_xxx flags + DWORD length; // length (in bytes) + DWORD max; // maximum simultaneous playbacks + DWORD origres; // original resolution bits + DWORD chans; // number of channels + DWORD mingap; // minimum gap (ms) between creating channels + DWORD mode3d; // BASS_3DMODE_xxx mode + float mindist; // minimum distance + float maxdist; // maximum distance + DWORD iangle; // angle of inside projection cone + DWORD oangle; // angle of outside projection cone + float outvol; // delta-volume outside the projection cone + DWORD vam; // voice allocation/management flags (BASS_VAM_xxx) + DWORD priority; // priority (0=lowest, 0xffffffff=highest) + } BASS_SAMPLE; #define BASS_SAMPLE_8BITS 1 // 8 bit #define BASS_SAMPLE_FLOAT 256 // 32 bit floating-point @@ -364,17 +369,18 @@ typedef struct { #define BASS_VAM_TERM_DIST 8 #define BASS_VAM_TERM_PRIO 16 -// Channel info structure -typedef struct { - DWORD freq; // default playback rate - DWORD chans; // channels - DWORD flags; // BASS_SAMPLE/STREAM/MUSIC/SPEAKER flags - DWORD ctype; // type of channel - DWORD origres; // original resolution - HPLUGIN plugin; // plugin - HSAMPLE sample; // sample - const char *filename; // filename -} BASS_CHANNELINFO; + // Channel info structure + typedef struct + { + DWORD freq; // default playback rate + DWORD chans; // channels + DWORD flags; // BASS_SAMPLE/STREAM/MUSIC/SPEAKER flags + DWORD ctype; // type of channel + DWORD origres; // original resolution + HPLUGIN plugin; // plugin + HSAMPLE sample; // sample + const char *filename; // filename + } BASS_CHANNELINFO; // BASS_CHANNELINFO types #define BASS_CTYPE_SAMPLE 1 @@ -397,34 +403,37 @@ typedef struct { #define BASS_CTYPE_MUSIC_IT 0x20004 #define BASS_CTYPE_MUSIC_MO3 0x00100 // MO3 flag -typedef struct { - DWORD ctype; // channel type + typedef struct + { + DWORD ctype; // channel type #if defined(_WIN32_WCE) || \ (WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - const wchar_t *name; // format description - const wchar_t *exts; // file extension filter (*.ext1;*.ext2;etc...) + const wchar_t *name; // format description + const wchar_t *exts; // file extension filter (*.ext1;*.ext2;etc...) #else const char *name; // format description const char *exts; // file extension filter (*.ext1;*.ext2;etc...) #endif -} BASS_PLUGINFORM; - -typedef struct { - DWORD version; // version (same form as BASS_GetVersion) - DWORD formatc; // number of formats - const BASS_PLUGINFORM *formats; // the array of formats -} BASS_PLUGININFO; - -// 3D vector (for 3D positions/velocities/orientations) -typedef struct BASS_3DVECTOR { + } BASS_PLUGINFORM; + + typedef struct + { + DWORD version; // version (same form as BASS_GetVersion) + DWORD formatc; // number of formats + const BASS_PLUGINFORM *formats; // the array of formats + } BASS_PLUGININFO; + + // 3D vector (for 3D positions/velocities/orientations) + typedef struct BASS_3DVECTOR + { #ifdef __cplusplus - BASS_3DVECTOR(){}; - BASS_3DVECTOR(float _x, float _y, float _z) : x(_x), y(_y), z(_z){}; + BASS_3DVECTOR(){}; + BASS_3DVECTOR(float _x, float _y, float _z) : x(_x), y(_y), z(_z){}; #endif - float x; // +=right, -=left - float y; // +=up, -=down - float z; // +=front, -=behind -} BASS_3DVECTOR; + float x; // +=right, -=left + float y; // +=up, -=down + float z; // +=front, -=behind + } BASS_3DVECTOR; // 3D channel modes #define BASS_3DMODE_NORMAL 0 // normal 3D processing @@ -437,37 +446,38 @@ typedef struct BASS_3DVECTOR { #define BASS_3DALG_FULL 2 #define BASS_3DALG_LIGHT 3 -// EAX environments, use with BASS_SetEAXParameters -enum { - EAX_ENVIRONMENT_GENERIC, - EAX_ENVIRONMENT_PADDEDCELL, - EAX_ENVIRONMENT_ROOM, - EAX_ENVIRONMENT_BATHROOM, - EAX_ENVIRONMENT_LIVINGROOM, - EAX_ENVIRONMENT_STONEROOM, - EAX_ENVIRONMENT_AUDITORIUM, - EAX_ENVIRONMENT_CONCERTHALL, - EAX_ENVIRONMENT_CAVE, - EAX_ENVIRONMENT_ARENA, - EAX_ENVIRONMENT_HANGAR, - EAX_ENVIRONMENT_CARPETEDHALLWAY, - EAX_ENVIRONMENT_HALLWAY, - EAX_ENVIRONMENT_STONECORRIDOR, - EAX_ENVIRONMENT_ALLEY, - EAX_ENVIRONMENT_FOREST, - EAX_ENVIRONMENT_CITY, - EAX_ENVIRONMENT_MOUNTAINS, - EAX_ENVIRONMENT_QUARRY, - EAX_ENVIRONMENT_PLAIN, - EAX_ENVIRONMENT_PARKINGLOT, - EAX_ENVIRONMENT_SEWERPIPE, - EAX_ENVIRONMENT_UNDERWATER, - EAX_ENVIRONMENT_DRUGGED, - EAX_ENVIRONMENT_DIZZY, - EAX_ENVIRONMENT_PSYCHOTIC, - - EAX_ENVIRONMENT_COUNT // total number of environments -}; + // EAX environments, use with BASS_SetEAXParameters + enum + { + EAX_ENVIRONMENT_GENERIC, + EAX_ENVIRONMENT_PADDEDCELL, + EAX_ENVIRONMENT_ROOM, + EAX_ENVIRONMENT_BATHROOM, + EAX_ENVIRONMENT_LIVINGROOM, + EAX_ENVIRONMENT_STONEROOM, + EAX_ENVIRONMENT_AUDITORIUM, + EAX_ENVIRONMENT_CONCERTHALL, + EAX_ENVIRONMENT_CAVE, + EAX_ENVIRONMENT_ARENA, + EAX_ENVIRONMENT_HANGAR, + EAX_ENVIRONMENT_CARPETEDHALLWAY, + EAX_ENVIRONMENT_HALLWAY, + EAX_ENVIRONMENT_STONECORRIDOR, + EAX_ENVIRONMENT_ALLEY, + EAX_ENVIRONMENT_FOREST, + EAX_ENVIRONMENT_CITY, + EAX_ENVIRONMENT_MOUNTAINS, + EAX_ENVIRONMENT_QUARRY, + EAX_ENVIRONMENT_PLAIN, + EAX_ENVIRONMENT_PARKINGLOT, + EAX_ENVIRONMENT_SEWERPIPE, + EAX_ENVIRONMENT_UNDERWATER, + EAX_ENVIRONMENT_DRUGGED, + EAX_ENVIRONMENT_DIZZY, + EAX_ENVIRONMENT_PSYCHOTIC, + + EAX_ENVIRONMENT_COUNT // total number of environments + }; // EAX presets, usage: BASS_SetEAXParameters(EAX_PRESET_xxx) #define EAX_PRESET_GENERIC EAX_ENVIRONMENT_GENERIC, 0.5F, 1.493F, 0.5F @@ -499,14 +509,14 @@ enum { #define EAX_PRESET_DIZZY EAX_ENVIRONMENT_DIZZY, 0.139F, 17.234F, 0.666F #define EAX_PRESET_PSYCHOTIC EAX_ENVIRONMENT_PSYCHOTIC, 0.486F, 7.563F, 0.806F -typedef DWORD(CALLBACK STREAMPROC)(HSTREAM handle, void *buffer, DWORD length, - void *user); -/* User stream callback function. NOTE: A stream function should obviously be as -quick as possible, other streams (and MOD musics) can't be mixed until it's -finished. handle : The stream that needs writing buffer : Buffer to write the -samples in length : Number of bytes to write user : The 'user' parameter value -given when calling BASS_StreamCreate RETURN : Number of bytes written. Set the -BASS_STREAMPROC_END flag to end the stream. */ + typedef DWORD(CALLBACK STREAMPROC)(HSTREAM handle, void *buffer, DWORD length, + void *user); + /* User stream callback function. NOTE: A stream function should obviously be + as quick as possible, other streams (and MOD musics) can't be mixed until it's + finished. handle : The stream that needs writing buffer : Buffer to write the + samples in length : Number of bytes to write user : The 'user' parameter + value given when calling BASS_StreamCreate RETURN : Number of bytes written. + Set the BASS_STREAMPROC_END flag to end the stream. */ #define BASS_STREAMPROC_END 0x80000000 // end of user stream flag @@ -519,18 +529,19 @@ BASS_STREAMPROC_END flag to end the stream. */ #define STREAMFILE_BUFFER 1 #define STREAMFILE_BUFFERPUSH 2 -// User file stream callback functions -typedef void(CALLBACK FILECLOSEPROC)(void *user); -typedef QWORD(CALLBACK FILELENPROC)(void *user); -typedef DWORD(CALLBACK FILEREADPROC)(void *buffer, DWORD length, void *user); -typedef BOOL(CALLBACK FILESEEKPROC)(QWORD offset, void *user); + // User file stream callback functions + typedef void(CALLBACK FILECLOSEPROC)(void *user); + typedef QWORD(CALLBACK FILELENPROC)(void *user); + typedef DWORD(CALLBACK FILEREADPROC)(void *buffer, DWORD length, void *user); + typedef BOOL(CALLBACK FILESEEKPROC)(QWORD offset, void *user); -typedef struct { - FILECLOSEPROC *close; - FILELENPROC *length; - FILEREADPROC *read; - FILESEEKPROC *seek; -} BASS_FILEPROCS; + typedef struct + { + FILECLOSEPROC *close; + FILELENPROC *length; + FILEREADPROC *read; + FILESEEKPROC *seek; + } BASS_FILEPROCS; // BASS_StreamPutFileData options #define BASS_FILEDATA_END 0 // end & close the file @@ -547,8 +558,8 @@ typedef struct { #define BASS_FILEPOS_ASYNCBUF 7 #define BASS_FILEPOS_SIZE 8 -typedef void(CALLBACK DOWNLOADPROC)(const void *buffer, DWORD length, - void *user); + typedef void(CALLBACK DOWNLOADPROC)(const void *buffer, DWORD length, + void *user); /* Internet stream download callback function. buffer : Buffer containing the downloaded data... NULL=end of download length : Number of bytes in the buffer @@ -570,30 +581,30 @@ user : The 'user' parameter value given when calling BASS_StreamCreateURL */ #define BASS_SYNC_MIXTIME 0x40000000 // flag: sync at mixtime, else at playtime #define BASS_SYNC_ONETIME 0x80000000 // flag: sync only once, else continuously -typedef void(CALLBACK SYNCPROC)(HSYNC handle, DWORD channel, DWORD data, - void *user); -/* Sync callback function. NOTE: a sync callback function should be very -quick as other syncs can't be processed until it has finished. If the sync -is a "mixtime" sync, then other streams and MOD musics can't be mixed until -it's finished either. -handle : The sync that has occured -channel: Channel that the sync occured in -data : Additional data associated with the sync's occurance -user : The 'user' parameter given when calling BASS_ChannelSetSync */ - -typedef void(CALLBACK DSPPROC)(HDSP handle, DWORD channel, void *buffer, - DWORD length, void *user); -/* DSP callback function. NOTE: A DSP function should obviously be as quick as -possible... other DSP functions, streams and MOD musics can not be processed -until it's finished. -handle : The DSP handle -channel: Channel that the DSP is being applied to -buffer : Buffer to apply the DSP to -length : Number of bytes in the buffer -user : The 'user' parameter given when calling BASS_ChannelSetDSP */ - -typedef BOOL(CALLBACK RECORDPROC)(HRECORD handle, const void *buffer, - DWORD length, void *user); + typedef void(CALLBACK SYNCPROC)(HSYNC handle, DWORD channel, DWORD data, + void *user); + /* Sync callback function. NOTE: a sync callback function should be very + quick as other syncs can't be processed until it has finished. If the sync + is a "mixtime" sync, then other streams and MOD musics can't be mixed until + it's finished either. + handle : The sync that has occured + channel: Channel that the sync occured in + data : Additional data associated with the sync's occurance + user : The 'user' parameter given when calling BASS_ChannelSetSync */ + + typedef void(CALLBACK DSPPROC)(HDSP handle, DWORD channel, void *buffer, + DWORD length, void *user); + /* DSP callback function. NOTE: A DSP function should obviously be as quick as + possible... other DSP functions, streams and MOD musics can not be processed + until it's finished. + handle : The DSP handle + channel: Channel that the DSP is being applied to + buffer : Buffer to apply the DSP to + length : Number of bytes in the buffer + user : The 'user' parameter given when calling BASS_ChannelSetDSP */ + + typedef BOOL(CALLBACK RECORDPROC)(HRECORD handle, const void *buffer, + DWORD length, void *user); /* Recording callback function. handle : The recording handle buffer : Buffer containing the recorded sample data @@ -687,23 +698,25 @@ RETURN : TRUE = continue recording, FALSE = stop */ #define BASS_TAG_MUSIC_SAMPLE \ 0x10300 // + sample #, MOD sample name : ANSI string -// ID3v1 tag structure -typedef struct { - char id[3]; - char title[30]; - char artist[30]; - char album[30]; - char year[4]; - char comment[30]; - BYTE genre; -} TAG_ID3; - -// Binary APE tag structure -typedef struct { - const char *key; - const void *data; - DWORD length; -} TAG_APE_BINARY; + // ID3v1 tag structure + typedef struct + { + char id[3]; + char title[30]; + char artist[30]; + char album[30]; + char year[4]; + char comment[30]; + BYTE genre; + } TAG_ID3; + + // Binary APE tag structure + typedef struct + { + const char *key; + const void *data; + DWORD length; + } TAG_APE_BINARY; // BWF "bext" tag structure #ifdef _MSC_VER @@ -711,84 +724,89 @@ typedef struct { #pragma warning(disable : 4200) #endif #pragma pack(push, 1) -typedef struct { - char Description[256]; // description - char Originator[32]; // name of the originator - char OriginatorReference[32]; // reference of the originator - char OriginationDate[10]; // date of creation (yyyy-mm-dd) - char OriginationTime[8]; // time of creation (hh-mm-ss) - QWORD TimeReference; // first sample count since midnight (little-endian) - WORD Version; // BWF version (little-endian) - BYTE UMID[64]; // SMPTE UMID - BYTE Reserved[190]; + typedef struct + { + char Description[256]; // description + char Originator[32]; // name of the originator + char OriginatorReference[32]; // reference of the originator + char OriginationDate[10]; // date of creation (yyyy-mm-dd) + char OriginationTime[8]; // time of creation (hh-mm-ss) + QWORD TimeReference; // first sample count since midnight (little-endian) + WORD Version; // BWF version (little-endian) + BYTE UMID[64]; // SMPTE UMID + BYTE Reserved[190]; #if defined(__GNUC__) && __GNUC__ < 3 - char CodingHistory[0]; // history -#elif 1 // change to 0 if compiler fails the following line + char CodingHistory[0]; // history +#elif 1 // change to 0 if compiler fails the following line char CodingHistory[]; // history #else char CodingHistory[1]; // history #endif -} TAG_BEXT; + } TAG_BEXT; #pragma pack(pop) -// BWF "cart" tag structures -typedef struct { - DWORD dwUsage; // FOURCC timer usage ID - DWORD dwValue; // timer value in samples from head -} TAG_CART_TIMER; - -typedef struct { - char Version[4]; // version of the data structure - char Title[64]; // title of cart audio sequence - char Artist[64]; // artist or creator name - char CutID[64]; // cut number identification - char ClientID[64]; // client identification - char Category[64]; // category ID, PSA, NEWS, etc - char Classification[64]; // classification or auxiliary key - char OutCue[64]; // out cue text - char StartDate[10]; // yyyy-mm-dd - char StartTime[8]; // hh:mm:ss - char EndDate[10]; // yyyy-mm-dd - char EndTime[8]; // hh:mm:ss - char ProducerAppID[64]; // name of vendor or application - char ProducerAppVersion[64]; // version of producer application - char UserDef[64]; // user defined text - DWORD dwLevelReference; // sample value for 0 dB reference - TAG_CART_TIMER PostTimer[8]; // 8 time markers after head - char Reserved[276]; - char URL[1024]; // uniform resource locator + // BWF "cart" tag structures + typedef struct + { + DWORD dwUsage; // FOURCC timer usage ID + DWORD dwValue; // timer value in samples from head + } TAG_CART_TIMER; + + typedef struct + { + char Version[4]; // version of the data structure + char Title[64]; // title of cart audio sequence + char Artist[64]; // artist or creator name + char CutID[64]; // cut number identification + char ClientID[64]; // client identification + char Category[64]; // category ID, PSA, NEWS, etc + char Classification[64]; // classification or auxiliary key + char OutCue[64]; // out cue text + char StartDate[10]; // yyyy-mm-dd + char StartTime[8]; // hh:mm:ss + char EndDate[10]; // yyyy-mm-dd + char EndTime[8]; // hh:mm:ss + char ProducerAppID[64]; // name of vendor or application + char ProducerAppVersion[64]; // version of producer application + char UserDef[64]; // user defined text + DWORD dwLevelReference; // sample value for 0 dB reference + TAG_CART_TIMER PostTimer[8]; // 8 time markers after head + char Reserved[276]; + char URL[1024]; // uniform resource locator #if defined(__GNUC__) && __GNUC__ < 3 - char TagText[0]; // free form text for scripts or tags -#elif 1 // change to 0 if compiler fails the following line + char TagText[0]; // free form text for scripts or tags +#elif 1 // change to 0 if compiler fails the following line char TagText[]; // free form text for scripts or tags #else char TagText[1]; // free form text for scripts or tags #endif -} TAG_CART; + } TAG_CART; #ifdef _MSC_VER #pragma warning(pop) #endif -// CoreAudio codec info structure -typedef struct { - DWORD ftype; // file format - DWORD atype; // audio format - const char *name; // description -} TAG_CA_CODEC; + // CoreAudio codec info structure + typedef struct + { + DWORD ftype; // file format + DWORD atype; // audio format + const char *name; // description + } TAG_CA_CODEC; #ifndef _WAVEFORMATEX_ #define _WAVEFORMATEX_ #pragma pack(push, 1) -typedef struct tWAVEFORMATEX { - WORD wFormatTag; - WORD nChannels; - DWORD nSamplesPerSec; - DWORD nAvgBytesPerSec; - WORD nBlockAlign; - WORD wBitsPerSample; - WORD cbSize; -} WAVEFORMATEX, *PWAVEFORMATEX, *LPWAVEFORMATEX; -typedef const WAVEFORMATEX *LPCWAVEFORMATEX; + typedef struct tWAVEFORMATEX + { + WORD wFormatTag; + WORD nChannels; + DWORD nSamplesPerSec; + DWORD nAvgBytesPerSec; + WORD nBlockAlign; + WORD wBitsPerSample; + WORD cbSize; + } WAVEFORMATEX, *PWAVEFORMATEX, *LPWAVEFORMATEX; + typedef const WAVEFORMATEX *LPCWAVEFORMATEX; #pragma pack(pop) #endif @@ -820,96 +838,106 @@ typedef const WAVEFORMATEX *LPCWAVEFORMATEX; #define BASS_INPUT_TYPE_AUX 0x09000000 #define BASS_INPUT_TYPE_ANALOG 0x0a000000 -// DX8 effect types, use with BASS_ChannelSetFX -enum { - BASS_FX_DX8_CHORUS, - BASS_FX_DX8_COMPRESSOR, - BASS_FX_DX8_DISTORTION, - BASS_FX_DX8_ECHO, - BASS_FX_DX8_FLANGER, - BASS_FX_DX8_GARGLE, - BASS_FX_DX8_I3DL2REVERB, - BASS_FX_DX8_PARAMEQ, - BASS_FX_DX8_REVERB -}; - -typedef struct { - float fWetDryMix; - float fDepth; - float fFeedback; - float fFrequency; - DWORD lWaveform; // 0=triangle, 1=sine - float fDelay; - DWORD lPhase; // BASS_DX8_PHASE_xxx -} BASS_DX8_CHORUS; - -typedef struct { - float fGain; - float fAttack; - float fRelease; - float fThreshold; - float fRatio; - float fPredelay; -} BASS_DX8_COMPRESSOR; - -typedef struct { - float fGain; - float fEdge; - float fPostEQCenterFrequency; - float fPostEQBandwidth; - float fPreLowpassCutoff; -} BASS_DX8_DISTORTION; - -typedef struct { - float fWetDryMix; - float fFeedback; - float fLeftDelay; - float fRightDelay; - BOOL lPanDelay; -} BASS_DX8_ECHO; - -typedef struct { - float fWetDryMix; - float fDepth; - float fFeedback; - float fFrequency; - DWORD lWaveform; // 0=triangle, 1=sine - float fDelay; - DWORD lPhase; // BASS_DX8_PHASE_xxx -} BASS_DX8_FLANGER; - -typedef struct { - DWORD dwRateHz; // Rate of modulation in hz - DWORD dwWaveShape; // 0=triangle, 1=square -} BASS_DX8_GARGLE; - -typedef struct { - int lRoom; // [-10000, 0] default: -1000 mB - int lRoomHF; // [-10000, 0] default: 0 mB - float flRoomRolloffFactor; // [0.0, 10.0] default: 0.0 - float flDecayTime; // [0.1, 20.0] default: 1.49s - float flDecayHFRatio; // [0.1, 2.0] default: 0.83 - int lReflections; // [-10000, 1000] default: -2602 mB - float flReflectionsDelay; // [0.0, 0.3] default: 0.007 s - int lReverb; // [-10000, 2000] default: 200 mB - float flReverbDelay; // [0.0, 0.1] default: 0.011 s - float flDiffusion; // [0.0, 100.0] default: 100.0 % - float flDensity; // [0.0, 100.0] default: 100.0 % - float flHFReference; // [20.0, 20000.0] default: 5000.0 Hz -} BASS_DX8_I3DL2REVERB; - -typedef struct { - float fCenter; - float fBandwidth; - float fGain; -} BASS_DX8_PARAMEQ; - -typedef struct { - float fInGain; // [-96.0,0.0] default: 0.0 dB - float fReverbMix; // [-96.0,0.0] default: 0.0 db - float fReverbTime; // [0.001,3000.0] default: 1000.0 ms - float fHighFreqRTRatio; // [0.001,0.999] default: 0.001 -} BASS_DX8_REVERB; + // DX8 effect types, use with BASS_ChannelSetFX + enum + { + BASS_FX_DX8_CHORUS, + BASS_FX_DX8_COMPRESSOR, + BASS_FX_DX8_DISTORTION, + BASS_FX_DX8_ECHO, + BASS_FX_DX8_FLANGER, + BASS_FX_DX8_GARGLE, + BASS_FX_DX8_I3DL2REVERB, + BASS_FX_DX8_PARAMEQ, + BASS_FX_DX8_REVERB + }; + + typedef struct + { + float fWetDryMix; + float fDepth; + float fFeedback; + float fFrequency; + DWORD lWaveform; // 0=triangle, 1=sine + float fDelay; + DWORD lPhase; // BASS_DX8_PHASE_xxx + } BASS_DX8_CHORUS; + + typedef struct + { + float fGain; + float fAttack; + float fRelease; + float fThreshold; + float fRatio; + float fPredelay; + } BASS_DX8_COMPRESSOR; + + typedef struct + { + float fGain; + float fEdge; + float fPostEQCenterFrequency; + float fPostEQBandwidth; + float fPreLowpassCutoff; + } BASS_DX8_DISTORTION; + + typedef struct + { + float fWetDryMix; + float fFeedback; + float fLeftDelay; + float fRightDelay; + BOOL lPanDelay; + } BASS_DX8_ECHO; + + typedef struct + { + float fWetDryMix; + float fDepth; + float fFeedback; + float fFrequency; + DWORD lWaveform; // 0=triangle, 1=sine + float fDelay; + DWORD lPhase; // BASS_DX8_PHASE_xxx + } BASS_DX8_FLANGER; + + typedef struct + { + DWORD dwRateHz; // Rate of modulation in hz + DWORD dwWaveShape; // 0=triangle, 1=square + } BASS_DX8_GARGLE; + + typedef struct + { + int lRoom; // [-10000, 0] default: -1000 mB + int lRoomHF; // [-10000, 0] default: 0 mB + float flRoomRolloffFactor; // [0.0, 10.0] default: 0.0 + float flDecayTime; // [0.1, 20.0] default: 1.49s + float flDecayHFRatio; // [0.1, 2.0] default: 0.83 + int lReflections; // [-10000, 1000] default: -2602 mB + float flReflectionsDelay; // [0.0, 0.3] default: 0.007 s + int lReverb; // [-10000, 2000] default: 200 mB + float flReverbDelay; // [0.0, 0.1] default: 0.011 s + float flDiffusion; // [0.0, 100.0] default: 100.0 % + float flDensity; // [0.0, 100.0] default: 100.0 % + float flHFReference; // [20.0, 20000.0] default: 5000.0 Hz + } BASS_DX8_I3DL2REVERB; + + typedef struct + { + float fCenter; + float fBandwidth; + float fGain; + } BASS_DX8_PARAMEQ; + + typedef struct + { + float fInGain; // [-96.0,0.0] default: 0.0 dB + float fReverbMix; // [-96.0,0.0] default: 0.0 db + float fReverbTime; // [0.001,3000.0] default: 1000.0 ms + float fHighFreqRTRatio; // [0.001,0.999] default: 0.001 + } BASS_DX8_REVERB; #define BASS_DX8_PHASE_NEG_180 0 #define BASS_DX8_PHASE_NEG_90 1 @@ -917,168 +945,172 @@ typedef struct { #define BASS_DX8_PHASE_90 3 #define BASS_DX8_PHASE_180 4 -typedef void(CALLBACK IOSNOTIFYPROC)(DWORD status); -/* iOS notification callback function. -status : The notification (BASS_IOSNOTIFY_xxx) */ + typedef void(CALLBACK IOSNOTIFYPROC)(DWORD status); + /* iOS notification callback function. + status : The notification (BASS_IOSNOTIFY_xxx) */ #define BASS_IOSNOTIFY_INTERRUPT 1 // interruption started #define BASS_IOSNOTIFY_INTERRUPT_END 2 // interruption ended -BOOL BASSDEF(BASS_SetConfig)(DWORD option, DWORD value); -DWORD BASSDEF(BASS_GetConfig)(DWORD option); -BOOL BASSDEF(BASS_SetConfigPtr)(DWORD option, const void *value); -void *BASSDEF(BASS_GetConfigPtr)(DWORD option); -DWORD BASSDEF(BASS_GetVersion)(); -int BASSDEF(BASS_ErrorGetCode)(); -BOOL BASSDEF(BASS_GetDeviceInfo)(DWORD device, BASS_DEVICEINFO *info); + BOOL BASSDEF(BASS_SetConfig)(DWORD option, DWORD value); + DWORD BASSDEF(BASS_GetConfig)(DWORD option); + BOOL BASSDEF(BASS_SetConfigPtr)(DWORD option, const void *value); + void *BASSDEF(BASS_GetConfigPtr)(DWORD option); + DWORD BASSDEF(BASS_GetVersion)(); + int BASSDEF(BASS_ErrorGetCode)(); + BOOL BASSDEF(BASS_GetDeviceInfo)(DWORD device, BASS_DEVICEINFO *info); #if defined(_WIN32) && !defined(_WIN32_WCE) && \ !(WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) -BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, HWND win, - const GUID *dsguid); + BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, HWND win, + const GUID *dsguid); #else BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, void *win, void *dsguid); #endif -BOOL BASSDEF(BASS_SetDevice)(DWORD device); -DWORD BASSDEF(BASS_GetDevice)(); -BOOL BASSDEF(BASS_Free)(); + BOOL BASSDEF(BASS_SetDevice)(DWORD device); + DWORD BASSDEF(BASS_GetDevice)(); + BOOL BASSDEF(BASS_Free)(); #if defined(_WIN32) && !defined(_WIN32_WCE) && \ !(WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) -void *BASSDEF(BASS_GetDSoundObject)(DWORD object); + void *BASSDEF(BASS_GetDSoundObject)(DWORD object); #endif -BOOL BASSDEF(BASS_GetInfo)(BASS_INFO *info); -BOOL BASSDEF(BASS_Update)(DWORD length); -float BASSDEF(BASS_GetCPU)(); -BOOL BASSDEF(BASS_Start)(); -BOOL BASSDEF(BASS_Stop)(); -BOOL BASSDEF(BASS_Pause)(); -BOOL BASSDEF(BASS_SetVolume)(float volume); -float BASSDEF(BASS_GetVolume)(); - -HPLUGIN BASSDEF(BASS_PluginLoad)(const char *file, DWORD flags); -BOOL BASSDEF(BASS_PluginFree)(HPLUGIN handle); -const BASS_PLUGININFO *BASSDEF(BASS_PluginGetInfo)(HPLUGIN handle); - -BOOL BASSDEF(BASS_Set3DFactors)(float distf, float rollf, float doppf); -BOOL BASSDEF(BASS_Get3DFactors)(float *distf, float *rollf, float *doppf); -BOOL BASSDEF(BASS_Set3DPosition)(const BASS_3DVECTOR *pos, - const BASS_3DVECTOR *vel, - const BASS_3DVECTOR *front, - const BASS_3DVECTOR *top); -BOOL BASSDEF(BASS_Get3DPosition)(BASS_3DVECTOR *pos, BASS_3DVECTOR *vel, - BASS_3DVECTOR *front, BASS_3DVECTOR *top); -void BASSDEF(BASS_Apply3D)(); + BOOL BASSDEF(BASS_GetInfo)(BASS_INFO *info); + BOOL BASSDEF(BASS_Update)(DWORD length); + float BASSDEF(BASS_GetCPU)(); + BOOL BASSDEF(BASS_Start)(); + BOOL BASSDEF(BASS_Stop)(); + BOOL BASSDEF(BASS_Pause)(); + BOOL BASSDEF(BASS_SetVolume)(float volume); + float BASSDEF(BASS_GetVolume)(); + + HPLUGIN BASSDEF(BASS_PluginLoad)(const char *file, DWORD flags); + BOOL BASSDEF(BASS_PluginFree)(HPLUGIN handle); + const BASS_PLUGININFO *BASSDEF(BASS_PluginGetInfo)(HPLUGIN handle); + + BOOL BASSDEF(BASS_Set3DFactors)(float distf, float rollf, float doppf); + BOOL BASSDEF(BASS_Get3DFactors)(float *distf, float *rollf, float *doppf); + BOOL BASSDEF(BASS_Set3DPosition)(const BASS_3DVECTOR *pos, + const BASS_3DVECTOR *vel, + const BASS_3DVECTOR *front, + const BASS_3DVECTOR *top); + BOOL BASSDEF(BASS_Get3DPosition)(BASS_3DVECTOR *pos, BASS_3DVECTOR *vel, + BASS_3DVECTOR *front, BASS_3DVECTOR *top); + void BASSDEF(BASS_Apply3D)(); #if defined(_WIN32) && !defined(_WIN32_WCE) && \ !(WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) -BOOL BASSDEF(BASS_SetEAXParameters)(int env, float vol, float decay, - float damp); -BOOL BASSDEF(BASS_GetEAXParameters)(DWORD *env, float *vol, float *decay, - float *damp); + BOOL BASSDEF(BASS_SetEAXParameters)(int env, float vol, float decay, + float damp); + BOOL BASSDEF(BASS_GetEAXParameters)(DWORD *env, float *vol, float *decay, + float *damp); #endif -HMUSIC BASSDEF(BASS_MusicLoad)(BOOL mem, const void *file, QWORD offset, - DWORD length, DWORD flags, DWORD freq); -BOOL BASSDEF(BASS_MusicFree)(HMUSIC handle); - -HSAMPLE BASSDEF(BASS_SampleLoad)(BOOL mem, const void *file, QWORD offset, - DWORD length, DWORD max, DWORD flags); -HSAMPLE BASSDEF(BASS_SampleCreate)(DWORD length, DWORD freq, DWORD chans, - DWORD max, DWORD flags); -BOOL BASSDEF(BASS_SampleFree)(HSAMPLE handle); -BOOL BASSDEF(BASS_SampleSetData)(HSAMPLE handle, const void *buffer); -BOOL BASSDEF(BASS_SampleGetData)(HSAMPLE handle, void *buffer); -BOOL BASSDEF(BASS_SampleGetInfo)(HSAMPLE handle, BASS_SAMPLE *info); -BOOL BASSDEF(BASS_SampleSetInfo)(HSAMPLE handle, const BASS_SAMPLE *info); -HCHANNEL BASSDEF(BASS_SampleGetChannel)(HSAMPLE handle, BOOL onlynew); -DWORD BASSDEF(BASS_SampleGetChannels)(HSAMPLE handle, HCHANNEL *channels); -BOOL BASSDEF(BASS_SampleStop)(HSAMPLE handle); - -HSTREAM BASSDEF(BASS_StreamCreate)(DWORD freq, DWORD chans, DWORD flags, - STREAMPROC *proc, void *user); -HSTREAM BASSDEF(BASS_StreamCreateFile)(BOOL mem, const void *file, QWORD offset, - QWORD length, DWORD flags); -HSTREAM BASSDEF(BASS_StreamCreateURL)(const char *url, DWORD offset, - DWORD flags, DOWNLOADPROC *proc, - void *user); -HSTREAM BASSDEF(BASS_StreamCreateFileUser)(DWORD system, DWORD flags, - const BASS_FILEPROCS *proc, - void *user); -BOOL BASSDEF(BASS_StreamFree)(HSTREAM handle); -QWORD BASSDEF(BASS_StreamGetFilePosition)(HSTREAM handle, DWORD mode); -DWORD BASSDEF(BASS_StreamPutData)(HSTREAM handle, const void *buffer, - DWORD length); -DWORD BASSDEF(BASS_StreamPutFileData)(HSTREAM handle, const void *buffer, - DWORD length); - -BOOL BASSDEF(BASS_RecordGetDeviceInfo)(DWORD device, BASS_DEVICEINFO *info); -BOOL BASSDEF(BASS_RecordInit)(int device); -BOOL BASSDEF(BASS_RecordSetDevice)(DWORD device); -DWORD BASSDEF(BASS_RecordGetDevice)(); -BOOL BASSDEF(BASS_RecordFree)(); -BOOL BASSDEF(BASS_RecordGetInfo)(BASS_RECORDINFO *info); -const char *BASSDEF(BASS_RecordGetInputName)(int input); -BOOL BASSDEF(BASS_RecordSetInput)(int input, DWORD flags, float volume); -DWORD BASSDEF(BASS_RecordGetInput)(int input, float *volume); -HRECORD BASSDEF(BASS_RecordStart)(DWORD freq, DWORD chans, DWORD flags, - RECORDPROC *proc, void *user); - -double BASSDEF(BASS_ChannelBytes2Seconds)(DWORD handle, QWORD pos); -QWORD BASSDEF(BASS_ChannelSeconds2Bytes)(DWORD handle, double pos); -DWORD BASSDEF(BASS_ChannelGetDevice)(DWORD handle); -BOOL BASSDEF(BASS_ChannelSetDevice)(DWORD handle, DWORD device); -DWORD BASSDEF(BASS_ChannelIsActive)(DWORD handle); -BOOL BASSDEF(BASS_ChannelGetInfo)(DWORD handle, BASS_CHANNELINFO *info); -const char *BASSDEF(BASS_ChannelGetTags)(DWORD handle, DWORD tags); -DWORD BASSDEF(BASS_ChannelFlags)(DWORD handle, DWORD flags, DWORD mask); -BOOL BASSDEF(BASS_ChannelUpdate)(DWORD handle, DWORD length); -BOOL BASSDEF(BASS_ChannelLock)(DWORD handle, BOOL lock); -BOOL BASSDEF(BASS_ChannelPlay)(DWORD handle, BOOL restart); -BOOL BASSDEF(BASS_ChannelStop)(DWORD handle); -BOOL BASSDEF(BASS_ChannelPause)(DWORD handle); -BOOL BASSDEF(BASS_ChannelSetAttribute)(DWORD handle, DWORD attrib, float value); -BOOL BASSDEF(BASS_ChannelGetAttribute)(DWORD handle, DWORD attrib, - float *value); -BOOL BASSDEF(BASS_ChannelSlideAttribute)(DWORD handle, DWORD attrib, - float value, DWORD time); -BOOL BASSDEF(BASS_ChannelIsSliding)(DWORD handle, DWORD attrib); -BOOL BASSDEF(BASS_ChannelSetAttributeEx)(DWORD handle, DWORD attrib, - void *value, DWORD size); -DWORD BASSDEF(BASS_ChannelGetAttributeEx)(DWORD handle, DWORD attrib, - void *value, DWORD size); -BOOL BASSDEF(BASS_ChannelSet3DAttributes)(DWORD handle, int mode, float min, - float max, int iangle, int oangle, - float outvol); -BOOL BASSDEF(BASS_ChannelGet3DAttributes)(DWORD handle, DWORD *mode, float *min, - float *max, DWORD *iangle, - DWORD *oangle, float *outvol); -BOOL BASSDEF(BASS_ChannelSet3DPosition)(DWORD handle, const BASS_3DVECTOR *pos, - const BASS_3DVECTOR *orient, - const BASS_3DVECTOR *vel); -BOOL BASSDEF(BASS_ChannelGet3DPosition)(DWORD handle, BASS_3DVECTOR *pos, - BASS_3DVECTOR *orient, - BASS_3DVECTOR *vel); -QWORD BASSDEF(BASS_ChannelGetLength)(DWORD handle, DWORD mode); -BOOL BASSDEF(BASS_ChannelSetPosition)(DWORD handle, QWORD pos, DWORD mode); -QWORD BASSDEF(BASS_ChannelGetPosition)(DWORD handle, DWORD mode); -DWORD BASSDEF(BASS_ChannelGetLevel)(DWORD handle); -BOOL BASSDEF(BASS_ChannelGetLevelEx)(DWORD handle, float *levels, float length, - DWORD flags); -DWORD BASSDEF(BASS_ChannelGetData)(DWORD handle, void *buffer, DWORD length); -HSYNC BASSDEF(BASS_ChannelSetSync)(DWORD handle, DWORD type, QWORD param, - SYNCPROC *proc, void *user); -BOOL BASSDEF(BASS_ChannelRemoveSync)(DWORD handle, HSYNC sync); -HDSP BASSDEF(BASS_ChannelSetDSP)(DWORD handle, DSPPROC *proc, void *user, - int priority); -BOOL BASSDEF(BASS_ChannelRemoveDSP)(DWORD handle, HDSP dsp); -BOOL BASSDEF(BASS_ChannelSetLink)(DWORD handle, DWORD chan); -BOOL BASSDEF(BASS_ChannelRemoveLink)(DWORD handle, DWORD chan); -HFX BASSDEF(BASS_ChannelSetFX)(DWORD handle, DWORD type, int priority); -BOOL BASSDEF(BASS_ChannelRemoveFX)(DWORD handle, HFX fx); - -BOOL BASSDEF(BASS_FXSetParameters)(HFX handle, const void *params); -BOOL BASSDEF(BASS_FXGetParameters)(HFX handle, void *params); -BOOL BASSDEF(BASS_FXReset)(HFX handle); -BOOL BASSDEF(BASS_FXSetPriority)(HFX handle, int priority); + HMUSIC BASSDEF(BASS_MusicLoad)(BOOL mem, const void *file, QWORD offset, + DWORD length, DWORD flags, DWORD freq); + BOOL BASSDEF(BASS_MusicFree)(HMUSIC handle); + + HSAMPLE BASSDEF(BASS_SampleLoad)(BOOL mem, const void *file, QWORD offset, + DWORD length, DWORD max, DWORD flags); + HSAMPLE BASSDEF(BASS_SampleCreate)(DWORD length, DWORD freq, DWORD chans, + DWORD max, DWORD flags); + BOOL BASSDEF(BASS_SampleFree)(HSAMPLE handle); + BOOL BASSDEF(BASS_SampleSetData)(HSAMPLE handle, const void *buffer); + BOOL BASSDEF(BASS_SampleGetData)(HSAMPLE handle, void *buffer); + BOOL BASSDEF(BASS_SampleGetInfo)(HSAMPLE handle, BASS_SAMPLE *info); + BOOL BASSDEF(BASS_SampleSetInfo)(HSAMPLE handle, const BASS_SAMPLE *info); + HCHANNEL BASSDEF(BASS_SampleGetChannel)(HSAMPLE handle, BOOL onlynew); + DWORD BASSDEF(BASS_SampleGetChannels)(HSAMPLE handle, HCHANNEL *channels); + BOOL BASSDEF(BASS_SampleStop)(HSAMPLE handle); + + HSTREAM BASSDEF(BASS_StreamCreate)(DWORD freq, DWORD chans, DWORD flags, + STREAMPROC *proc, void *user); + HSTREAM BASSDEF(BASS_StreamCreateFile)(BOOL mem, const void *file, + QWORD offset, QWORD length, + DWORD flags); + HSTREAM BASSDEF(BASS_StreamCreateURL)(const char *url, DWORD offset, + DWORD flags, DOWNLOADPROC *proc, + void *user); + HSTREAM BASSDEF(BASS_StreamCreateFileUser)(DWORD system, DWORD flags, + const BASS_FILEPROCS *proc, + void *user); + BOOL BASSDEF(BASS_StreamFree)(HSTREAM handle); + QWORD BASSDEF(BASS_StreamGetFilePosition)(HSTREAM handle, DWORD mode); + DWORD BASSDEF(BASS_StreamPutData)(HSTREAM handle, const void *buffer, + DWORD length); + DWORD BASSDEF(BASS_StreamPutFileData)(HSTREAM handle, const void *buffer, + DWORD length); + + BOOL BASSDEF(BASS_RecordGetDeviceInfo)(DWORD device, BASS_DEVICEINFO *info); + BOOL BASSDEF(BASS_RecordInit)(int device); + BOOL BASSDEF(BASS_RecordSetDevice)(DWORD device); + DWORD BASSDEF(BASS_RecordGetDevice)(); + BOOL BASSDEF(BASS_RecordFree)(); + BOOL BASSDEF(BASS_RecordGetInfo)(BASS_RECORDINFO *info); + const char *BASSDEF(BASS_RecordGetInputName)(int input); + BOOL BASSDEF(BASS_RecordSetInput)(int input, DWORD flags, float volume); + DWORD BASSDEF(BASS_RecordGetInput)(int input, float *volume); + HRECORD BASSDEF(BASS_RecordStart)(DWORD freq, DWORD chans, DWORD flags, + RECORDPROC *proc, void *user); + + double BASSDEF(BASS_ChannelBytes2Seconds)(DWORD handle, QWORD pos); + QWORD BASSDEF(BASS_ChannelSeconds2Bytes)(DWORD handle, double pos); + DWORD BASSDEF(BASS_ChannelGetDevice)(DWORD handle); + BOOL BASSDEF(BASS_ChannelSetDevice)(DWORD handle, DWORD device); + DWORD BASSDEF(BASS_ChannelIsActive)(DWORD handle); + BOOL BASSDEF(BASS_ChannelGetInfo)(DWORD handle, BASS_CHANNELINFO *info); + const char *BASSDEF(BASS_ChannelGetTags)(DWORD handle, DWORD tags); + DWORD BASSDEF(BASS_ChannelFlags)(DWORD handle, DWORD flags, DWORD mask); + BOOL BASSDEF(BASS_ChannelUpdate)(DWORD handle, DWORD length); + BOOL BASSDEF(BASS_ChannelLock)(DWORD handle, BOOL lock); + BOOL BASSDEF(BASS_ChannelPlay)(DWORD handle, BOOL restart); + BOOL BASSDEF(BASS_ChannelStop)(DWORD handle); + BOOL BASSDEF(BASS_ChannelPause)(DWORD handle); + BOOL BASSDEF(BASS_ChannelSetAttribute)(DWORD handle, DWORD attrib, + float value); + BOOL BASSDEF(BASS_ChannelGetAttribute)(DWORD handle, DWORD attrib, + float *value); + BOOL BASSDEF(BASS_ChannelSlideAttribute)(DWORD handle, DWORD attrib, + float value, DWORD time); + BOOL BASSDEF(BASS_ChannelIsSliding)(DWORD handle, DWORD attrib); + BOOL BASSDEF(BASS_ChannelSetAttributeEx)(DWORD handle, DWORD attrib, + void *value, DWORD size); + DWORD BASSDEF(BASS_ChannelGetAttributeEx)(DWORD handle, DWORD attrib, + void *value, DWORD size); + BOOL BASSDEF(BASS_ChannelSet3DAttributes)(DWORD handle, int mode, float min, + float max, int iangle, int oangle, + float outvol); + BOOL BASSDEF(BASS_ChannelGet3DAttributes)(DWORD handle, DWORD *mode, + float *min, float *max, + DWORD *iangle, DWORD *oangle, + float *outvol); + BOOL BASSDEF(BASS_ChannelSet3DPosition)(DWORD handle, + const BASS_3DVECTOR *pos, + const BASS_3DVECTOR *orient, + const BASS_3DVECTOR *vel); + BOOL BASSDEF(BASS_ChannelGet3DPosition)(DWORD handle, BASS_3DVECTOR *pos, + BASS_3DVECTOR *orient, + BASS_3DVECTOR *vel); + QWORD BASSDEF(BASS_ChannelGetLength)(DWORD handle, DWORD mode); + BOOL BASSDEF(BASS_ChannelSetPosition)(DWORD handle, QWORD pos, DWORD mode); + QWORD BASSDEF(BASS_ChannelGetPosition)(DWORD handle, DWORD mode); + DWORD BASSDEF(BASS_ChannelGetLevel)(DWORD handle); + BOOL BASSDEF(BASS_ChannelGetLevelEx)(DWORD handle, float *levels, + float length, DWORD flags); + DWORD BASSDEF(BASS_ChannelGetData)(DWORD handle, void *buffer, DWORD length); + HSYNC BASSDEF(BASS_ChannelSetSync)(DWORD handle, DWORD type, QWORD param, + SYNCPROC *proc, void *user); + BOOL BASSDEF(BASS_ChannelRemoveSync)(DWORD handle, HSYNC sync); + HDSP BASSDEF(BASS_ChannelSetDSP)(DWORD handle, DSPPROC *proc, void *user, + int priority); + BOOL BASSDEF(BASS_ChannelRemoveDSP)(DWORD handle, HDSP dsp); + BOOL BASSDEF(BASS_ChannelSetLink)(DWORD handle, DWORD chan); + BOOL BASSDEF(BASS_ChannelRemoveLink)(DWORD handle, DWORD chan); + HFX BASSDEF(BASS_ChannelSetFX)(DWORD handle, DWORD type, int priority); + BOOL BASSDEF(BASS_ChannelRemoveFX)(DWORD handle, HFX fx); + + BOOL BASSDEF(BASS_FXSetParameters)(HFX handle, const void *params); + BOOL BASSDEF(BASS_FXGetParameters)(HFX handle, void *params); + BOOL BASSDEF(BASS_FXReset)(HFX handle); + BOOL BASSDEF(BASS_FXSetPriority)(HFX handle, int priority); #ifdef __cplusplus } diff --git a/charselect.cpp b/charselect.cpp index f138caf9e..86ae66eb9 100644 --- a/charselect.cpp +++ b/charselect.cpp @@ -65,7 +65,8 @@ void Courtroom::reconstruct_char_select() max_chars_on_page = char_columns * char_rows; - for (int n = 0; n < max_chars_on_page; ++n) { + for (int n = 0; n < max_chars_on_page; ++n) + { int x_pos = (button_width + x_spacing) * x_mod_count; int y_pos = (button_height + y_spacing) * y_mod_count; @@ -83,7 +84,8 @@ void Courtroom::reconstruct_char_select() ++x_mod_count; - if (x_mod_count == char_columns) { + if (x_mod_count == char_columns) + { ++y_mod_count; x_mod_count = 0; } @@ -107,7 +109,8 @@ void Courtroom::set_char_select() pos_size_type f_charselect = ao_app->get_element_dimensions("char_select", filename); - if (f_charselect.width < 0 || f_charselect.height < 0) { + if (f_charselect.width < 0 || f_charselect.height < 0) + { qDebug() << "W: did not find courtroom width or height in courtroom_design.ini!"; this->resize(714, 668); @@ -126,7 +129,8 @@ void Courtroom::set_char_select_page() ui_char_select_left->hide(); ui_char_select_right->hide(); - for (AOCharButton *button : ui_char_button_list) { + for (AOCharButton *button : ui_char_button_list) + { button->reset(); button->hide(); } @@ -134,7 +138,8 @@ void Courtroom::set_char_select_page() int total_pages = char_list.size() / max_chars_on_page; int chars_on_page = 0; - if (char_list.size() % max_chars_on_page != 0) { + if (char_list.size() % max_chars_on_page != 0) + { ++total_pages; // i. e. not on the last page if (total_pages > current_char_page + 1) @@ -152,7 +157,8 @@ void Courtroom::set_char_select_page() ui_char_select_left->show(); // show all buttons for this page - for (int n_button = 0; n_button < chars_on_page; ++n_button) { + for (int n_button = 0; n_button < chars_on_page; ++n_button) + { if (char_list.length() <= n_button) continue; @@ -175,16 +181,19 @@ void Courtroom::char_clicked(int n_char) ao_app->get_character_path(char_list.at(n_real_char).name) + "char.ini"; qDebug() << "char_ini_path" << char_ini_path; - if (!file_exists(char_ini_path)) { + if (!file_exists(char_ini_path)) + { qDebug() << "did not find " << char_ini_path; call_notice("Could not find " + char_ini_path); return; } - if (n_real_char == m_cid) { + if (n_real_char == m_cid) + { enter_courtroom(m_cid); } - else { + else + { QString content = "CC#" + QString::number(ao_app->s_pv) + "#" + QString::number(n_real_char) + "#" + get_hdid() + "#%"; ao_app->send_server_packet(new AOPacket(content)); diff --git a/courtroom.cpp b/courtroom.cpp index 3f68dad59..8d3b02fdd 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -47,11 +47,13 @@ void Courtroom::enter_courtroom(int p_cid) set_char_rpc(); - if (m_cid == -1) { + if (m_cid == -1) + { ao_app->discord->state_spectate(); f_char = ""; } - else { + else + { f_char = ao_app->get_char_name(char_list.at(m_cid).name); QString r_char = f_char; // regex for removing non letter (except _) characters @@ -59,10 +61,12 @@ void Courtroom::enter_courtroom(int p_cid) QString::fromUtf8("[-`~!@#$%^&*()—+=|:;<>«»,.?/{}\'\"\\[\\]]")); r_char.remove(re); - if (!rpc_char_list.contains(f_char.toLower())) { + if (!rpc_char_list.contains(f_char.toLower())) + { ao_app->discord->toggle(1); } - else { + else + { ao_app->discord->toggle(0); } @@ -182,7 +186,8 @@ void Courtroom::set_scene() QString f_side = m_chatmessage[SIDE]; QString ini_path = ao_app->get_background_path() + "backgrounds.ini"; - if (file_exists(ini_path)) { + if (file_exists(ini_path)) + { f_background = ao_app->read_design_ini(f_side, ini_path); f_desk_image = ao_app->read_design_ini(f_side + "_desk", ini_path); @@ -191,28 +196,35 @@ void Courtroom::set_scene() ui_vp_desk->hide(); } } - else { - if (f_side == "def") { + else + { + if (f_side == "def") + { f_background = "defenseempty"; f_desk_image = "defensedesk"; } - else if (f_side == "pro") { + else if (f_side == "pro") + { f_background = "prosecutorempty"; f_desk_image = "prosecutiondesk"; } - else if (f_side == "jud") { + else if (f_side == "jud") + { f_background = "judgestand"; f_desk_image = "judgedesk"; } - else if (f_side == "hld") { + else if (f_side == "hld") + { f_background = "helperstand"; f_desk_image = "helperdesk"; } - else if (f_side == "hlp") { + else if (f_side == "hlp") + { f_background = "prohelperstand"; f_desk_image = "prohelperdesk"; } - else { + else + { f_desk_image = "stand"; } @@ -248,14 +260,16 @@ void Courtroom::set_char_rpc() rpc_char_list.clear(); QFile config_file(ao_app->get_base_path() + rpc_ini); - if (!config_file.open(QIODevice::ReadOnly)) { + if (!config_file.open(QIODevice::ReadOnly)) + { qDebug() << "Error reading" << ao_app->get_base_path() + rpc_ini; return; } QTextStream in(&config_file); - while (!in.atEnd()) { + while (!in.atEnd()) + { QString f_line = in.readLine().trimmed(); QStringList line_elements = f_line.split("-"); @@ -268,7 +282,8 @@ void Courtroom::set_char_rpc() void Courtroom::set_taken(int n_char, bool p_taken) { - if (n_char >= char_list.size()) { + if (n_char >= char_list.size()) + { qDebug() << "W: set_taken attempted to set an index bigger than char_list size"; return; @@ -341,21 +356,25 @@ void Courtroom::list_music() int n_listed_songs = 0; - for (int n_song = 0; n_song < music_list.size(); ++n_song) { + for (int n_song = 0; n_song < music_list.size(); ++n_song) + { QString i_song = music_list.at(n_song); bool found = false; - for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) { + for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) + { QString r_song = i_song + ext; QString song_path = ao_app->get_base_path() + "sounds/music/" + r_song.toLower(); - if (file_exists(song_path)) { + if (file_exists(song_path)) + { found = true; break; } } - if (i_song.toLower().contains(ui_music_search->text().toLower())) { + if (i_song.toLower().contains(ui_music_search->text().toLower())) + { ui_music_list->addItem(i_song); if (found) @@ -385,12 +404,14 @@ void Courtroom::list_areas() int n_listed_areas = 0; - for (int n_area = 0; n_area < area_list.size(); ++n_area) { + for (int n_area = 0; n_area < area_list.size(); ++n_area) + { QString i_area = ""; i_area.append(area_list.at(n_area)); - if (i_area.toLower().contains(ui_music_search->text().toLower())) { + if (i_area.toLower().contains(ui_music_search->text().toLower())) + { ui_area_list->addItem(i_area); // area_names.append(i_); @@ -421,7 +442,8 @@ void Courtroom::list_sfx() int n_listed_sfxs = 0; - for (int n_sfx = 0; n_sfx < sfx_list.size(); ++n_sfx) { + for (int n_sfx = 0; n_sfx < sfx_list.size(); ++n_sfx) + { QStringList sfx = sfx_list.at(n_sfx).split("="); QString i_sfx = sfx.at(0).trimmed(); QString d_sfx = ""; @@ -433,7 +455,8 @@ void Courtroom::list_sfx() // qDebug() << "isfx=" << i_sfx << "dsfx=" << d_sfx << "sfx=" << sfx; - if (i_sfx.toLower().contains(ui_sfx_search->text().toLower())) { + if (i_sfx.toLower().contains(ui_sfx_search->text().toLower())) + { ui_sfx_list->addItem(d_sfx); ui_sfx_list->item(ui_sfx_list->count() - 1) ->setStatusTip(QString::number(n_sfx + 2)); @@ -455,7 +478,8 @@ void Courtroom::list_note_files() { QString f_config = ao_app->get_base_path() + file_select_ini; QFile f_file(f_config); - if (!f_file.open(QIODevice::ReadOnly)) { + if (!f_file.open(QIODevice::ReadOnly)) + { qDebug() << "Couldn't open" << f_config; return; } @@ -469,7 +493,8 @@ void Courtroom::list_note_files() QVBoxLayout *f_layout = ui_note_area->m_layout; - while (!in.atEnd()) { + while (!in.atEnd()) + { QString line = in.readLine().trimmed(); QStringList f_contents = line.split("="); @@ -562,7 +587,8 @@ void Courtroom::on_chat_return_pressed() QString f_desk_mod = "chat"; - if (ao_app->desk_mod_enabled) { + if (ao_app->desk_mod_enabled) + { f_desk_mod = QString::number(ao_app->get_desk_mod(current_char, current_emote)); if (f_desk_mod == "-1") @@ -602,19 +628,22 @@ void Courtroom::on_chat_return_pressed() int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); // needed or else legacy won't understand what we're saying - if (m_objection_state > 0) { + if (m_objection_state > 0) + { if (f_emote_mod == 5) f_emote_mod = 6; else f_emote_mod = 2; } - else if (ui_pre->isChecked()) { + else if (ui_pre->isChecked()) + { if (f_emote_mod == 0) f_emote_mod = 1; else if (f_emote_mod == 5 && ao_app->prezoom_enabled) f_emote_mod = 4; } - else { + else + { if (f_emote_mod == 1) f_emote_mod = 0; else if (f_emote_mod == 4) @@ -646,7 +675,8 @@ void Courtroom::on_chat_return_pressed() QString f_flip; - if (ao_app->flipping_enabled) { + if (ao_app->flipping_enabled) + { if (ui_flip->isChecked()) f_flip = "1"; else @@ -682,7 +712,8 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) else if (p_contents->size() == 15) p_contents->append(""); - for (int n_string = 0; n_string < chatmessage_size; ++n_string) { + for (int n_string = 0; n_string < chatmessage_size; ++n_string) + { m_chatmessage[n_string] = p_contents->at(n_string); // qDebug() << "m_chatmessage[" << n_string << "] = " << // m_chatmessage[n_string]; @@ -690,7 +721,8 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) int f_char_id = m_chatmessage[CHAR_ID].toInt(); - if (f_char_id == -1) { + if (f_char_id == -1) + { is_system_speaking = true; m_chatmessage[CHAR_ID] = "0"; f_char_id = 0; @@ -709,7 +741,8 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) m_msg_is_first_person = false; // reset our ui state - if (m_cid == f_char_id && !is_system_speaking) { + if (m_cid == f_char_id && !is_system_speaking) + { ui_ic_chat_message->clear(); ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); @@ -733,10 +766,12 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) qDebug() << m_chatmessage[SHOWNAME] << ao_app->get_showname(char_list.at(f_char_id).name); - if (m_chatmessage[SHOWNAME].isEmpty()) { + if (m_chatmessage[SHOWNAME].isEmpty()) + { f_showname = ao_app->get_showname(char_list.at(f_char_id).name); } - else { + else + { f_showname = m_chatmessage[SHOWNAME]; } @@ -771,7 +806,8 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) QString f_custom_theme = ao_app->get_char_shouts(f_char); // if an objection is used - if (objection_mod > 0) { + if (objection_mod > 0) + { int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); if (emote_mod == 0) m_chatmessage[EMOTE_MOD] = 1; @@ -804,10 +840,12 @@ void Courtroom::handle_chatmessage_2() // handles IC QString f_showname; - if (m_chatmessage[SHOWNAME].isEmpty()) { + if (m_chatmessage[SHOWNAME].isEmpty()) + { f_showname = ao_app->get_showname(real_name); } - else { + else + { f_showname = m_chatmessage[SHOWNAME]; } @@ -827,7 +865,8 @@ void Courtroom::handle_chatmessage_2() // handles IC QString chatbox = ao_app->get_chat(m_chatmessage[CHAR_NAME]); if (chatbox == "") - if (ao_app->read_theme_ini("daynight_theme", cc_config_ini) == "true") { + if (ao_app->read_theme_ini("daynight_theme", cc_config_ini) == "true") + { if (current_clock < 0) ui_vp_chatbox->set_image("chatmed.png"); else if (current_clock >= 7 && current_clock < 22) @@ -837,12 +876,14 @@ void Courtroom::handle_chatmessage_2() // handles IC } else ui_vp_chatbox->set_image("chatmed.png"); - else { + else + { QString chatbox_path = ao_app->get_base_path() + "misc/" + chatbox + ".png"; ui_vp_chatbox->set_image_from_path(chatbox_path); } - if (!m_msg_is_first_person) { + if (!m_msg_is_first_person) + { set_scene(); } @@ -855,7 +896,8 @@ void Courtroom::handle_chatmessage_2() // handles IC else ui_vp_player_char->set_mirror_enabled(false); - switch (emote_mod) { + switch (emote_mod) + { case 1: case 2: case 6: @@ -879,7 +921,8 @@ void Courtroom::handle_chatmessage_3() int f_evi_id = m_chatmessage[EVIDENCE_ID].toInt(); QString f_side = m_chatmessage[SIDE]; - if (f_evi_id > 0 && f_evi_id <= local_evidence_list.size()) { + 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; // def jud and hlp should display the evidence icon on the RIGHT side @@ -890,7 +933,8 @@ void Courtroom::handle_chatmessage_3() int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); - if (emote_mod == 5 || emote_mod == 6) { + if (emote_mod == 5 || emote_mod == 6) + { QString side = m_chatmessage[SIDE]; ui_vp_desk->hide(); @@ -924,21 +968,24 @@ void Courtroom::handle_chatmessage_3() QString ext = file_exists(ao_app->get_character_path(f_char) + "showname", exts); if (ext != "" && !chatmessage_is_empty && - ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == - "true") { + ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") + { ui_vp_showname->hide(); QString path = ao_app->get_character_path(f_char) + "showname" + ext; ui_vp_showname_image->set_image_from_path(path); ui_vp_showname_image->show(); } - else { + else + { ui_vp_showname->show(); ui_vp_showname_image->hide(); } - switch (f_anim_state) { + switch (f_anim_state) + { case 2: - if (!m_msg_is_first_person) { + if (!m_msg_is_first_person) + { ui_vp_player_char->play_talking(f_char, f_emote, true); } anim_state = 2; @@ -946,7 +993,8 @@ void Courtroom::handle_chatmessage_3() default: qDebug() << "W: invalid anim_state: " << f_anim_state; case 3: - if (!m_msg_is_first_person) { + if (!m_msg_is_first_person) + { ui_vp_player_char->play_idle(f_char, f_emote, true); } anim_state = 3; @@ -965,7 +1013,8 @@ void Courtroom::handle_chatmessage_3() bool do_it = ao_app->read_theme_ini("non_vanilla_effects", cc_config_ini) == "true"; - if (effect == 1 && !do_it) { + if (effect == 1 && !do_it) + { if (overlay_sfx == "") overlay_sfx = ao_app->get_sfx("effect_flash"); m_effects_player->play(overlay_sfx); @@ -1007,8 +1056,10 @@ void Courtroom::handle_chatmessage_3() QString f_message = m_chatmessage[MESSAGE]; QStringList callwords = ao_app->get_callwords(); - for (QString word : callwords) { - if (f_message.contains(word, Qt::CaseInsensitive)) { + for (QString word : callwords) + { + if (f_message.contains(word, Qt::CaseInsensitive)) + { m_system_player->play(ao_app->get_sfx("word_call")); ao_app->alert(this); ui_server_chatlog->append_chatmessage( @@ -1065,14 +1116,16 @@ void Courtroom::update_ic_log(bool p_reset_log) * */ record_type_array records_to_add; // populate - if (p_reset_log) { + if (p_reset_log) + { // we need the entire recordings records_to_add = m_ic_records; // clear log ui_ic_chatlog->clear(); } - else { + else + { records_to_add.append(m_ic_records.last()); } @@ -1120,14 +1173,17 @@ void Courtroom::update_ic_log(bool p_reset_log) const QTextCursor::MoveOperation move_type = m_chatlog_scrolldown ? QTextCursor::End : QTextCursor::Start; - for (record_type_ptr record : records_to_add) { + for (record_type_ptr record : records_to_add) + { // move cursor cursor.movePosition(move_type); - if (record->system) { + if (record->system) + { cursor.insertText(record->line + QChar::LineFeed, system_format); } - else { + else + { QString separator; if (m_chatlog_newline) separator = QString(QChar::LineFeed); @@ -1201,13 +1257,15 @@ void Courtroom::update_ic_log(bool p_reset_log) * this also removes the last character of the block that remains. That's why * we have to do this whole complicated process. * */ - if (prev_cursor.hasSelection() || !is_scrolled) { + if (prev_cursor.hasSelection() || !is_scrolled) + { // restore previous selection and vscrollbar ui_ic_chatlog->setTextCursor(prev_cursor); vscrollbar->setValue(scroll_pos); } // scroll up/down depending on context - else { + else + { ui_ic_chatlog->moveCursor(move_type); vscrollbar->setValue(m_chatlog_scrolldown ? vscrollbar->maximum() : vscrollbar->minimum()); @@ -1247,17 +1305,20 @@ void Courtroom::play_preanim() // set state anim_state = 1; - if (!m_msg_is_first_person) { + if (!m_msg_is_first_person) + { QString f_anim_path = ao_app->get_character_path(f_char) + f_preanim.toLower(); - if (ui_vp_player_char->play_pre(f_char, f_preanim, true)) { + if (ui_vp_player_char->play_pre(f_char, f_preanim, true)) + { if (text_delay >= 0) text_delay_timer->start(text_delay); // finished return; } - else { + else + { qDebug() << "could not find " + f_anim_path; } } @@ -1281,7 +1342,8 @@ void Courtroom::start_chat_ticking() if (text_state != 0) return; - if (chatmessage_is_empty) { + if (chatmessage_is_empty) + { // since the message is empty, it's technically done ticking text_state = 2; return; @@ -1314,12 +1376,14 @@ void Courtroom::chat_tick() QString f_message = m_chatmessage[MESSAGE]; - if (tick_pos >= f_message.size()) { + if (tick_pos >= f_message.size()) + { text_state = 2; chat_tick_timer->stop(); anim_state = 3; - if (!m_msg_is_first_person) { + if (!m_msg_is_first_person) + { ui_vp_player_char->play_idle(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE], true); } @@ -1328,15 +1392,18 @@ void Courtroom::chat_tick() m_color_stack.clear(); } - else { + else + { QString f_character = f_message.at(tick_pos); if (f_character == " ") ui_vp_message->insertPlainText(" "); - else if (m_chatmessage[TEXT_COLOR].toInt() == RAINBOW) { + else if (m_chatmessage[TEXT_COLOR].toInt() == RAINBOW) + { QString html_color; - switch (rainbow_counter) { + switch (rainbow_counter) + { case 0: html_color = "#BA1518"; break; @@ -1363,7 +1430,8 @@ void Courtroom::chat_tick() ui_vp_message->textCursor().insertText(f_character, vp_message_format); } else if (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == - "true") { + "true") + { bool highlight_found = false; bool render_character = true; // render_character should only be false if the character is a highlight @@ -1373,8 +1441,10 @@ void Courtroom::chat_tick() if (m_color_stack.isEmpty()) m_color_stack.push(""); - for (const auto &col : f_vec) { - if (f_character == col[0][0] && m_string_color != col[1]) { + for (const auto &col : f_vec) + { + if (f_character == col[0][0] && m_string_color != col[1]) + { m_color_stack.push(col[1]); m_string_color = m_color_stack.top(); highlight_found = true; @@ -1386,7 +1456,8 @@ void Courtroom::chat_tick() // Apply color to the next character if (m_string_color.isEmpty()) vp_message_format.setForeground(m_base_string_color); - else { + else + { QColor textColor; textColor.setNamedColor(m_string_color); vp_message_format.setForeground(textColor); @@ -1394,8 +1465,10 @@ void Courtroom::chat_tick() QString m_future_string_color = m_string_color; - for (const auto &col : f_vec) { - if (f_character == col[0][1] && !highlight_found) { + for (const auto &col : f_vec) + { + if (f_character == col[0][1] && !highlight_found) + { if (m_color_stack.size() > 1) m_color_stack.pop(); m_future_string_color = m_color_stack.top(); @@ -1410,16 +1483,19 @@ void Courtroom::chat_tick() m_string_color = m_future_string_color; } - else { + else + { ui_vp_message->textCursor().insertText(f_character, vp_message_format); } QScrollBar *scroll = ui_vp_message->verticalScrollBar(); scroll->setValue(scroll->maximum()); - if ((f_message.at(tick_pos) != ' ' || ao_config->blank_blips_enabled())) { + if ((f_message.at(tick_pos) != ' ' || ao_config->blank_blips_enabled())) + { - if (blip_pos % ao_app->read_blip_rate() == 0) { + if (blip_pos % ao_app->read_blip_rate() == 0) + { blip_pos = 0; // play blip @@ -1471,7 +1547,8 @@ void Courtroom::play_sfx() void Courtroom::set_text_color() { - switch (m_chatmessage[TEXT_COLOR].toInt()) { + switch (m_chatmessage[TEXT_COLOR].toInt()) + { case GREEN: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setRgb(101, 200, 86); @@ -1524,7 +1601,8 @@ void Courtroom::set_mute(bool p_muted, int p_cid) if (p_muted) ui_muted->show(); - else { + else + { ui_muted->hide(); ui_ic_chat_message->setFocus(); } @@ -1558,20 +1636,24 @@ void Courtroom::handle_song(QStringList *p_contents) QString f_song = f_contents.at(0); int n_char = f_contents.at(1).toInt(); - for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) { + for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) + { QString r_song = f_song + ext; QString song_path = ao_app->get_base_path() + "sounds/music/" + r_song.toLower(); - if (file_exists(song_path)) { + if (file_exists(song_path)) + { f_song = r_song; break; } } - if (n_char < 0 || n_char >= char_list.size()) { + if (n_char < 0 || n_char >= char_list.size()) + { m_music_player->play(f_song); } - else { + else + { // This 2th argument corresponds to the showname to use when displaying the // music change message in IC // Backwards compatibility is explicitly kept for older versions of @@ -1579,23 +1661,29 @@ void Courtroom::handle_song(QStringList *p_contents) // If there is an empty showname, the client will use instead the default // showname of the character. QString f_showname; - if (f_contents.size() == 3) { + if (f_contents.size() == 3) + { f_showname = f_contents.at(2); } - else { + else + { f_showname = ""; } QString str_char; - if (f_showname.isEmpty()) { + if (f_showname.isEmpty()) + { str_char = ao_app->get_showname(char_list.at(n_char).name); } - else { + else + { str_char = f_showname; } - if (!mute_map.value(n_char)) { - if (ao_app->get_music_change_log_enabled()) { + if (!mute_map.value(n_char)) + { + if (ao_app->get_music_change_log_enabled()) + { append_ic_text(str_char, "has played a song: " + f_song, false, true); } m_music_player->play(f_song); @@ -1619,10 +1707,12 @@ void Courtroom::handle_wtce(QString p_wtce) wtce_names.size() > 0) // check to prevent crash { p_wtce.chop(1); // looking for the 'testimony' part - if (p_wtce == "testimony") { + if (p_wtce == "testimony") + { m_effects_player->play(ao_app->get_sfx(wtce_names[index - 1])); ui_vp_wtce->play(wtce_names[index - 1]); - if (index == 1) { + if (index == 1) + { testimony_in_progress = true; } else if (index == 2) @@ -1636,11 +1726,13 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) if (p_state < 0 || p_state > 10) return; - if (p_bar == 1) { + if (p_bar == 1) + { ui_defense_bar->set_image("defensebar" + QString::number(p_state) + ".png"); defense_bar_state = p_state; } - else if (p_bar == 2) { + else if (p_bar == 2) + { ui_prosecution_bar->set_image("prosecutionbar" + QString::number(p_state) + ".png"); prosecution_bar_state = p_state; @@ -1660,7 +1752,8 @@ void Courtroom::set_character_position(QString p_pos, bool refresh_dropdown) void Courtroom::mod_called(QString p_ip) { ui_server_chatlog->append(p_ip); - if (ao_app->get_server_alerts_enabled()) { + if (ao_app->get_server_alerts_enabled()) + { m_system_player->play(ao_app->get_sfx("mod_call")); ao_app->alert(this); } @@ -1671,14 +1764,17 @@ void Courtroom::on_ooc_return_pressed() QString ooc_name = ui_ooc_chat_name->text(); QString ooc_message = ui_ooc_chat_message->text(); - if (ooc_message.isEmpty()) { + if (ooc_message.isEmpty()) + { append_server_chatmessage("CLIENT", "You cannot send an empty message."); return; } - if (ooc_name.isEmpty()) { + if (ooc_name.isEmpty()) + { bool ok; QString name; - do { + do + { ooc_name = QInputDialog::getText( this, "Enter a name", "You must have a name to talk in OOC chat. Enter a name: ", @@ -1690,27 +1786,33 @@ void Courtroom::on_ooc_return_pressed() ao_config->set_username(ooc_name); } else if (ooc_message.startsWith("/rainbow") && ao_app->yellow_text_enabled && - !rainbow_appended) { + !rainbow_appended) + { ui_text_color->addItem("Rainbow"); ui_ooc_chat_message->clear(); rainbow_appended = true; return; } - else if (ooc_message.startsWith("/switch_am")) { + else if (ooc_message.startsWith("/switch_am")) + { on_switch_area_music_clicked(); ui_ooc_chat_message->clear(); return; } - else if (ooc_message.startsWith("/rollp")) { + else if (ooc_message.startsWith("/rollp")) + { m_effects_player->play(ao_app->get_sfx("dice")); } - else if (ooc_message.startsWith("/roll")) { + else if (ooc_message.startsWith("/roll")) + { m_effects_player->play(ao_app->get_sfx("dice")); } - else if (ooc_message.startsWith("/coinflip")) { + else if (ooc_message.startsWith("/coinflip")) + { m_effects_player->play(ao_app->get_sfx("coinflip")); } - else if (ooc_message.startsWith("/tr ")) { + else if (ooc_message.startsWith("/tr ")) + { // Timer resume int space_location = ooc_message.indexOf(" "); @@ -1721,7 +1823,8 @@ void Courtroom::on_ooc_return_pressed() timer_id = ooc_message.mid(space_location + 1).toInt(); resume_timer(timer_id); } - else if (ooc_message.startsWith("/ts ")) { + else if (ooc_message.startsWith("/ts ")) + { // Timer set QStringList arguments = ooc_message.split(" "); int size = arguments.size(); @@ -1739,7 +1842,8 @@ void Courtroom::on_ooc_return_pressed() set_timer_timestep(timer_id, timestep_length); set_timer_firing(timer_id, firing_interval); } - else if (ooc_message.startsWith("/tp ")) { + else if (ooc_message.startsWith("/tp ")) + { // Timer pause int space_location = ooc_message.indexOf(" "); @@ -1787,7 +1891,8 @@ void Courtroom::on_pos_dropdown_changed(int p_index) QString f_pos; - switch (p_index) { + switch (p_index) + { case 0: f_pos = "wit"; break; @@ -1835,21 +1940,25 @@ void Courtroom::on_mute_list_clicked(QModelIndex p_index) int f_cid = -1; - for (int n_char = 0; n_char < char_list.size(); n_char++) { + for (int n_char = 0; n_char < char_list.size(); n_char++) + { if (char_list.at(n_char).name == real_char) f_cid = n_char; } - if (f_cid < 0 || f_cid >= char_list.size()) { + if (f_cid < 0 || f_cid >= char_list.size()) + { qDebug() << "W: " << real_char << " not present in char_list"; return; } - if (mute_map.value(f_cid)) { + if (mute_map.value(f_cid)) + { mute_map.insert(f_cid, false); f_item->setText(real_char); } - else { + else + { mute_map.insert(f_cid, true); f_item->setText(real_char + " [x]"); } @@ -1933,7 +2042,8 @@ void Courtroom::on_cycle_clicked() AOButton *f_cycle_button = static_cast(sender()); int f_cycle_id = f_cycle_button->property("cycle_id").toInt(); - switch (f_cycle_id) { + switch (f_cycle_id) + { case 5: cycle_wtce(-1); break; @@ -1966,7 +2076,8 @@ void Courtroom::on_cycle_clicked() void Courtroom::cycle_shout(int p_index) { int n = ui_shouts.size(); - do { + do + { m_shout_state = (m_shout_state - p_index + n) % n; } while (!shouts_enabled[m_shout_state]); @@ -1976,7 +2087,8 @@ void Courtroom::cycle_shout(int p_index) void Courtroom::cycle_effect(int p_index) { int n = ui_effects.size(); - do { + do + { m_effect_current = (m_effect_current - p_index + n) % n; } while (!effects_enabled[m_effect_current]); @@ -1986,7 +2098,8 @@ void Courtroom::cycle_effect(int p_index) void Courtroom::cycle_wtce(int p_index) { int n = ui_wtce.size(); - do { + do + { m_wtce_current = (m_wtce_current - p_index + n) % n; } while (!wtce_enabled[m_wtce_current]); @@ -1996,7 +2109,8 @@ void Courtroom::cycle_wtce(int p_index) void Courtroom::reset_effect_buttons() { // effect names does not necessarily have the same size as ui effects - for (int i = 0; i < effect_names.size(); ++i) { + for (int i = 0; i < effect_names.size(); ++i) + { // qDebug() << effect_names << i; ui_effects[i]->set_image(effect_names.at(i) + ".png"); } @@ -2023,11 +2137,13 @@ void Courtroom::on_effect_button_clicked() void Courtroom::on_mute_clicked() { - if (ui_mute_list->isHidden()) { + if (ui_mute_list->isHidden()) + { ui_mute_list->show(); ui_mute->set_image("mute_pressed.png"); } - else { + else + { ui_mute_list->hide(); ui_mute->set_image("mute.png"); } @@ -2190,7 +2306,8 @@ void Courtroom::on_call_mod_clicked() QMessageBox::warning(this, "Warning", warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - if (reply == QMessageBox::Yes) { + if (reply == QMessageBox::Yes) + { ao_app->send_server_packet(new AOPacket("ZZ#%")); qDebug() << "Called mod"; } @@ -2202,11 +2319,13 @@ void Courtroom::on_call_mod_clicked() void Courtroom::on_switch_area_music_clicked() { - if (ui_area_list->isHidden()) { + if (ui_area_list->isHidden()) + { ui_area_list->show(); ui_music_list->hide(); } - else { + else + { ui_area_list->hide(); ui_music_list->show(); } @@ -2220,11 +2339,13 @@ void Courtroom::on_hidden_clicked() { ui_ic_chat_message->setFocus(); } void Courtroom::on_evidence_button_clicked() { - if (ui_evidence->isHidden()) { + if (ui_evidence->isHidden()) + { ui_evidence->show(); ui_evidence_overlay->hide(); } - else { + else + { ui_evidence->hide(); } } @@ -2237,14 +2358,16 @@ void Courtroom::on_config_panel_clicked() void Courtroom::on_note_button_clicked() { - if (!note_shown) { + if (!note_shown) + { load_note(); ui_vp_notepad_image->show(); ui_vp_notepad->show(); ui_vp_notepad->setFocus(); note_shown = true; } - else { + else + { save_note(); ui_vp_notepad_image->hide(); ui_vp_notepad->hide(); diff --git a/courtroom.h b/courtroom.h index 3cf02d2eb..1bde3c25f 100644 --- a/courtroom.h +++ b/courtroom.h @@ -46,7 +46,8 @@ class AOApplication; -class Courtroom : public QMainWindow { +class Courtroom : public QMainWindow +{ Q_OBJECT public: explicit Courtroom(AOApplication *p_ao_app); @@ -60,7 +61,8 @@ class Courtroom : public QMainWindow { void fix_last_area() { - if (area_list.size() > 0) { + if (area_list.size() > 0) + { QString malplaced = area_list.last(); area_list.removeLast(); append_music(malplaced); @@ -182,8 +184,8 @@ class Courtroom : public QMainWindow { void append_system_text(QString p_line); // prints who played the song to IC chat and plays said song(if found on local - // filesystem) takes in a list where the first element is the song name and the - // second is the char id of who played it + // filesystem) takes in a list where the first element is the song name and + // the second is the char id of who played it void handle_song(QStringList *p_contents); // animates music text diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index d630557ba..974a503ba 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -185,7 +185,8 @@ void Courtroom::create_widgets() ui_note_button = new AOButton(this, ao_app); ui_label_images.resize(label_images.size()); - for (int i = 0; i < ui_label_images.size(); ++i) { + for (int i = 0; i < ui_label_images.size(); ++i) + { ui_label_images[i] = new AOImage(this, ao_app); } @@ -216,7 +217,8 @@ void Courtroom::create_widgets() ui_text_color->addItem("Red"); ui_text_color->addItem("Orange"); ui_text_color->addItem("Blue"); - if (ao_app->yellow_text_enabled) { + if (ao_app->yellow_text_enabled) + { ui_text_color->addItem("Yellow"); ui_text_color->addItem("Purple"); ui_text_color->addItem("Pink"); @@ -499,10 +501,12 @@ void Courtroom::set_widget_layers() QStringList recorded_widgets; // read the entire thing - for (QString path : paths) { + for (QString path : paths) + { QFile layer_ini(path); - if (layer_ini.open(QFile::ReadOnly)) { + if (layer_ini.open(QFile::ReadOnly)) + { QTextStream in(&layer_ini); // current parent's name @@ -510,7 +514,8 @@ void Courtroom::set_widget_layers() // the courtroom is ALWAYS going to be recorded recorded_widgets.append(parent_name); - while (!in.atEnd()) { + while (!in.atEnd()) + { QString line = in.readLine().trimmed(); // skip if line is empty @@ -518,16 +523,19 @@ void Courtroom::set_widget_layers() continue; // revert to default parent if we encounter an end scope - if (line.startsWith("[\\")) { + if (line.startsWith("[\\")) + { parent_name = "courtroom"; } // is this a parent? - else if (line.startsWith("[")) { + else if (line.startsWith("[")) + { // update the current parent parent_name = line.remove(0, 1).chopped(1).trimmed(); } // if this is not a parent, it's a child - else { + else + { // if the child is already known, skip if (recorded_widgets.contains(line)) continue; @@ -567,7 +575,8 @@ void Courtroom::set_widget_layers() // particular, make it visible and raise it in front of all assets. This can // help assist a theme designer who accidentally missed config_panel and would // have become unable to reload themes had they closed the config panel - if (!recorded_widgets.contains("config_panel")) { + if (!recorded_widgets.contains("config_panel")) + { ui_config_panel->setParent(this); ui_config_panel->raise(); ui_config_panel->setVisible(true); @@ -580,12 +589,14 @@ void Courtroom::set_widgets() pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", filename); - if (f_courtroom.width < 0 || f_courtroom.height < 0) { + if (f_courtroom.width < 0 || f_courtroom.height < 0) + { qDebug() << "W: did not find courtroom width or height in " << filename; this->resize(714, 668); } - else { + else + { m_courtroom_width = f_courtroom.width; m_courtroom_height = f_courtroom.height; @@ -733,7 +744,8 @@ void Courtroom::set_widgets() // set_size_and_pos(ui_shouts[4], "got_it"); // set_size_and_pos(ui_shouts[5], "cross_swords"); // set_size_and_pos(ui_shouts[6], "counter_alt"); - for (int i = 0; i < shout_names.size(); ++i) { + for (int i = 0; i < shout_names.size(); ++i) + { set_size_and_pos(ui_shouts[i], shout_names[i]); } // ui_shouts[0]->show(); @@ -750,7 +762,8 @@ void Courtroom::set_widgets() // courtroom_config.ini necessary + check for crash if (ao_app->read_theme_ini("enable_single_shout", cc_config_ini) == "true" && - ui_shouts.size() > 0) { + ui_shouts.size() > 0) + { for (auto &shout : ui_shouts) move_widget(shout, "bullet"); @@ -764,7 +777,8 @@ void Courtroom::set_widgets() // set_size_and_pos(ui_effects[1], "effect_gloom"); // set_size_and_pos(ui_effects[2], "effect_question"); // set_size_and_pos(ui_effects[3], "effect_pow"); - for (int i = 0; i < effect_names.size(); ++i) { + for (int i = 0; i < effect_names.size(); ++i) + { set_size_and_pos(ui_effects[i], effect_names[i]); } reset_effect_buttons(); @@ -795,7 +809,8 @@ void Courtroom::set_widgets() ui_wtce_down->set_image("wtcedown.png"); ui_wtce_down->hide(); - for (int i = 0; i < wtce_names.size(); ++i) { + for (int i = 0; i < wtce_names.size(); ++i) + { set_size_and_pos(ui_wtce[i], wtce_names[i]); } @@ -811,7 +826,8 @@ void Courtroom::set_widgets() // this will reset the image reset_judge_wtce_buttons(); - for (int i = 0; i < free_block_names.size(); ++i) { + for (int i = 0; i < free_block_names.size(); ++i) + { set_size_and_pos(ui_free_blocks[i], free_block_names[i]); } set_free_blocks(); @@ -836,7 +852,8 @@ void Courtroom::set_widgets() ui_config_panel->setStyleSheet(""); ui_note_button->setStyleSheet(""); - if (ao_app->read_theme_ini("enable_button_images", cc_config_ini) == "true") { + if (ao_app->read_theme_ini("enable_button_images", cc_config_ini) == "true") + { // Set files, ask questions later // set_image first tries the theme variant folder, then the theme folder, // then falls back to the default theme @@ -867,7 +884,8 @@ void Courtroom::set_widgets() // found in courtroom_design.ini This is to assist with people who switch to // incompatible and/or smaller themes and have the button disappear if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || - !ui_config_panel->isVisible()) { + !ui_config_panel->isVisible()) + { ui_config_panel->setVisible(true); ui_config_panel->move(0, 0); // Moreover, if the width or height is invalid, change it to some fixed @@ -883,11 +901,13 @@ void Courtroom::set_widgets() set_size_and_pos(ui_hidden, "hidden"); - for (int i = 0; i < ui_label_images.size(); ++i) { + for (int i = 0; i < ui_label_images.size(); ++i) + { set_size_and_pos(ui_label_images[i], label_images[i].toLower() + "_image"); } - if (ao_app->read_theme_ini("enable_label_images", cc_config_ini) == "true") { + if (ao_app->read_theme_ini("enable_label_images", cc_config_ini) == "true") + { for (int i = 0; i < ui_checks.size(); ++i) // loop through checks { QString image = label_images[i].toLower() + ".png"; @@ -911,7 +931,8 @@ void Courtroom::set_widgets() ui_labels[i]->setText(label_images[j]); } } - else { + else + { for (int i = 0; i < ui_checks.size(); ++i) // same thing { ui_checks[i]->setText(label_images[i]); @@ -1011,7 +1032,8 @@ void Courtroom::set_widgets() list_note_files(); - if (!contains_add_button) { + if (!contains_add_button) + { ui_note_area->m_layout->addWidget(ui_note_area->add_button); contains_add_button = true; } @@ -1028,11 +1050,13 @@ void Courtroom::set_size_and_pos(QWidget *p_widget, QString p_identifier) pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - if (design_ini_result.width < 0 || design_ini_result.height < 0) { + if (design_ini_result.width < 0 || design_ini_result.height < 0) + { qDebug() << "W: could not find \"" << p_identifier << "\" in " << filename; p_widget->hide(); } - else { + else + { p_widget->move(design_ini_result.x, design_ini_result.y); p_widget->resize(design_ini_result.width, design_ini_result.height); } @@ -1045,11 +1069,13 @@ void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - if (design_ini_result.width < 0 || design_ini_result.height < 0) { + if (design_ini_result.width < 0 || design_ini_result.height < 0) + { qDebug() << "W: could not find \"" << p_identifier << "\" in " << filename; p_widget->hide(); } - else { + else + { p_widget->move(design_ini_result.x, design_ini_result.y); } } @@ -1071,16 +1097,20 @@ int Courtroom::adapt_numbered_items(QVector &item_vector, // Decide what to do if the new theme has a different amount of items than the // old one - if (new_item_number < current_item_number) { + if (new_item_number < current_item_number) + { // Hide old items if there are any. - for (int i = new_item_number; i < current_item_number; i++) { + for (int i = new_item_number; i < current_item_number; i++) + { item_vector[i]->hide(); } } - else if (current_item_number < new_item_number) { + else if (current_item_number < new_item_number) + { // Create new items item_vector.resize(new_item_number); - for (int i = current_item_number; i < new_item_number; i++) { + for (int i = current_item_number; i < new_item_number; i++) + { item_vector[i] = new T(this); item_vector[i]->stackUnder(item_vector[i - 1]); // index i-1 exists as i >= current_item_number == item_vector.size() >= 1 @@ -1090,7 +1120,8 @@ int Courtroom::adapt_numbered_items(QVector &item_vector, // item_vector is non-decreasing. This is because we want to allow for items // to, say, run in the background as invisible. With that said, we can now // properly format our new items - for (int i = 0; i < new_item_number; i++) { + for (int i = 0; i < new_item_number; i++) + { item_vector[i]->show(); set_size_and_pos(item_vector[i], item_name + "_" + QString::number(i)); // Note that show is deliberately placed before set_size_and_pos @@ -1106,7 +1137,8 @@ void Courtroom::check_effects() QString char_path = ao_app->get_character_path(current_char); QString theme_variant_path = ao_app->get_theme_variant_path(); QString theme_path = ao_app->get_theme_path(); - for (int i = 0; i < ui_effects.size(); ++i) { + for (int i = 0; i < ui_effects.size(); ++i) + { QStringList paths{char_path + effect_names.at(i) + ".webp", char_path + effect_names.at(i) + ".gif", theme_variant_path + effect_names.at(i) + ".webp", @@ -1118,8 +1150,10 @@ void Courtroom::check_effects() // Assume the effect does not exist until a matching file is found effects_enabled[i] = false; - for (QString path : paths) { - if (file_exists(path)) { + for (QString path : paths) + { + if (file_exists(path)) + { effects_enabled[i] = true; break; } @@ -1132,7 +1166,8 @@ void Courtroom::check_free_blocks() QString char_path = ao_app->get_character_path(current_char); QString theme_variant_path = ao_app->get_theme_variant_path(); QString theme_path = ao_app->get_theme_path(); - for (int i = 0; i < ui_free_blocks.size(); ++i) { + for (int i = 0; i < ui_free_blocks.size(); ++i) + { QStringList paths{char_path + free_block_names.at(i) + ".webp", char_path + free_block_names.at(i) + ".gif", theme_variant_path + free_block_names.at(i) + ".webp", @@ -1144,8 +1179,10 @@ void Courtroom::check_free_blocks() // Assume the free block does not exist until a matching file is found free_blocks_enabled[i] = false; - for (QString path : paths) { - if (file_exists(path)) { + for (QString path : paths) + { + if (file_exists(path)) + { free_blocks_enabled[i] = true; break; } @@ -1158,7 +1195,8 @@ void Courtroom::check_shouts() QString char_path = ao_app->get_character_path(current_char); QString theme_variant_path = ao_app->get_theme_variant_path(); QString theme_path = ao_app->get_theme_path(); - for (int i = 0; i < ui_shouts.size(); ++i) { + for (int i = 0; i < ui_shouts.size(); ++i) + { QStringList paths{char_path + shout_names.at(i) + ".webp", char_path + shout_names.at(i) + ".gif", theme_variant_path + shout_names.at(i) + ".webp", @@ -1170,8 +1208,10 @@ void Courtroom::check_shouts() // Assume the shout does not exist until a matching file is found shouts_enabled[i] = false; - for (QString path : paths) { - if (file_exists(path)) { + for (QString path : paths) + { + if (file_exists(path)) + { shouts_enabled[i] = true; break; } @@ -1184,7 +1224,8 @@ void Courtroom::check_wtce() QString char_path = ao_app->get_character_path(current_char); QString theme_variant_path = ao_app->get_theme_variant_path(); QString theme_path = ao_app->get_theme_path(); - for (int i = 0; i < ui_wtce.size(); ++i) { + for (int i = 0; i < ui_wtce.size(); ++i) + { QStringList paths{char_path + wtce_names.at(i) + ".webp", char_path + wtce_names.at(i) + ".gif", theme_variant_path + wtce_names.at(i) + ".webp", @@ -1196,8 +1237,10 @@ void Courtroom::check_wtce() // Assume the judge button does not exist until a matching file is found wtce_enabled[i] = false; - for (QString path : paths) { - if (file_exists(path)) { + for (QString path : paths) + { + if (file_exists(path)) + { wtce_enabled[i] = true; break; } @@ -1238,7 +1281,8 @@ void Courtroom::load_effects() effects_enabled.resize(effect_number); ui_effects.resize(effect_number); - for (int i = 0; i < ui_effects.size(); ++i) { + for (int i = 0; i < ui_effects.size(); ++i) + { ui_effects[i] = new AOButton(this, ao_app); ui_effects[i]->setProperty("effect_id", i + 1); ui_effects[i]->stackUnder(ui_effect_up); @@ -1252,9 +1296,11 @@ void Courtroom::load_effects() // And add names effect_names.clear(); - for (int i = 1; i <= ui_effects.size(); ++i) { + for (int i = 1; i <= ui_effects.size(); ++i) + { QStringList names = ao_app->get_effect(i); - if (!names.isEmpty()) { + if (!names.isEmpty()) + { QString name = names.at(0).trimmed(); effect_names.append(name); } @@ -1272,7 +1318,8 @@ void Courtroom::load_free_blocks() free_blocks_enabled.resize(free_block_number); ui_free_blocks.resize(free_block_number); - for (int i = 0; i < ui_free_blocks.size(); ++i) { + for (int i = 0; i < ui_free_blocks.size(); ++i) + { ui_free_blocks[i] = new AOMovie(this, ao_app); // ui_free_blocks[i]->setProperty("free_block_id", i+1); ui_free_blocks[i]->set_play_once(false); @@ -1281,10 +1328,12 @@ void Courtroom::load_free_blocks() // And add names free_block_names.clear(); - for (int i = 1; i <= ui_free_blocks.size(); ++i) { + for (int i = 1; i <= ui_free_blocks.size(); ++i) + { QString name = "free_block_" + ao_app->get_spbutton("[FREE BLOCKS]", i).trimmed(); - if (!name.isEmpty()) { + if (!name.isEmpty()) + { free_block_names.append(name); widget_names[name] = ui_free_blocks[i - 1]; ui_free_blocks[i - 1]->setObjectName(name); @@ -1304,7 +1353,8 @@ void Courtroom::load_shouts() shouts_enabled.resize(shout_number); ui_shouts.resize(shout_number); - for (int i = 0; i < ui_shouts.size(); ++i) { + for (int i = 0; i < ui_shouts.size(); ++i) + { ui_shouts[i] = new AOButton(this, ao_app); ui_shouts[i]->setProperty("shout_id", i + 1); ui_shouts[i]->stackUnder(ui_shout_up); @@ -1317,9 +1367,11 @@ void Courtroom::load_shouts() // And add names shout_names.clear(); - for (int i = 1; i <= ui_shouts.size(); ++i) { + for (int i = 1; i <= ui_shouts.size(); ++i) + { QString name = ao_app->get_spbutton("[SHOUTS]", i).trimmed(); - if (!name.isEmpty()) { + if (!name.isEmpty()) + { qDebug() << "SHOUT " << name << " " << ui_shouts[i - 1]; shout_names.append(name); widget_names[name] = ui_shouts[i - 1]; @@ -1339,7 +1391,8 @@ void Courtroom::load_wtce() wtce_enabled.resize(wtce_number); ui_wtce.resize(wtce_number); - for (int i = 0; i < ui_wtce.size(); ++i) { + for (int i = 0; i < ui_wtce.size(); ++i) + { ui_wtce[i] = new AOButton(this, ao_app); ui_wtce[i]->setProperty("wtce_id", i + 1); ui_wtce[i]->stackUnder(ui_wtce_up); @@ -1352,9 +1405,11 @@ void Courtroom::load_wtce() // And add names wtce_names.clear(); - for (int i = 1; i <= ui_wtce.size(); ++i) { + for (int i = 1; i <= ui_wtce.size(); ++i) + { QString name = ao_app->get_spbutton("[WTCE]", i).trimmed(); - if (!name.isEmpty()) { + if (!name.isEmpty()) + { wtce_names.append(name); widget_names[name] = ui_wtce[i - 1]; ui_wtce[i - 1]->setObjectName(name); @@ -1412,10 +1467,12 @@ void Courtroom::set_judge_wtce() return; // set visibility based off parameter - if (is_single_wtce == true) { + if (is_single_wtce == true) + { ui_wtce[m_wtce_current]->show(); } - else { + else + { for (AOButton *i_wtce : ui_wtce) i_wtce->show(); } @@ -1423,7 +1480,8 @@ void Courtroom::set_judge_wtce() void Courtroom::set_free_blocks() { - for (int i = 0; i < ui_free_blocks.size(); i++) { + for (int i = 0; i < ui_free_blocks.size(); i++) + { AOMovie *free_block = ui_free_blocks[i]; free_block->play(free_block_names[i]); } @@ -1463,7 +1521,8 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, ao_app->get_font_name("font_" + p_identifier, design_file); widget->setFont(QFont(font_name, f_weight)); - if (override_color.isEmpty()) { + if (override_color.isEmpty()) + { QString color = ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); if (color.isEmpty()) @@ -1514,7 +1573,8 @@ void Courtroom::set_fonts() set_font(ui_sfx_list, "sfx_list"); set_qtextedit_font(ui_vp_music_name, "music_name"); set_qtextedit_font(ui_vp_notepad, "notepad"); - for (int i = 0; i < timer_number; i++) { + for (int i = 0; i < timer_number; i++) + { set_qtextedit_font(ui_timers[i], "timer_" + QString::number(i)); } } @@ -1524,7 +1584,8 @@ void Courtroom::set_mute_list() mute_map.clear(); // maps which characters are muted based on cid, none are muted by default - for (int n_cid = 0; n_cid < char_list.size(); n_cid++) { + for (int n_cid = 0; n_cid < char_list.size(); n_cid++) + { mute_map.insert(n_cid, false); } @@ -1535,7 +1596,8 @@ void Courtroom::set_mute_list() sorted_mute_list.sort(); - for (QString i_name : sorted_mute_list) { + for (QString i_name : sorted_mute_list) + { // mute_map.insert(i_name, false); ui_mute_list->addItem(i_name); } diff --git a/datatypes.h b/datatypes.h index 41212b1cb..3651ff8d6 100644 --- a/datatypes.h +++ b/datatypes.h @@ -4,7 +4,8 @@ #include #include -struct record_type { +struct record_type +{ QString name; QString line; QString color; @@ -24,14 +25,16 @@ typedef std::shared_ptr record_type_ptr; typedef QVector record_type_array; -struct server_type { +struct server_type +{ QString name; QString desc; QString ip; int port; }; -struct emote_type { +struct emote_type +{ QString comment; QString preanim; QString anim; @@ -41,20 +44,23 @@ struct emote_type { int sfx_duration; }; -struct char_type { +struct char_type +{ QString name; QString description; QString evidence_string; bool taken; }; -struct evi_type { +struct evi_type +{ QString name; QString description; QString image; }; -struct chatmessage_type { +struct chatmessage_type +{ QString message; QString character; QString side; @@ -71,24 +77,28 @@ struct chatmessage_type { int flip; }; -struct area_type { +struct area_type +{ QString name; QString background; }; -struct pos_type { +struct pos_type +{ int x; int y; }; -struct pos_size_type { +struct pos_size_type +{ int x = 0; int y = 0; int width = 0; int height = 0; }; -enum CHAT_MESSAGE { +enum CHAT_MESSAGE +{ DESK_MOD = 0, PRE_EMOTE, CHAR_NAME, @@ -107,7 +117,8 @@ enum CHAT_MESSAGE { SHOWNAME }; -enum COLOR { +enum COLOR +{ WHITE = 0, GREEN, RED, diff --git a/discord-rpc.h b/discord-rpc.h index 455f62aa4..6795d18aa 100644 --- a/discord-rpc.h +++ b/discord-rpc.h @@ -20,67 +20,72 @@ // clang-format on #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif -typedef struct DiscordRichPresence { - const char *state; /* max 128 bytes */ - const char *details; /* max 128 bytes */ - int64_t startTimestamp; - int64_t endTimestamp; - const char *largeImageKey; /* max 32 bytes */ - const char *largeImageText; /* max 128 bytes */ - const char *smallImageKey; /* max 32 bytes */ - const char *smallImageText; /* max 128 bytes */ - const char *partyId; /* max 128 bytes */ - int partySize; - int partyMax; - const char *matchSecret; /* max 128 bytes */ - const char *joinSecret; /* max 128 bytes */ - const char *spectateSecret; /* max 128 bytes */ - int8_t instance; -} DiscordRichPresence; + typedef struct DiscordRichPresence + { + const char *state; /* max 128 bytes */ + const char *details; /* max 128 bytes */ + int64_t startTimestamp; + int64_t endTimestamp; + const char *largeImageKey; /* max 32 bytes */ + const char *largeImageText; /* max 128 bytes */ + const char *smallImageKey; /* max 32 bytes */ + const char *smallImageText; /* max 128 bytes */ + const char *partyId; /* max 128 bytes */ + int partySize; + int partyMax; + const char *matchSecret; /* max 128 bytes */ + const char *joinSecret; /* max 128 bytes */ + const char *spectateSecret; /* max 128 bytes */ + int8_t instance; + } DiscordRichPresence; -typedef struct DiscordJoinRequest { - const char *userId; - const char *username; - const char *discriminator; - const char *avatar; -} DiscordJoinRequest; + typedef struct DiscordJoinRequest + { + const char *userId; + const char *username; + const char *discriminator; + const char *avatar; + } DiscordJoinRequest; -typedef struct DiscordEventHandlers { - void (*ready)(void); - void (*disconnected)(int errorCode, const char *message); - void (*errored)(int errorCode, const char *message); - void (*joinGame)(const char *joinSecret); - void (*spectateGame)(const char *spectateSecret); - void (*joinRequest)(const DiscordJoinRequest *request); -} DiscordEventHandlers; + typedef struct DiscordEventHandlers + { + void (*ready)(void); + void (*disconnected)(int errorCode, const char *message); + void (*errored)(int errorCode, const char *message); + void (*joinGame)(const char *joinSecret); + void (*spectateGame)(const char *spectateSecret); + void (*joinRequest)(const DiscordJoinRequest *request); + } DiscordEventHandlers; #define DISCORD_REPLY_NO 0 #define DISCORD_REPLY_YES 1 #define DISCORD_REPLY_IGNORE 2 -DISCORD_EXPORT void Discord_Initialize(const char *applicationId, - DiscordEventHandlers *handlers, - int autoRegister, - const char *optionalSteamId); -DISCORD_EXPORT void Discord_Shutdown(void); + DISCORD_EXPORT void Discord_Initialize(const char *applicationId, + DiscordEventHandlers *handlers, + int autoRegister, + const char *optionalSteamId); + DISCORD_EXPORT void Discord_Shutdown(void); -/* checks for incoming messages, dispatches callbacks */ -DISCORD_EXPORT void Discord_RunCallbacks(void); + /* checks for incoming messages, dispatches callbacks */ + DISCORD_EXPORT void Discord_RunCallbacks(void); /* If you disable the lib starting its own io thread, you'll need to call this * from your own */ #ifdef DISCORD_DISABLE_IO_THREAD -DISCORD_EXPORT void Discord_UpdateConnection(void); + DISCORD_EXPORT void Discord_UpdateConnection(void); #endif -DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence *presence); -DISCORD_EXPORT void Discord_ClearPresence(void); + DISCORD_EXPORT void + Discord_UpdatePresence(const DiscordRichPresence *presence); + DISCORD_EXPORT void Discord_ClearPresence(void); -DISCORD_EXPORT void Discord_Respond(const char *userid, - /* DISCORD_REPLY_ */ int reply); + DISCORD_EXPORT void Discord_Respond(const char *userid, + /* DISCORD_REPLY_ */ int reply); #ifdef __cplusplus } /* extern "C" */ diff --git a/discord_register.h b/discord_register.h index 3dc89071b..efc2adc8a 100644 --- a/discord_register.h +++ b/discord_register.h @@ -15,13 +15,14 @@ #endif #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif -DISCORD_EXPORT void Discord_Register(const char *applicationId, - const char *command); -DISCORD_EXPORT void Discord_RegisterSteamGame(const char *applicationId, - const char *steamId); + DISCORD_EXPORT void Discord_Register(const char *applicationId, + const char *command); + DISCORD_EXPORT void Discord_RegisterSteamGame(const char *applicationId, + const char *steamId); #ifdef __cplusplus } diff --git a/discord_rich_presence.cpp b/discord_rich_presence.cpp index a7466ba26..cdcbf0beb 100644 --- a/discord_rich_presence.cpp +++ b/discord_rich_presence.cpp @@ -5,7 +5,8 @@ #include -namespace AttorneyOnline { +namespace AttorneyOnline +{ Discord::Discord() { @@ -53,8 +54,10 @@ void Discord::restart(const char *APPLICATION_ID) void Discord::toggle(int p_index) { - if (p_index >= 0 && p_index < 2) { - if (p_index != m_index) { + if (p_index >= 0 && p_index < 2) + { + if (p_index != m_index) + { restart(APPLICATION_ID[p_index]); m_index = p_index; } diff --git a/discord_rich_presence.h b/discord_rich_presence.h index 94dac1808..1107d4f5c 100644 --- a/discord_rich_presence.h +++ b/discord_rich_presence.h @@ -4,9 +4,11 @@ #include #include -namespace AttorneyOnline { +namespace AttorneyOnline +{ -class Discord { +class Discord +{ private: const char *APPLICATION_ID[2] = { "538080629535801347", diff --git a/discord_rpc.h b/discord_rpc.h index 72d2e7f2a..8dcf72c1f 100644 --- a/discord_rpc.h +++ b/discord_rpc.h @@ -20,67 +20,72 @@ // clang-format on #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif -typedef struct DiscordRichPresence { - const char *state; /* max 128 bytes */ - const char *details; /* max 128 bytes */ - int64_t startTimestamp; - int64_t endTimestamp; - const char *largeImageKey; /* max 32 bytes */ - const char *largeImageText; /* max 128 bytes */ - const char *smallImageKey; /* max 32 bytes */ - const char *smallImageText; /* max 128 bytes */ - const char *partyId; /* max 128 bytes */ - int partySize; - int partyMax; - const char *matchSecret; /* max 128 bytes */ - const char *joinSecret; /* max 128 bytes */ - const char *spectateSecret; /* max 128 bytes */ - int8_t instance; -} DiscordRichPresence; + typedef struct DiscordRichPresence + { + const char *state; /* max 128 bytes */ + const char *details; /* max 128 bytes */ + int64_t startTimestamp; + int64_t endTimestamp; + const char *largeImageKey; /* max 32 bytes */ + const char *largeImageText; /* max 128 bytes */ + const char *smallImageKey; /* max 32 bytes */ + const char *smallImageText; /* max 128 bytes */ + const char *partyId; /* max 128 bytes */ + int partySize; + int partyMax; + const char *matchSecret; /* max 128 bytes */ + const char *joinSecret; /* max 128 bytes */ + const char *spectateSecret; /* max 128 bytes */ + int8_t instance; + } DiscordRichPresence; -typedef struct DiscordJoinRequest { - const char *userId; - const char *username; - const char *discriminator; - const char *avatar; -} DiscordJoinRequest; + typedef struct DiscordJoinRequest + { + const char *userId; + const char *username; + const char *discriminator; + const char *avatar; + } DiscordJoinRequest; -typedef struct DiscordEventHandlers { - void (*ready)(void); - void (*disconnected)(int errorCode, const char *message); - void (*errored)(int errorCode, const char *message); - void (*joinGame)(const char *joinSecret); - void (*spectateGame)(const char *spectateSecret); - void (*joinRequest)(const DiscordJoinRequest *request); -} DiscordEventHandlers; + typedef struct DiscordEventHandlers + { + void (*ready)(void); + void (*disconnected)(int errorCode, const char *message); + void (*errored)(int errorCode, const char *message); + void (*joinGame)(const char *joinSecret); + void (*spectateGame)(const char *spectateSecret); + void (*joinRequest)(const DiscordJoinRequest *request); + } DiscordEventHandlers; #define DISCORD_REPLY_NO 0 #define DISCORD_REPLY_YES 1 #define DISCORD_REPLY_IGNORE 2 -DISCORD_EXPORT void Discord_Initialize(const char *applicationId, - DiscordEventHandlers *handlers, - int autoRegister, - const char *optionalSteamId); -DISCORD_EXPORT void Discord_Shutdown(void); + DISCORD_EXPORT void Discord_Initialize(const char *applicationId, + DiscordEventHandlers *handlers, + int autoRegister, + const char *optionalSteamId); + DISCORD_EXPORT void Discord_Shutdown(void); -/* checks for incoming messages, dispatches callbacks */ -DISCORD_EXPORT void Discord_RunCallbacks(void); + /* checks for incoming messages, dispatches callbacks */ + DISCORD_EXPORT void Discord_RunCallbacks(void); /* If you disable the lib starting its own io thread, you'll need to call this * from your own */ #ifdef DISCORD_DISABLE_IO_THREAD -DISCORD_EXPORT void Discord_UpdateConnection(void); + DISCORD_EXPORT void Discord_UpdateConnection(void); #endif -DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence *presence); -DISCORD_EXPORT void Discord_ClearPresence(void); + DISCORD_EXPORT void + Discord_UpdatePresence(const DiscordRichPresence *presence); + DISCORD_EXPORT void Discord_ClearPresence(void); -DISCORD_EXPORT void Discord_Respond(const char *userid, - /* DISCORD_REPLY_ */ int reply); + DISCORD_EXPORT void Discord_Respond(const char *userid, + /* DISCORD_REPLY_ */ int reply); #ifdef __cplusplus } /* extern "C" */ diff --git a/emotes.cpp b/emotes.cpp index 7f8a50b3a..ed9319b9c 100644 --- a/emotes.cpp +++ b/emotes.cpp @@ -50,7 +50,8 @@ void Courtroom::reconstruct_emotes() max_emotes_on_page = emote_columns * emote_rows; - for (int n = 0; n < max_emotes_on_page; ++n) { + for (int n = 0; n < max_emotes_on_page; ++n) + { int x_pos = (button_width + x_spacing) * x_mod_count; int y_pos = (button_height + y_spacing) * y_mod_count; @@ -65,7 +66,8 @@ void Courtroom::reconstruct_emotes() ++x_mod_count; - if (x_mod_count == emote_columns) { + if (x_mod_count == emote_columns) + { ++y_mod_count; x_mod_count = 0; } @@ -98,14 +100,16 @@ void Courtroom::set_emote_page() ui_emote_left->hide(); ui_emote_right->hide(); - for (AOEmoteButton *i_button : ui_emote_list) { + for (AOEmoteButton *i_button : ui_emote_list) + { i_button->hide(); } int total_pages = total_emotes / max_emotes_on_page; int emotes_on_page = 0; - if (total_emotes % max_emotes_on_page != 0) { + if (total_emotes % max_emotes_on_page != 0) + { ++total_pages; // i. e. not on the last page if (total_pages > current_emote_page + 1) @@ -122,7 +126,8 @@ void Courtroom::set_emote_page() if (current_emote_page > 0) ui_emote_left->show(); - for (int n_emote = 0; n_emote < emotes_on_page; ++n_emote) { + for (int n_emote = 0; n_emote < emotes_on_page; ++n_emote) + { int n_real_emote = n_emote + current_emote_page * max_emotes_on_page; AOEmoteButton *f_emote = ui_emote_list.at(n_emote); @@ -142,7 +147,8 @@ void Courtroom::set_emote_dropdown() int total_emotes = ao_app->get_emote_number(current_char); QStringList emote_list; - for (int n = 0; n < total_emotes; ++n) { + for (int n = 0; n < total_emotes; ++n) + { emote_list.append(ao_app->get_emote_comment(current_char, n)); } diff --git a/encryption_functions.cpp b/encryption_functions.cpp index 49bb99d08..22de1c003 100644 --- a/encryption_functions.cpp +++ b/encryption_functions.cpp @@ -20,7 +20,8 @@ QString fanta_encrypt(QString temp_input, unsigned int p_key) QVector temp_result; std::string input = temp_input.toUtf8().constData(); - for (unsigned int pos = 0; pos < input.size(); ++pos) { + for (unsigned int pos = 0; pos < input.size(); ++pos) + { uint_fast8_t output = input.at(pos) ^ (key >> 8) % 256; temp_result.append(output); key = (temp_result.at(pos) + key) * C1 + C2; @@ -28,7 +29,8 @@ QString fanta_encrypt(QString temp_input, unsigned int p_key) std::string result = ""; - for (uint_fast8_t i_int : temp_result) { + for (uint_fast8_t i_int : temp_result) + { result += omni::int_to_hex(i_int); } @@ -43,7 +45,8 @@ QString fanta_decrypt(QString temp_input, unsigned int key) QVector unhexed_vector; - for (unsigned int i = 0; i < input.length(); i += 2) { + for (unsigned int i = 0; i < input.length(); i += 2) + { std::string byte = input.substr(i, 2); unsigned int hex_int = strtoul(byte.c_str(), nullptr, 16); unhexed_vector.append(hex_int); @@ -54,7 +57,8 @@ QString fanta_decrypt(QString temp_input, unsigned int key) std::string result = ""; - for (int pos = 0; pos < unhexed_vector.size(); ++pos) { + for (int pos = 0; pos < unhexed_vector.size(); ++pos) + { unsigned char output = unhexed_vector.at(pos) ^ (key >> 8) % 256; result += output; key = (unhexed_vector.at(pos) + key) * C1 + C2; diff --git a/evidence.cpp b/evidence.cpp index e107cbf13..c5bb7de36 100644 --- a/evidence.cpp +++ b/evidence.cpp @@ -55,7 +55,8 @@ void Courtroom::construct_evidence() max_evidence_on_page = evidence_columns * evidence_rows; - for (int n = 0; n < max_evidence_on_page; ++n) { + for (int n = 0; n < max_evidence_on_page; ++n) + { int x_pos = (button_width + x_spacing) * x_mod_count; int y_pos = (button_height + y_spacing) * y_mod_count; @@ -75,7 +76,8 @@ void Courtroom::construct_evidence() ++x_mod_count; - if (x_mod_count == evidence_columns) { + if (x_mod_count == evidence_columns) + { ++y_mod_count; x_mod_count = 0; } @@ -116,7 +118,8 @@ void Courtroom::set_evidence_page() ui_evidence_left->hide(); ui_evidence_right->hide(); - for (AOEvidenceButton *i_button : ui_evidence_list) { + for (AOEvidenceButton *i_button : ui_evidence_list) + { i_button->reset(); } @@ -126,7 +129,8 @@ void Courtroom::set_evidence_page() int total_pages = total_evidence / max_evidence_on_page; int evidence_on_page = 0; - if ((total_evidence % max_evidence_on_page) != 0) { + if ((total_evidence % max_evidence_on_page) != 0) + { ++total_pages; // i. e. not on the last page if (total_pages > current_evidence_page + 1) @@ -144,7 +148,8 @@ void Courtroom::set_evidence_page() ui_evidence_left->show(); for (int n_evidence_button = 0; n_evidence_button < evidence_on_page; - ++n_evidence_button) { + ++n_evidence_button) + { int n_real_evidence = n_evidence_button + current_evidence_page * max_evidence_on_page; AOEvidenceButton *f_evidence_button = @@ -153,7 +158,8 @@ void Courtroom::set_evidence_page() // ie. the add evidence button if (n_real_evidence == (total_evidence - 1)) f_evidence_button->set_theme_image("addevidence.png"); - else if (n_real_evidence < (total_evidence - 1)) { + else if (n_real_evidence < (total_evidence - 1)) + { f_evidence_button->set_image( local_evidence_list.at(n_real_evidence).image); @@ -236,7 +242,8 @@ void Courtroom::on_evidence_clicked(int p_id) int f_real_id = p_id + max_evidence_on_page * current_evidence_page; - if (f_real_id == local_evidence_list.size()) { + if (f_real_id == local_evidence_list.size()) + { ao_app->send_server_packet( new AOPacket("PE###empty.png#%")); return; @@ -282,7 +289,8 @@ void Courtroom::on_evidence_hover(int p_id, bool p_state) ui_evidence_name->setReadOnly(true); int final_id = p_id + max_evidence_on_page * current_evidence_page; - if (p_state) { + if (p_state) + { if (final_id == local_evidence_list.size()) ui_evidence_name->setText("Add new evidence..."); else if (final_id < local_evidence_list.size()) diff --git a/file_functions.cpp b/file_functions.cpp index 50a91345e..c89f8f774 100644 --- a/file_functions.cpp +++ b/file_functions.cpp @@ -12,7 +12,8 @@ bool file_exists(QString file_path) QString file_exists(QString file_path, QVector p_exts) { - for (auto &ext : p_exts) { + for (auto &ext : p_exts) + { if (file_exists(file_path + ext)) return ext; } diff --git a/hardware_functions.cpp b/hardware_functions.cpp index 1a252a55e..733d95e85 100644 --- a/hardware_functions.cpp +++ b/hardware_functions.cpp @@ -34,10 +34,12 @@ QString get_hdid() QTextStream in(&fstab_file); - while (!in.atEnd()) { + while (!in.atEnd()) + { QString line = in.readLine(); - if (line.startsWith("UUID")) { + if (line.startsWith("UUID")) + { QStringList line_elements = line.split("="); if (line_elements.size() > 1) diff --git a/hex_functions.cpp b/hex_functions.cpp index d7d8f4e8b..7e2ac941f 100644 --- a/hex_functions.cpp +++ b/hex_functions.cpp @@ -3,13 +3,15 @@ #include "hex_functions.h" -namespace omni { +namespace omni +{ char halfword_to_hex_char(unsigned int input) { if (input > 127) return 'F'; - switch (input) { + switch (input) + { case 0: return '0'; case 1: diff --git a/hex_functions.h b/hex_functions.h index c37e7997a..c8388e0f0 100644 --- a/hex_functions.h +++ b/hex_functions.h @@ -5,7 +5,8 @@ #include #include -namespace omni { +namespace omni +{ char halfword_to_hex_char(unsigned int input); std::string int_to_hex(unsigned int input); } // namespace omni diff --git a/lobby.cpp b/lobby.cpp index e001d7a75..d4ca350c7 100644 --- a/lobby.cpp +++ b/lobby.cpp @@ -76,7 +76,8 @@ void Lobby::set_widgets() pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", filename); - if (f_lobby.width < 0 || f_lobby.height < 0) { + if (f_lobby.width < 0 || f_lobby.height < 0) + { qDebug() << "W: did not find lobby width or height in " << filename; // Most common symptom of bad config files and missing assets. @@ -89,7 +90,8 @@ void Lobby::set_widgets() this->resize(517, 666); } - else { + else + { this->resize(f_lobby.width, f_lobby.height); } @@ -175,11 +177,13 @@ void Lobby::set_size_and_pos(QWidget *p_widget, QString p_identifier) pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - if (design_ini_result.width < 0 || design_ini_result.height < 0) { + if (design_ini_result.width < 0 || design_ini_result.height < 0) + { qDebug() << "W: could not find " << p_identifier << " in " << filename; p_widget->hide(); } - else { + else + { p_widget->move(design_ini_result.x, design_ini_result.y); p_widget->resize(design_ini_result.width, design_ini_result.height); } @@ -382,7 +386,8 @@ void Lobby::on_server_list_clicked(QModelIndex p_model) if (n_server < 0) return; - if (public_servers_selected) { + if (public_servers_selected) + { QVector f_server_list = ao_app->get_server_list(); if (n_server >= f_server_list.size()) @@ -390,7 +395,8 @@ void Lobby::on_server_list_clicked(QModelIndex p_model) f_last_server = f_server_list.at(p_model.row()); } - else { + else + { if (n_server >= ao_app->get_favorite_list().size()) return; @@ -430,7 +436,8 @@ void Lobby::list_servers() ui_server_list->clear(); - for (server_type i_server : ao_app->get_server_list()) { + for (server_type i_server : ao_app->get_server_list()) + { ui_server_list->addItem(i_server.name); } } @@ -439,7 +446,8 @@ void Lobby::list_favorites() { ui_server_list->clear(); - for (server_type i_server : ao_app->get_favorite_list()) { + for (server_type i_server : ao_app->get_favorite_list()) + { ui_server_list->addItem(i_server.name); } } diff --git a/lobby.h b/lobby.h index 219ba70df..c78e26d1d 100644 --- a/lobby.h +++ b/lobby.h @@ -16,7 +16,8 @@ class AOApplication; -class Lobby : public QMainWindow { +class Lobby : public QMainWindow +{ Q_OBJECT public: diff --git a/main.cpp b/main.cpp index 3cff1c383..1e71c4564 100644 --- a/main.cpp +++ b/main.cpp @@ -22,7 +22,8 @@ int main(int argc, char *argv[]) AOApplication app(argc, argv); QPluginLoader apng("qapng"); - if (!apng.load()) { + if (!apng.load()) + { #ifdef QT_NO_DEBUG call_error("APNG plugin could not be loaded."); #endif diff --git a/networkmanager.cpp b/networkmanager.cpp index c4b2aa59f..1563ab8d8 100644 --- a/networkmanager.cpp +++ b/networkmanager.cpp @@ -64,10 +64,12 @@ void NetworkManager::disconnect_from_server() void NetworkManager::ship_ms_packet(QString p_packet) { - if (!ms_socket->isOpen()) { + if (!ms_socket->isOpen()) + { retry_ms_connect(); } - else { + else + { ms_socket->write(p_packet.toUtf8()); } } @@ -82,14 +84,17 @@ void NetworkManager::handle_ms_packet() QByteArray buffer = ms_socket->readAll(); QString in_data = QString::fromUtf8(buffer, buffer.size()); - if (!in_data.endsWith("%")) { + if (!in_data.endsWith("%")) + { ms_partial_packet = true; ms_temp_packet += in_data; return; } - else { - if (ms_partial_packet) { + else + { + if (ms_partial_packet) + { in_data = ms_temp_packet + in_data; ms_temp_packet = ""; ms_partial_packet = false; @@ -99,7 +104,8 @@ void NetworkManager::handle_ms_packet() QStringList packet_list = in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); - for (QString packet : packet_list) { + for (QString packet : packet_list) + { AOPacket *f_packet = new AOPacket(packet); ao_app->ms_packet_received(f_packet); @@ -120,27 +126,33 @@ void NetworkManager::on_srv_lookup() { #ifdef MS_FAILOVER_SUPPORTED bool connected = false; - if (ms_dns->error() != QDnsLookup::NoError) { + if (ms_dns->error() != QDnsLookup::NoError) + { qWarning("SRV lookup of the master server DNS failed."); ms_dns->deleteLater(); } - else { + else + { const auto srv_records = ms_dns->serviceRecords(); - for (const QDnsServiceRecord &record : srv_records) { + for (const QDnsServiceRecord &record : srv_records) + { qDebug() << "Connecting to " << record.target() << ":" << record.port(); ms_socket->connectToHost(record.target(), record.port()); QTime timer; timer.start(); - do { + do + { ao_app->processEvents(); - if (ms_socket->state() == QAbstractSocket::ConnectedState) { + if (ms_socket->state() == QAbstractSocket::ConnectedState) + { connected = true; break; } else if (ms_socket->state() != QAbstractSocket::ConnectingState && ms_socket->state() != QAbstractSocket::HostLookupState && - ms_socket->error() != -1) { + ms_socket->error() != -1) + { qDebug() << ms_socket->error(); qWarning() << "Error connecting to master server:" << ms_socket->errorString(); @@ -151,7 +163,8 @@ void NetworkManager::on_srv_lookup() } while (timer.elapsed() < timeout_milliseconds); // Very expensive spin-wait loop - it will // bring CPU to 100%! - if (connected) { + if (connected) + { // Connect a one-shot signal in case the master server disconnects // randomly QObject::connect( @@ -159,7 +172,8 @@ void NetworkManager::on_srv_lookup() SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); break; } - else { + else + { ms_socket->abort(); ms_socket->close(); } @@ -213,14 +227,17 @@ void NetworkManager::handle_server_packet() QByteArray buffer = server_socket->readAll(); QString in_data = QString::fromUtf8(buffer, buffer.size()); - if (!in_data.endsWith("%")) { + if (!in_data.endsWith("%")) + { partial_packet = true; temp_packet += in_data; return; } - else { - if (partial_packet) { + else + { + if (partial_packet) + { in_data = temp_packet + in_data; temp_packet = ""; partial_packet = false; @@ -230,7 +247,8 @@ void NetworkManager::handle_server_packet() QStringList packet_list = in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); - for (QString packet : packet_list) { + for (QString packet : packet_list) + { AOPacket *f_packet = new AOPacket(packet); ao_app->server_packet_received(f_packet); diff --git a/networkmanager.h b/networkmanager.h index 5a4122709..5f5eb7a5a 100644 --- a/networkmanager.h +++ b/networkmanager.h @@ -22,7 +22,8 @@ #include #include -class NetworkManager : public QObject { +class NetworkManager : public QObject +{ Q_OBJECT public: diff --git a/packet_distribution.cpp b/packet_distribution.cpp index ec1ac8ecd..b679ac9d0 100644 --- a/packet_distribution.cpp +++ b/packet_distribution.cpp @@ -20,14 +20,17 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) if (header != "CHECK") qDebug() << "R(ms):" << p_packet->to_string(); - if (header == "ALL") { + if (header == "ALL") + { server_list.clear(); - for (QString i_string : p_packet->get_contents()) { + for (QString i_string : p_packet->get_contents()) + { server_type f_server; QStringList sub_contents = i_string.split("&"); - if (sub_contents.size() < 4) { + if (sub_contents.size() < 4) + { qDebug() << "W: malformed packet"; continue; } @@ -40,29 +43,35 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) server_list.append(f_server); } - if (lobby_constructed) { + if (lobby_constructed) + { w_lobby->list_servers(); } } - else if (header == "CT") { + else if (header == "CT") + { QString f_name, f_message; - if (f_contents.size() == 1) { + if (f_contents.size() == 1) + { f_name = ""; f_message = f_contents.at(0); } - else if (f_contents.size() >= 2) { + else if (f_contents.size() >= 2) + { f_name = f_contents.at(0); f_message = f_contents.at(1); } else goto end; - if (lobby_constructed) { + if (lobby_constructed) + { w_lobby->append_chatmessage(f_name, f_message); } } - else if (header == "AO2CHECK") { + else if (header == "AO2CHECK") + { send_ms_packet(new AOPacket("ID#AO2#" + get_version_string() + "#%")); send_ms_packet(new AOPacket("HI#" + get_hdid() + "#%")); @@ -80,10 +89,12 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) if (get_release() > f_release) goto end; - else if (get_release() == f_release) { + else if (get_release() == f_release) + { if (get_major_version() > f_major) goto end; - else if (get_major_version() == f_major) { + else if (get_major_version() == f_major) + { if (get_minor_version() >= f_minor) goto end; } @@ -94,7 +105,8 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) destruct_courtroom(); destruct_lobby(); } - else if (header == "DOOM") { + else if (header == "DOOM") + { call_notice("You have been exiled from AO." "Have a nice day."); destruct_courtroom(); @@ -117,7 +129,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (header != "checkconnection") qDebug() << "R:" << f_packet; - if (header == "decryptor") { + if (header == "decryptor") + { if (f_contents.size() == 0) goto end; @@ -144,7 +157,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) AOPacket *hi_packet = new AOPacket("HI#" + f_hdid + "#%"); send_server_packet(hi_packet); } - else if (header == "ID") { + else if (header == "ID") + { if (f_contents.size() < 2) goto end; @@ -153,7 +167,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) send_server_packet(new AOPacket("ID#AO2#" + get_version_string() + "#%")); } - else if (header == "CT") { + else if (header == "CT") + { if (f_contents.size() < 2) goto end; @@ -161,7 +176,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) w_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1)); } - else if (header == "FL") { + else if (header == "FL") + { if (f_packet.contains("yellowtext", Qt::CaseInsensitive)) yellow_text_enabled = true; if (f_packet.contains("flipping", Qt::CaseInsensitive)) @@ -177,14 +193,16 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (f_packet.contains("evidence", Qt::CaseInsensitive)) evidence_enabled = true; } - else if (header == "PN") { + else if (header == "PN") + { if (f_contents.size() < 2) goto end; w_lobby->set_player_count(f_contents.at(0).toInt(), f_contents.at(1).toInt()); } - else if (header == "SI") { + else if (header == "SI") + { if (f_contents.size() != 3) goto end; @@ -207,16 +225,20 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int selected_server = w_lobby->get_selected_server(); QString server_address = "", server_name = ""; - if (w_lobby->public_servers_selected) { - if (selected_server >= 0 && selected_server < server_list.size()) { + if (w_lobby->public_servers_selected) + { + if (selected_server >= 0 && selected_server < server_list.size()) + { auto info = server_list.at(selected_server); server_name = info.name; server_address = info.ip + info.port; window_title += ": " + server_name; } } - else { - if (selected_server >= 0 && selected_server < favorite_list.size()) { + else + { + if (selected_server >= 0 && selected_server < favorite_list.size()) + { auto info = favorite_list.at(selected_server); server_name = info.name; server_address = info.ip + info.port; @@ -244,11 +266,13 @@ void AOApplication::server_packet_received(AOPacket *p_packet) discord->state_server(server_name.toStdString(), hash.result().toBase64().toStdString()); } - else if (header == "CI") { + else if (header == "CI") + { if (!courtroom_constructed) goto end; - for (int n_element = 0; n_element < f_contents.size(); n_element += 2) { + for (int n_element = 0; n_element < f_contents.size(); n_element += 2) + { if (f_contents.at(n_element).toInt() != loaded_chars) break; @@ -285,13 +309,15 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (improved_loading_enabled) send_server_packet(new AOPacket("RE#%")); - else { + else + { QString next_packet_number = QString::number(((loaded_chars - 1) / 10) + 1); send_server_packet(new AOPacket("AN#" + next_packet_number + "#%")); } } - else if (header == "EI") { + else if (header == "EI") + { if (!courtroom_constructed) goto end; @@ -331,14 +357,16 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QString next_packet_number = QString::number(loaded_evidence); send_server_packet(new AOPacket("AE#" + next_packet_number + "#%")); } - else if (header == "EM") { + else if (header == "EM") + { if (!courtroom_constructed) goto end; bool music_turn = false; int areas = 0; - for (int n_element = 0; n_element < f_contents.size(); n_element += 2) { + for (int n_element = 0; n_element < f_contents.size(); n_element += 2) + { if (f_contents.at(n_element).toInt() != loaded_music) break; @@ -353,19 +381,23 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QString::number(loaded_music) + "/" + QString::number(music_list_size)); - if (music_turn) { + if (music_turn) + { w_courtroom->append_music(f_music); } - else { + else + { if (f_music.endsWith(".wav") || f_music.endsWith(".mp3") || f_music.endsWith(".mp4") || f_music.endsWith(".ogg") || - f_music.endsWith(".opus")) { + f_music.endsWith(".opus")) + { music_turn = true; areas--; w_courtroom->fix_last_area(); w_courtroom->append_music(f_music); } - else { + else + { w_courtroom->append_area(f_music); areas++; } @@ -383,11 +415,13 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QString next_packet_number = QString::number(((loaded_music - 1) / 10) + 1); send_server_packet(new AOPacket("AM#" + next_packet_number + "#%")); } - else if (header == "CharsCheck") { + else if (header == "CharsCheck") + { if (!courtroom_constructed) goto end; - for (int n_char = 0; n_char < f_contents.size(); ++n_char) { + for (int n_char = 0; n_char < f_contents.size(); ++n_char) + { if (f_contents.at(n_char) == "-1") w_courtroom->set_taken(n_char, true); else @@ -395,11 +429,13 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } } - else if (header == "SC") { + else if (header == "SC") + { if (!courtroom_constructed) goto end; - for (int n_element = 0; n_element < f_contents.size(); ++n_element) { + for (int n_element = 0; n_element < f_contents.size(); ++n_element) + { QStringList sub_elements = f_contents.at(n_element).split("&"); char_type f_char; @@ -427,35 +463,41 @@ void AOApplication::server_packet_received(AOPacket *p_packet) send_server_packet(new AOPacket("RM#%")); } - else if (header == "SM") { + else if (header == "SM") + { if (!courtroom_constructed) goto end; bool musics_time = false; int areas = 0; - for (int n_element = 0; n_element < f_contents.size(); ++n_element) { + for (int n_element = 0; n_element < f_contents.size(); ++n_element) + { ++loaded_music; w_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + QString::number(music_list_size)); - if (musics_time) { + if (musics_time) + { w_courtroom->append_music(f_contents.at(n_element)); } - else { + else + { if (f_contents.at(n_element).endsWith(".wav") || f_contents.at(n_element).endsWith(".mp3") || f_contents.at(n_element).endsWith(".mp4") || f_contents.at(n_element).endsWith(".ogg") || - f_contents.at(n_element).endsWith(".opus")) { + f_contents.at(n_element).endsWith(".opus")) + { musics_time = true; w_courtroom->fix_last_area(); w_courtroom->append_music(f_contents.at(n_element)); areas--; } - else { + else + { w_courtroom->append_area(f_contents.at(n_element)); areas++; } @@ -470,7 +512,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) send_server_packet(new AOPacket("RD#%")); } - else if (header == "FM") { + else if (header == "FM") + { if (!courtroom_constructed) goto end; @@ -480,23 +523,28 @@ void AOApplication::server_packet_received(AOPacket *p_packet) bool musics_time = false; int areas = 0; - for (int n_element = 0; n_element < f_contents.size(); ++n_element) { - if (musics_time) { + for (int n_element = 0; n_element < f_contents.size(); ++n_element) + { + if (musics_time) + { w_courtroom->append_music(f_contents.at(n_element)); } - else { + else + { if (f_contents.at(n_element).endsWith(".wav") || f_contents.at(n_element).endsWith(".mp3") || f_contents.at(n_element).endsWith(".mp4") || f_contents.at(n_element).endsWith(".ogg") || - f_contents.at(n_element).endsWith(".opus")) { + f_contents.at(n_element).endsWith(".opus")) + { musics_time = true; w_courtroom->fix_last_area(); w_courtroom->append_music(f_contents.at(n_element)); areas--; // qDebug() << "wtf!!" << f_contents.at(n_element); } - else { + else + { w_courtroom->append_area(f_contents.at(n_element)); areas++; } @@ -506,7 +554,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) w_courtroom->list_music(); w_courtroom->list_areas(); } - else if (header == "DONE") { + else if (header == "DONE") + { if (!courtroom_constructed) goto end; @@ -516,47 +565,57 @@ void AOApplication::server_packet_received(AOPacket *p_packet) destruct_lobby(); } - else if (header == "BN") { + else if (header == "BN") + { if (f_contents.size() < 1) goto end; - if (courtroom_constructed) { + if (courtroom_constructed) + { w_courtroom->set_background(f_contents.at(0)); w_courtroom->set_scene(); } } // server accepting char request(CC) packet - else if (header == "PV") { + else if (header == "PV") + { if (f_contents.size() < 3) goto end; if (courtroom_constructed) w_courtroom->enter_courtroom(f_contents.at(2).toInt()); } - else if (header == "MS") { + else if (header == "MS") + { if (courtroom_constructed && courtroom_loaded) w_courtroom->handle_chatmessage(&p_packet->get_contents()); } - else if (header == "MC") { + else if (header == "MC") + { if (courtroom_constructed && courtroom_loaded) w_courtroom->handle_song(&p_packet->get_contents()); } - else if (header == "RT") { + else if (header == "RT") + { if (f_contents.size() < 1) goto end; if (courtroom_constructed) w_courtroom->handle_wtce(f_contents.at(0)); } - else if (header == "HP") { + 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()); } - else if (header == "LE") { - if (courtroom_constructed) { + else if (header == "LE") + { + if (courtroom_constructed) + { QVector f_evi_list; - for (QString f_string : f_contents) { + for (QString f_string : f_contents) + { QStringList sub_contents = f_string.split("&"); if (sub_contents.size() < 3) @@ -573,20 +632,25 @@ void AOApplication::server_packet_received(AOPacket *p_packet) w_courtroom->set_evidence_list(f_evi_list); } } - else if (header == "IL") { + else if (header == "IL") + { if (courtroom_constructed && f_contents.size() > 0) w_courtroom->set_ip_list(f_contents.at(0)); } - else if (header == "MU") { + else if (header == "MU") + { if (courtroom_constructed && f_contents.size() > 0) w_courtroom->set_mute(true, f_contents.at(0).toInt()); } - else if (header == "UM") { + else if (header == "UM") + { if (courtroom_constructed && f_contents.size() > 0) w_courtroom->set_mute(false, f_contents.at(0).toInt()); } - else if (header == "KK") { - if (courtroom_constructed && f_contents.size() > 0) { + else if (header == "KK") + { + if (courtroom_constructed && f_contents.size() > 0) + { int f_cid = w_courtroom->get_cid(); int remote_cid = f_contents.at(0).toInt(); @@ -598,26 +662,33 @@ void AOApplication::server_packet_received(AOPacket *p_packet) destruct_courtroom(); } } - else if (header == "KB") { + else if (header == "KB") + { if (courtroom_constructed && f_contents.size() > 0) w_courtroom->set_ban(f_contents.at(0).toInt()); } - else if (header == "BD") { + else if (header == "BD") + { call_notice("You are banned on this server."); } - else if (header == "ZZ") { + else if (header == "ZZ") + { if (courtroom_constructed && f_contents.size() > 0) w_courtroom->mod_called(f_contents.at(0)); } - else if (header == "CL") { + else if (header == "CL") + { w_courtroom->handle_clock(f_contents.at(1)); } - else if (header == "VA") { - if (courtroom_constructed) { + else if (header == "VA") + { + if (courtroom_constructed) + { w_courtroom->handle_theme_variant(f_contents.at(0)); } } - else if (header == "TR") { + else if (header == "TR") + { // Timer resume if (f_contents.size() != 1) goto end; @@ -625,7 +696,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int timer_id = f_contents.at(0).toInt(); w_courtroom->resume_timer(timer_id); } - else if (header == "TST") { + else if (header == "TST") + { // Timer set time if (f_contents.size() != 2) goto end; @@ -634,7 +706,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int new_time = f_contents.at(1).toInt(); w_courtroom->set_timer_time(timer_id, new_time); } - else if (header == "TSS") { + else if (header == "TSS") + { // Timer set timeStep length if (f_contents.size() != 2) goto end; @@ -643,7 +716,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int timestep_length = f_contents.at(1).toInt(); w_courtroom->set_timer_timestep(timer_id, timestep_length); } - else if (header == "TSF") { + else if (header == "TSF") + { // Timer set Firing interval if (f_contents.size() != 2) goto end; @@ -652,7 +726,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int firing_interval = f_contents.at(1).toInt(); w_courtroom->set_timer_firing(timer_id, firing_interval); } - else if (header == "TP") { + else if (header == "TP") + { // Timer pause if (f_contents.size() != 1) goto end; @@ -660,7 +735,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int timer_id = f_contents.at(0).toInt(); w_courtroom->pause_timer(timer_id); } - else if (header == "SP") { + else if (header == "SP") + { // Set position if (f_contents.size() != 1) goto end; @@ -693,13 +769,15 @@ void AOApplication::send_server_packet(AOPacket *p_packet, bool encoded) QString f_packet = p_packet->to_string(); - if (encryption_needed) { + if (encryption_needed) + { qDebug() << "S(e):" << f_packet; p_packet->encrypt_header(s_decryptor); f_packet = p_packet->to_string(); } - else { + else + { qDebug() << "S:" << f_packet; } diff --git a/path_functions.cpp b/path_functions.cpp index e14af8672..dae6a838b 100644 --- a/path_functions.cpp +++ b/path_functions.cpp @@ -12,15 +12,18 @@ QString base_path = ""; QString AOApplication::get_base_path() { - if (base_path == "") { + if (base_path == "") + { #ifdef BASE_OVERRIDE base_path = base_override; #elif defined(ANDROID) QString sdcard_storage = getenv("SECONDARY_STORAGE"); - if (dir_exists(sdcard_storage + "/AO2/")) { + if (dir_exists(sdcard_storage + "/AO2/")) + { base_path = sdcard_storage + "/AO2/"; } - else { + else + { QString external_storage = getenv("EXTERNAL_STORAGE"); base_path = external_storage + "/AO2/"; } diff --git a/text_file_functions.cpp b/text_file_functions.cpp index a77948911..e1e0810db 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -40,7 +40,8 @@ QString AOApplication::read_note(QString filename) { QFile note_txt(filename); - if (!note_txt.open(QIODevice::ReadOnly | QFile::Text)) { + if (!note_txt.open(QIODevice::ReadOnly | QFile::Text)) + { qDebug() << "Couldn't open" << filename; return ""; } @@ -54,7 +55,8 @@ QString AOApplication::read_note(QString filename) void AOApplication::write_note(QString p_text, QString p_file) { QFile f_log(p_file); - if (f_log.open(QIODevice::WriteOnly | QFile::Text)) { + if (f_log.open(QIODevice::WriteOnly | QFile::Text)) + { QTextStream out(&f_log); out << p_text; @@ -67,7 +69,8 @@ void AOApplication::write_note(QString p_text, QString p_file) void AOApplication::append_note(QString p_line, QString p_file) { QFile f_log(p_file); - if (f_log.open(QIODevice::WriteOnly | QIODevice::Append)) { + if (f_log.open(QIODevice::WriteOnly | QIODevice::Append)) + { QTextStream out(&f_log); out << p_line << "\r\n"; @@ -84,7 +87,8 @@ void AOApplication::write_to_serverlist_txt(QString p_line) serverlist_txt.setFileName(serverlist_txt_path); - if (!serverlist_txt.open(QIODevice::WriteOnly | QIODevice::Append)) { + if (!serverlist_txt.open(QIODevice::WriteOnly | QIODevice::Append)) + { return; } @@ -104,13 +108,15 @@ QVector AOApplication::read_serverlist_txt() serverlist_txt.setFileName(serverlist_txt_path); - if (!serverlist_txt.open(QIODevice::ReadOnly)) { + if (!serverlist_txt.open(QIODevice::ReadOnly)) + { return f_server_list; } QTextStream in(&serverlist_txt); - while (!in.atEnd()) { + while (!in.atEnd()) + { QString line = in.readLine(); server_type f_server; QStringList line_contents = line.split(":"); @@ -136,14 +142,16 @@ QString AOApplication::read_design_ini(QString p_identifier, design_ini.setFileName(p_design_path); - if (!design_ini.open(QIODevice::ReadOnly)) { + if (!design_ini.open(QIODevice::ReadOnly)) + { return ""; } QTextStream in(&design_ini); QString result = ""; - while (!in.atEnd()) { + while (!in.atEnd()) + { QString f_line = in.readLine().trimmed(); if (!f_line.startsWith(p_identifier)) @@ -269,7 +277,8 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) QStringList paths{get_theme_variant_path() + p_file, get_theme_path() + p_file}; - for (QString path : paths) { + for (QString path : paths) + { QFile design_ini; design_ini.setFileName(path); if (!design_ini.open(QIODevice::ReadOnly)) @@ -279,14 +288,17 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) QString f_text; bool tag_found = false; - while (!in.atEnd()) { + while (!in.atEnd()) + { QString line = in.readLine(); - if (line.startsWith(target_tag, Qt::CaseInsensitive)) { + if (line.startsWith(target_tag, Qt::CaseInsensitive)) + { tag_found = true; continue; } - if (tag_found) { + if (tag_found) + { if ((line.startsWith("[") && line.endsWith("]"))) break; f_text.append(line); @@ -308,7 +320,8 @@ QVector AOApplication::get_highlight_color() QStringList paths{get_theme_variant_path() + p_file, get_theme_path() + p_file}; - for (QString path : paths) { + for (QString path : paths) + { QVector f_vec; QFile design_ini; @@ -319,15 +332,18 @@ QVector AOApplication::get_highlight_color() QTextStream in(&design_ini); bool tag_found = false; - while (!in.atEnd()) { + while (!in.atEnd()) + { QString line = in.readLine(); - if (line.startsWith("[HIGHLIGHTS]", Qt::CaseInsensitive)) { + if (line.startsWith("[HIGHLIGHTS]", Qt::CaseInsensitive)) + { tag_found = true; continue; } - if (tag_found) { + if (tag_found) + { if ((line.startsWith("[") && line.endsWith("]"))) break; // Syntax @@ -361,7 +377,8 @@ QString AOApplication::get_spbutton(QString p_tag, int index) QStringList paths{get_theme_variant_path() + p_file, get_theme_path() + p_file}; - for (QString path : paths) { + for (QString path : paths) + { QString res = ""; QFile design_ini; @@ -372,15 +389,18 @@ QString AOApplication::get_spbutton(QString p_tag, int index) QTextStream in(&design_ini); bool tag_found = false; - while (!in.atEnd()) { + while (!in.atEnd()) + { QString line = in.readLine(); - if (line.startsWith(p_tag, Qt::CaseInsensitive)) { + if (line.startsWith(p_tag, Qt::CaseInsensitive)) + { tag_found = true; continue; } - if (tag_found) { + if (tag_found) + { if ((line.startsWith("[") && line.endsWith("]"))) break; @@ -405,7 +425,8 @@ QStringList AOApplication::get_effect(int index) QStringList paths{get_theme_variant_path() + p_file, get_theme_path() + p_file}; - for (QString path : paths) { + for (QString path : paths) + { QStringList res; QFile design_ini; @@ -416,15 +437,18 @@ QStringList AOApplication::get_effect(int index) QTextStream in(&design_ini); bool tag_found = false; - while (!in.atEnd()) { + while (!in.atEnd()) + { QString line = in.readLine(); - if (line.startsWith("[EFFECTS]", Qt::CaseInsensitive)) { + if (line.startsWith("[EFFECTS]", Qt::CaseInsensitive)) + { tag_found = true; continue; } - if (tag_found) { + if (tag_found) + { if ((line.startsWith("[") && line.endsWith("]"))) break; @@ -458,19 +482,22 @@ QStringList AOApplication::get_sfx_list() "sounds.ini"); if (!char_sfx_list_ini.open(QIODevice::ReadOnly) && - !base_sfx_list_ini.open(QIODevice::ReadOnly)) { + !base_sfx_list_ini.open(QIODevice::ReadOnly)) + { return return_value; } QTextStream in_a(&base_sfx_list_ini); QTextStream in_b(&char_sfx_list_ini); - while (!in_a.atEnd()) { + while (!in_a.atEnd()) + { QString line = in_a.readLine(); return_value.append(line); } - while (!in_b.atEnd()) { + while (!in_b.atEnd()) + { QString line = in_b.readLine(); return_value.append(line); } @@ -479,8 +506,8 @@ QStringList AOApplication::get_sfx_list() } // returns whatever is to the right of "search_line =" within target_tag and -// terminator_tag, trimmed returns the empty string if the search line couldnt be -// found +// terminator_tag, trimmed returns the empty string if the search line couldnt +// be found QString AOApplication::read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag) { @@ -497,13 +524,15 @@ QString AOApplication::read_char_ini(QString p_char, QString p_search_line, bool tag_found = false; - while (!in.atEnd()) { + while (!in.atEnd()) + { QString line = in.readLine(); if (QString::compare(line, terminator_tag, Qt::CaseInsensitive) == 0) break; - if (line.startsWith(target_tag, Qt::CaseInsensitive)) { + if (line.startsWith(target_tag, Qt::CaseInsensitive)) + { tag_found = true; continue; } @@ -520,7 +549,8 @@ QString AOApplication::read_char_ini(QString p_char, QString p_search_line, if (line_elements.size() < 2) continue; - if (tag_found) { + if (tag_found) + { char_ini.close(); return line_elements.at(1).trimmed(); } @@ -556,13 +586,15 @@ QString AOApplication::read_showname(QString p_char) { QString f_filename = get_base_path() + "configs/shownames.ini"; QFile f_file(f_filename); - if (!f_file.open(QIODevice::ReadOnly)) { + if (!f_file.open(QIODevice::ReadOnly)) + { qDebug() << "Error reading" << f_filename; return ""; } QTextStream in(&f_file); - while (!in.atEnd()) { + while (!in.atEnd()) + { QString f_line = in.readLine(); if (!f_line.startsWith(p_char)) continue; @@ -627,7 +659,8 @@ QString AOApplication::get_emote_comment(QString p_char, int p_emote) QStringList result_contents = f_result.split("#"); - if (result_contents.size() < 4) { + if (result_contents.size() < 4) + { qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; return "normal"; } @@ -642,7 +675,8 @@ QString AOApplication::get_pre_emote(QString p_char, int p_emote) QStringList result_contents = f_result.split("#"); - if (result_contents.size() < 4) { + if (result_contents.size() < 4) + { qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; return ""; } @@ -657,7 +691,8 @@ QString AOApplication::get_emote(QString p_char, int p_emote) QStringList result_contents = f_result.split("#"); - if (result_contents.size() < 4) { + if (result_contents.size() < 4) + { qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; return "normal"; } @@ -672,7 +707,8 @@ int AOApplication::get_emote_mod(QString p_char, int p_emote) QStringList result_contents = f_result.split("#"); - if (result_contents.size() < 4) { + if (result_contents.size() < 4) + { qDebug() << "W: misformatted char.ini: " << p_char << ", " << QString::number(p_emote); return 0; @@ -768,7 +804,8 @@ QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) get_default_theme_path() + p_file, }; - for (QString path : paths) { + for (QString path : paths) + { QString f_result = read_design_ini(p_identifier, path); if (!f_result.isEmpty()) return f_result; From 15e869c581d42efe794e6b74716652ab308c0622 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 8 Sep 2020 17:16:59 +0200 Subject: [PATCH 084/842] Formatting: short blocks on separate lines --- .clang-format | 7 ++ aoabstractplayer.cpp | 5 +- aoapplication.cpp | 30 ++++++-- aoapplication.h | 25 +++++-- aobasshandle.cpp | 19 ++++-- aocharbutton.cpp | 10 ++- aocharmovie.cpp | 10 ++- aoconfig.cpp | 140 ++++++++++++++++++++++++++++++-------- aoemotebutton.cpp | 5 +- aoemotebutton.h | 10 ++- aoevidencebutton.cpp | 5 +- aoevidencebutton.h | 5 +- aoevidencedescription.cpp | 5 +- aoevidencedisplay.cpp | 5 +- aoexception.cpp | 4 +- aolineedit.cpp | 5 +- aomovie.cpp | 10 ++- aomusicplayer.cpp | 5 +- aopacket.h | 10 ++- aopixmap.cpp | 4 +- aosfxplayer.cpp | 5 +- aoshoutplayer.cpp | 5 +- aotextarea.cpp | 4 +- aotimer.cpp | 15 +++- aotimer.h | 15 +++- audio_functions.cpp | 5 +- charselect.cpp | 5 +- courtroom.cpp | 45 +++++++++--- courtroom.h | 40 ++++++++--- discord_rich_presence.cpp | 9 ++- lobby.cpp | 5 +- lobby.h | 10 ++- networkmanager.cpp | 4 +- path_functions.cpp | 5 +- text_file_functions.cpp | 35 ++++++++-- 35 files changed, 421 insertions(+), 105 deletions(-) diff --git a/.clang-format b/.clang-format index b91e86c94..05fa7ae1e 100644 --- a/.clang-format +++ b/.clang-format @@ -1,2 +1,9 @@ BasedOnStyle : LLVM BreakBeforeBraces : Allman +AllowShortBlocksOnASingleLine : Never +AllowShortCaseLabelsOnASingleLine : false +AllowShortFunctionsOnASingleLine : None +AllowShortIfStatementsOnASingleLine : Never +AllowShortLambdasOnASingleLine : None +AllowShortLoopsOnASingleLine : false +# AllowShortEnumsOnASingleLine : false \ No newline at end of file diff --git a/aoabstractplayer.cpp b/aoabstractplayer.cpp index 1207446fd..a2dd74acf 100644 --- a/aoabstractplayer.cpp +++ b/aoabstractplayer.cpp @@ -5,7 +5,10 @@ AOAbstractPlayer::AOAbstractPlayer(QObject *p_parent, AOApplication *p_ao_app) { } -int AOAbstractPlayer::get_volume() { return m_volume; } +int AOAbstractPlayer::get_volume() +{ + return m_volume; +} void AOAbstractPlayer::set_volume(int p_volume) { diff --git a/aoapplication.cpp b/aoapplication.cpp index 1beacd310..ca4291197 100644 --- a/aoapplication.cpp +++ b/aoapplication.cpp @@ -124,11 +124,20 @@ void AOApplication::set_theme_variant(QString p_variant) emit reload_theme(); } -void AOApplication::on_config_theme_changed() { emit reload_theme(); } +void AOApplication::on_config_theme_changed() +{ + emit reload_theme(); +} -void AOApplication::on_config_reload_theme_requested() { emit reload_theme(); } +void AOApplication::on_config_reload_theme_requested() +{ + emit reload_theme(); +} -void AOApplication::on_config_theme_variant_changed() { emit reload_theme(); } +void AOApplication::on_config_theme_variant_changed() +{ + emit reload_theme(); +} void AOApplication::set_favorite_list() { @@ -177,7 +186,10 @@ bool AOApplication::get_chatlog_scrolldown() return config->log_goes_downward_enabled(); } -int AOApplication::get_chatlog_max_lines() { return config->log_max_lines(); } +int AOApplication::get_chatlog_max_lines() +{ + return config->log_max_lines(); +} int AOApplication::get_chat_tick_interval() { @@ -263,6 +275,12 @@ void AOApplication::ms_connect_finished(bool connected, bool will_retry) } } -void AOApplication::on_courtroom_closing() { config_panel->hide(); } +void AOApplication::on_courtroom_closing() +{ + config_panel->hide(); +} -void AOApplication::on_courtroom_destroyed() { config_panel->hide(); } +void AOApplication::on_courtroom_destroyed() +{ + config_panel->hide(); +} diff --git a/aoapplication.h b/aoapplication.h index ea54aca7f..b9524b0bf 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -76,19 +76,34 @@ class AOApplication : public QApplication //////////////////versioning/////////////// - int get_release() { return RELEASE; } - int get_major_version() { return MAJOR_VERSION; } - int get_minor_version() { return MINOR_VERSION; } + int get_release() + { + return RELEASE; + } + int get_major_version() + { + return MAJOR_VERSION; + } + int get_minor_version() + { + return MINOR_VERSION; + } QString get_version_string(); /////////////////////////////////////////// void set_favorite_list(); - QVector &get_favorite_list() { return favorite_list; } + QVector &get_favorite_list() + { + return favorite_list; + } void add_favorite_server(int p_server); void set_server_list(); - QVector &get_server_list() { return server_list; } + QVector &get_server_list() + { + return server_list; + } // reads the theme from config.ini and sets it accordingly void set_theme_name(QString p_name); diff --git a/aobasshandle.cpp b/aobasshandle.cpp index 438312cfe..e9725f533 100644 --- a/aobasshandle.cpp +++ b/aobasshandle.cpp @@ -1,6 +1,8 @@ #include "aobasshandle.h" -AOBassHandle::AOBassHandle(QObject *p_parent) : QObject(p_parent) {} +AOBassHandle::AOBassHandle(QObject *p_parent) : QObject(p_parent) +{ +} AOBassHandle::AOBassHandle(QString p_file, bool p_suicide, QObject *p_parent) noexcept(false) @@ -34,7 +36,10 @@ void AOBassHandle::suicide() delete this; } -QString AOBassHandle::get_file() { return m_file; } +QString AOBassHandle::get_file() +{ + return m_file; +} void AOBassHandle::set_file(QString p_file, bool p_suicide) noexcept(false) { @@ -70,9 +75,15 @@ void AOBassHandle::set_volume(int p_volume) BASS_ChannelSetAttribute(m_handle, BASS_ATTRIB_VOL, p_volume / 100.0f); } -void AOBassHandle::play() { BASS_ChannelPlay(m_handle, FALSE); } +void AOBassHandle::play() +{ + BASS_ChannelPlay(m_handle, FALSE); +} -void AOBassHandle::stop() { BASS_ChannelStop(m_handle); } +void AOBassHandle::stop() +{ + BASS_ChannelStop(m_handle); +} void CALLBACK AOBassHandle::static_sync(HSYNC handle, DWORD channel, DWORD data, void *user) diff --git a/aocharbutton.cpp b/aocharbutton.cpp index 9ad213a67..eaf278869 100644 --- a/aocharbutton.cpp +++ b/aocharbutton.cpp @@ -20,9 +20,15 @@ AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, ui_taken->hide(); } -void AOCharButton::reset() { ui_taken->hide(); } +void AOCharButton::reset() +{ + ui_taken->hide(); +} -void AOCharButton::set_taken() { ui_taken->show(); } +void AOCharButton::set_taken() +{ + ui_taken->show(); +} void AOCharButton::set_image(QString p_character) { diff --git a/aocharmovie.cpp b/aocharmovie.cpp index 38fa97a96..1b42c82eb 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -132,7 +132,10 @@ void AOCharMovie::play_idle(QString p_char, QString p_emote, bool p_visible) play(p_char, p_emote, "(a)", p_visible); } -void AOCharMovie::set_mirror_enabled(bool p_enable) { m_mirror = p_enable; } +void AOCharMovie::set_mirror_enabled(bool p_enable) +{ + m_mirror = p_enable; +} void AOCharMovie::stop() { @@ -175,4 +178,7 @@ void AOCharMovie::on_frame_changed(int p_frame_num) } } -void AOCharMovie::timer_done() { done(); } +void AOCharMovie::timer_done() +{ + done(); +} diff --git a/aoconfig.cpp b/aoconfig.cpp index 3d5423082..fb3a90f29 100644 --- a/aoconfig.cpp +++ b/aoconfig.cpp @@ -47,7 +47,10 @@ class AOConfigPrivate : public QObject { read_file(); } - ~AOConfigPrivate() { save_file(); } + ~AOConfigPrivate() + { + save_file(); + } // setters public slots: @@ -271,47 +274,110 @@ int AOConfig::get_number(QString p_name, int p_default) return d->cfg.value(p_name, p_default).toInt(); } -QString AOConfig::username() { return d->username; } +QString AOConfig::username() +{ + return d->username; +} -QString AOConfig::callwords() { return d->callwords; } +QString AOConfig::callwords() +{ + return d->callwords; +} -QString AOConfig::theme() { return d->theme; } +QString AOConfig::theme() +{ + return d->theme; +} -QString AOConfig::theme_variant() { return d->theme_variant; } +QString AOConfig::theme_variant() +{ + return d->theme_variant; +} -bool AOConfig::always_pre_enabled() { return d->always_pre; } +bool AOConfig::always_pre_enabled() +{ + return d->always_pre; +} -int AOConfig::chat_tick_interval() { return d->chat_tick_interval; } +int AOConfig::chat_tick_interval() +{ + return d->chat_tick_interval; +} -bool AOConfig::server_alerts_enabled() { return d->server_alerts; } +bool AOConfig::server_alerts_enabled() +{ + return d->server_alerts; +} -int AOConfig::log_max_lines() { return d->log_max_lines; } +int AOConfig::log_max_lines() +{ + return d->log_max_lines; +} -bool AOConfig::log_goes_downward_enabled() { return d->log_goes_downward; } +bool AOConfig::log_goes_downward_enabled() +{ + return d->log_goes_downward; +} -bool AOConfig::log_uses_newline_enabled() { return d->log_uses_newline; } +bool AOConfig::log_uses_newline_enabled() +{ + return d->log_uses_newline; +} -bool AOConfig::log_music_enabled() { return d->log_music; } +bool AOConfig::log_music_enabled() +{ + return d->log_music; +} -bool AOConfig::log_is_recording_enabled() { return d->log_is_recording; } +bool AOConfig::log_is_recording_enabled() +{ + return d->log_is_recording; +} -int AOConfig::effects_volume() { return d->effects_volume; } +int AOConfig::effects_volume() +{ + return d->effects_volume; +} -int AOConfig::system_volume() { return d->system_volume; } +int AOConfig::system_volume() +{ + return d->system_volume; +} -int AOConfig::music_volume() { return d->music_volume; } +int AOConfig::music_volume() +{ + return d->music_volume; +} -int AOConfig::blips_volume() { return d->blips_volume; } +int AOConfig::blips_volume() +{ + return d->blips_volume; +} -int AOConfig::blip_rate() { return d->blip_rate; } +int AOConfig::blip_rate() +{ + return d->blip_rate; +} -bool AOConfig::blank_blips_enabled() { return d->blank_blips; } +bool AOConfig::blank_blips_enabled() +{ + return d->blank_blips; +} -void AOConfig::set_username(QString p_string) { d->set_username(p_string); } +void AOConfig::set_username(QString p_string) +{ + d->set_username(p_string); +} -void AOConfig::set_callwords(QString p_string) { d->set_callwords(p_string); } +void AOConfig::set_callwords(QString p_string) +{ + d->set_callwords(p_string); +} -void AOConfig::set_theme(QString p_string) { d->set_theme(p_string); } +void AOConfig::set_theme(QString p_string) +{ + d->set_theme(p_string); +} void AOConfig::set_theme_variant(QString p_string) { @@ -323,7 +389,10 @@ void AOConfig::set_always_pre(int p_state) set_always_pre(p_state == Qt::Checked); } -void AOConfig::set_always_pre(bool p_enabled) { d->set_always_pre(p_enabled); } +void AOConfig::set_always_pre(bool p_enabled) +{ + d->set_always_pre(p_enabled); +} void AOConfig::set_chat_tick_interval(int p_number) { @@ -365,7 +434,10 @@ void AOConfig::set_log_uses_newline(int p_state) set_log_uses_newline(p_state == Qt::Checked); } -void AOConfig::set_log_music(bool p_enabled) { d->set_log_music(p_enabled); } +void AOConfig::set_log_music(bool p_enabled) +{ + d->set_log_music(p_enabled); +} void AOConfig::set_log_music(int p_state) { @@ -392,11 +464,20 @@ void AOConfig::set_system_volume(int p_number) d->set_system_volume(p_number); } -void AOConfig::set_music_volume(int p_number) { d->set_music_volume(p_number); } +void AOConfig::set_music_volume(int p_number) +{ + d->set_music_volume(p_number); +} -void AOConfig::set_blips_volume(int p_number) { d->set_blips_volume(p_number); } +void AOConfig::set_blips_volume(int p_number) +{ + d->set_blips_volume(p_number); +} -void AOConfig::set_blip_rate(int p_number) { d->set_blip_rate(p_number); } +void AOConfig::set_blip_rate(int p_number) +{ + d->set_blip_rate(p_number); +} void AOConfig::set_blank_blips(bool p_enabled) { @@ -408,7 +489,10 @@ void AOConfig::set_blank_blips(int p_state) set_blank_blips(p_state == Qt::Checked); } -void AOConfig::save_file() { d->save_file(); } +void AOConfig::save_file() +{ + d->save_file(); +} // moc #include "aoconfig.moc" diff --git a/aoemotebutton.cpp b/aoemotebutton.cpp index 882021a97..4d0b87a27 100644 --- a/aoemotebutton.cpp +++ b/aoemotebutton.cpp @@ -51,4 +51,7 @@ void AOEmoteButton::set_image(QString p_char, int p_emote, QString suffix) } } -void AOEmoteButton::on_clicked() { emote_clicked(m_id); } +void AOEmoteButton::on_clicked() +{ + emote_clicked(m_id); +} diff --git a/aoemotebutton.h b/aoemotebutton.h index 5bb69fc99..4aa85a579 100644 --- a/aoemotebutton.h +++ b/aoemotebutton.h @@ -14,8 +14,14 @@ class AOEmoteButton : public QPushButton void set_image(QString p_char, int p_emote, QString suffix); - void set_id(int p_id) { m_id = p_id; } - int get_id() { return m_id; } + void set_id(int p_id) + { + m_id = p_id; + } + int get_id() + { + return m_id; + } private: AOApplication *ao_app = nullptr; diff --git a/aoevidencebutton.cpp b/aoevidencebutton.cpp index 077723a89..8bbaef849 100644 --- a/aoevidencebutton.cpp +++ b/aoevidencebutton.cpp @@ -68,7 +68,10 @@ void AOEvidenceButton::set_selected(bool p_selected) ui_selected->hide(); } -void AOEvidenceButton::on_clicked() { evidence_clicked(m_id); } +void AOEvidenceButton::on_clicked() +{ + evidence_clicked(m_id); +} void AOEvidenceButton::mouseDoubleClickEvent(QMouseEvent *e) { diff --git a/aoevidencebutton.h b/aoevidencebutton.h index ece4f8d14..8c2e3eb1d 100644 --- a/aoevidencebutton.h +++ b/aoevidencebutton.h @@ -18,7 +18,10 @@ class AOEvidenceButton : public QPushButton void reset(); void set_image(QString p_image); void set_theme_image(QString p_image); - void set_id(int p_id) { m_id = p_id; } + void set_id(int p_id) + { + m_id = p_id; + } void set_selected(bool p_selected); diff --git a/aoevidencedescription.cpp b/aoevidencedescription.cpp index f697547af..d5bb0a1a5 100644 --- a/aoevidencedescription.cpp +++ b/aoevidencedescription.cpp @@ -15,4 +15,7 @@ void AOEvidenceDescription::mouseDoubleClickEvent(QMouseEvent *e) this->setReadOnly(false); } -void AOEvidenceDescription::on_enter_pressed() { this->setReadOnly(true); } +void AOEvidenceDescription::on_enter_pressed() +{ + this->setReadOnly(true); +} diff --git a/aoevidencedisplay.cpp b/aoevidencedisplay.cpp index 2d3e3d468..77d436dd7 100644 --- a/aoevidencedisplay.cpp +++ b/aoevidencedisplay.cpp @@ -83,4 +83,7 @@ void AOEvidenceDisplay::reset() this->clear(); } -QLabel *AOEvidenceDisplay::get_evidence_icon() { return evidence_icon; } +QLabel *AOEvidenceDisplay::get_evidence_icon() +{ + return evidence_icon; +} diff --git a/aoexception.cpp b/aoexception.cpp index 456fbd64e..6100aa69e 100644 --- a/aoexception.cpp +++ b/aoexception.cpp @@ -1,6 +1,8 @@ #include "aoexception.h" -AOException::AOException(QString p_msg) : m_msg(p_msg) {} +AOException::AOException(QString p_msg) : m_msg(p_msg) +{ +} const char *AOException::what() const noexcept { diff --git a/aolineedit.cpp b/aolineedit.cpp index 211d9f784..f6026e142 100644 --- a/aolineedit.cpp +++ b/aolineedit.cpp @@ -15,4 +15,7 @@ void AOLineEdit::mouseDoubleClickEvent(QMouseEvent *e) this->setReadOnly(false); } -void AOLineEdit::on_enter_pressed() { this->setReadOnly(true); } +void AOLineEdit::on_enter_pressed() +{ + this->setReadOnly(true); +} diff --git a/aomovie.cpp b/aomovie.cpp index 2ead87a77..8b0833a8a 100644 --- a/aomovie.cpp +++ b/aomovie.cpp @@ -15,9 +15,15 @@ AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); } -AOMovie::~AOMovie() { delete m_movie; } +AOMovie::~AOMovie() +{ + delete m_movie; +} -void AOMovie::set_play_once(bool p_play_once) { play_once = p_play_once; } +void AOMovie::set_play_once(bool p_play_once) +{ + play_once = p_play_once; +} void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) { diff --git a/aomusicplayer.cpp b/aomusicplayer.cpp index e0801d8b1..31969fbb5 100644 --- a/aomusicplayer.cpp +++ b/aomusicplayer.cpp @@ -38,4 +38,7 @@ void AOMusicPlayer::play(QString p_file) } } -void AOMusicPlayer::stop() { emit stopping(); } +void AOMusicPlayer::stop() +{ + emit stopping(); +} diff --git a/aopacket.h b/aopacket.h index f97b15149..eefac88f2 100644 --- a/aopacket.h +++ b/aopacket.h @@ -10,8 +10,14 @@ class AOPacket AOPacket(QString p_packet_string); AOPacket(QString header, QStringList &p_contents); - QString get_header() { return m_header; } - QStringList &get_contents() { return m_contents; } + QString get_header() + { + return m_header; + } + QStringList &get_contents() + { + return m_contents; + } QString to_string(); void encrypt_header(unsigned int p_key); diff --git a/aopixmap.cpp b/aopixmap.cpp index ab3ce79e6..6848cd873 100644 --- a/aopixmap.cpp +++ b/aopixmap.cpp @@ -8,7 +8,9 @@ AOPixmap::AOPixmap(QPixmap p_pixmap) : m_pixmap(p_pixmap) } } -AOPixmap::AOPixmap(QString p_file_path) : AOPixmap(QPixmap(p_file_path)) {} +AOPixmap::AOPixmap(QString p_file_path) : AOPixmap(QPixmap(p_file_path)) +{ +} void AOPixmap::clear() { diff --git a/aosfxplayer.cpp b/aosfxplayer.cpp index 6cf8e2c64..694b5ade0 100644 --- a/aosfxplayer.cpp +++ b/aosfxplayer.cpp @@ -27,4 +27,7 @@ void AOSfxPlayer::play(QString p_name) } } -void AOSfxPlayer::stop() { emit stopping(); } +void AOSfxPlayer::stop() +{ + emit stopping(); +} diff --git a/aoshoutplayer.cpp b/aoshoutplayer.cpp index 4938ffd42..6900041ff 100644 --- a/aoshoutplayer.cpp +++ b/aoshoutplayer.cpp @@ -40,4 +40,7 @@ void AOShoutPlayer::play(QString p_name, QString p_char) } } -void AOShoutPlayer::stop() { emit stopping(); } +void AOShoutPlayer::stop() +{ + emit stopping(); +} diff --git a/aotextarea.cpp b/aotextarea.cpp index f6c5ec918..e9b30cbfd 100644 --- a/aotextarea.cpp +++ b/aotextarea.cpp @@ -5,7 +5,9 @@ #include #include -AOTextArea::AOTextArea(QWidget *p_parent) : QTextBrowser(p_parent) {} +AOTextArea::AOTextArea(QWidget *p_parent) : QTextBrowser(p_parent) +{ +} void AOTextArea::append_chatmessage(QString p_name, QString p_message) { diff --git a/aotimer.cpp b/aotimer.cpp index 3083bc763..805fa954c 100644 --- a/aotimer.cpp +++ b/aotimer.cpp @@ -60,11 +60,20 @@ void AOTimer::update_time() firing_timer.start(firing_timer_length); } -void AOTimer::set() { set_time(start_time); } +void AOTimer::set() +{ + set_time(start_time); +} -void AOTimer::resume() { firing_timer.start(firing_timer_length); } +void AOTimer::resume() +{ + firing_timer.start(firing_timer_length); +} -void AOTimer::pause() { firing_timer.stop(); } +void AOTimer::pause() +{ + firing_timer.stop(); +} void AOTimer::redraw() { diff --git a/aotimer.h b/aotimer.h index 1ab051e52..0256155c0 100644 --- a/aotimer.h +++ b/aotimer.h @@ -15,10 +15,19 @@ class ManualTimer int timestep_length; public: - QTime get_time() { return current_time; } - int get_timestep_length() { return timestep_length; } + QTime get_time() + { + return current_time; + } + int get_timestep_length() + { + return timestep_length; + } - void set_time(QTime new_time) { current_time = new_time; } + void set_time(QTime new_time) + { + current_time = new_time; + } void set_timestep_length(int new_timestep_length) { timestep_length = new_timestep_length; diff --git a/audio_functions.cpp b/audio_functions.cpp index 79bb34ac3..3e7824c20 100644 --- a/audio_functions.cpp +++ b/audio_functions.cpp @@ -1,6 +1,9 @@ #include "courtroom.h" -bool Courtroom::is_audio_muted() { return m_audio_mute; } +bool Courtroom::is_audio_muted() +{ + return m_audio_mute; +} void Courtroom::set_audio_mute_enabled(bool p_enabled) { diff --git a/charselect.cpp b/charselect.cpp index 86ae66eb9..307ede6aa 100644 --- a/charselect.cpp +++ b/charselect.cpp @@ -209,4 +209,7 @@ void Courtroom::char_mouse_entered(AOCharButton *p_caller) ui_char_button_selector->show(); } -void Courtroom::char_mouse_left() { ui_char_button_selector->hide(); } +void Courtroom::char_mouse_left() +{ + ui_char_button_selector->hide(); +} diff --git a/courtroom.cpp b/courtroom.cpp index 8d3b02fdd..f338986ae 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -827,7 +827,10 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) handle_chatmessage_2(); } -void Courtroom::objection_done() { handle_chatmessage_2(); } +void Courtroom::objection_done() +{ + handle_chatmessage_2(); +} void Courtroom::handle_chatmessage_2() // handles IC { @@ -1327,9 +1330,15 @@ void Courtroom::play_preanim() preanim_done(); } -void Courtroom::preanim_done() { handle_chatmessage_3(); } +void Courtroom::preanim_done() +{ + handle_chatmessage_3(); +} -void Courtroom::realization_done() { ui_vp_effect->stop(); } +void Courtroom::realization_done() +{ + ui_vp_effect->stop(); +} void Courtroom::start_chat_ticking() { @@ -1981,9 +1990,15 @@ void Courtroom::on_mute_list_clicked(QModelIndex p_index) */ } -void Courtroom::on_music_list_clicked() { ui_ic_chat_message->setFocus(); } +void Courtroom::on_music_list_clicked() +{ + ui_ic_chat_message->setFocus(); +} -void Courtroom::on_area_list_clicked() { ui_ic_chat_message->setFocus(); } +void Courtroom::on_area_list_clicked() +{ + ui_ic_chat_message->setFocus(); +} void Courtroom::on_music_list_double_clicked(QModelIndex p_model) { @@ -2331,11 +2346,20 @@ void Courtroom::on_switch_area_music_clicked() } } -void Courtroom::on_pre_clicked() { ui_ic_chat_message->setFocus(); } +void Courtroom::on_pre_clicked() +{ + ui_ic_chat_message->setFocus(); +} -void Courtroom::on_flip_clicked() { ui_ic_chat_message->setFocus(); } +void Courtroom::on_flip_clicked() +{ + ui_ic_chat_message->setFocus(); +} -void Courtroom::on_hidden_clicked() { ui_ic_chat_message->setFocus(); } +void Courtroom::on_hidden_clicked() +{ + ui_ic_chat_message->setFocus(); +} void Courtroom::on_evidence_button_clicked() { @@ -2393,7 +2417,10 @@ void Courtroom::closeEvent(QCloseEvent *event) QMainWindow::closeEvent(event); } -void Courtroom::on_sfx_list_clicked() { ui_ic_chat_message->setFocus(); } +void Courtroom::on_sfx_list_clicked() +{ + ui_ic_chat_message->setFocus(); +} void Courtroom::on_set_notes_clicked() { diff --git a/courtroom.h b/courtroom.h index 1bde3c25f..0f13b5dc0 100644 --- a/courtroom.h +++ b/courtroom.h @@ -52,12 +52,30 @@ class Courtroom : public QMainWindow public: explicit Courtroom(AOApplication *p_ao_app); - void append_char(char_type p_char) { char_list.append(p_char); } - void append_evidence(evi_type p_evi) { evidence_list.append(p_evi); } - void append_music(QString f_music) { music_list.append(f_music); } - void append_area(QString f_area) { area_list.append(f_area); } - void clear_music() { music_list.clear(); } - void clear_areas() { area_list.clear(); } + void append_char(char_type p_char) + { + char_list.append(p_char); + } + void append_evidence(evi_type p_evi) + { + evidence_list.append(p_evi); + } + void append_music(QString f_music) + { + music_list.append(f_music); + } + void append_area(QString f_area) + { + area_list.append(f_area); + } + void clear_music() + { + music_list.clear(); + } + void clear_areas() + { + area_list.clear(); + } void fix_last_area() { @@ -138,8 +156,14 @@ class Courtroom : public QMainWindow QString get_default_background_path(); // cid = character id, returns the cid of the currently selected character - int get_cid() { return m_cid; } - QString get_current_char() { return current_char; } + int get_cid() + { + return m_cid; + } + QString get_current_char() + { + return current_char; + } // properly sets up some varibles: resets user state void enter_courtroom(int p_cid); diff --git a/discord_rich_presence.cpp b/discord_rich_presence.cpp index cdcbf0beb..0e85fc25f 100644 --- a/discord_rich_presence.cpp +++ b/discord_rich_presence.cpp @@ -33,7 +33,9 @@ void Discord::start(const char *APPLICATION_ID) DiscordEventHandlers handlers; std::memset(&handlers, 0, sizeof(handlers)); handlers = {}; - handlers.ready = [] { qInfo() << "Discord RPC ready"; }; + handlers.ready = [] { + qInfo() << "Discord RPC ready"; + }; handlers.disconnected = [](int errorCode, const char *message) { qInfo() << "Discord RPC disconnected! " << message; }; @@ -44,7 +46,10 @@ void Discord::start(const char *APPLICATION_ID) Discord_Initialize(APPLICATION_ID, &handlers, 1, nullptr); } -Discord::~Discord() { Discord_Shutdown(); } +Discord::~Discord() +{ + Discord_Shutdown(); +} void Discord::restart(const char *APPLICATION_ID) { diff --git a/lobby.cpp b/lobby.cpp index d4ca350c7..de9f19bec 100644 --- a/lobby.cpp +++ b/lobby.cpp @@ -284,7 +284,10 @@ QString Lobby::get_chatlog() return return_value; } -int Lobby::get_selected_server() { return ui_server_list->currentRow(); } +int Lobby::get_selected_server() +{ + return ui_server_list->currentRow(); +} void Lobby::set_loading_value(int p_value) { diff --git a/lobby.h b/lobby.h index c78e26d1d..4bf7fdfd6 100644 --- a/lobby.h +++ b/lobby.h @@ -36,8 +36,14 @@ class Lobby : public QMainWindow void set_fonts(); void set_font(QWidget *widget, QString p_identifier); void set_qtextedit_font(QTextEdit *widget, QString p_identifier); - void show_loading_overlay() { ui_loading_background->show(); } - void hide_loading_overlay() { ui_loading_background->hide(); } + void show_loading_overlay() + { + ui_loading_background->show(); + } + void hide_loading_overlay() + { + ui_loading_background->hide(); + } QString get_chatlog(); int get_selected_server(); diff --git a/networkmanager.cpp b/networkmanager.cpp index 1563ab8d8..0ddc62003 100644 --- a/networkmanager.cpp +++ b/networkmanager.cpp @@ -26,7 +26,9 @@ NetworkManager::NetworkManager(AOApplication *parent) : QObject(parent) SLOT(server_disconnected())); } -NetworkManager::~NetworkManager() {} +NetworkManager::~NetworkManager() +{ +} void NetworkManager::connect_to_master() { diff --git a/path_functions.cpp b/path_functions.cpp index dae6a838b..a3322d0af 100644 --- a/path_functions.cpp +++ b/path_functions.cpp @@ -34,7 +34,10 @@ QString AOApplication::get_base_path() return base_path; } -QString AOApplication::get_data_path() { return get_base_path() + "data/"; } +QString AOApplication::get_data_path() +{ + return get_base_path() + "data/"; +} QString AOApplication::get_theme_path() { diff --git a/text_file_functions.cpp b/text_file_functions.cpp index e1e0810db..c83086949 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -10,22 +10,40 @@ #include #include -QString AOApplication::get_theme() { return config->theme(); } +QString AOApplication::get_theme() +{ + return config->theme(); +} -QString AOApplication::get_theme_variant() { return config->theme_variant(); } +QString AOApplication::get_theme_variant() +{ + return config->theme_variant(); +} -int AOApplication::read_blip_rate() { return config->blip_rate(); } +int AOApplication::read_blip_rate() +{ + return config->blip_rate(); +} bool AOApplication::read_chatlog_newline() { return config->log_uses_newline_enabled(); } -int AOApplication::get_default_music() { return config->music_volume(); } +int AOApplication::get_default_music() +{ + return config->music_volume(); +} -int AOApplication::get_default_sfx() { return config->effects_volume(); } +int AOApplication::get_default_sfx() +{ + return config->effects_volume(); +} -int AOApplication::get_default_blip() { return config->blips_volume(); } +int AOApplication::get_default_blip() +{ + return config->blips_volume(); +} QStringList AOApplication::get_callwords() { @@ -792,7 +810,10 @@ int AOApplication::get_text_delay(QString p_char, QString p_emote) return f_result.toInt(); } -bool AOApplication::get_blank_blip() { return config->blank_blips_enabled(); } +bool AOApplication::get_blank_blip() +{ + return config->blank_blips_enabled(); +} QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) { From 20502c257f0d279da4b2b959d9b1777ca052150b Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 8 Sep 2020 20:00:28 +0200 Subject: [PATCH 085/842] Linux fixes HOLY SHIT --- aoapplication.h | 49 ++++++++++--- aoblipplayer.cpp | 2 +- aocharbutton.cpp | 21 +++--- aocharmovie.cpp | 22 +++--- aoemotebutton.cpp | 13 ++-- aoevidencebutton.cpp | 2 +- aoevidencedisplay.cpp | 2 +- aoimage.cpp | 2 +- aomovie.cpp | 27 ++++---- aoscene.cpp | 4 +- aosfxplayer.cpp | 2 +- aoshoutplayer.cpp | 4 +- charselect.cpp | 2 +- courtroom.cpp | 27 ++++---- courtroom.h | 3 +- courtroom_widgets.cpp | 88 +++++++++++------------- evidence.cpp | 2 +- file_functions.cpp | 10 --- file_functions.h | 3 +- path_functions.cpp | 148 +++++++++++++++++++++++++++++----------- text_file_functions.cpp | 30 ++++---- 21 files changed, 267 insertions(+), 196 deletions(-) diff --git a/aoapplication.h b/aoapplication.h index b9524b0bf..a7f3f4c44 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -114,16 +114,47 @@ class AOApplication : public QApplication // implementation in path_functions.cpp QString get_base_path(); QString get_data_path(); - QString get_theme_path(); - QString get_theme_variant_path(); - QString get_default_theme_path(); - QString get_character_path(QString p_character); - QString get_demothings_path(); - QString get_sounds_path(); + QString get_theme_path(QString p_file); + QString get_theme_variant_path(QString p_file); + QString get_default_theme_path(QString p_file); + QString get_character_path(QString p_character, QString p_file); + // QString get_demothings_path(); + QString get_sounds_path(QString p_file); QString get_music_path(QString p_song); - QString get_background_path(); - QString get_default_background_path(); - QString get_evidence_path(); + QString get_background_path(QString p_file); + QString get_default_background_path(QString p_file); + QString get_evidence_path(QString p_file); + + /** + * @brief Searches for a file with any of the given extensions, and returns + * the first extension that actually matches to an existing file. + * + * @param p_file The path to the file, without extension. + * @param p_exts The potential extensions the file could have. + * + * @return The first extension with which a file exists, or an empty string, + * if not one does. + */ + QString get_file_extension(QString p_file, QVector p_exts); + + /** + * @brief Returns the 'correct' path for the file given as the parameter by + * trying to match the case of the actual path. + * + * @details This function is mostly used on case-sensitive file systems, like + * ext4, generally used on Linux. On FAT, there is no difference between + * "file" and "FILE". On ext4, those are two different files. This results in + * assets that are detected correctly on Windows not being detected on Linux. + * + * For this reason, the implementation of this function is system-dependent: + * on case-insensitive systems, it just returns the parameter itself. + * + * @param p_file The path whose casing must be checked against the actual + * directory structure. + * + * @return The parameter path with fixed casing. + */ + QString get_case_sensitive_path(QString p_file); ////// Functions for accessing the config panel ////// diff --git a/aoblipplayer.cpp b/aoblipplayer.cpp index 0f7060cc8..9326b967d 100644 --- a/aoblipplayer.cpp +++ b/aoblipplayer.cpp @@ -8,7 +8,7 @@ AOBlipPlayer::AOBlipPlayer(QWidget *parent, AOApplication *p_ao_app) void AOBlipPlayer::set_blips(QString p_sfx) { - QString f_path = ao_app->get_sounds_path() + p_sfx.toLower(); + QString f_path = ao_app->get_sounds_path(p_sfx); for (int n_stream = 0; n_stream < BLIP_COUNT; ++n_stream) { diff --git a/aocharbutton.cpp b/aocharbutton.cpp index eaf278869..74d038a9d 100644 --- a/aocharbutton.cpp +++ b/aocharbutton.cpp @@ -32,23 +32,20 @@ void AOCharButton::set_taken() void AOCharButton::set_image(QString p_character) { - QString image_path = - ao_app->get_character_path(p_character) + "char_icon.png"; - QString legacy_path = - ao_app->get_demothings_path() + p_character.toLower() + "_char_icon.png"; - QString alt_path = - ao_app->get_demothings_path() + p_character.toLower() + "_off.png"; + QString image_path = ao_app->get_character_path(p_character, "char_icon.png"); + // QString legacy_path = ao_app->get_demothings_path() + p_character.toLower() + // + "_char_icon.png"; QString alt_path = ao_app->get_demothings_path() + + // p_character.toLower() + "_off.png"; this->setText(""); if (file_exists(image_path)) this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - else if (file_exists(legacy_path)) - { - this->setStyleSheet("border-image:url(\"" + legacy_path + "\")"); - // ninja optimization - QFile::copy(legacy_path, image_path); - } + // else if (file_exists(legacy_path)) { + // this->setStyleSheet("border-image:url(\"" + legacy_path + "\")"); + // // ninja optimization + // QFile::copy(legacy_path, image_path); + //} else { this->setStyleSheet("border-image:url()"); diff --git a/aocharmovie.cpp b/aocharmovie.cpp index 1b42c82eb..7e811e548 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -28,12 +28,11 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, { QString target_path; QStringList f_paths{ - ao_app->get_character_path(p_char) + p_emote_prefix + - p_emote.toLower(), // .gif - ao_app->get_character_path(p_char) + p_emote.toLower(), // .png - ao_app->get_theme_variant_path() + "placeholder", // .gif - ao_app->get_theme_path() + "placeholder", // .gif - ao_app->get_default_theme_path() + "placeholder" // .gif + ao_app->get_character_path(p_char, p_emote_prefix + p_emote), // .gif + ao_app->get_character_path(p_char, p_emote), // .png + ao_app->get_theme_variant_path("placeholder"), // .gif + ao_app->get_theme_path("placeholder"), // .gif + ao_app->get_default_theme_path("placeholder") // .gif }; for (auto &f_file : f_paths) @@ -77,12 +76,11 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) { - QString f_file_path = ao_app->get_character_path(p_char) + p_emote.toLower(); + QString f_file_path = ao_app->get_character_path(p_char, p_emote); bool f_file_exist = false; { // figure out what extension the animation is using - QString f_source_path = - ao_app->get_character_path(p_char) + p_emote.toLower(); + QString f_source_path = ao_app->get_character_path(p_char, p_emote); for (QString &i_ext : QStringList{".webp", ".apng", ".gif", ".png"}) { QString f_target_path = f_source_path + i_ext; @@ -110,8 +108,7 @@ bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) void AOCharMovie::play_talking(QString p_char, QString p_emote, bool p_visible) { - QString gif_path = - ao_app->get_character_path(p_char) + "(b)" + p_emote.toLower(); + QString gif_path = ao_app->get_character_path(p_char, "(b)" + p_emote); m_reader->stop(); this->clear(); @@ -122,8 +119,7 @@ void AOCharMovie::play_talking(QString p_char, QString p_emote, bool p_visible) void AOCharMovie::play_idle(QString p_char, QString p_emote, bool p_visible) { - QString gif_path = - ao_app->get_character_path(p_char) + "(a)" + p_emote.toLower(); + QString gif_path = ao_app->get_character_path(p_char, "(a)" + p_emote); this->clear(); m_reader->stop(); diff --git a/aoemotebutton.cpp b/aoemotebutton.cpp index 4d0b87a27..4b5879b11 100644 --- a/aoemotebutton.cpp +++ b/aoemotebutton.cpp @@ -18,13 +18,12 @@ AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, void AOEmoteButton::set_image(QString p_char, int p_emote, QString suffix) { QString emotion_number = QString::number(p_emote + 1); - QString image_path = ao_app->get_character_path(p_char) + - "emotions/ao2/button" + emotion_number + suffix; - QString alt_path = ao_app->get_character_path(p_char) + "emotions/button" + - emotion_number + suffix; - QString hover_path = ao_app->get_character_path(p_char) + - "emotions/hovers/button" + emotion_number + "_hover" + - suffix; + QString image_path = ao_app->get_character_path( + p_char, "emotions/ao2/button" + emotion_number + suffix); + QString alt_path = ao_app->get_character_path( + p_char, "emotions/button" + emotion_number + suffix); + QString hover_path = ao_app->get_character_path( + p_char, "emotions/hovers/button" + emotion_number + "_hover" + suffix); if (file_exists(image_path)) { diff --git a/aoevidencebutton.cpp b/aoevidencebutton.cpp index 8bbaef849..3401ebaac 100644 --- a/aoevidencebutton.cpp +++ b/aoevidencebutton.cpp @@ -39,7 +39,7 @@ void AOEvidenceButton::reset() void AOEvidenceButton::set_image(QString p_image) { - QString image_path = ao_app->get_evidence_path() + p_image; + QString image_path = ao_app->get_evidence_path(p_image); if (file_exists(image_path)) { diff --git a/aoevidencedisplay.cpp b/aoevidencedisplay.cpp index 77d436dd7..8924c87b7 100644 --- a/aoevidencedisplay.cpp +++ b/aoevidencedisplay.cpp @@ -24,7 +24,7 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, { this->reset(); - QString f_evidence_path = ao_app->get_evidence_path() + p_evidence_image; + QString f_evidence_path = ao_app->get_evidence_path(p_evidence_image); AOPixmap f_pixmap(f_evidence_path); diff --git a/aoimage.cpp b/aoimage.cpp index 0352ce1d8..94c2bd80d 100644 --- a/aoimage.cpp +++ b/aoimage.cpp @@ -24,7 +24,7 @@ void AOImage::set_image(QString p_image) void AOImage::set_image_from_path(QString p_path) { - QString default_path = ao_app->get_default_theme_path() + "chatmed.png"; + QString default_path = ao_app->get_default_theme_path("chatmed.png"); QString final_path; diff --git a/aomovie.cpp b/aomovie.cpp index 8b0833a8a..18decdec9 100644 --- a/aomovie.cpp +++ b/aomovie.cpp @@ -39,20 +39,21 @@ void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) QString custom_path; if (p_file == "custom") - custom_path = ao_app->get_character_path(p_char) + p_file; + custom_path = ao_app->get_character_path(p_char, p_file); else - custom_path = ao_app->get_character_path(p_char) + p_file + "_bubble"; - - QStringList f_paths{custom_path, - ao_app->get_character_path(p_char) + "overlay/" + p_file, - ao_app->get_base_path() + "themes/" + p_custom_theme + - "/" + p_file, - ao_app->get_theme_variant_path() + p_file, - ao_app->get_theme_path() + p_file, - ao_app->get_default_theme_path() + p_file, - ao_app->get_theme_variant_path() + "placeholder", - ao_app->get_theme_path() + "placeholder", - ao_app->get_default_theme_path() + "placeholder"}; + custom_path = ao_app->get_character_path(p_char, p_file + "_bubble"); + + QStringList f_paths{ + custom_path, + ao_app->get_character_path(p_char, "overlay/" + p_file), + ao_app->get_case_sensitive_path(ao_app->get_base_path() + "themes/" + + p_custom_theme + "/" + p_file), + ao_app->get_theme_variant_path(p_file), + ao_app->get_theme_path(p_file), + ao_app->get_default_theme_path(p_file), + ao_app->get_theme_variant_path("placeholder"), + ao_app->get_theme_path("placeholder"), + ao_app->get_default_theme_path("placeholder")}; for (auto &f_file : f_paths) { diff --git a/aoscene.cpp b/aoscene.cpp index 95c8d7b83..97943ef6e 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -15,10 +15,10 @@ AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) void AOScene::set_image(QString p_image) { - QString target_path = ao_app->get_default_background_path() + p_image; + QString target_path = ao_app->get_default_background_path(p_image); // background specific path - QString background_path = ao_app->get_background_path() + p_image; + QString background_path = ao_app->get_background_path(p_image); for (auto &ext : QVector{".webp", ".apng", ".gif", ".png"}) { diff --git a/aosfxplayer.cpp b/aosfxplayer.cpp index 694b5ade0..1ee91e28a 100644 --- a/aosfxplayer.cpp +++ b/aosfxplayer.cpp @@ -11,7 +11,7 @@ AOSfxPlayer::AOSfxPlayer(QObject *p_parent, AOApplication *p_ao_app) void AOSfxPlayer::play(QString p_name) { - QString f_file = ao_app->get_sounds_path() + p_name.toLower(); + QString f_file = ao_app->get_sounds_path(p_name); try { diff --git a/aoshoutplayer.cpp b/aoshoutplayer.cpp index 6900041ff..b6a784d99 100644 --- a/aoshoutplayer.cpp +++ b/aoshoutplayer.cpp @@ -12,8 +12,8 @@ void AOShoutPlayer::play(QString p_name, QString p_char) { QString f_file; - QString char_path = ao_app->get_character_path(p_char) + p_name.toLower(); - QString theme_path = ao_app->get_image_path(p_name.toLower()); + QString char_path = ao_app->get_character_path(p_char, p_name); + QString theme_path = ao_app->get_image_path(p_name); qDebug() << char_path; qDebug() << theme_path; diff --git a/charselect.cpp b/charselect.cpp index 307ede6aa..7f096ef55 100644 --- a/charselect.cpp +++ b/charselect.cpp @@ -178,7 +178,7 @@ void Courtroom::char_clicked(int n_char) int n_real_char = n_char + current_char_page * max_chars_on_page; QString char_ini_path = - ao_app->get_character_path(char_list.at(n_real_char).name) + "char.ini"; + ao_app->get_character_path(char_list.at(n_real_char).name, "char.ini"); qDebug() << "char_ini_path" << char_ini_path; if (!file_exists(char_ini_path)) diff --git a/courtroom.cpp b/courtroom.cpp index f338986ae..321269671 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -184,7 +184,7 @@ void Courtroom::set_scene() QString f_desk_image = "stand"; QString f_desk_mod = m_chatmessage[DESK_MOD]; QString f_side = m_chatmessage[SIDE]; - QString ini_path = ao_app->get_background_path() + "backgrounds.ini"; + QString ini_path = ao_app->get_background_path("backgrounds.ini"); if (file_exists(ini_path)) { @@ -228,15 +228,17 @@ void Courtroom::set_scene() f_desk_image = "stand"; } - QString bg_path = get_background_path(); QVector exts{".webp", ".apng", ".gif", ".png"}; bool has_all_desks; - if (file_exists(bg_path + "defensedesk", exts) == "") + if (ao_app->get_file_extension(get_background_path("defensedesk"), exts) == + "") has_all_desks = false; - else if (file_exists(bg_path + "prosecutiondesk", exts) == "") + else if (ao_app->get_file_extension(get_background_path("prosecutiondesk"), + exts) == "") has_all_desks = false; - else if (file_exists(bg_path + "stand", exts) == "") + else if (ao_app->get_file_extension(get_background_path("stand"), exts) == + "") has_all_desks = false; else has_all_desks = true; @@ -968,13 +970,13 @@ void Courtroom::handle_chatmessage_3() ui_vp_showname_image->show(); QVector exts = {".png", ".jpg", ".bmp"}; - QString ext = - file_exists(ao_app->get_character_path(f_char) + "showname", exts); + QString ext = ao_app->get_file_extension( + ao_app->get_character_path(f_char, "showname"), exts); if (ext != "" && !chatmessage_is_empty && ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") { ui_vp_showname->hide(); - QString path = ao_app->get_character_path(f_char) + "showname" + ext; + QString path = ao_app->get_character_path(f_char, "showname" + ext); ui_vp_showname_image->set_image_from_path(path); ui_vp_showname_image->show(); } @@ -1310,8 +1312,7 @@ void Courtroom::play_preanim() if (!m_msg_is_first_person) { - QString f_anim_path = - ao_app->get_character_path(f_char) + f_preanim.toLower(); + QString f_anim_path = ao_app->get_character_path(f_char, f_preanim); if (ui_vp_player_char->play_pre(f_char, f_preanim, true)) { if (text_delay >= 0) @@ -1546,11 +1547,11 @@ void Courtroom::play_sfx() return; QVector extensions{"", ".ogg", ".wav", ".mp3"}; - QString general_path = ao_app->get_base_path() + "/sounds/general/"; - QString f_ext = file_exists(general_path + sfx_name, extensions); + QString f_file = + ao_app->get_file_extension(ao_app->get_sounds_path(sfx_name), extensions); - m_effects_player->play(sfx_name + f_ext); + m_effects_player->play(f_file); } void Courtroom::set_text_color() diff --git a/courtroom.h b/courtroom.h index 0f13b5dc0..3f904b84e 100644 --- a/courtroom.h +++ b/courtroom.h @@ -152,8 +152,7 @@ class Courtroom : public QMainWindow void set_ban(int p_cid); // implementations in path_functions.cpp - QString get_background_path(); - QString get_default_background_path(); + QString get_background_path(QString p_file); // cid = character id, returns the cid of the currently selected character int get_cid() diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 974a503ba..6bef5bd30 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -492,9 +492,9 @@ void Courtroom::set_widget_names() void Courtroom::set_widget_layers() { QStringList paths{ - ao_app->get_theme_variant_path() + "courtroom_layers.ini", - ao_app->get_theme_path() + "courtroom_layers.ini", - ao_app->get_default_theme_path() + "courtroom_layers.ini", + ao_app->get_theme_variant_path("courtroom_layers.ini"), + ao_app->get_theme_path("courtroom_layers.ini"), + ao_app->get_default_theme_path("courtroom_layers.ini"), }; // needed to avoid cyclic parenting @@ -1134,19 +1134,17 @@ int Courtroom::adapt_numbered_items(QVector &item_vector, void Courtroom::check_effects() { - QString char_path = ao_app->get_character_path(current_char); - QString theme_variant_path = ao_app->get_theme_variant_path(); - QString theme_path = ao_app->get_theme_path(); for (int i = 0; i < ui_effects.size(); ++i) { - QStringList paths{char_path + effect_names.at(i) + ".webp", - char_path + effect_names.at(i) + ".gif", - theme_variant_path + effect_names.at(i) + ".webp", - theme_variant_path + effect_names.at(i) + ".gif", - theme_variant_path + effect_names.at(i) + ".apng", - theme_path + effect_names.at(i) + ".webp", - theme_path + effect_names.at(i) + ".gif", - theme_path + effect_names.at(i) + ".apng"}; + QStringList paths{ + ao_app->get_character_path(current_char, effect_names.at(i) + ".webp"), + ao_app->get_character_path(current_char, effect_names.at(i) + ".gif"), + ao_app->get_theme_variant_path(effect_names.at(i) + ".webp"), + ao_app->get_theme_variant_path(effect_names.at(i) + ".gif"), + ao_app->get_theme_variant_path(effect_names.at(i) + ".apng"), + ao_app->get_theme_path(effect_names.at(i) + ".webp"), + ao_app->get_theme_path(effect_names.at(i) + ".gif"), + ao_app->get_theme_path(effect_names.at(i) + ".apng")}; // Assume the effect does not exist until a matching file is found effects_enabled[i] = false; @@ -1163,19 +1161,19 @@ void Courtroom::check_effects() void Courtroom::check_free_blocks() { - QString char_path = ao_app->get_character_path(current_char); - QString theme_variant_path = ao_app->get_theme_variant_path(); - QString theme_path = ao_app->get_theme_path(); for (int i = 0; i < ui_free_blocks.size(); ++i) { - QStringList paths{char_path + free_block_names.at(i) + ".webp", - char_path + free_block_names.at(i) + ".gif", - theme_variant_path + free_block_names.at(i) + ".webp", - theme_variant_path + free_block_names.at(i) + ".gif", - theme_variant_path + free_block_names.at(i) + ".apng", - theme_path + free_block_names.at(i) + ".webp", - theme_path + free_block_names.at(i) + ".gif", - theme_path + free_block_names.at(i) + ".apng"}; + QStringList paths{ + ao_app->get_character_path(current_char, + free_block_names.at(i) + ".webp"), + ao_app->get_character_path(current_char, + free_block_names.at(i) + ".gif"), + ao_app->get_theme_variant_path(free_block_names.at(i) + ".webp"), + ao_app->get_theme_variant_path(free_block_names.at(i) + ".gif"), + ao_app->get_theme_variant_path(free_block_names.at(i) + ".apng"), + ao_app->get_theme_path(free_block_names.at(i) + ".webp"), + ao_app->get_theme_path(free_block_names.at(i) + ".gif"), + ao_app->get_theme_path(free_block_names.at(i) + ".apng")}; // Assume the free block does not exist until a matching file is found free_blocks_enabled[i] = false; @@ -1192,19 +1190,17 @@ void Courtroom::check_free_blocks() void Courtroom::check_shouts() { - QString char_path = ao_app->get_character_path(current_char); - QString theme_variant_path = ao_app->get_theme_variant_path(); - QString theme_path = ao_app->get_theme_path(); for (int i = 0; i < ui_shouts.size(); ++i) { - QStringList paths{char_path + shout_names.at(i) + ".webp", - char_path + shout_names.at(i) + ".gif", - theme_variant_path + shout_names.at(i) + ".webp", - theme_variant_path + shout_names.at(i) + ".gif", - theme_variant_path + shout_names.at(i) + ".apng", - theme_path + shout_names.at(i) + ".webp", - theme_path + shout_names.at(i) + ".gif", - theme_path + shout_names.at(i) + ".apng"}; + QStringList paths{ + ao_app->get_character_path(current_char, shout_names.at(i) + ".webp"), + ao_app->get_character_path(current_char, shout_names.at(i) + ".gif"), + ao_app->get_theme_variant_path(shout_names.at(i) + ".webp"), + ao_app->get_theme_variant_path(shout_names.at(i) + ".gif"), + ao_app->get_theme_variant_path(shout_names.at(i) + ".apng"), + ao_app->get_theme_path(shout_names.at(i) + ".webp"), + ao_app->get_theme_path(shout_names.at(i) + ".gif"), + ao_app->get_theme_path(shout_names.at(i) + ".apng")}; // Assume the shout does not exist until a matching file is found shouts_enabled[i] = false; @@ -1221,19 +1217,17 @@ void Courtroom::check_shouts() void Courtroom::check_wtce() { - QString char_path = ao_app->get_character_path(current_char); - QString theme_variant_path = ao_app->get_theme_variant_path(); - QString theme_path = ao_app->get_theme_path(); for (int i = 0; i < ui_wtce.size(); ++i) { - QStringList paths{char_path + wtce_names.at(i) + ".webp", - char_path + wtce_names.at(i) + ".gif", - theme_variant_path + wtce_names.at(i) + ".webp", - theme_variant_path + wtce_names.at(i) + ".gif", - theme_variant_path + wtce_names.at(i) + ".apng", - theme_path + wtce_names.at(i) + ".webp", - theme_path + wtce_names.at(i) + ".gif", - theme_path + wtce_names.at(i) + ".apng"}; + QStringList paths{ + ao_app->get_character_path(current_char, wtce_names.at(i) + ".webp"), + ao_app->get_character_path(current_char, wtce_names.at(i) + ".gif"), + ao_app->get_theme_variant_path(wtce_names.at(i) + ".webp"), + ao_app->get_theme_variant_path(wtce_names.at(i) + ".gif"), + ao_app->get_theme_variant_path(wtce_names.at(i) + ".apng"), + ao_app->get_theme_path(wtce_names.at(i) + ".webp"), + ao_app->get_theme_path(wtce_names.at(i) + ".gif"), + ao_app->get_theme_path(wtce_names.at(i) + ".apng")}; // Assume the judge button does not exist until a matching file is found wtce_enabled[i] = false; diff --git a/evidence.cpp b/evidence.cpp index c5bb7de36..1c0381ab1 100644 --- a/evidence.cpp +++ b/evidence.cpp @@ -215,7 +215,7 @@ void Courtroom::on_evidence_image_button_clicked() dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilter(tr("Images (*.png)")); dialog.setViewMode(QFileDialog::List); - dialog.setDirectory(ao_app->get_evidence_path()); + dialog.setDirectory(ao_app->get_evidence_path("")); QStringList filenames; diff --git a/file_functions.cpp b/file_functions.cpp index c89f8f774..c7a249c93 100644 --- a/file_functions.cpp +++ b/file_functions.cpp @@ -10,16 +10,6 @@ bool file_exists(QString file_path) return check_file.exists() && check_file.isFile(); } -QString file_exists(QString file_path, QVector p_exts) -{ - for (auto &ext : p_exts) - { - if (file_exists(file_path + ext)) - return ext; - } - return ""; -} - bool dir_exists(QString dir_path) { QDir check_dir(dir_path); diff --git a/file_functions.h b/file_functions.h index 97b4da7a8..af58a3ef8 100644 --- a/file_functions.h +++ b/file_functions.h @@ -5,7 +5,6 @@ #include bool file_exists(QString file_path); -QString file_exists(QString file_path, QVector p_exts); bool dir_exists(QString file_path); -#endif // FILE_FUNCTIONS_H +#endif // FILE_FUNCTIONS_H \ No newline at end of file diff --git a/path_functions.cpp b/path_functions.cpp index a3322d0af..44ad14bd6 100644 --- a/path_functions.cpp +++ b/path_functions.cpp @@ -5,6 +5,16 @@ #include #include +// Copied over from Vanilla. +// As said in the comments there, this is a *super broad* definition. +// Linux is guaranteed to be case-sensitive, however, in case of Mac, it... +// depends. On Mac, it can be toggled. So, should the time ever come to that, +// manually define CASE_SENSITIVE_FILESYSTEM if you're working on a Mac that +// has, well, a case-sensitive filesystem. +#if (defined(LINUX) || defined(__linux__)) +#define CASE_SENSITIVE_FILESYSTEM +#endif + #ifdef BASE_OVERRIDE #include "base_override.h" #endif @@ -39,80 +49,138 @@ QString AOApplication::get_data_path() return get_base_path() + "data/"; } -QString AOApplication::get_theme_path() +QString AOApplication::get_theme_path(QString p_file) { - return get_base_path() + "themes/" + get_theme().toLower() + "/"; + QString path = get_base_path() + "themes/" + get_theme() + "/" + p_file; + return get_case_sensitive_path(path); } -QString AOApplication::get_theme_variant_path() +QString AOApplication::get_theme_variant_path(QString p_file) { - return get_base_path() + "themes/" + get_theme().toLower() + "/variants/" + - get_theme_variant() + "/"; + QString path = get_base_path() + "themes/" + get_theme().toLower() + + "/variants/" + get_theme_variant() + "/" + p_file; + return get_case_sensitive_path(path); } -QString AOApplication::get_default_theme_path() +QString AOApplication::get_default_theme_path(QString p_file) { - return get_base_path() + "themes/default/"; -} + QString path = get_base_path() + "themes/default/" + p_file; -QString AOApplication::get_character_path(QString p_character) -{ - return get_base_path() + "characters/" + p_character.toLower() + "/"; + return get_case_sensitive_path(path); } -QString AOApplication::get_demothings_path() +QString AOApplication::get_character_path(QString p_character, QString p_file) { - QString default_path = "misc/demothings/"; - QString alt_path = "misc/RosterImages"; - if (dir_exists(default_path)) - return get_base_path() + default_path; - else if (dir_exists(alt_path)) - return get_base_path() + alt_path; - else - return get_base_path() + default_path; + QString path = get_base_path() + "characters/" + p_character + "/" + p_file; + return get_case_sensitive_path(path); } -QString AOApplication::get_sounds_path() + +// QString AOApplication::get_demothings_path() { +// QString default_path = "misc/demothings/"; +// QString alt_path = "misc/RosterImages"; +// if (dir_exists(default_path)) +// return get_base_path() + default_path; +// else if (dir_exists(alt_path)) +// return get_base_path() + alt_path; +// else +// return get_base_path() + default_path; +//} + +QString AOApplication::get_sounds_path(QString p_file) { - return get_base_path() + "sounds/general/"; + QString path = get_base_path() + "sounds/general/" + p_file; + return get_case_sensitive_path(path); } + QString AOApplication::get_music_path(QString p_song) { - return get_base_path() + "sounds/music/" + p_song.toLower(); + QString path = get_base_path() + "sounds/music/" + p_song; + return get_case_sensitive_path(path); } -QString AOApplication::get_background_path() +QString AOApplication::get_background_path(QString p_file) { if (courtroom_constructed) - return w_courtroom->get_background_path(); + { + return get_case_sensitive_path(w_courtroom->get_background_path(p_file)); + } // this function being called when the courtroom isn't constructed makes no // sense return ""; } -QString AOApplication::get_default_background_path() +QString AOApplication::get_default_background_path(QString p_file) { - return get_base_path() + "background/gs4/"; + QString path = get_base_path() + "background/gs4/" + p_file; + return get_case_sensitive_path(path); } -QString AOApplication::get_evidence_path() +QString AOApplication::get_evidence_path(QString p_file) { - QString default_path = "evidence/"; - QString alt_path = "items/"; - if (dir_exists(default_path)) - return get_base_path() + default_path; - else if (dir_exists(alt_path)) - return get_base_path() + alt_path; + QString default_path = + get_case_sensitive_path(get_base_path() + "evidence/" + p_file); + QString alt_path = + get_case_sensitive_path(get_base_path() + "items/" + p_file); + + if (QFile(default_path).exists()) + return default_path; else - return get_base_path() + default_path; + return alt_path; +} + +QString Courtroom::get_background_path(QString p_file) +{ + return ao_app->get_base_path() + "background/" + current_background + "/" + + p_file; } -QString Courtroom::get_background_path() +QString AOApplication::get_file_extension(QString p_file, + QVector p_exts) { - return ao_app->get_base_path() + "background/" + - current_background.toLower() + "/"; + for (auto &ext : p_exts) + { + if (file_exists(get_case_sensitive_path(p_file + ext))) + return ext; + } + return ""; } -QString Courtroom::get_default_background_path() +#ifndef CASE_SENSITIVE_FILESYSTEM +QString AOApplication::get_case_sensitive_path(QString p_file) { - return ao_app->get_base_path() + "background/gs4/"; + return p_file; +} +#else +QString AOApplication::get_case_sensitive_path(QString p_file) +{ + // First, check to see if the file already exists as it is. + if (QFile(p_file).exists()) + return p_file; + + QFileInfo file(p_file); + + QString file_basename = file.fileName(); + QString file_parent_dir = get_case_sensitive_path(file.absolutePath()); + + // Second, does it exist in the new parent directory? + if (QFile(file_parent_dir + "/" + file_basename).exists()) + return file_parent_dir + "/" + file_basename; + + // In case it doesn't, look through the entries in the parent directory, and + // try and find it based on a case-insensitive regex search. + // Note also the fixed string search here. This is so that, for example, music + // files with parentheses don't get interpreted as grouping for a regex + // search. + QRegExp file_rx = + QRegExp(file_basename, Qt::CaseInsensitive, QRegExp::FixedString); + QStringList files = QDir(file_parent_dir).entryList(); + + int result = files.indexOf(file_rx); + + if (result != -1) + return file_parent_dir + "/" + files.at(result); + + // If nothing is found, we let the caller handle that case. + return file_parent_dir + "/" + file_basename; } +#endif diff --git a/text_file_functions.cpp b/text_file_functions.cpp index c83086949..dbc6f189a 100644 --- a/text_file_functions.cpp +++ b/text_file_functions.cpp @@ -292,8 +292,7 @@ QString AOApplication::get_sfx(QString p_identifier) QString AOApplication::get_stylesheet(QString target_tag, QString p_file) { - QStringList paths{get_theme_variant_path() + p_file, - get_theme_path() + p_file}; + QStringList paths{get_theme_variant_path(p_file), get_theme_path(p_file)}; for (QString path : paths) { @@ -335,8 +334,7 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) QVector AOApplication::get_highlight_color() { QString p_file = "courtroom_config.ini"; - QStringList paths{get_theme_variant_path() + p_file, - get_theme_path() + p_file}; + QStringList paths{get_theme_variant_path(p_file), get_theme_path(p_file)}; for (QString path : paths) { @@ -392,8 +390,7 @@ QVector AOApplication::get_highlight_color() QString AOApplication::get_spbutton(QString p_tag, int index) { QString p_file = "courtroom_config.ini"; - QStringList paths{get_theme_variant_path() + p_file, - get_theme_path() + p_file}; + QStringList paths{get_theme_variant_path(p_file), get_theme_path(p_file)}; for (QString path : paths) { @@ -440,8 +437,7 @@ QString AOApplication::get_spbutton(QString p_tag, int index) QStringList AOApplication::get_effect(int index) { QString p_file = "courtroom_config.ini"; - QStringList paths{get_theme_variant_path() + p_file, - get_theme_path() + p_file}; + QStringList paths{get_theme_variant_path(p_file), get_theme_path(p_file)}; for (QString path : paths) { @@ -496,8 +492,8 @@ QStringList AOApplication::get_sfx_list() QFile char_sfx_list_ini; base_sfx_list_ini.setFileName(get_base_path() + "configs/sounds.ini"); - char_sfx_list_ini.setFileName(get_character_path(get_current_char()) + - "sounds.ini"); + char_sfx_list_ini.setFileName( + get_character_path(get_current_char(), "sounds.ini")); if (!char_sfx_list_ini.open(QIODevice::ReadOnly) && !base_sfx_list_ini.open(QIODevice::ReadOnly)) @@ -529,7 +525,7 @@ QStringList AOApplication::get_sfx_list() QString AOApplication::read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag) { - QString char_ini_path = get_character_path(p_char) + "char.ini"; + QString char_ini_path = get_character_path(p_char, "char.ini"); QFile char_ini; @@ -820,9 +816,9 @@ QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) // Try to obtain a theme ini from either the current theme variant folder, // the current theme folder or the default theme folder QStringList paths{ - get_theme_variant_path() + p_file, - get_theme_path() + p_file, - get_default_theme_path() + p_file, + get_theme_variant_path(p_file), + get_theme_path(p_file), + get_default_theme_path(p_file), }; for (QString path : paths) @@ -837,9 +833,9 @@ QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) QString AOApplication::get_image_path(QString p_image) { - QString theme_variant_image_path = get_theme_variant_path() + p_image; - QString theme_image_path = get_theme_path() + p_image; - QString default_image_path = get_default_theme_path() + p_image; + QString theme_variant_image_path = get_theme_variant_path(p_image); + QString theme_image_path = get_theme_path(p_image); + QString default_image_path = get_default_theme_path(p_image); QString final_image_path; From 4a861354984029f44793cfefb04b7e07558bd4cf Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 8 Sep 2020 22:42:25 +0200 Subject: [PATCH 086/842] Add light-up to selected SFX list item --- courtroom.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++------- courtroom.h | 1 + 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 321269671..e08609014 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -366,8 +366,7 @@ void Courtroom::list_music() for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) { QString r_song = i_song + ext; - QString song_path = - ao_app->get_base_path() + "sounds/music/" + r_song.toLower(); + QString song_path = ao_app->get_music_path(r_song); if (file_exists(song_path)) { found = true; @@ -463,8 +462,7 @@ void Courtroom::list_sfx() ui_sfx_list->item(ui_sfx_list->count() - 1) ->setStatusTip(QString::number(n_sfx + 2)); - QString sfx_path = - ao_app->get_base_path() + "sounds/general/" + i_sfx.toLower(); + QString sfx_path = ao_app->get_sounds_path(i_sfx + ".wav"); if (file_exists(sfx_path)) ui_sfx_list->item(n_listed_sfxs)->setBackground(found_brush); @@ -1551,7 +1549,7 @@ void Courtroom::play_sfx() QString f_file = ao_app->get_file_extension(ao_app->get_sounds_path(sfx_name), extensions); - m_effects_player->play(f_file); + m_effects_player->play(sfx_name + f_file); } void Courtroom::set_text_color() @@ -1649,8 +1647,7 @@ void Courtroom::handle_song(QStringList *p_contents) for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) { QString r_song = f_song + ext; - QString song_path = - ao_app->get_base_path() + "sounds/music/" + r_song.toLower(); + QString song_path = ao_app->get_music_path(r_song); if (file_exists(song_path)) { f_song = r_song; @@ -2420,6 +2417,44 @@ void Courtroom::closeEvent(QCloseEvent *event) void Courtroom::on_sfx_list_clicked() { + QListWidgetItem *new_sfx = ui_sfx_list->currentItem(); + + QBrush found_brush(ao_app->get_color("found_song_color", design_ini)); + QBrush missing_brush(ao_app->get_color("missing_song_color", design_ini)); + + if (-1 != current_sfx_id) + { + QListWidgetItem *old_sfx = ui_sfx_list->item(current_sfx_id); + + QString sfx_path = + ao_app->get_sounds_path(sfx_names.at(current_sfx_id) + ".wav"); + + if (file_exists(sfx_path)) + old_sfx->setBackground(found_brush); + else + old_sfx->setBackground(missing_brush); + } + + // Grab the colour of the selected row's brush. + QBrush selected_brush = new_sfx->background(); + QColor selected_col = selected_brush.color(); + + // Calculate the amount of lightness it would take to light up the row. We + // also limit it to 1.0, as giving lightness values above 1.0 to QColor does + // nothing. +0.4 is just an arbitrarily chosen number. + double final_lightness = qMin(1.0, selected_col.lightnessF() + 0.4); + + // This is just the reverse of the above, basically. We set the colour, and we + // set the brush to have that colour. + selected_col.setHslF(selected_col.hueF(), selected_col.saturationF(), + final_lightness); + selected_brush.setColor(selected_col); + + // Finally, we set the selected SFX's background to be the lightened-up brush. + new_sfx->setBackground(selected_brush); + + current_sfx_id = ui_sfx_list->currentRow(); + ui_ic_chat_message->setFocus(); } diff --git a/courtroom.h b/courtroom.h index 3f904b84e..cd674b156 100644 --- a/courtroom.h +++ b/courtroom.h @@ -410,6 +410,7 @@ class Courtroom : public QMainWindow int current_clock = -1; int timer_number = 0; + int current_sfx_id = -1; QString current_background = "gs4"; From ca9cf1c37c22988baa163c31e123dc89882735f3 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 8 Sep 2020 22:58:20 +0200 Subject: [PATCH 087/842] Check for all extensions when listing SFXes --- courtroom.cpp | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index e08609014..c147962f5 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -462,9 +462,20 @@ void Courtroom::list_sfx() ui_sfx_list->item(ui_sfx_list->count() - 1) ->setStatusTip(QString::number(n_sfx + 2)); - QString sfx_path = ao_app->get_sounds_path(i_sfx + ".wav"); + bool found = false; - if (file_exists(sfx_path)) + for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) + { + QString r_sfx = i_sfx + ext; + QString sfx_path = ao_app->get_sounds_path(r_sfx); + if (file_exists(sfx_path)) + { + found = true; + break; + } + } + + if (found) ui_sfx_list->item(n_listed_sfxs)->setBackground(found_brush); else ui_sfx_list->item(n_listed_sfxs)->setBackground(missing_brush); @@ -2426,10 +2437,20 @@ void Courtroom::on_sfx_list_clicked() { QListWidgetItem *old_sfx = ui_sfx_list->item(current_sfx_id); - QString sfx_path = - ao_app->get_sounds_path(sfx_names.at(current_sfx_id) + ".wav"); + bool found = false; + + for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) + { + QString r_sfx = sfx_names.at(current_sfx_id) + ext; + QString sfx_path = ao_app->get_sounds_path(r_sfx); + if (file_exists(sfx_path)) + { + found = true; + break; + } + } - if (file_exists(sfx_path)) + if (found) old_sfx->setBackground(found_brush); else old_sfx->setBackground(missing_brush); From a0ee600ea9db5aac23c42e5dc5345cf84fecd748 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Wed, 9 Sep 2020 16:16:09 +0200 Subject: [PATCH 088/842] Clear SFX selection when sending a message --- courtroom.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/courtroom.cpp b/courtroom.cpp index c147962f5..74fb958f0 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -634,6 +634,9 @@ void Courtroom::on_chat_return_pressed() qDebug() << ind; packet_contents.append(sfx_names.at(ind)); // packet_contents.append(sfx_names.at(row)); + + ui_sfx_list->clearSelection(); + list_sfx(); } int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); From dec207f23e9e8aff32816e0423ffc7b868df150c Mon Sep 17 00:00:00 2001 From: Cerapter Date: Thu, 10 Sep 2020 19:04:56 +0200 Subject: [PATCH 089/842] Replace [X] with checkboxes for mute list --- courtroom.cpp | 38 +++++--------------------------------- courtroom.h | 2 +- courtroom_widgets.cpp | 12 +++++++----- 3 files changed, 13 insertions(+), 39 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 74fb958f0..9b0d2b321 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1948,58 +1948,30 @@ void Courtroom::on_pos_dropdown_changed(int p_index) // ao_app->send_server_packet(new AOPacket("SP#" + f_pos + "#%")); } -void Courtroom::on_mute_list_clicked(QModelIndex p_index) +void Courtroom::on_mute_list_item_changed(QListWidgetItem *p_item) { - QListWidgetItem *f_item = ui_mute_list->item(p_index.row()); - QString f_char = f_item->text(); - QString real_char; - - if (f_char.endsWith(" [x]")) - real_char = f_char.left(f_char.size() - 4); - else - real_char = f_char; - int f_cid = -1; for (int n_char = 0; n_char < char_list.size(); n_char++) { - if (char_list.at(n_char).name == real_char) + if (char_list.at(n_char).name == p_item->text()) f_cid = n_char; } if (f_cid < 0 || f_cid >= char_list.size()) { - qDebug() << "W: " << real_char << " not present in char_list"; + qDebug() << "W: " << p_item->text() << " not present in char_list"; return; } - if (mute_map.value(f_cid)) - { - mute_map.insert(f_cid, false); - f_item->setText(real_char); - } - else + if (Qt::CheckState::Checked == p_item->checkState()) { mute_map.insert(f_cid, true); - f_item->setText(real_char + " [x]"); - } - - /* - if (f_char.endsWith(" [x]")) - { - real_char = f_char.left(f_char.size() - 4); - mute_map.remove(real_char); - mute_map.insert(real_char, false); - f_item->setText(real_char); } else { - real_char = f_char; - mute_map.remove(real_char); - mute_map.insert(real_char, true); - f_item->setText(real_char + " [x]"); + mute_map.insert(f_cid, false); } - */ } void Courtroom::on_music_list_clicked() diff --git a/courtroom.h b/courtroom.h index cd674b156..febfcaac9 100644 --- a/courtroom.h +++ b/courtroom.h @@ -656,7 +656,7 @@ private slots: void chat_tick(); - void on_mute_list_clicked(QModelIndex p_index); + void on_mute_list_item_changed(QListWidgetItem *p_item); void on_chat_return_pressed(); void on_chat_config_changed(); diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 6bef5bd30..9f3b64563 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -273,8 +273,8 @@ void Courtroom::connect_widgets() connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); - connect(ui_mute_list, SIGNAL(clicked(QModelIndex)), this, - SLOT(on_mute_list_clicked(QModelIndex))); + connect(ui_mute_list, SIGNAL(itemChanged(QListWidgetItem *)), this, + SLOT(on_mute_list_item_changed(QListWidgetItem *))); connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_chat_return_pressed())); @@ -1590,9 +1590,11 @@ void Courtroom::set_mute_list() sorted_mute_list.sort(); - for (QString i_name : sorted_mute_list) + for (int i = 0; i < sorted_mute_list.size(); ++i) { - // mute_map.insert(i_name, false); - ui_mute_list->addItem(i_name); + ui_mute_list->addItem(sorted_mute_list[i]); + ui_mute_list->item(i)->setFlags(ui_mute_list->item(i)->flags() | + Qt::ItemFlag::ItemIsUserCheckable); + ui_mute_list->item(i)->setCheckState(Qt::CheckState::Unchecked); } } From 760c1e54dc1a6ca4c551514fb11ec01b47aa2f19 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Thu, 10 Sep 2020 19:27:42 +0200 Subject: [PATCH 090/842] Simplify iteration through mute list --- courtroom_widgets.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 9f3b64563..78aae1f24 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -1590,11 +1590,10 @@ void Courtroom::set_mute_list() sorted_mute_list.sort(); - for (int i = 0; i < sorted_mute_list.size(); ++i) + for (QString i_chr_name : sorted_mute_list) { - ui_mute_list->addItem(sorted_mute_list[i]); - ui_mute_list->item(i)->setFlags(ui_mute_list->item(i)->flags() | - Qt::ItemFlag::ItemIsUserCheckable); - ui_mute_list->item(i)->setCheckState(Qt::CheckState::Unchecked); + QListWidgetItem *i_item = new QListWidgetItem(i_chr_name, ui_mute_list); + i_item->setFlags(i_item->flags() | Qt::ItemFlag::ItemIsUserCheckable); + i_item->setCheckState(Qt::Unchecked); } } From 20b0b5fae49811d3d4fad60f5b013783b0107f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 11 Sep 2020 13:47:48 +0200 Subject: [PATCH 091/842] Update .clang-format --- .clang-format | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.clang-format b/.clang-format index 05fa7ae1e..2788dd839 100644 --- a/.clang-format +++ b/.clang-format @@ -1,9 +1,9 @@ -BasedOnStyle : LLVM -BreakBeforeBraces : Allman -AllowShortBlocksOnASingleLine : Never -AllowShortCaseLabelsOnASingleLine : false -AllowShortFunctionsOnASingleLine : None -AllowShortIfStatementsOnASingleLine : Never -AllowShortLambdasOnASingleLine : None -AllowShortLoopsOnASingleLine : false -# AllowShortEnumsOnASingleLine : false \ No newline at end of file +BasedOnStyle: LLVM +BreakBeforeBraces: Allman +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: None +AllowShortLoopsOnASingleLine: false +AllowShortEnumsOnASingleLine: false \ No newline at end of file From e585a6573a3ad4ffc0fa131b44a93eb11b6c48cb Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 11 Sep 2020 11:50:58 -0400 Subject: [PATCH 092/842] Fix position changing partially to character's original position if changed and player reloaded theme Redone to prevent merge conflicts --- courtroom.cpp | 14 ++++++++++---- courtroom.h | 2 +- packet_distribution.cpp | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 9b0d2b321..ee764b16e 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -98,8 +98,14 @@ void Courtroom::enter_courtroom(int p_cid) set_evidence_page(); - // Refresh character position and dropdown if needed - set_character_position(ao_app->get_char_side(f_char), changed_character); + // Refresh character position. If the character was changed, use the new + // position, otherwise use the old one. Even if the else is useless now (it + // can be omitted), I am keeping it in case we expand set_character_position + // to do more. + if (changed_character) + set_character_position(ao_app->get_char_side(f_char)); + else + set_character_position(ui_pos_dropdown->currentText()); // Update widgets first, then check if everything is valid // This will also handle showing the correct shouts, effects and wtce buttons, @@ -1760,10 +1766,10 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) } } -void Courtroom::set_character_position(QString p_pos, bool refresh_dropdown) +void Courtroom::set_character_position(QString p_pos) { int index = ui_pos_dropdown->findData(p_pos); - if (index != -1 && refresh_dropdown) + if (index != -1) ui_pos_dropdown->setCurrentIndex(index); // enable judge mechanics if appropriate diff --git a/courtroom.h b/courtroom.h index febfcaac9..fcf2d70ff 100644 --- a/courtroom.h +++ b/courtroom.h @@ -126,7 +126,7 @@ class Courtroom : public QMainWindow void set_evidence_list(QVector &p_evi_list); // sets the character position - void set_character_position(QString p_pos, bool refresh_dropdown); + void set_character_position(QString p_pos); // called when a DONE#% from the server was received void done_received(); diff --git a/packet_distribution.cpp b/packet_distribution.cpp index b679ac9d0..83cda4f48 100644 --- a/packet_distribution.cpp +++ b/packet_distribution.cpp @@ -741,7 +741,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (f_contents.size() != 1) goto end; - w_courtroom->set_character_position(f_contents.at(0), true); + w_courtroom->set_character_position(f_contents.at(0)); } end: From c711371e64ddcb9eaa42f905823662fb5be9443a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 11 Sep 2020 18:26:30 +0200 Subject: [PATCH 093/842] Updated cpanel - reworded `log_goes_downward` to `log_is_topdown` - redesigned `IC` tab to `Game` - fixed `server_alerts_changed` not being emitted --- aoapplication.cpp | 2 +- aoconfig.cpp | 26 ++-- aoconfig.h | 13 +- aoconfigpanel.cpp | 22 ++- aoconfigpanel.h | 5 +- courtroom.cpp | 3 +- courtroom_widgets.cpp | 2 +- res/ui/config_panel.ui | 329 ++++++++++++++++++++++++++--------------- 8 files changed, 253 insertions(+), 149 deletions(-) diff --git a/aoapplication.cpp b/aoapplication.cpp index ca4291197..d937e2083 100644 --- a/aoapplication.cpp +++ b/aoapplication.cpp @@ -183,7 +183,7 @@ bool AOApplication::get_server_alerts_enabled() bool AOApplication::get_chatlog_scrolldown() { - return config->log_goes_downward_enabled(); + return config->log_is_topdown_enabled(); } int AOApplication::get_chatlog_max_lines() diff --git a/aoconfig.cpp b/aoconfig.cpp index fb3a90f29..bf715ba23 100644 --- a/aoconfig.cpp +++ b/aoconfig.cpp @@ -29,7 +29,7 @@ class AOConfigPrivate : public QObject int chat_tick_interval; bool server_alerts; int log_max_lines; - bool log_goes_downward; + bool log_is_topdown; bool log_uses_newline; bool log_music; bool log_is_recording; @@ -110,12 +110,12 @@ public slots: log_max_lines = p_number; invoke_parents("log_max_lines_changed", Q_ARG(int, p_number)); } - void set_log_goes_downward(bool p_enabled) + void set_log_is_topdown(bool p_enabled) { - if (log_goes_downward == p_enabled) + if (log_is_topdown == p_enabled) return; - log_goes_downward = p_enabled; - invoke_parents("log_goes_downward_changed", Q_ARG(bool, p_enabled)); + log_is_topdown = p_enabled; + invoke_parents("log_is_topdown_changed", Q_ARG(bool, p_enabled)); } void set_log_uses_newline(bool p_enabled) { @@ -190,7 +190,7 @@ public slots: chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); server_alerts = cfg.value("server_alerts", true).toBool(); log_max_lines = cfg.value("chatlog_limit", 200).toInt(); - log_goes_downward = cfg.value("chatlog_scrolldown", true).toBool(); + log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); log_uses_newline = cfg.value("chatlog_newline", false).toBool(); log_music = cfg.value("music_change_log", true).toBool(); log_is_recording = cfg.value("enable_logging", true).toBool(); @@ -211,7 +211,7 @@ public slots: cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("server_alerts", server_alerts); cfg.setValue("chatlog_limit", log_max_lines); - cfg.setValue("chatlog_scrolldown", log_goes_downward); + cfg.setValue("chatlog_scrolldown", log_is_topdown); cfg.setValue("chatlog_newline", log_uses_newline); cfg.setValue("music_change_log", log_music); cfg.setValue("enable_logging", log_is_recording); @@ -314,9 +314,9 @@ int AOConfig::log_max_lines() return d->log_max_lines; } -bool AOConfig::log_goes_downward_enabled() +bool AOConfig::log_is_topdown_enabled() { - return d->log_goes_downward; + return d->log_is_topdown; } bool AOConfig::log_uses_newline_enabled() @@ -414,14 +414,14 @@ void AOConfig::set_log_max_lines(int p_number) d->set_log_max_lines(p_number); } -void AOConfig::set_log_goes_downward(bool p_enabled) +void AOConfig::set_log_is_topdown(bool p_enabled) { - d->set_log_goes_downward(p_enabled); + d->set_log_is_topdown(p_enabled); } -void AOConfig::set_log_goes_downward(int p_state) +void AOConfig::set_log_is_topdown(int p_state) { - set_log_goes_downward(p_state == Qt::Checked); + set_log_is_topdown(p_state == Qt::Checked); } void AOConfig::set_log_uses_newline(bool p_enabled) diff --git a/aoconfig.h b/aoconfig.h index 5210ffa67..d592c9bbc 100644 --- a/aoconfig.h +++ b/aoconfig.h @@ -25,7 +25,7 @@ class AOConfig : public QObject int chat_tick_interval(); bool server_alerts_enabled(); int log_max_lines(); - bool log_goes_downward_enabled(); + bool log_is_topdown_enabled(); bool log_uses_newline_enabled(); bool log_music_enabled(); bool log_is_recording_enabled(); @@ -46,14 +46,14 @@ public slots: void set_callwords(QString p_string); void set_theme(QString p_string); void set_theme_variant(QString p_string); + void set_server_alerts(bool p_enabled); + void set_server_alerts(int p_state); void set_always_pre(bool p_enabled); void set_always_pre(int p_state); void set_chat_tick_interval(int p_number); - void set_server_alerts(bool p_enabled); - void set_server_alerts(int p_state); void set_log_max_lines(int p_number); - void set_log_goes_downward(bool p_enabled); - void set_log_goes_downward(int p_state); + void set_log_is_topdown(bool p_enabled); + void set_log_is_topdown(int p_state); void set_log_uses_newline(bool p_enabled); void set_log_uses_newline(int p_state); void set_log_music(bool p_enabled); @@ -74,10 +74,11 @@ public slots: void callwords_changed(QString); void theme_changed(QString); void theme_variant_changed(QString); + void server_alerts_changed(bool); void always_pre_changed(bool); void chat_tick_interval_changed(int); void log_max_lines_changed(int); - void log_goes_downward_changed(bool); + void log_is_topdown_changed(bool); void log_uses_newline_changed(bool); void log_music_changed(bool); void log_is_recording_changed(bool); diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp index 0b5f217ba..af683d621 100644 --- a/aoconfigpanel.cpp +++ b/aoconfigpanel.cpp @@ -31,7 +31,8 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) // IC Chatlog w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); - w_log_goes_downward = AO_GUI_WIDGET(QCheckBox, "log_downward"); + w_log_orientation_top_down = AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); + w_log_orientation_bottom_up = AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); w_log_music = AO_GUI_WIDGET(QCheckBox, "log_music"); w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); @@ -68,8 +69,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); - connect(m_config, SIGNAL(log_goes_downward_changed(bool)), - w_log_goes_downward, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, @@ -109,8 +109,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(set_server_alerts(int))); connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); - connect(w_log_goes_downward, SIGNAL(stateChanged(int)), m_config, - SLOT(set_log_goes_downward(int))); + connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_topdown(bool))); connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_uses_newline(int))); connect(w_log_music, SIGNAL(stateChanged(int)), m_config, @@ -147,7 +146,10 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) w_chat_tick_interval->setValue(m_config->chat_tick_interval()); w_server_alerts->setChecked(m_config->server_alerts_enabled()); w_log_max_lines->setValue(m_config->log_max_lines()); - w_log_goes_downward->setChecked(m_config->log_goes_downward_enabled()); + if (m_config->log_is_topdown_enabled()) + w_log_orientation_top_down->setChecked(true); + else + w_log_orientation_bottom_up->setChecked(true); w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); w_log_music->setChecked(m_config->log_music_enabled()); w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); @@ -192,7 +194,7 @@ void AOConfigPanel::refresh_theme_variant_list() w_theme_variant->clear(); // add empty entry indicating no variant chosen - w_theme_variant->addItem("", ""); + w_theme_variant->addItem("Default"); // themes for (QString i_folder : QDir(QDir::currentPath() + "/base/themes/" + m_config->theme() + "/variants/") @@ -221,6 +223,12 @@ void AOConfigPanel::on_reload_theme_clicked() emit reload_theme(); } +void AOConfigPanel::on_log_is_topdown_changed(bool p_enabled) +{ + w_log_orientation_top_down->setChecked(p_enabled); + w_log_orientation_bottom_up->setChecked(!p_enabled); +} + void AOConfigPanel::on_effects_value_changed(int p_num) { w_effects_value->setText(QString::number(p_num) + "%"); diff --git a/aoconfigpanel.h b/aoconfigpanel.h index 51310cee5..5afa3e31b 100644 --- a/aoconfigpanel.h +++ b/aoconfigpanel.h @@ -9,6 +9,7 @@ #include #include #include +#include #include // src #include "aoconfig.h" @@ -33,7 +34,8 @@ class AOConfigPanel : public QWidget // IC Chatlog QSpinBox *w_log_max_lines = nullptr; QCheckBox *w_log_uses_newline = nullptr; - QCheckBox *w_log_goes_downward = nullptr; + QRadioButton *w_log_orientation_top_down = nullptr; + QRadioButton *w_log_orientation_bottom_up = nullptr; QCheckBox *w_log_music = nullptr; QCheckBox *w_log_is_recording = nullptr; @@ -67,6 +69,7 @@ public slots: private slots: void on_reload_theme_clicked(); + void on_log_is_topdown_changed(bool p_enabled); void on_effects_value_changed(int p_num); void on_system_value_changed(int p_num); void on_music_value_changed(int p_num); diff --git a/courtroom.cpp b/courtroom.cpp index 9b0d2b321..74853fc9f 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -85,6 +85,7 @@ void Courtroom::enter_courtroom(int p_cid) set_emote_page(); set_emote_dropdown(); + select_emote(current_emote); current_evidence_page = 0; current_evidence = 0; @@ -1105,7 +1106,7 @@ void Courtroom::on_chat_config_changed() chatlog_changed = true; m_chatlog_limit = chatlog_limit; - bool chatlog_scrolldown = ao_config->log_goes_downward_enabled(); + bool chatlog_scrolldown = ao_config->log_is_topdown_enabled(); if (m_chatlog_scrolldown != chatlog_scrolldown) chatlog_changed = true; m_chatlog_scrolldown = chatlog_scrolldown; diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index 78aae1f24..b5638bea9 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -323,7 +323,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, SLOT(on_chat_config_changed())); - connect(ao_config, SIGNAL(log_goes_downward_changed(bool)), this, + connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_uses_newline_changed(bool)), this, SLOT(on_chat_config_changed())); diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 9ccbf2c19..30947d3fd 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -6,8 +6,8 @@ 0 0 - 390 - 268 + 487 + 361 @@ -18,8 +18,8 @@ - 390 - 268 + 487 + 361 @@ -36,9 +36,6 @@ General - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - @@ -52,7 +49,7 @@ - Callwords (separate by spaces): + Callwords: true @@ -60,7 +57,11 @@ - + + + Separate words by space + + @@ -116,27 +117,16 @@
- - - - - - - - - - - Server alerts: - - - - + Theme variant: + + + @@ -144,125 +134,76 @@ - - - - - - - - IC - - - - - - Log length: - - - - - - - line(s) - - - 0 - - - 10000 - - - 200 - - - - - + + - Log uses newline: + Server alerts: - - + + 0 0 - - - - - Log is downward: + - - - - - 0 - 0 - + + + + Qt::Vertical - + + + 20 + 40 + + + - - + + + + + + 0 + 0 + + + + + 429 + 284 + + + + Game + + + + - Log shows music changes: + Always-anim: true - - - - - 0 - 0 - - - - - - - - - - - Log is recorded: - - - - - + + 0 0 - - - - - - Pre-animation always selected: - - - true - - - - - @@ -271,21 +212,21 @@ - - + + Qt::Horizontal - + - Chat tick interval: + Chat-tick interval: - + ms @@ -298,6 +239,143 @@ + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Log + + + + + + Max length: + + + + + + + + 0 + 0 + + + + line(s) + + + 0 + + + 10000 + + + 200 + + + + + + + Newline padding: + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + Orientation + + + + + + Top-down + + + + + + + Bottom-up + + + + + + + + + + Music switching: + + + true + + + + + + + + 0 + 0 + + + + + + + + + + + Save to disk: + + + + + + + + 0 + 0 + + + + + + + @@ -526,6 +604,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + From b7fd6088418166be6e6c8c61c2da78596d38c817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 11 Sep 2020 18:28:02 +0200 Subject: [PATCH 094/842] Update aoconfigpanel.cpp --- aoconfigpanel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp index af683d621..c91e580f4 100644 --- a/aoconfigpanel.cpp +++ b/aoconfigpanel.cpp @@ -194,7 +194,7 @@ void AOConfigPanel::refresh_theme_variant_list() w_theme_variant->clear(); // add empty entry indicating no variant chosen - w_theme_variant->addItem("Default"); + w_theme_variant->addItem("(default)"); // themes for (QString i_folder : QDir(QDir::currentPath() + "/base/themes/" + m_config->theme() + "/variants/") From b3e83a47564c47331ba8abaf4162c687f5b5ea13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 11 Sep 2020 19:34:39 +0200 Subject: [PATCH 095/842] Updated theme_variant - Added `` variant instead of leaving it as an empty space. --- aoconfigpanel.cpp | 17 +++++++++-------- aoconfigpanel.h | 1 + courtroom.cpp | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/aoconfigpanel.cpp b/aoconfigpanel.cpp index c91e580f4..090aa92c7 100644 --- a/aoconfigpanel.cpp +++ b/aoconfigpanel.cpp @@ -99,8 +99,8 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(set_theme(QString))); connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); - connect(w_theme_variant, SIGNAL(currentIndexChanged(QString)), m_config, - SLOT(set_theme_variant(QString))); + connect(w_theme_variant, SIGNAL(currentIndexChanged(QString)), this, + SLOT(on_theme_variant_index_changed(QString))); connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, @@ -194,7 +194,7 @@ void AOConfigPanel::refresh_theme_variant_list() w_theme_variant->clear(); // add empty entry indicating no variant chosen - w_theme_variant->addItem("(default)"); + w_theme_variant->addItem(""); // themes for (QString i_folder : QDir(QDir::currentPath() + "/base/themes/" + m_config->theme() + "/variants/") @@ -205,11 +205,6 @@ void AOConfigPanel::refresh_theme_variant_list() w_theme_variant->addItem(i_folder, i_folder); } - // if the current theme does not have a variant folder for the current folder, - // add the variant to the combobox anyway. Selecting it will not do anything - if (w_theme_variant->findText(m_config->theme_variant()) == -1) - w_theme_variant->addItem(m_config->theme_variant(), - m_config->theme_variant()); // restore previous selection w_theme_variant->setCurrentText(p_prev_text); @@ -223,6 +218,12 @@ void AOConfigPanel::on_reload_theme_clicked() emit reload_theme(); } +void AOConfigPanel::on_theme_variant_index_changed(QString p_text) +{ + Q_UNUSED(p_text); + m_config->set_theme_variant(w_theme_variant->currentData().toString()); +} + void AOConfigPanel::on_log_is_topdown_changed(bool p_enabled) { w_log_orientation_top_down->setChecked(p_enabled); diff --git a/aoconfigpanel.h b/aoconfigpanel.h index 5afa3e31b..867620f52 100644 --- a/aoconfigpanel.h +++ b/aoconfigpanel.h @@ -69,6 +69,7 @@ public slots: private slots: void on_reload_theme_clicked(); + void on_theme_variant_index_changed(QString p_text); void on_log_is_topdown_changed(bool p_enabled); void on_effects_value_changed(int p_num); void on_system_value_changed(int p_num); diff --git a/courtroom.cpp b/courtroom.cpp index 74853fc9f..0070bf75d 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -85,7 +85,7 @@ void Courtroom::enter_courtroom(int p_cid) set_emote_page(); set_emote_dropdown(); - select_emote(current_emote); + ui_pre->setChecked(ui_pre || ao_config->always_pre_enabled()); current_evidence_page = 0; current_evidence = 0; From 0941dca2539719295647957adebdb63efaaa66dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 11 Sep 2020 20:50:22 +0200 Subject: [PATCH 096/842] Update ui_pre - fixed `ui_pre` always being active after sending a message - fixed sound selection not automatically enabling `ui_pre` --- courtroom.cpp | 14 ++++++++++---- courtroom.h | 2 +- courtroom_widgets.cpp | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 0070bf75d..5529447e9 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -635,9 +635,6 @@ void Courtroom::on_chat_return_pressed() qDebug() << ind; packet_contents.append(sfx_names.at(ind)); // packet_contents.append(sfx_names.at(row)); - - ui_sfx_list->clearSelection(); - list_sfx(); } int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); @@ -717,6 +714,12 @@ void Courtroom::on_chat_return_pressed() prev_emote = current_emote; + { // reset states + ui_pre->setChecked(ao_config->always_pre_enabled()); + ui_sfx_list->clearSelection(); + list_sfx(); + } + ao_app->send_server_packet(new AOPacket("MS", packet_contents)); } @@ -2402,8 +2405,11 @@ void Courtroom::closeEvent(QCloseEvent *event) QMainWindow::closeEvent(event); } -void Courtroom::on_sfx_list_clicked() +void Courtroom::on_sfx_list_clicked(QModelIndex p_index) { + if (p_index.isValid()) + ui_pre->setChecked(p_index.isValid()); + QListWidgetItem *new_sfx = ui_sfx_list->currentItem(); QBrush found_brush(ao_app->get_color("found_song_color", design_ini)); diff --git a/courtroom.h b/courtroom.h index febfcaac9..1d331083b 100644 --- a/courtroom.h +++ b/courtroom.h @@ -755,7 +755,7 @@ private slots: void on_flip_clicked(); void on_hidden_clicked(); - void on_sfx_list_clicked(); + void on_sfx_list_clicked(QModelIndex p_index); void on_evidence_button_clicked(); diff --git a/courtroom_widgets.cpp b/courtroom_widgets.cpp index b5638bea9..8a230b816 100644 --- a/courtroom_widgets.cpp +++ b/courtroom_widgets.cpp @@ -353,7 +353,7 @@ void Courtroom::connect_widgets() connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, - SLOT(on_sfx_list_clicked())); + SLOT(on_sfx_list_clicked(QModelIndex))); connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); From 57d9971ef3475a662f71c254172efdfc7a313512 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 12 Sep 2020 13:41:56 -0400 Subject: [PATCH 097/842] Showname per character per theme: base/themes/THEME/characters/CHARACTER/showname.ext Redone to avoid merge conflict --- courtroom.cpp | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 9b0d2b321..0d5b5a642 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -980,15 +980,36 @@ void Courtroom::handle_chatmessage_3() QString f_emote = m_chatmessage[EMOTE]; ui_vp_showname_image->show(); + + // Check for any of 9 possible ways that showname images are QVector exts = {".png", ".jpg", ".bmp"}; + // 3 places (in order) + // 1. Variant folder in character folder in theme folder + // 2. Character folder in folder folder + // 3. Character folder + QStringList directories = { + ao_app->get_theme_variant_path("characters/" + f_char.toLower() + + "/showname"), + ao_app->get_theme_path("characters/" + f_char.toLower() + "/showname"), + ao_app->get_character_path(f_char, "showname"), + }; + + qDebug() << directories; + QString ext, path; + for (QString directory : directories) + { + ext = ao_app->get_file_extension(directory, exts); + if (!ext.isEmpty()) + { + path = directory + ext; + break; + } + } - QString ext = ao_app->get_file_extension( - ao_app->get_character_path(f_char, "showname"), exts); - if (ext != "" && !chatmessage_is_empty && + if (!path.isEmpty() && !chatmessage_is_empty && ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") { ui_vp_showname->hide(); - QString path = ao_app->get_character_path(f_char, "showname" + ext); ui_vp_showname_image->set_image_from_path(path); ui_vp_showname_image->show(); } From 8defc9c980f1042bbaa8ab9ef3be4912fbe0d660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 12 Sep 2020 19:52:55 +0200 Subject: [PATCH 098/842] #44 opus-not-found - fix `opus` file issues for #44 --- courtroom.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 5529447e9..6d3af49b8 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -364,7 +364,7 @@ void Courtroom::list_music() QString i_song = music_list.at(n_song); bool found = false; - for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) + for (auto &ext : QStringList{"", ".wav", ".ogg", ".opus", ".mp3"}) { QString r_song = i_song + ext; QString song_path = ao_app->get_music_path(r_song); @@ -465,7 +465,7 @@ void Courtroom::list_sfx() bool found = false; - for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) + for (auto &ext : QStringList{"", ".wav", ".ogg", ".opus", ".mp3"}) { QString r_sfx = i_sfx + ext; QString sfx_path = ao_app->get_sounds_path(r_sfx); @@ -1562,7 +1562,7 @@ void Courtroom::play_sfx() if (sfx_name == "1") return; - QVector extensions{"", ".ogg", ".wav", ".mp3"}; + QVector extensions{"", ".wav", ".ogg", ".opus", ".mp3"}; QString f_file = ao_app->get_file_extension(ao_app->get_sounds_path(sfx_name), extensions); @@ -1662,7 +1662,7 @@ void Courtroom::handle_song(QStringList *p_contents) QString f_song = f_contents.at(0); int n_char = f_contents.at(1).toInt(); - for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) + for (auto &ext : QStringList{"", ".wav", ".ogg", ".opus", ".mp3"}) { QString r_song = f_song + ext; QString song_path = ao_app->get_music_path(r_song); @@ -2421,7 +2421,7 @@ void Courtroom::on_sfx_list_clicked(QModelIndex p_index) bool found = false; - for (auto &ext : QStringList{"", ".wav", ".ogg", ".mp3"}) + for (auto &ext : QStringList{"", ".wav", ".ogg", ".opus", ".mp3"}) { QString r_sfx = sfx_names.at(current_sfx_id) + ext; QString sfx_path = ao_app->get_sounds_path(r_sfx); From 7f7b7e64c69db748224a48ecb15be1a0a0ca7190 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 12 Sep 2020 16:11:36 -0400 Subject: [PATCH 099/842] Remove unneeded lowercasing of directories+Improve comments --- courtroom.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index 0d5b5a642..5d8cab5de 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -984,13 +984,12 @@ void Courtroom::handle_chatmessage_3() // Check for any of 9 possible ways that showname images are QVector exts = {".png", ".jpg", ".bmp"}; // 3 places (in order) - // 1. Variant folder in character folder in theme folder - // 2. Character folder in folder folder + // 1. Character folder in variant folder in theme folder + // 2. Character folder in theme folder // 3. Character folder QStringList directories = { - ao_app->get_theme_variant_path("characters/" + f_char.toLower() + - "/showname"), - ao_app->get_theme_path("characters/" + f_char.toLower() + "/showname"), + ao_app->get_theme_variant_path("characters/" + f_char + "/showname"), + ao_app->get_theme_path("characters/" + f_char + "/showname"), ao_app->get_character_path(f_char, "showname"), }; From 69d3c8c1c64ee18f4f7d3e1951b9532e2afd595a Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 12 Sep 2020 22:47:58 +0200 Subject: [PATCH 100/842] Add showname support for system IC messages --- courtroom.cpp | 11 +++++++---- courtroom.h | 9 ++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/courtroom.cpp b/courtroom.cpp index d07435804..c4fb379ea 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -790,7 +790,10 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) qDebug() << m_chatmessage[SHOWNAME] << ao_app->get_showname(char_list.at(f_char_id).name); - if (m_chatmessage[SHOWNAME].isEmpty()) + // We actually DO wanna fail here if the showname is empty but the system is + // speaking. + // Having an empty showname for system is actually what we expect. + if (m_chatmessage[SHOWNAME].isEmpty() && !is_system_speaking) { f_showname = ao_app->get_showname(char_list.at(f_char_id).name); } @@ -817,7 +820,7 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) ui_vp_effect->stop(); if (is_system_speaking) - append_system_text(m_chatmessage[MESSAGE]); + append_system_text(f_showname, m_chatmessage[MESSAGE]); else append_ic_text(f_showname, m_chatmessage[MESSAGE], false, false); @@ -1330,11 +1333,11 @@ void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, update_ic_log(false); } -void Courtroom::append_system_text(QString p_line) +void Courtroom::append_system_text(QString p_showname, QString p_line) { if (chatmessage_is_empty) return; - append_ic_text("", p_line, true, false); + append_ic_text(p_showname, p_line, true, false); } void Courtroom::play_preanim() diff --git a/courtroom.h b/courtroom.h index 6f28e180a..0de53d261 100644 --- a/courtroom.h +++ b/courtroom.h @@ -204,7 +204,14 @@ class Courtroom : public QMainWindow void update_ic_log(bool p_reset_log); void append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music); - void append_system_text(QString p_line); + + /** + * @brief Appends a message arriving from system to the IC chatlog. + * + * @param p_showname The showname used by the system. Can be an empty string. + * @param p_line The message that the system is sending. + */ + void append_system_text(QString p_showname, QString p_line); // prints who played the song to IC chat and plays said song(if found on local // filesystem) takes in a list where the first element is the song name and From 101e77dde55e30ccb5b74ba8ef2af50414234189 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 12 Sep 2020 22:57:55 +0200 Subject: [PATCH 101/842] Clean up warnings in code --- aoevidencebutton.cpp | 14 -------------- aoevidencebutton.h | 2 -- 2 files changed, 16 deletions(-) diff --git a/aoevidencebutton.cpp b/aoevidencebutton.cpp index 3401ebaac..d9c3dab63 100644 --- a/aoevidencebutton.cpp +++ b/aoevidencebutton.cpp @@ -79,20 +79,6 @@ void AOEvidenceButton::mouseDoubleClickEvent(QMouseEvent *e) evidence_double_clicked(m_id); } -void AOEvidenceButton::dragLeaveEvent(QMouseEvent *e) -{ - // QWidget::dragLeaveEvent(e); - - qDebug() << "drag leave event"; -} - -void AOEvidenceButton::dragEnterEvent(QMouseEvent *e) -{ - // QWidget::dragEnterEvent(e); - - qDebug() << "drag enter event"; -} - void AOEvidenceButton::enterEvent(QEvent *e) { ui_selector->show(); diff --git a/aoevidencebutton.h b/aoevidencebutton.h index 8c2e3eb1d..492f19e80 100644 --- a/aoevidencebutton.h +++ b/aoevidencebutton.h @@ -37,8 +37,6 @@ class AOEvidenceButton : public QPushButton void enterEvent(QEvent *e); void leaveEvent(QEvent *e); void mouseDoubleClickEvent(QMouseEvent *e); - void dragLeaveEvent(QMouseEvent *e); - void dragEnterEvent(QMouseEvent *e); signals: void evidence_clicked(int p_id); From c6768ce8ccf76c22fcb5d4b302cdc3050ef95de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 12 Sep 2020 23:51:34 +0200 Subject: [PATCH 102/842] Update main.cpp --- main.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/main.cpp b/main.cpp index 1e71c4564..b7831bfd3 100644 --- a/main.cpp +++ b/main.cpp @@ -21,14 +21,6 @@ int main(int argc, char *argv[]) #endif AOApplication app(argc, argv); - QPluginLoader apng("qapng"); - if (!apng.load()) - { -#ifdef QT_NO_DEBUG - call_error("APNG plugin could not be loaded."); -#endif - } - app.construct_lobby(); #ifdef QT_NO_DEBUG app.net_manager->connect_to_master(); From c87f5f4043235dc7b4e11b4f1b2f6f363e86cf16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sun, 13 Sep 2020 00:03:51 +0200 Subject: [PATCH 103/842] Revert "Update main.cpp" This reverts commit c6768ce8ccf76c22fcb5d4b302cdc3050ef95de3. --- main.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/main.cpp b/main.cpp index b7831bfd3..1e71c4564 100644 --- a/main.cpp +++ b/main.cpp @@ -21,6 +21,14 @@ int main(int argc, char *argv[]) #endif AOApplication app(argc, argv); + QPluginLoader apng("qapng"); + if (!apng.load()) + { +#ifdef QT_NO_DEBUG + call_error("APNG plugin could not be loaded."); +#endif + } + app.construct_lobby(); #ifdef QT_NO_DEBUG app.net_manager->connect_to_master(); From 44602d9ac4d176ef68a86627a4e351efd6cb1c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sun, 13 Sep 2020 00:07:47 +0200 Subject: [PATCH 104/842] Removed APNG plugin loader Qt loads the APNG plugin by itself given the location is within the folder the client is ran from (`imageformats/`). The QPluginLoader is thus no longer necessary and is now redundant, thus safe to remove. TLDR; It's no longer needed and we required a long-worded explanation as to why. --- main.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/main.cpp b/main.cpp index 1e71c4564..b7831bfd3 100644 --- a/main.cpp +++ b/main.cpp @@ -21,14 +21,6 @@ int main(int argc, char *argv[]) #endif AOApplication app(argc, argv); - QPluginLoader apng("qapng"); - if (!apng.load()) - { -#ifdef QT_NO_DEBUG - call_error("APNG plugin could not be loaded."); -#endif - } - app.construct_lobby(); #ifdef QT_NO_DEBUG app.net_manager->connect_to_master(); From de9bdda4dc5a94b87035d4bf7c743ccaa521202e Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sun, 13 Sep 2020 01:03:23 +0200 Subject: [PATCH 105/842] Change version and software to DRO 1.0.0 --- Attorney_Online_remake.pro | 2 +- aoapplication.h | 6 +++--- packet_distribution.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Attorney_Online_remake.pro b/Attorney_Online_remake.pro index dc84e2ace..528ade369 100644 --- a/Attorney_Online_remake.pro +++ b/Attorney_Online_remake.pro @@ -13,7 +13,7 @@ RC_ICONS = logo.ico TARGET = Attorney_Online_remake TEMPLATE = app -VERSION = 2.4.8.0 +VERSION = 1.0.0.0 SOURCES += main.cpp\ aoconfig.cpp \ diff --git a/aoapplication.h b/aoapplication.h index a7f3f4c44..2410680aa 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -353,9 +353,9 @@ class AOApplication : public QApplication void reload_theme(); private: - const int RELEASE = 2; - const int MAJOR_VERSION = 4; - const int MINOR_VERSION = 8; + const int RELEASE = 1; + const int MAJOR_VERSION = 0; + const int MINOR_VERSION = 0; QVector server_list; QVector favorite_list; diff --git a/packet_distribution.cpp b/packet_distribution.cpp index 83cda4f48..8bad16914 100644 --- a/packet_distribution.cpp +++ b/packet_distribution.cpp @@ -72,7 +72,7 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) } else if (header == "AO2CHECK") { - send_ms_packet(new AOPacket("ID#AO2#" + get_version_string() + "#%")); + send_ms_packet(new AOPacket("ID#DRO#" + get_version_string() + "#%")); send_ms_packet(new AOPacket("HI#" + get_hdid() + "#%")); if (f_contents.size() < 1) @@ -165,7 +165,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) s_pv = f_contents.at(0).toInt(); server_software = f_contents.at(1); - send_server_packet(new AOPacket("ID#AO2#" + get_version_string() + "#%")); + send_server_packet(new AOPacket("ID#DRO#" + get_version_string() + "#%")); } else if (header == "CT") { From b23bc80c8137857acc4ab53a8eb26b2fc5fc5a04 Mon Sep 17 00:00:00 2001 From: Cerapter <43446478+Cerapter@users.noreply.github.com> Date: Mon, 14 Sep 2020 18:37:42 +0200 Subject: [PATCH 106/842] Fix missing case sensitive path getting in places (#61) There were a select few places where the case sensitive path version wasn't grabbed, resulting in always missed path lookups on Linux. Good example is `base/characters/Tenko_HD/disappointed.png`. The client looks for a case-insensitive `Disappointed`, without extension and with capitalisation, fails, then case-sensitively looks it up with various extensions -- which all fail, because the file itself is uncapitalised. --- aocharmovie.cpp | 5 +++-- aomovie.cpp | 2 +- aoscene.cpp | 3 ++- courtroom.cpp | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/aocharmovie.cpp b/aocharmovie.cpp index 7e811e548..6a31bfd75 100644 --- a/aocharmovie.cpp +++ b/aocharmovie.cpp @@ -40,7 +40,7 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, bool found = false; for (auto &ext : QStringList{".webp", ".apng", ".gif", ".png"}) { - QString fullPath = f_file + ext; + QString fullPath = ao_app->get_case_sensitive_path(f_file + ext); found = file_exists(fullPath); if (found) { @@ -83,7 +83,8 @@ bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) QString f_source_path = ao_app->get_character_path(p_char, p_emote); for (QString &i_ext : QStringList{".webp", ".apng", ".gif", ".png"}) { - QString f_target_path = f_source_path + i_ext; + QString f_target_path = + ao_app->get_case_sensitive_path(f_source_path + i_ext); if (file_exists(f_target_path)) { f_file_path = f_target_path; diff --git a/aomovie.cpp b/aomovie.cpp index 18decdec9..549d368cb 100644 --- a/aomovie.cpp +++ b/aomovie.cpp @@ -60,7 +60,7 @@ void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) bool found = false; for (auto &ext : decltype(f_vec){".webp", ".apng", ".gif", ".png"}) { - QString fullPath = f_file + ext; + QString fullPath = ao_app->get_case_sensitive_path(f_file + ext); found = file_exists(fullPath); if (found) { diff --git a/aoscene.cpp b/aoscene.cpp index 97943ef6e..2b816fbb6 100644 --- a/aoscene.cpp +++ b/aoscene.cpp @@ -22,7 +22,8 @@ void AOScene::set_image(QString p_image) for (auto &ext : QVector{".webp", ".apng", ".gif", ".png"}) { - QString full_background_path = background_path + ext; + QString full_background_path = + ao_app->get_case_sensitive_path(background_path + ext); if (file_exists(full_background_path)) { diff --git a/courtroom.cpp b/courtroom.cpp index c4fb379ea..5fef2cbb3 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -1013,7 +1013,7 @@ void Courtroom::handle_chatmessage_3() ext = ao_app->get_file_extension(directory, exts); if (!ext.isEmpty()) { - path = directory + ext; + path = ao_app->get_case_sensitive_path(directory + ext); break; } } From 890d2efc76099229c18710827f64bf0009ed1b5d Mon Sep 17 00:00:00 2001 From: Cerapter <43446478+Cerapter@users.noreply.github.com> Date: Mon, 14 Sep 2020 21:22:44 +0200 Subject: [PATCH 107/842] Add ability to close notes through deselecting and deleting (#62) Adds the ability to close notes by clicking the note selection button again, and fixes a bug where you could still edit notes after deleting them off the list, if you had them selected. This feature keeps the "ephemeral storage" function for the notepad if there is no selected note at the moment. So you could open a note, edit it, close it, keep the text from it, edit the text (without editing the note), and then reopen the note, discarding your previous edits while no note was open. Also fixes the issue where the wrong button (the file selection button) would change to the image of the slot activation button, instead of the slot activation button changing sprites. Also limits the note selection to `.txt` files only, and starts the file chooser dialog from the current working directory. --- aonotepicker.cpp | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/aonotepicker.cpp b/aonotepicker.cpp index 05f625625..0aa90e84f 100644 --- a/aonotepicker.cpp +++ b/aonotepicker.cpp @@ -1,6 +1,7 @@ #include "aonotepicker.h" #include "courtroom.h" +#include "debug_functions.h" #include #include @@ -22,19 +23,44 @@ void Courtroom::on_file_selected() AOButton *f_button = static_cast(sender()); AONotePicker *f_notepicker = static_cast(f_button->parent()); - current_file = f_notepicker->real_file; - load_note(); - f_button->set_image("note_select_selected.png"); + + if (f_notepicker->real_file.isEmpty()) + { + call_notice("You must give a filepath to load a note from!"); + return; + } + + if (current_file != f_notepicker->real_file) + { + current_file = f_notepicker->real_file; + load_note(); + f_notepicker->m_hover->set_image("note_select_selected.png"); + } + else + { + current_file = ""; + } } void Courtroom::on_set_file_button_clicked() { AOButton *f_button = static_cast(sender()); AONotePicker *f_notepicker = static_cast(f_button->parent()); - QString f_filename = QFileDialog::getOpenFileName(this, "Open File"); + QString f_filename = QFileDialog::getOpenFileName( + this, "Open File", QDir::currentPath(), "Text files (*.txt)"); + if (f_filename != "") { f_notepicker->m_line->setText(f_filename); + + // If this notepicker is the currently selected slot, update the notepad as + // the file given changes. + if (f_notepicker->real_file == current_file) + { + current_file = f_filename; + load_note(); + } + f_notepicker->real_file = f_filename; set_note_files(); @@ -45,6 +71,12 @@ void Courtroom::on_delete_button_clicked() { AOButton *f_button = static_cast(sender()); AONotePicker *f_notepicker = static_cast(f_button->parent()); + + if (current_file == f_notepicker->real_file) + { + current_file = ""; + } + ui_note_area->m_layout->removeWidget(f_notepicker); delete f_notepicker; set_note_files(); From 6cf76e530c749959cb8262ffbc414316cb7ff213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Wed, 16 Sep 2020 19:02:57 +0200 Subject: [PATCH 108/842] Project Restructuring A basic restructure of the project. - Moved 3rd parties (BASS, Discord) to the `3rd` folder, makes a hella more sense than having it sitting inside the same folder as the project's headers and sources. - Separated headers and sources in their own folders, respectively `include` and `src` - Renamed AttorneyOnline_remake to dronline-client - Reorganized the `.pro` file. - Updated `.gitignore` to include the `3rd` folder. --- .gitignore | 6 +- Attorney_Online_remake.pro | 132 -- bass.h | 1161 ----------------- discord-rpc.h | 92 -- discord_register.h | 29 - discord_rpc.h | 92 -- dronline-client.pro | 126 ++ .../aoabstractplayer.h | 0 aoapplication.h => include/aoapplication.h | 0 aobasshandle.h => include/aobasshandle.h | 0 aoblipplayer.h => include/aoblipplayer.h | 8 +- aobutton.h => include/aobutton.h | 0 aocharbutton.h => include/aocharbutton.h | 0 aocharmovie.h => include/aocharmovie.h | 0 aoconfig.h => include/aoconfig.h | 0 aoconfigpanel.h => include/aoconfigpanel.h | 0 aoemotebutton.h => include/aoemotebutton.h | 0 .../aoevidencebutton.h | 0 .../aoevidencedescription.h | 0 .../aoevidencedisplay.h | 0 aoexception.h => include/aoexception.h | 0 aoguiloader.h => include/aoguiloader.h | 0 aoimage.h => include/aoimage.h | 0 aolabel.h => include/aolabel.h | 0 aolineedit.h => include/aolineedit.h | 0 aomovie.h => include/aomovie.h | 0 aomusicplayer.h => include/aomusicplayer.h | 0 aonotearea.h => include/aonotearea.h | 0 aonotepad.h => include/aonotepad.h | 0 aonotepicker.h => include/aonotepicker.h | 0 aopacket.h => include/aopacket.h | 0 aopixmap.h => include/aopixmap.h | 0 aoscene.h => include/aoscene.h | 0 aosfxplayer.h => include/aosfxplayer.h | 0 aoshoutplayer.h => include/aoshoutplayer.h | 0 aotextarea.h => include/aotextarea.h | 0 aotimer.h => include/aotimer.h | 0 courtroom.h => include/courtroom.h | 0 datatypes.h => include/datatypes.h | 0 .../debug_functions.h | 0 .../discord_rich_presence.h | 3 +- .../encryption_functions.h | 0 file_functions.h => include/file_functions.h | 0 .../hardware_functions.h | 0 hex_functions.h => include/hex_functions.h | 0 lobby.h => include/lobby.h | 0 misc_functions.h => include/misc_functions.h | 0 networkmanager.h => include/networkmanager.h | 0 .../aoabstractplayer.cpp | 0 aoapplication.cpp => src/aoapplication.cpp | 0 aobasshandle.cpp => src/aobasshandle.cpp | 0 aoblipplayer.cpp => src/aoblipplayer.cpp | 0 aobutton.cpp => src/aobutton.cpp | 0 aocharbutton.cpp => src/aocharbutton.cpp | 0 aocharmovie.cpp => src/aocharmovie.cpp | 0 aoconfig.cpp => src/aoconfig.cpp | 0 aoconfigpanel.cpp => src/aoconfigpanel.cpp | 0 aoemotebutton.cpp => src/aoemotebutton.cpp | 0 .../aoevidencebutton.cpp | 0 .../aoevidencedescription.cpp | 0 .../aoevidencedisplay.cpp | 0 aoexception.cpp => src/aoexception.cpp | 0 aoguiloader.cpp => src/aoguiloader.cpp | 0 aoimage.cpp => src/aoimage.cpp | 0 aolabel.cpp => src/aolabel.cpp | 0 aolineedit.cpp => src/aolineedit.cpp | 0 aomovie.cpp => src/aomovie.cpp | 0 aomusicplayer.cpp => src/aomusicplayer.cpp | 0 aonotearea.cpp => src/aonotearea.cpp | 0 aonotepad.cpp => src/aonotepad.cpp | 0 aonotepicker.cpp => src/aonotepicker.cpp | 0 aopacket.cpp => src/aopacket.cpp | 0 aopixmap.cpp => src/aopixmap.cpp | 0 aoscene.cpp => src/aoscene.cpp | 0 aosfxplayer.cpp => src/aosfxplayer.cpp | 0 aoshoutplayer.cpp => src/aoshoutplayer.cpp | 0 aotextarea.cpp => src/aotextarea.cpp | 0 aotimer.cpp => src/aotimer.cpp | 0 .../audio_functions.cpp | 0 charselect.cpp => src/charselect.cpp | 0 courtroom.cpp => src/courtroom.cpp | 0 .../courtroom_widgets.cpp | 0 .../debug_functions.cpp | 0 .../discord_rich_presence.cpp | 0 emotes.cpp => src/emotes.cpp | 0 .../encryption_functions.cpp | 0 evidence.cpp => src/evidence.cpp | 0 file_functions.cpp => src/file_functions.cpp | 0 .../hardware_functions.cpp | 0 hex_functions.cpp => src/hex_functions.cpp | 0 lobby.cpp => src/lobby.cpp | 0 main.cpp => src/main.cpp | 0 misc_functions.cpp => src/misc_functions.cpp | 0 networkmanager.cpp => src/networkmanager.cpp | 0 .../packet_distribution.cpp | 0 path_functions.cpp => src/path_functions.cpp | 0 .../text_file_functions.cpp | 0 97 files changed, 135 insertions(+), 1514 deletions(-) delete mode 100644 Attorney_Online_remake.pro delete mode 100644 bass.h delete mode 100644 discord-rpc.h delete mode 100644 discord_register.h delete mode 100644 discord_rpc.h create mode 100644 dronline-client.pro rename aoabstractplayer.h => include/aoabstractplayer.h (100%) rename aoapplication.h => include/aoapplication.h (100%) rename aobasshandle.h => include/aobasshandle.h (100%) rename aoblipplayer.h => include/aoblipplayer.h (91%) rename aobutton.h => include/aobutton.h (100%) rename aocharbutton.h => include/aocharbutton.h (100%) rename aocharmovie.h => include/aocharmovie.h (100%) rename aoconfig.h => include/aoconfig.h (100%) rename aoconfigpanel.h => include/aoconfigpanel.h (100%) rename aoemotebutton.h => include/aoemotebutton.h (100%) rename aoevidencebutton.h => include/aoevidencebutton.h (100%) rename aoevidencedescription.h => include/aoevidencedescription.h (100%) rename aoevidencedisplay.h => include/aoevidencedisplay.h (100%) rename aoexception.h => include/aoexception.h (100%) rename aoguiloader.h => include/aoguiloader.h (100%) rename aoimage.h => include/aoimage.h (100%) rename aolabel.h => include/aolabel.h (100%) rename aolineedit.h => include/aolineedit.h (100%) rename aomovie.h => include/aomovie.h (100%) rename aomusicplayer.h => include/aomusicplayer.h (100%) rename aonotearea.h => include/aonotearea.h (100%) rename aonotepad.h => include/aonotepad.h (100%) rename aonotepicker.h => include/aonotepicker.h (100%) rename aopacket.h => include/aopacket.h (100%) rename aopixmap.h => include/aopixmap.h (100%) rename aoscene.h => include/aoscene.h (100%) rename aosfxplayer.h => include/aosfxplayer.h (100%) rename aoshoutplayer.h => include/aoshoutplayer.h (100%) rename aotextarea.h => include/aotextarea.h (100%) rename aotimer.h => include/aotimer.h (100%) rename courtroom.h => include/courtroom.h (100%) rename datatypes.h => include/datatypes.h (100%) rename debug_functions.h => include/debug_functions.h (100%) rename discord_rich_presence.h => include/discord_rich_presence.h (98%) rename encryption_functions.h => include/encryption_functions.h (100%) rename file_functions.h => include/file_functions.h (100%) rename hardware_functions.h => include/hardware_functions.h (100%) rename hex_functions.h => include/hex_functions.h (100%) rename lobby.h => include/lobby.h (100%) rename misc_functions.h => include/misc_functions.h (100%) rename networkmanager.h => include/networkmanager.h (100%) rename aoabstractplayer.cpp => src/aoabstractplayer.cpp (100%) rename aoapplication.cpp => src/aoapplication.cpp (100%) rename aobasshandle.cpp => src/aobasshandle.cpp (100%) rename aoblipplayer.cpp => src/aoblipplayer.cpp (100%) rename aobutton.cpp => src/aobutton.cpp (100%) rename aocharbutton.cpp => src/aocharbutton.cpp (100%) rename aocharmovie.cpp => src/aocharmovie.cpp (100%) rename aoconfig.cpp => src/aoconfig.cpp (100%) rename aoconfigpanel.cpp => src/aoconfigpanel.cpp (100%) rename aoemotebutton.cpp => src/aoemotebutton.cpp (100%) rename aoevidencebutton.cpp => src/aoevidencebutton.cpp (100%) rename aoevidencedescription.cpp => src/aoevidencedescription.cpp (100%) rename aoevidencedisplay.cpp => src/aoevidencedisplay.cpp (100%) rename aoexception.cpp => src/aoexception.cpp (100%) rename aoguiloader.cpp => src/aoguiloader.cpp (100%) rename aoimage.cpp => src/aoimage.cpp (100%) rename aolabel.cpp => src/aolabel.cpp (100%) rename aolineedit.cpp => src/aolineedit.cpp (100%) rename aomovie.cpp => src/aomovie.cpp (100%) rename aomusicplayer.cpp => src/aomusicplayer.cpp (100%) rename aonotearea.cpp => src/aonotearea.cpp (100%) rename aonotepad.cpp => src/aonotepad.cpp (100%) rename aonotepicker.cpp => src/aonotepicker.cpp (100%) rename aopacket.cpp => src/aopacket.cpp (100%) rename aopixmap.cpp => src/aopixmap.cpp (100%) rename aoscene.cpp => src/aoscene.cpp (100%) rename aosfxplayer.cpp => src/aosfxplayer.cpp (100%) rename aoshoutplayer.cpp => src/aoshoutplayer.cpp (100%) rename aotextarea.cpp => src/aotextarea.cpp (100%) rename aotimer.cpp => src/aotimer.cpp (100%) rename audio_functions.cpp => src/audio_functions.cpp (100%) rename charselect.cpp => src/charselect.cpp (100%) rename courtroom.cpp => src/courtroom.cpp (100%) rename courtroom_widgets.cpp => src/courtroom_widgets.cpp (100%) rename debug_functions.cpp => src/debug_functions.cpp (100%) rename discord_rich_presence.cpp => src/discord_rich_presence.cpp (100%) rename emotes.cpp => src/emotes.cpp (100%) rename encryption_functions.cpp => src/encryption_functions.cpp (100%) rename evidence.cpp => src/evidence.cpp (100%) rename file_functions.cpp => src/file_functions.cpp (100%) rename hardware_functions.cpp => src/hardware_functions.cpp (100%) rename hex_functions.cpp => src/hex_functions.cpp (100%) rename lobby.cpp => src/lobby.cpp (100%) rename main.cpp => src/main.cpp (100%) rename misc_functions.cpp => src/misc_functions.cpp (100%) rename networkmanager.cpp => src/networkmanager.cpp (100%) rename packet_distribution.cpp => src/packet_distribution.cpp (100%) rename path_functions.cpp => src/path_functions.cpp (100%) rename text_file_functions.cpp => src/text_file_functions.cpp (100%) diff --git a/.gitignore b/.gitignore index 9028beab7..054156108 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,6 @@ *.dll *.so base_override.h - -base-full/ -bass.lib -discord-rpc.lib send-presence.exe +3rd/ +base-full/ \ No newline at end of file diff --git a/Attorney_Online_remake.pro b/Attorney_Online_remake.pro deleted file mode 100644 index 528ade369..000000000 --- a/Attorney_Online_remake.pro +++ /dev/null @@ -1,132 +0,0 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2016-12-29T01:14:46 -# -#------------------------------------------------- - -QT += core gui widgets uitools multimedia network - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -RC_ICONS = logo.ico - -TARGET = Attorney_Online_remake -TEMPLATE = app - -VERSION = 1.0.0.0 - -SOURCES += main.cpp\ - aoconfig.cpp \ - aoconfigpanel.cpp \ - aoevidencedescription.cpp \ - aoguiloader.cpp \ - aopixmap.cpp \ - aotimer.cpp \ - audio_functions.cpp \ - lobby.cpp \ - text_file_functions.cpp \ - path_functions.cpp \ - aoimage.cpp \ - file_functions.cpp \ - aobutton.cpp \ - debug_functions.cpp \ - networkmanager.cpp \ - aoapplication.cpp \ - aopacket.cpp \ - packet_distribution.cpp \ - hex_functions.cpp \ - encryption_functions.cpp \ - courtroom.cpp \ - aocharbutton.cpp \ - hardware_functions.cpp \ - aoscene.cpp \ - aomovie.cpp \ - misc_functions.cpp \ - aocharmovie.cpp \ - aoemotebutton.cpp \ - emotes.cpp \ - aosfxplayer.cpp \ - aomusicplayer.cpp \ - aoblipplayer.cpp \ - evidence.cpp \ - aoevidencebutton.cpp \ - charselect.cpp \ - aotextarea.cpp \ - aolineedit.cpp \ - aoevidencedisplay.cpp \ - discord_rich_presence.cpp \ - aonotepad.cpp \ - aobasshandle.cpp \ - aoexception.cpp \ - aoabstractplayer.cpp \ - aoshoutplayer.cpp \ - aonotearea.cpp \ - aonotepicker.cpp \ - aolabel.cpp \ - courtroom_widgets.cpp - -HEADERS += lobby.h \ - aoabstractplayer.h \ - aobasshandle.h \ - aoconfig.h \ - aoconfigpanel.h \ - aoevidencedescription.h \ - aoexception.h \ - aoguiloader.h \ - aoimage.h \ - aolabel.h \ - aonotearea.h \ - aonotepicker.h \ - aopixmap.h \ - aoshoutplayer.h \ - aotimer.h \ - file_functions.h \ - aobutton.h \ - debug_functions.h \ - networkmanager.h \ - aoapplication.h \ - datatypes.h \ - aopacket.h \ - hex_functions.h \ - encryption_functions.h \ - courtroom.h \ - aocharbutton.h \ - hardware_functions.h \ - aoscene.h \ - aomovie.h \ - misc_functions.h \ - aocharmovie.h \ - aoemotebutton.h \ - bass.h \ - aosfxplayer.h \ - aomusicplayer.h \ - aoblipplayer.h \ - aoevidencebutton.h \ - aotextarea.h \ - aolineedit.h \ - aoevidencedisplay.h \ - discord_rich_presence.h \ - discord-rpc.h \ - aonotepad.h - -# 1. You need to get BASS and put the x86 bass DLL/headers in the project root folder -# AND the compilation output folder. If you want a static link, you'll probably -# need the .lib file too. MinGW-GCC is really finicky finding BASS, it seems. -# 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers -# in the same way as BASS. Discord RPC uses CMake, which does not play nicely with -# QMake, so this step must be manual. -unix:LIBS += -L$$PWD -lbass -ldiscord-rpc -win32:LIBS += -L$$PWD "$$PWD/bass.lib" -ldiscord-rpc #"$$PWD/discord-rpc.dll" -android:LIBS += -L$$PWD\android\libs\armeabi-v7a\ -lbass - -CONFIG += c++11 - -ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android - -RESOURCES += \ - res.qrc - -DISTFILES += - -FORMS += \ - res/ui/config_panel.ui diff --git a/bass.h b/bass.h deleted file mode 100644 index 77db5da97..000000000 --- a/bass.h +++ /dev/null @@ -1,1161 +0,0 @@ -/* - BASS 2.4 C/C++ header file - Copyright (c) 1999-2016 Un4seen Developments Ltd. - - See the BASS.CHM file for more detailed documentation -*/ - -#ifndef BASS_H -#define BASS_H - -#ifdef _WIN32 -#include -typedef unsigned __int64 QWORD; -#else -#include -#define WINAPI -#define CALLBACK -typedef uint8_t BYTE; -typedef uint16_t WORD; -typedef uint32_t DWORD; -typedef uint64_t QWORD; -#ifndef __OBJC__ -typedef int BOOL; -#endif -#ifndef TRUE -#define TRUE 1 -#define FALSE 0 -#endif -#define LOBYTE(a) (BYTE)(a) -#define HIBYTE(a) (BYTE)((a) >> 8) -#define LOWORD(a) (WORD)(a) -#define HIWORD(a) (WORD)((a) >> 16) -#define MAKEWORD(a, b) (WORD)(((a)&0xff) | ((b) << 8)) -#define MAKELONG(a, b) (DWORD)(((a)&0xffff) | ((b) << 16)) -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - -#define BASSVERSION 0x204 // API version -#define BASSVERSIONTEXT "2.4" - -#ifndef BASSDEF -#define BASSDEF(f) WINAPI f -#else -#define NOBASSOVERLOADS -#endif - - typedef DWORD HMUSIC; // MOD music handle - typedef DWORD HSAMPLE; // sample handle - typedef DWORD HCHANNEL; // playing sample's channel handle - typedef DWORD HSTREAM; // sample stream handle - typedef DWORD HRECORD; // recording handle - typedef DWORD HSYNC; // synchronizer handle - typedef DWORD HDSP; // DSP handle - typedef DWORD HFX; // DX8 effect handle - typedef DWORD HPLUGIN; // Plugin handle - -// Error codes returned by BASS_ErrorGetCode -#define BASS_OK 0 // all is OK -#define BASS_ERROR_MEM 1 // memory error -#define BASS_ERROR_FILEOPEN 2 // can't open the file -#define BASS_ERROR_DRIVER 3 // can't find a free/valid driver -#define BASS_ERROR_BUFLOST 4 // the sample buffer was lost -#define BASS_ERROR_HANDLE 5 // invalid handle -#define BASS_ERROR_FORMAT 6 // unsupported sample format -#define BASS_ERROR_POSITION 7 // invalid position -#define BASS_ERROR_INIT 8 // BASS_Init has not been successfully called -#define BASS_ERROR_START 9 // BASS_Start has not been successfully called -#define BASS_ERROR_SSL 10 // SSL/HTTPS support isn't available -#define BASS_ERROR_ALREADY 14 // already initialized/paused/whatever -#define BASS_ERROR_NOCHAN 18 // can't get a free channel -#define BASS_ERROR_ILLTYPE 19 // an illegal type was specified -#define BASS_ERROR_ILLPARAM 20 // an illegal parameter was specified -#define BASS_ERROR_NO3D 21 // no 3D support -#define BASS_ERROR_NOEAX 22 // no EAX support -#define BASS_ERROR_DEVICE 23 // illegal device number -#define BASS_ERROR_NOPLAY 24 // not playing -#define BASS_ERROR_FREQ 25 // illegal sample rate -#define BASS_ERROR_NOTFILE 27 // the stream is not a file stream -#define BASS_ERROR_NOHW 29 // no hardware voices available -#define BASS_ERROR_EMPTY 31 // the MOD music has no sequence data -#define BASS_ERROR_NONET 32 // no internet connection could be opened -#define BASS_ERROR_CREATE 33 // couldn't create the file -#define BASS_ERROR_NOFX 34 // effects are not available -#define BASS_ERROR_NOTAVAIL 37 // requested data is not available -#define BASS_ERROR_DECODE 38 // the channel is/isn't a "decoding channel" -#define BASS_ERROR_DX 39 // a sufficient DirectX version is not installed -#define BASS_ERROR_TIMEOUT 40 // connection timedout -#define BASS_ERROR_FILEFORM 41 // unsupported file format -#define BASS_ERROR_SPEAKER 42 // unavailable speaker -#define BASS_ERROR_VERSION 43 // invalid BASS version (used by add-ons) -#define BASS_ERROR_CODEC 44 // codec is not available/supported -#define BASS_ERROR_ENDED 45 // the channel/file has ended -#define BASS_ERROR_BUSY 46 // the device is busy -#define BASS_ERROR_UNKNOWN -1 // some other mystery problem - -// BASS_SetConfig options -#define BASS_CONFIG_BUFFER 0 -#define BASS_CONFIG_UPDATEPERIOD 1 -#define BASS_CONFIG_GVOL_SAMPLE 4 -#define BASS_CONFIG_GVOL_STREAM 5 -#define BASS_CONFIG_GVOL_MUSIC 6 -#define BASS_CONFIG_CURVE_VOL 7 -#define BASS_CONFIG_CURVE_PAN 8 -#define BASS_CONFIG_FLOATDSP 9 -#define BASS_CONFIG_3DALGORITHM 10 -#define BASS_CONFIG_NET_TIMEOUT 11 -#define BASS_CONFIG_NET_BUFFER 12 -#define BASS_CONFIG_PAUSE_NOPLAY 13 -#define BASS_CONFIG_NET_PREBUF 15 -#define BASS_CONFIG_NET_PASSIVE 18 -#define BASS_CONFIG_REC_BUFFER 19 -#define BASS_CONFIG_NET_PLAYLIST 21 -#define BASS_CONFIG_MUSIC_VIRTUAL 22 -#define BASS_CONFIG_VERIFY 23 -#define BASS_CONFIG_UPDATETHREADS 24 -#define BASS_CONFIG_DEV_BUFFER 27 -#define BASS_CONFIG_VISTA_TRUEPOS 30 -#define BASS_CONFIG_IOS_MIXAUDIO 34 -#define BASS_CONFIG_DEV_DEFAULT 36 -#define BASS_CONFIG_NET_READTIMEOUT 37 -#define BASS_CONFIG_VISTA_SPEAKERS 38 -#define BASS_CONFIG_IOS_SPEAKER 39 -#define BASS_CONFIG_MF_DISABLE 40 -#define BASS_CONFIG_HANDLES 41 -#define BASS_CONFIG_UNICODE 42 -#define BASS_CONFIG_SRC 43 -#define BASS_CONFIG_SRC_SAMPLE 44 -#define BASS_CONFIG_ASYNCFILE_BUFFER 45 -#define BASS_CONFIG_OGG_PRESCAN 47 -#define BASS_CONFIG_MF_VIDEO 48 -#define BASS_CONFIG_AIRPLAY 49 -#define BASS_CONFIG_DEV_NONSTOP 50 -#define BASS_CONFIG_IOS_NOCATEGORY 51 -#define BASS_CONFIG_VERIFY_NET 52 -#define BASS_CONFIG_DEV_PERIOD 53 -#define BASS_CONFIG_FLOAT 54 -#define BASS_CONFIG_NET_SEEK 56 - -// BASS_SetConfigPtr options -#define BASS_CONFIG_NET_AGENT 16 -#define BASS_CONFIG_NET_PROXY 17 -#define BASS_CONFIG_IOS_NOTIFY 46 - -// BASS_Init flags -#define BASS_DEVICE_8BITS 1 // 8 bit -#define BASS_DEVICE_MONO 2 // mono -#define BASS_DEVICE_3D 4 // enable 3D functionality -#define BASS_DEVICE_16BITS 8 // limit output to 16 bit -#define BASS_DEVICE_LATENCY 0x100 // calculate device latency (BASS_INFO struct) -#define BASS_DEVICE_CPSPEAKERS \ - 0x400 // detect speakers via Windows control panel -#define BASS_DEVICE_SPEAKERS 0x800 // force enabling of speaker assignment -#define BASS_DEVICE_NOSPEAKER 0x1000 // ignore speaker arrangement -#define BASS_DEVICE_DMIX 0x2000 // use ALSA "dmix" plugin -#define BASS_DEVICE_FREQ 0x4000 // set device sample rate -#define BASS_DEVICE_STEREO 0x8000 // limit output to stereo - -// DirectSound interfaces (for use with BASS_GetDSoundObject) -#define BASS_OBJECT_DS 1 // IDirectSound -#define BASS_OBJECT_DS3DL 2 // IDirectSound3DListener - - // Device info structure - typedef struct - { -#if defined(_WIN32_WCE) || \ - (WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - const wchar_t *name; // description - const wchar_t *driver; // driver -#else - const char *name; // description - const char *driver; // driver -#endif - DWORD flags; - } BASS_DEVICEINFO; - -// BASS_DEVICEINFO flags -#define BASS_DEVICE_ENABLED 1 -#define BASS_DEVICE_DEFAULT 2 -#define BASS_DEVICE_INIT 4 - -#define BASS_DEVICE_TYPE_MASK 0xff000000 -#define BASS_DEVICE_TYPE_NETWORK 0x01000000 -#define BASS_DEVICE_TYPE_SPEAKERS 0x02000000 -#define BASS_DEVICE_TYPE_LINE 0x03000000 -#define BASS_DEVICE_TYPE_HEADPHONES 0x04000000 -#define BASS_DEVICE_TYPE_MICROPHONE 0x05000000 -#define BASS_DEVICE_TYPE_HEADSET 0x06000000 -#define BASS_DEVICE_TYPE_HANDSET 0x07000000 -#define BASS_DEVICE_TYPE_DIGITAL 0x08000000 -#define BASS_DEVICE_TYPE_SPDIF 0x09000000 -#define BASS_DEVICE_TYPE_HDMI 0x0a000000 -#define BASS_DEVICE_TYPE_DISPLAYPORT 0x40000000 - -// BASS_GetDeviceInfo flags -#define BASS_DEVICES_AIRPLAY 0x1000000 - - typedef struct - { - DWORD flags; // device capabilities (DSCAPS_xxx flags) - DWORD hwsize; // size of total device hardware memory - DWORD hwfree; // size of free device hardware memory - DWORD freesam; // number of free sample slots in the hardware - DWORD free3d; // number of free 3D sample slots in the hardware - DWORD minrate; // min sample rate supported by the hardware - DWORD maxrate; // max sample rate supported by the hardware - BOOL eax; // device supports EAX? (always FALSE if BASS_DEVICE_3D was not - // used) - DWORD minbuf; // recommended minimum buffer length in ms (requires - // BASS_DEVICE_LATENCY) - DWORD dsver; // DirectSound version - DWORD latency; // delay (in ms) before start of playback (requires - // BASS_DEVICE_LATENCY) - DWORD initflags; // BASS_Init "flags" parameter - DWORD speakers; // number of speakers available - DWORD freq; // current output rate - } BASS_INFO; - -// BASS_INFO flags (from DSOUND.H) -#define DSCAPS_CONTINUOUSRATE \ - 0x00000010 // supports all sample rates between min/maxrate -#define DSCAPS_EMULDRIVER \ - 0x00000020 // device does NOT have hardware DirectSound support -#define DSCAPS_CERTIFIED \ - 0x00000040 // device driver has been certified by Microsoft -#define DSCAPS_SECONDARYMONO 0x00000100 // mono -#define DSCAPS_SECONDARYSTEREO 0x00000200 // stereo -#define DSCAPS_SECONDARY8BIT 0x00000400 // 8 bit -#define DSCAPS_SECONDARY16BIT 0x00000800 // 16 bit - - // Recording device info structure - typedef struct - { - DWORD flags; // device capabilities (DSCCAPS_xxx flags) - DWORD formats; // supported standard formats (WAVE_FORMAT_xxx flags) - DWORD inputs; // number of inputs - BOOL singlein; // TRUE = only 1 input can be set at a time - DWORD freq; // current input rate - } BASS_RECORDINFO; - -// BASS_RECORDINFO flags (from DSOUND.H) -#define DSCCAPS_EMULDRIVER \ - DSCAPS_EMULDRIVER // device does NOT have hardware DirectSound recording - // support -#define DSCCAPS_CERTIFIED \ - DSCAPS_CERTIFIED // device driver has been certified by Microsoft - -// defines for formats field of BASS_RECORDINFO (from MMSYSTEM.H) -#ifndef WAVE_FORMAT_1M08 -#define WAVE_FORMAT_1M08 0x00000001 /* 11.025 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_1S08 0x00000002 /* 11.025 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_1M16 0x00000004 /* 11.025 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_1S16 0x00000008 /* 11.025 kHz, Stereo, 16-bit */ -#define WAVE_FORMAT_2M08 0x00000010 /* 22.05 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_2S08 0x00000020 /* 22.05 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_2M16 0x00000040 /* 22.05 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_2S16 0x00000080 /* 22.05 kHz, Stereo, 16-bit */ -#define WAVE_FORMAT_4M08 0x00000100 /* 44.1 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_4S08 0x00000200 /* 44.1 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_4M16 0x00000400 /* 44.1 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_4S16 0x00000800 /* 44.1 kHz, Stereo, 16-bit */ -#endif - - // Sample info structure - typedef struct - { - DWORD freq; // default playback rate - float volume; // default volume (0-1) - float pan; // default pan (-1=left, 0=middle, 1=right) - DWORD flags; // BASS_SAMPLE_xxx flags - DWORD length; // length (in bytes) - DWORD max; // maximum simultaneous playbacks - DWORD origres; // original resolution bits - DWORD chans; // number of channels - DWORD mingap; // minimum gap (ms) between creating channels - DWORD mode3d; // BASS_3DMODE_xxx mode - float mindist; // minimum distance - float maxdist; // maximum distance - DWORD iangle; // angle of inside projection cone - DWORD oangle; // angle of outside projection cone - float outvol; // delta-volume outside the projection cone - DWORD vam; // voice allocation/management flags (BASS_VAM_xxx) - DWORD priority; // priority (0=lowest, 0xffffffff=highest) - } BASS_SAMPLE; - -#define BASS_SAMPLE_8BITS 1 // 8 bit -#define BASS_SAMPLE_FLOAT 256 // 32 bit floating-point -#define BASS_SAMPLE_MONO 2 // mono -#define BASS_SAMPLE_LOOP 4 // looped -#define BASS_SAMPLE_3D 8 // 3D functionality -#define BASS_SAMPLE_SOFTWARE 16 // not using hardware mixing -#define BASS_SAMPLE_MUTEMAX 32 // mute at max distance (3D only) -#define BASS_SAMPLE_VAM 64 // DX7 voice allocation & management -#define BASS_SAMPLE_FX 128 // old implementation of DX8 effects -#define BASS_SAMPLE_OVER_VOL 0x10000 // override lowest volume -#define BASS_SAMPLE_OVER_POS 0x20000 // override longest playing -#define BASS_SAMPLE_OVER_DIST \ - 0x30000 // override furthest from listener (3D only) - -#define BASS_STREAM_PRESCAN \ - 0x20000 // enable pin-point seeking/length (MP3/MP2/MP1) -#define BASS_MP3_SETPOS BASS_STREAM_PRESCAN -#define BASS_STREAM_AUTOFREE \ - 0x40000 // automatically free the stream when it stop/ends -#define BASS_STREAM_RESTRATE \ - 0x80000 // restrict the download rate of internet file streams -#define BASS_STREAM_BLOCK \ - 0x100000 // download/play internet file stream in small blocks -#define BASS_STREAM_DECODE \ - 0x200000 // don't play the stream, only decode (BASS_ChannelGetData) -#define BASS_STREAM_STATUS \ - 0x800000 // give server status info (HTTP/ICY tags) in DOWNLOADPROC - -#define BASS_MUSIC_FLOAT BASS_SAMPLE_FLOAT -#define BASS_MUSIC_MONO BASS_SAMPLE_MONO -#define BASS_MUSIC_LOOP BASS_SAMPLE_LOOP -#define BASS_MUSIC_3D BASS_SAMPLE_3D -#define BASS_MUSIC_FX BASS_SAMPLE_FX -#define BASS_MUSIC_AUTOFREE BASS_STREAM_AUTOFREE -#define BASS_MUSIC_DECODE BASS_STREAM_DECODE -#define BASS_MUSIC_PRESCAN BASS_STREAM_PRESCAN // calculate playback length -#define BASS_MUSIC_CALCLEN BASS_MUSIC_PRESCAN -#define BASS_MUSIC_RAMP 0x200 // normal ramping -#define BASS_MUSIC_RAMPS 0x400 // sensitive ramping -#define BASS_MUSIC_SURROUND 0x800 // surround sound -#define BASS_MUSIC_SURROUND2 0x1000 // surround sound (mode 2) -#define BASS_MUSIC_FT2PAN 0x2000 // apply FastTracker 2 panning to XM files -#define BASS_MUSIC_FT2MOD 0x2000 // play .MOD as FastTracker 2 does -#define BASS_MUSIC_PT1MOD 0x4000 // play .MOD as ProTracker 1 does -#define BASS_MUSIC_NONINTER 0x10000 // non-interpolated sample mixing -#define BASS_MUSIC_SINCINTER 0x800000 // sinc interpolated sample mixing -#define BASS_MUSIC_POSRESET 0x8000 // stop all notes when moving position -#define BASS_MUSIC_POSRESETEX \ - 0x400000 // stop all notes and reset bmp/etc when moving position -#define BASS_MUSIC_STOPBACK 0x80000 // stop the music on a backwards jump effect -#define BASS_MUSIC_NOSAMPLE 0x100000 // don't load the samples - -// Speaker assignment flags -#define BASS_SPEAKER_FRONT 0x1000000 // front speakers -#define BASS_SPEAKER_REAR 0x2000000 // rear/side speakers -#define BASS_SPEAKER_CENLFE 0x3000000 // center & LFE speakers (5.1) -#define BASS_SPEAKER_REAR2 0x4000000 // rear center speakers (7.1) -#define BASS_SPEAKER_N(n) ((n) << 24) // n'th pair of speakers (max 15) -#define BASS_SPEAKER_LEFT 0x10000000 // modifier: left -#define BASS_SPEAKER_RIGHT 0x20000000 // modifier: right -#define BASS_SPEAKER_FRONTLEFT BASS_SPEAKER_FRONT | BASS_SPEAKER_LEFT -#define BASS_SPEAKER_FRONTRIGHT BASS_SPEAKER_FRONT | BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_REARLEFT BASS_SPEAKER_REAR | BASS_SPEAKER_LEFT -#define BASS_SPEAKER_REARRIGHT BASS_SPEAKER_REAR | BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_CENTER BASS_SPEAKER_CENLFE | BASS_SPEAKER_LEFT -#define BASS_SPEAKER_LFE BASS_SPEAKER_CENLFE | BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_REAR2LEFT BASS_SPEAKER_REAR2 | BASS_SPEAKER_LEFT -#define BASS_SPEAKER_REAR2RIGHT BASS_SPEAKER_REAR2 | BASS_SPEAKER_RIGHT - -#define BASS_ASYNCFILE 0x40000000 -#define BASS_UNICODE 0x80000000 - -#define BASS_RECORD_PAUSE 0x8000 // start recording paused -#define BASS_RECORD_ECHOCANCEL 0x2000 -#define BASS_RECORD_AGC 0x4000 - -// DX7 voice allocation & management flags -#define BASS_VAM_HARDWARE 1 -#define BASS_VAM_SOFTWARE 2 -#define BASS_VAM_TERM_TIME 4 -#define BASS_VAM_TERM_DIST 8 -#define BASS_VAM_TERM_PRIO 16 - - // Channel info structure - typedef struct - { - DWORD freq; // default playback rate - DWORD chans; // channels - DWORD flags; // BASS_SAMPLE/STREAM/MUSIC/SPEAKER flags - DWORD ctype; // type of channel - DWORD origres; // original resolution - HPLUGIN plugin; // plugin - HSAMPLE sample; // sample - const char *filename; // filename - } BASS_CHANNELINFO; - -// BASS_CHANNELINFO types -#define BASS_CTYPE_SAMPLE 1 -#define BASS_CTYPE_RECORD 2 -#define BASS_CTYPE_STREAM 0x10000 -#define BASS_CTYPE_STREAM_OGG 0x10002 -#define BASS_CTYPE_STREAM_MP1 0x10003 -#define BASS_CTYPE_STREAM_MP2 0x10004 -#define BASS_CTYPE_STREAM_MP3 0x10005 -#define BASS_CTYPE_STREAM_AIFF 0x10006 -#define BASS_CTYPE_STREAM_CA 0x10007 -#define BASS_CTYPE_STREAM_MF 0x10008 -#define BASS_CTYPE_STREAM_WAV 0x40000 // WAVE flag, LOWORD=codec -#define BASS_CTYPE_STREAM_WAV_PCM 0x50001 -#define BASS_CTYPE_STREAM_WAV_FLOAT 0x50003 -#define BASS_CTYPE_MUSIC_MOD 0x20000 -#define BASS_CTYPE_MUSIC_MTM 0x20001 -#define BASS_CTYPE_MUSIC_S3M 0x20002 -#define BASS_CTYPE_MUSIC_XM 0x20003 -#define BASS_CTYPE_MUSIC_IT 0x20004 -#define BASS_CTYPE_MUSIC_MO3 0x00100 // MO3 flag - - typedef struct - { - DWORD ctype; // channel type -#if defined(_WIN32_WCE) || \ - (WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - const wchar_t *name; // format description - const wchar_t *exts; // file extension filter (*.ext1;*.ext2;etc...) -#else - const char *name; // format description - const char *exts; // file extension filter (*.ext1;*.ext2;etc...) -#endif - } BASS_PLUGINFORM; - - typedef struct - { - DWORD version; // version (same form as BASS_GetVersion) - DWORD formatc; // number of formats - const BASS_PLUGINFORM *formats; // the array of formats - } BASS_PLUGININFO; - - // 3D vector (for 3D positions/velocities/orientations) - typedef struct BASS_3DVECTOR - { -#ifdef __cplusplus - BASS_3DVECTOR(){}; - BASS_3DVECTOR(float _x, float _y, float _z) : x(_x), y(_y), z(_z){}; -#endif - float x; // +=right, -=left - float y; // +=up, -=down - float z; // +=front, -=behind - } BASS_3DVECTOR; - -// 3D channel modes -#define BASS_3DMODE_NORMAL 0 // normal 3D processing -#define BASS_3DMODE_RELATIVE 1 // position is relative to the listener -#define BASS_3DMODE_OFF 2 // no 3D processing - -// software 3D mixing algorithms (used with BASS_CONFIG_3DALGORITHM) -#define BASS_3DALG_DEFAULT 0 -#define BASS_3DALG_OFF 1 -#define BASS_3DALG_FULL 2 -#define BASS_3DALG_LIGHT 3 - - // EAX environments, use with BASS_SetEAXParameters - enum - { - EAX_ENVIRONMENT_GENERIC, - EAX_ENVIRONMENT_PADDEDCELL, - EAX_ENVIRONMENT_ROOM, - EAX_ENVIRONMENT_BATHROOM, - EAX_ENVIRONMENT_LIVINGROOM, - EAX_ENVIRONMENT_STONEROOM, - EAX_ENVIRONMENT_AUDITORIUM, - EAX_ENVIRONMENT_CONCERTHALL, - EAX_ENVIRONMENT_CAVE, - EAX_ENVIRONMENT_ARENA, - EAX_ENVIRONMENT_HANGAR, - EAX_ENVIRONMENT_CARPETEDHALLWAY, - EAX_ENVIRONMENT_HALLWAY, - EAX_ENVIRONMENT_STONECORRIDOR, - EAX_ENVIRONMENT_ALLEY, - EAX_ENVIRONMENT_FOREST, - EAX_ENVIRONMENT_CITY, - EAX_ENVIRONMENT_MOUNTAINS, - EAX_ENVIRONMENT_QUARRY, - EAX_ENVIRONMENT_PLAIN, - EAX_ENVIRONMENT_PARKINGLOT, - EAX_ENVIRONMENT_SEWERPIPE, - EAX_ENVIRONMENT_UNDERWATER, - EAX_ENVIRONMENT_DRUGGED, - EAX_ENVIRONMENT_DIZZY, - EAX_ENVIRONMENT_PSYCHOTIC, - - EAX_ENVIRONMENT_COUNT // total number of environments - }; - -// EAX presets, usage: BASS_SetEAXParameters(EAX_PRESET_xxx) -#define EAX_PRESET_GENERIC EAX_ENVIRONMENT_GENERIC, 0.5F, 1.493F, 0.5F -#define EAX_PRESET_PADDEDCELL EAX_ENVIRONMENT_PADDEDCELL, 0.25F, 0.1F, 0.0F -#define EAX_PRESET_ROOM EAX_ENVIRONMENT_ROOM, 0.417F, 0.4F, 0.666F -#define EAX_PRESET_BATHROOM EAX_ENVIRONMENT_BATHROOM, 0.653F, 1.499F, 0.166F -#define EAX_PRESET_LIVINGROOM EAX_ENVIRONMENT_LIVINGROOM, 0.208F, 0.478F, 0.0F -#define EAX_PRESET_STONEROOM EAX_ENVIRONMENT_STONEROOM, 0.5F, 2.309F, 0.888F -#define EAX_PRESET_AUDITORIUM EAX_ENVIRONMENT_AUDITORIUM, 0.403F, 4.279F, 0.5F -#define EAX_PRESET_CONCERTHALL EAX_ENVIRONMENT_CONCERTHALL, 0.5F, 3.961F, 0.5F -#define EAX_PRESET_CAVE EAX_ENVIRONMENT_CAVE, 0.5F, 2.886F, 1.304F -#define EAX_PRESET_ARENA EAX_ENVIRONMENT_ARENA, 0.361F, 7.284F, 0.332F -#define EAX_PRESET_HANGAR EAX_ENVIRONMENT_HANGAR, 0.5F, 10.0F, 0.3F -#define EAX_PRESET_CARPETEDHALLWAY \ - EAX_ENVIRONMENT_CARPETEDHALLWAY, 0.153F, 0.259F, 2.0F -#define EAX_PRESET_HALLWAY EAX_ENVIRONMENT_HALLWAY, 0.361F, 1.493F, 0.0F -#define EAX_PRESET_STONECORRIDOR \ - EAX_ENVIRONMENT_STONECORRIDOR, 0.444F, 2.697F, 0.638F -#define EAX_PRESET_ALLEY EAX_ENVIRONMENT_ALLEY, 0.25F, 1.752F, 0.776F -#define EAX_PRESET_FOREST EAX_ENVIRONMENT_FOREST, 0.111F, 3.145F, 0.472F -#define EAX_PRESET_CITY EAX_ENVIRONMENT_CITY, 0.111F, 2.767F, 0.224F -#define EAX_PRESET_MOUNTAINS EAX_ENVIRONMENT_MOUNTAINS, 0.194F, 7.841F, 0.472F -#define EAX_PRESET_QUARRY EAX_ENVIRONMENT_QUARRY, 1.0F, 1.499F, 0.5F -#define EAX_PRESET_PLAIN EAX_ENVIRONMENT_PLAIN, 0.097F, 2.767F, 0.224F -#define EAX_PRESET_PARKINGLOT EAX_ENVIRONMENT_PARKINGLOT, 0.208F, 1.652F, 1.5F -#define EAX_PRESET_SEWERPIPE EAX_ENVIRONMENT_SEWERPIPE, 0.652F, 2.886F, 0.25F -#define EAX_PRESET_UNDERWATER EAX_ENVIRONMENT_UNDERWATER, 1.0F, 1.499F, 0.0F -#define EAX_PRESET_DRUGGED EAX_ENVIRONMENT_DRUGGED, 0.875F, 8.392F, 1.388F -#define EAX_PRESET_DIZZY EAX_ENVIRONMENT_DIZZY, 0.139F, 17.234F, 0.666F -#define EAX_PRESET_PSYCHOTIC EAX_ENVIRONMENT_PSYCHOTIC, 0.486F, 7.563F, 0.806F - - typedef DWORD(CALLBACK STREAMPROC)(HSTREAM handle, void *buffer, DWORD length, - void *user); - /* User stream callback function. NOTE: A stream function should obviously be - as quick as possible, other streams (and MOD musics) can't be mixed until it's - finished. handle : The stream that needs writing buffer : Buffer to write the - samples in length : Number of bytes to write user : The 'user' parameter - value given when calling BASS_StreamCreate RETURN : Number of bytes written. - Set the BASS_STREAMPROC_END flag to end the stream. */ - -#define BASS_STREAMPROC_END 0x80000000 // end of user stream flag - -// special STREAMPROCs -#define STREAMPROC_DUMMY (STREAMPROC *)0 // "dummy" stream -#define STREAMPROC_PUSH (STREAMPROC *)-1 // push stream - -// BASS_StreamCreateFileUser file systems -#define STREAMFILE_NOBUFFER 0 -#define STREAMFILE_BUFFER 1 -#define STREAMFILE_BUFFERPUSH 2 - - // User file stream callback functions - typedef void(CALLBACK FILECLOSEPROC)(void *user); - typedef QWORD(CALLBACK FILELENPROC)(void *user); - typedef DWORD(CALLBACK FILEREADPROC)(void *buffer, DWORD length, void *user); - typedef BOOL(CALLBACK FILESEEKPROC)(QWORD offset, void *user); - - typedef struct - { - FILECLOSEPROC *close; - FILELENPROC *length; - FILEREADPROC *read; - FILESEEKPROC *seek; - } BASS_FILEPROCS; - -// BASS_StreamPutFileData options -#define BASS_FILEDATA_END 0 // end & close the file - -// BASS_StreamGetFilePosition modes -#define BASS_FILEPOS_CURRENT 0 -#define BASS_FILEPOS_DECODE BASS_FILEPOS_CURRENT -#define BASS_FILEPOS_DOWNLOAD 1 -#define BASS_FILEPOS_END 2 -#define BASS_FILEPOS_START 3 -#define BASS_FILEPOS_CONNECTED 4 -#define BASS_FILEPOS_BUFFER 5 -#define BASS_FILEPOS_SOCKET 6 -#define BASS_FILEPOS_ASYNCBUF 7 -#define BASS_FILEPOS_SIZE 8 - - typedef void(CALLBACK DOWNLOADPROC)(const void *buffer, DWORD length, - void *user); -/* Internet stream download callback function. -buffer : Buffer containing the downloaded data... NULL=end of download -length : Number of bytes in the buffer -user : The 'user' parameter value given when calling BASS_StreamCreateURL */ - -// BASS_ChannelSetSync types -#define BASS_SYNC_POS 0 -#define BASS_SYNC_END 2 -#define BASS_SYNC_META 4 -#define BASS_SYNC_SLIDE 5 -#define BASS_SYNC_STALL 6 -#define BASS_SYNC_DOWNLOAD 7 -#define BASS_SYNC_FREE 8 -#define BASS_SYNC_SETPOS 11 -#define BASS_SYNC_MUSICPOS 10 -#define BASS_SYNC_MUSICINST 1 -#define BASS_SYNC_MUSICFX 3 -#define BASS_SYNC_OGG_CHANGE 12 -#define BASS_SYNC_MIXTIME 0x40000000 // flag: sync at mixtime, else at playtime -#define BASS_SYNC_ONETIME 0x80000000 // flag: sync only once, else continuously - - typedef void(CALLBACK SYNCPROC)(HSYNC handle, DWORD channel, DWORD data, - void *user); - /* Sync callback function. NOTE: a sync callback function should be very - quick as other syncs can't be processed until it has finished. If the sync - is a "mixtime" sync, then other streams and MOD musics can't be mixed until - it's finished either. - handle : The sync that has occured - channel: Channel that the sync occured in - data : Additional data associated with the sync's occurance - user : The 'user' parameter given when calling BASS_ChannelSetSync */ - - typedef void(CALLBACK DSPPROC)(HDSP handle, DWORD channel, void *buffer, - DWORD length, void *user); - /* DSP callback function. NOTE: A DSP function should obviously be as quick as - possible... other DSP functions, streams and MOD musics can not be processed - until it's finished. - handle : The DSP handle - channel: Channel that the DSP is being applied to - buffer : Buffer to apply the DSP to - length : Number of bytes in the buffer - user : The 'user' parameter given when calling BASS_ChannelSetDSP */ - - typedef BOOL(CALLBACK RECORDPROC)(HRECORD handle, const void *buffer, - DWORD length, void *user); -/* Recording callback function. -handle : The recording handle -buffer : Buffer containing the recorded sample data -length : Number of bytes -user : The 'user' parameter value given when calling BASS_RecordStart -RETURN : TRUE = continue recording, FALSE = stop */ - -// BASS_ChannelIsActive return values -#define BASS_ACTIVE_STOPPED 0 -#define BASS_ACTIVE_PLAYING 1 -#define BASS_ACTIVE_STALLED 2 -#define BASS_ACTIVE_PAUSED 3 - -// Channel attributes -#define BASS_ATTRIB_FREQ 1 -#define BASS_ATTRIB_VOL 2 -#define BASS_ATTRIB_PAN 3 -#define BASS_ATTRIB_EAXMIX 4 -#define BASS_ATTRIB_NOBUFFER 5 -#define BASS_ATTRIB_VBR 6 -#define BASS_ATTRIB_CPU 7 -#define BASS_ATTRIB_SRC 8 -#define BASS_ATTRIB_NET_RESUME 9 -#define BASS_ATTRIB_SCANINFO 10 -#define BASS_ATTRIB_NORAMP 11 -#define BASS_ATTRIB_BITRATE 12 -#define BASS_ATTRIB_MUSIC_AMPLIFY 0x100 -#define BASS_ATTRIB_MUSIC_PANSEP 0x101 -#define BASS_ATTRIB_MUSIC_PSCALER 0x102 -#define BASS_ATTRIB_MUSIC_BPM 0x103 -#define BASS_ATTRIB_MUSIC_SPEED 0x104 -#define BASS_ATTRIB_MUSIC_VOL_GLOBAL 0x105 -#define BASS_ATTRIB_MUSIC_ACTIVE 0x106 -#define BASS_ATTRIB_MUSIC_VOL_CHAN 0x200 // + channel # -#define BASS_ATTRIB_MUSIC_VOL_INST 0x300 // + instrument # - -// BASS_ChannelGetData flags -#define BASS_DATA_AVAILABLE 0 // query how much data is buffered -#define BASS_DATA_FIXED 0x20000000 // flag: return 8.24 fixed-point data -#define BASS_DATA_FLOAT 0x40000000 // flag: return floating-point sample data -#define BASS_DATA_FFT256 0x80000000 // 256 sample FFT -#define BASS_DATA_FFT512 0x80000001 // 512 FFT -#define BASS_DATA_FFT1024 0x80000002 // 1024 FFT -#define BASS_DATA_FFT2048 0x80000003 // 2048 FFT -#define BASS_DATA_FFT4096 0x80000004 // 4096 FFT -#define BASS_DATA_FFT8192 0x80000005 // 8192 FFT -#define BASS_DATA_FFT16384 0x80000006 // 16384 FFT -#define BASS_DATA_FFT32768 0x80000007 // 32768 FFT -#define BASS_DATA_FFT_INDIVIDUAL \ - 0x10 // FFT flag: FFT for each channel, else all combined -#define BASS_DATA_FFT_NOWINDOW 0x20 // FFT flag: no Hanning window -#define BASS_DATA_FFT_REMOVEDC 0x40 // FFT flag: pre-remove DC bias -#define BASS_DATA_FFT_COMPLEX 0x80 // FFT flag: return complex data - -// BASS_ChannelGetLevelEx flags -#define BASS_LEVEL_MONO 1 -#define BASS_LEVEL_STEREO 2 -#define BASS_LEVEL_RMS 4 - -// BASS_ChannelGetTags types : what's returned -#define BASS_TAG_ID3 0 // ID3v1 tags : TAG_ID3 structure -#define BASS_TAG_ID3V2 1 // ID3v2 tags : variable length block -#define BASS_TAG_OGG 2 // OGG comments : series of null-terminated UTF-8 strings -#define BASS_TAG_HTTP 3 // HTTP headers : series of null-terminated ANSI strings -#define BASS_TAG_ICY 4 // ICY headers : series of null-terminated ANSI strings -#define BASS_TAG_META 5 // ICY metadata : ANSI string -#define BASS_TAG_APE 6 // APE tags : series of null-terminated UTF-8 strings -#define BASS_TAG_MP4 \ - 7 // MP4/iTunes metadata : series of null-terminated UTF-8 strings -#define BASS_TAG_WMA 8 // WMA tags : series of null-terminated UTF-8 strings -#define BASS_TAG_VENDOR 9 // OGG encoder : UTF-8 string -#define BASS_TAG_LYRICS3 10 // Lyric3v2 tag : ASCII string -#define BASS_TAG_CA_CODEC 11 // CoreAudio codec info : TAG_CA_CODEC structure -#define BASS_TAG_MF \ - 13 // Media Foundation tags : series of null-terminated UTF-8 strings -#define BASS_TAG_WAVEFORMAT 14 // WAVE format : WAVEFORMATEEX structure -#define BASS_TAG_RIFF_INFO \ - 0x100 // RIFF "INFO" tags : series of null-terminated ANSI strings -#define BASS_TAG_RIFF_BEXT 0x101 // RIFF/BWF "bext" tags : TAG_BEXT structure -#define BASS_TAG_RIFF_CART 0x102 // RIFF/BWF "cart" tags : TAG_CART structure -#define BASS_TAG_RIFF_DISP 0x103 // RIFF "DISP" text tag : ANSI string -#define BASS_TAG_APE_BINARY \ - 0x1000 // + index #, binary APE tag : TAG_APE_BINARY structure -#define BASS_TAG_MUSIC_NAME 0x10000 // MOD music name : ANSI string -#define BASS_TAG_MUSIC_MESSAGE 0x10001 // MOD message : ANSI string -#define BASS_TAG_MUSIC_ORDERS \ - 0x10002 // MOD order list : BYTE array of pattern numbers -#define BASS_TAG_MUSIC_AUTH 0x10003 // MOD author : UTF-8 string -#define BASS_TAG_MUSIC_INST \ - 0x10100 // + instrument #, MOD instrument name : ANSI string -#define BASS_TAG_MUSIC_SAMPLE \ - 0x10300 // + sample #, MOD sample name : ANSI string - - // ID3v1 tag structure - typedef struct - { - char id[3]; - char title[30]; - char artist[30]; - char album[30]; - char year[4]; - char comment[30]; - BYTE genre; - } TAG_ID3; - - // Binary APE tag structure - typedef struct - { - const char *key; - const void *data; - DWORD length; - } TAG_APE_BINARY; - -// BWF "bext" tag structure -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4200) -#endif -#pragma pack(push, 1) - typedef struct - { - char Description[256]; // description - char Originator[32]; // name of the originator - char OriginatorReference[32]; // reference of the originator - char OriginationDate[10]; // date of creation (yyyy-mm-dd) - char OriginationTime[8]; // time of creation (hh-mm-ss) - QWORD TimeReference; // first sample count since midnight (little-endian) - WORD Version; // BWF version (little-endian) - BYTE UMID[64]; // SMPTE UMID - BYTE Reserved[190]; -#if defined(__GNUC__) && __GNUC__ < 3 - char CodingHistory[0]; // history -#elif 1 // change to 0 if compiler fails the following line - char CodingHistory[]; // history -#else - char CodingHistory[1]; // history -#endif - } TAG_BEXT; -#pragma pack(pop) - - // BWF "cart" tag structures - typedef struct - { - DWORD dwUsage; // FOURCC timer usage ID - DWORD dwValue; // timer value in samples from head - } TAG_CART_TIMER; - - typedef struct - { - char Version[4]; // version of the data structure - char Title[64]; // title of cart audio sequence - char Artist[64]; // artist or creator name - char CutID[64]; // cut number identification - char ClientID[64]; // client identification - char Category[64]; // category ID, PSA, NEWS, etc - char Classification[64]; // classification or auxiliary key - char OutCue[64]; // out cue text - char StartDate[10]; // yyyy-mm-dd - char StartTime[8]; // hh:mm:ss - char EndDate[10]; // yyyy-mm-dd - char EndTime[8]; // hh:mm:ss - char ProducerAppID[64]; // name of vendor or application - char ProducerAppVersion[64]; // version of producer application - char UserDef[64]; // user defined text - DWORD dwLevelReference; // sample value for 0 dB reference - TAG_CART_TIMER PostTimer[8]; // 8 time markers after head - char Reserved[276]; - char URL[1024]; // uniform resource locator -#if defined(__GNUC__) && __GNUC__ < 3 - char TagText[0]; // free form text for scripts or tags -#elif 1 // change to 0 if compiler fails the following line - char TagText[]; // free form text for scripts or tags -#else - char TagText[1]; // free form text for scripts or tags -#endif - } TAG_CART; -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - // CoreAudio codec info structure - typedef struct - { - DWORD ftype; // file format - DWORD atype; // audio format - const char *name; // description - } TAG_CA_CODEC; - -#ifndef _WAVEFORMATEX_ -#define _WAVEFORMATEX_ -#pragma pack(push, 1) - typedef struct tWAVEFORMATEX - { - WORD wFormatTag; - WORD nChannels; - DWORD nSamplesPerSec; - DWORD nAvgBytesPerSec; - WORD nBlockAlign; - WORD wBitsPerSample; - WORD cbSize; - } WAVEFORMATEX, *PWAVEFORMATEX, *LPWAVEFORMATEX; - typedef const WAVEFORMATEX *LPCWAVEFORMATEX; -#pragma pack(pop) -#endif - -// BASS_ChannelGetLength/GetPosition/SetPosition modes -#define BASS_POS_BYTE 0 // byte position -#define BASS_POS_MUSIC_ORDER 1 // order.row position, MAKELONG(order,row) -#define BASS_POS_OGG 3 // OGG bitstream number -#define BASS_POS_INEXACT 0x8000000 // flag: allow seeking to inexact position -#define BASS_POS_DECODE \ - 0x10000000 // flag: get the decoding (not playing) position -#define BASS_POS_DECODETO \ - 0x20000000 // flag: decode to the position instead of seeking -#define BASS_POS_SCAN 0x40000000 // flag: scan to the position - -// BASS_RecordSetInput flags -#define BASS_INPUT_OFF 0x10000 -#define BASS_INPUT_ON 0x20000 - -#define BASS_INPUT_TYPE_MASK 0xff000000 -#define BASS_INPUT_TYPE_UNDEF 0x00000000 -#define BASS_INPUT_TYPE_DIGITAL 0x01000000 -#define BASS_INPUT_TYPE_LINE 0x02000000 -#define BASS_INPUT_TYPE_MIC 0x03000000 -#define BASS_INPUT_TYPE_SYNTH 0x04000000 -#define BASS_INPUT_TYPE_CD 0x05000000 -#define BASS_INPUT_TYPE_PHONE 0x06000000 -#define BASS_INPUT_TYPE_SPEAKER 0x07000000 -#define BASS_INPUT_TYPE_WAVE 0x08000000 -#define BASS_INPUT_TYPE_AUX 0x09000000 -#define BASS_INPUT_TYPE_ANALOG 0x0a000000 - - // DX8 effect types, use with BASS_ChannelSetFX - enum - { - BASS_FX_DX8_CHORUS, - BASS_FX_DX8_COMPRESSOR, - BASS_FX_DX8_DISTORTION, - BASS_FX_DX8_ECHO, - BASS_FX_DX8_FLANGER, - BASS_FX_DX8_GARGLE, - BASS_FX_DX8_I3DL2REVERB, - BASS_FX_DX8_PARAMEQ, - BASS_FX_DX8_REVERB - }; - - typedef struct - { - float fWetDryMix; - float fDepth; - float fFeedback; - float fFrequency; - DWORD lWaveform; // 0=triangle, 1=sine - float fDelay; - DWORD lPhase; // BASS_DX8_PHASE_xxx - } BASS_DX8_CHORUS; - - typedef struct - { - float fGain; - float fAttack; - float fRelease; - float fThreshold; - float fRatio; - float fPredelay; - } BASS_DX8_COMPRESSOR; - - typedef struct - { - float fGain; - float fEdge; - float fPostEQCenterFrequency; - float fPostEQBandwidth; - float fPreLowpassCutoff; - } BASS_DX8_DISTORTION; - - typedef struct - { - float fWetDryMix; - float fFeedback; - float fLeftDelay; - float fRightDelay; - BOOL lPanDelay; - } BASS_DX8_ECHO; - - typedef struct - { - float fWetDryMix; - float fDepth; - float fFeedback; - float fFrequency; - DWORD lWaveform; // 0=triangle, 1=sine - float fDelay; - DWORD lPhase; // BASS_DX8_PHASE_xxx - } BASS_DX8_FLANGER; - - typedef struct - { - DWORD dwRateHz; // Rate of modulation in hz - DWORD dwWaveShape; // 0=triangle, 1=square - } BASS_DX8_GARGLE; - - typedef struct - { - int lRoom; // [-10000, 0] default: -1000 mB - int lRoomHF; // [-10000, 0] default: 0 mB - float flRoomRolloffFactor; // [0.0, 10.0] default: 0.0 - float flDecayTime; // [0.1, 20.0] default: 1.49s - float flDecayHFRatio; // [0.1, 2.0] default: 0.83 - int lReflections; // [-10000, 1000] default: -2602 mB - float flReflectionsDelay; // [0.0, 0.3] default: 0.007 s - int lReverb; // [-10000, 2000] default: 200 mB - float flReverbDelay; // [0.0, 0.1] default: 0.011 s - float flDiffusion; // [0.0, 100.0] default: 100.0 % - float flDensity; // [0.0, 100.0] default: 100.0 % - float flHFReference; // [20.0, 20000.0] default: 5000.0 Hz - } BASS_DX8_I3DL2REVERB; - - typedef struct - { - float fCenter; - float fBandwidth; - float fGain; - } BASS_DX8_PARAMEQ; - - typedef struct - { - float fInGain; // [-96.0,0.0] default: 0.0 dB - float fReverbMix; // [-96.0,0.0] default: 0.0 db - float fReverbTime; // [0.001,3000.0] default: 1000.0 ms - float fHighFreqRTRatio; // [0.001,0.999] default: 0.001 - } BASS_DX8_REVERB; - -#define BASS_DX8_PHASE_NEG_180 0 -#define BASS_DX8_PHASE_NEG_90 1 -#define BASS_DX8_PHASE_ZERO 2 -#define BASS_DX8_PHASE_90 3 -#define BASS_DX8_PHASE_180 4 - - typedef void(CALLBACK IOSNOTIFYPROC)(DWORD status); - /* iOS notification callback function. - status : The notification (BASS_IOSNOTIFY_xxx) */ - -#define BASS_IOSNOTIFY_INTERRUPT 1 // interruption started -#define BASS_IOSNOTIFY_INTERRUPT_END 2 // interruption ended - - BOOL BASSDEF(BASS_SetConfig)(DWORD option, DWORD value); - DWORD BASSDEF(BASS_GetConfig)(DWORD option); - BOOL BASSDEF(BASS_SetConfigPtr)(DWORD option, const void *value); - void *BASSDEF(BASS_GetConfigPtr)(DWORD option); - DWORD BASSDEF(BASS_GetVersion)(); - int BASSDEF(BASS_ErrorGetCode)(); - BOOL BASSDEF(BASS_GetDeviceInfo)(DWORD device, BASS_DEVICEINFO *info); -#if defined(_WIN32) && !defined(_WIN32_WCE) && \ - !(WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, HWND win, - const GUID *dsguid); -#else -BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, void *win, - void *dsguid); -#endif - BOOL BASSDEF(BASS_SetDevice)(DWORD device); - DWORD BASSDEF(BASS_GetDevice)(); - BOOL BASSDEF(BASS_Free)(); -#if defined(_WIN32) && !defined(_WIN32_WCE) && \ - !(WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - void *BASSDEF(BASS_GetDSoundObject)(DWORD object); -#endif - BOOL BASSDEF(BASS_GetInfo)(BASS_INFO *info); - BOOL BASSDEF(BASS_Update)(DWORD length); - float BASSDEF(BASS_GetCPU)(); - BOOL BASSDEF(BASS_Start)(); - BOOL BASSDEF(BASS_Stop)(); - BOOL BASSDEF(BASS_Pause)(); - BOOL BASSDEF(BASS_SetVolume)(float volume); - float BASSDEF(BASS_GetVolume)(); - - HPLUGIN BASSDEF(BASS_PluginLoad)(const char *file, DWORD flags); - BOOL BASSDEF(BASS_PluginFree)(HPLUGIN handle); - const BASS_PLUGININFO *BASSDEF(BASS_PluginGetInfo)(HPLUGIN handle); - - BOOL BASSDEF(BASS_Set3DFactors)(float distf, float rollf, float doppf); - BOOL BASSDEF(BASS_Get3DFactors)(float *distf, float *rollf, float *doppf); - BOOL BASSDEF(BASS_Set3DPosition)(const BASS_3DVECTOR *pos, - const BASS_3DVECTOR *vel, - const BASS_3DVECTOR *front, - const BASS_3DVECTOR *top); - BOOL BASSDEF(BASS_Get3DPosition)(BASS_3DVECTOR *pos, BASS_3DVECTOR *vel, - BASS_3DVECTOR *front, BASS_3DVECTOR *top); - void BASSDEF(BASS_Apply3D)(); -#if defined(_WIN32) && !defined(_WIN32_WCE) && \ - !(WINAPI_FAMILY && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - BOOL BASSDEF(BASS_SetEAXParameters)(int env, float vol, float decay, - float damp); - BOOL BASSDEF(BASS_GetEAXParameters)(DWORD *env, float *vol, float *decay, - float *damp); -#endif - - HMUSIC BASSDEF(BASS_MusicLoad)(BOOL mem, const void *file, QWORD offset, - DWORD length, DWORD flags, DWORD freq); - BOOL BASSDEF(BASS_MusicFree)(HMUSIC handle); - - HSAMPLE BASSDEF(BASS_SampleLoad)(BOOL mem, const void *file, QWORD offset, - DWORD length, DWORD max, DWORD flags); - HSAMPLE BASSDEF(BASS_SampleCreate)(DWORD length, DWORD freq, DWORD chans, - DWORD max, DWORD flags); - BOOL BASSDEF(BASS_SampleFree)(HSAMPLE handle); - BOOL BASSDEF(BASS_SampleSetData)(HSAMPLE handle, const void *buffer); - BOOL BASSDEF(BASS_SampleGetData)(HSAMPLE handle, void *buffer); - BOOL BASSDEF(BASS_SampleGetInfo)(HSAMPLE handle, BASS_SAMPLE *info); - BOOL BASSDEF(BASS_SampleSetInfo)(HSAMPLE handle, const BASS_SAMPLE *info); - HCHANNEL BASSDEF(BASS_SampleGetChannel)(HSAMPLE handle, BOOL onlynew); - DWORD BASSDEF(BASS_SampleGetChannels)(HSAMPLE handle, HCHANNEL *channels); - BOOL BASSDEF(BASS_SampleStop)(HSAMPLE handle); - - HSTREAM BASSDEF(BASS_StreamCreate)(DWORD freq, DWORD chans, DWORD flags, - STREAMPROC *proc, void *user); - HSTREAM BASSDEF(BASS_StreamCreateFile)(BOOL mem, const void *file, - QWORD offset, QWORD length, - DWORD flags); - HSTREAM BASSDEF(BASS_StreamCreateURL)(const char *url, DWORD offset, - DWORD flags, DOWNLOADPROC *proc, - void *user); - HSTREAM BASSDEF(BASS_StreamCreateFileUser)(DWORD system, DWORD flags, - const BASS_FILEPROCS *proc, - void *user); - BOOL BASSDEF(BASS_StreamFree)(HSTREAM handle); - QWORD BASSDEF(BASS_StreamGetFilePosition)(HSTREAM handle, DWORD mode); - DWORD BASSDEF(BASS_StreamPutData)(HSTREAM handle, const void *buffer, - DWORD length); - DWORD BASSDEF(BASS_StreamPutFileData)(HSTREAM handle, const void *buffer, - DWORD length); - - BOOL BASSDEF(BASS_RecordGetDeviceInfo)(DWORD device, BASS_DEVICEINFO *info); - BOOL BASSDEF(BASS_RecordInit)(int device); - BOOL BASSDEF(BASS_RecordSetDevice)(DWORD device); - DWORD BASSDEF(BASS_RecordGetDevice)(); - BOOL BASSDEF(BASS_RecordFree)(); - BOOL BASSDEF(BASS_RecordGetInfo)(BASS_RECORDINFO *info); - const char *BASSDEF(BASS_RecordGetInputName)(int input); - BOOL BASSDEF(BASS_RecordSetInput)(int input, DWORD flags, float volume); - DWORD BASSDEF(BASS_RecordGetInput)(int input, float *volume); - HRECORD BASSDEF(BASS_RecordStart)(DWORD freq, DWORD chans, DWORD flags, - RECORDPROC *proc, void *user); - - double BASSDEF(BASS_ChannelBytes2Seconds)(DWORD handle, QWORD pos); - QWORD BASSDEF(BASS_ChannelSeconds2Bytes)(DWORD handle, double pos); - DWORD BASSDEF(BASS_ChannelGetDevice)(DWORD handle); - BOOL BASSDEF(BASS_ChannelSetDevice)(DWORD handle, DWORD device); - DWORD BASSDEF(BASS_ChannelIsActive)(DWORD handle); - BOOL BASSDEF(BASS_ChannelGetInfo)(DWORD handle, BASS_CHANNELINFO *info); - const char *BASSDEF(BASS_ChannelGetTags)(DWORD handle, DWORD tags); - DWORD BASSDEF(BASS_ChannelFlags)(DWORD handle, DWORD flags, DWORD mask); - BOOL BASSDEF(BASS_ChannelUpdate)(DWORD handle, DWORD length); - BOOL BASSDEF(BASS_ChannelLock)(DWORD handle, BOOL lock); - BOOL BASSDEF(BASS_ChannelPlay)(DWORD handle, BOOL restart); - BOOL BASSDEF(BASS_ChannelStop)(DWORD handle); - BOOL BASSDEF(BASS_ChannelPause)(DWORD handle); - BOOL BASSDEF(BASS_ChannelSetAttribute)(DWORD handle, DWORD attrib, - float value); - BOOL BASSDEF(BASS_ChannelGetAttribute)(DWORD handle, DWORD attrib, - float *value); - BOOL BASSDEF(BASS_ChannelSlideAttribute)(DWORD handle, DWORD attrib, - float value, DWORD time); - BOOL BASSDEF(BASS_ChannelIsSliding)(DWORD handle, DWORD attrib); - BOOL BASSDEF(BASS_ChannelSetAttributeEx)(DWORD handle, DWORD attrib, - void *value, DWORD size); - DWORD BASSDEF(BASS_ChannelGetAttributeEx)(DWORD handle, DWORD attrib, - void *value, DWORD size); - BOOL BASSDEF(BASS_ChannelSet3DAttributes)(DWORD handle, int mode, float min, - float max, int iangle, int oangle, - float outvol); - BOOL BASSDEF(BASS_ChannelGet3DAttributes)(DWORD handle, DWORD *mode, - float *min, float *max, - DWORD *iangle, DWORD *oangle, - float *outvol); - BOOL BASSDEF(BASS_ChannelSet3DPosition)(DWORD handle, - const BASS_3DVECTOR *pos, - const BASS_3DVECTOR *orient, - const BASS_3DVECTOR *vel); - BOOL BASSDEF(BASS_ChannelGet3DPosition)(DWORD handle, BASS_3DVECTOR *pos, - BASS_3DVECTOR *orient, - BASS_3DVECTOR *vel); - QWORD BASSDEF(BASS_ChannelGetLength)(DWORD handle, DWORD mode); - BOOL BASSDEF(BASS_ChannelSetPosition)(DWORD handle, QWORD pos, DWORD mode); - QWORD BASSDEF(BASS_ChannelGetPosition)(DWORD handle, DWORD mode); - DWORD BASSDEF(BASS_ChannelGetLevel)(DWORD handle); - BOOL BASSDEF(BASS_ChannelGetLevelEx)(DWORD handle, float *levels, - float length, DWORD flags); - DWORD BASSDEF(BASS_ChannelGetData)(DWORD handle, void *buffer, DWORD length); - HSYNC BASSDEF(BASS_ChannelSetSync)(DWORD handle, DWORD type, QWORD param, - SYNCPROC *proc, void *user); - BOOL BASSDEF(BASS_ChannelRemoveSync)(DWORD handle, HSYNC sync); - HDSP BASSDEF(BASS_ChannelSetDSP)(DWORD handle, DSPPROC *proc, void *user, - int priority); - BOOL BASSDEF(BASS_ChannelRemoveDSP)(DWORD handle, HDSP dsp); - BOOL BASSDEF(BASS_ChannelSetLink)(DWORD handle, DWORD chan); - BOOL BASSDEF(BASS_ChannelRemoveLink)(DWORD handle, DWORD chan); - HFX BASSDEF(BASS_ChannelSetFX)(DWORD handle, DWORD type, int priority); - BOOL BASSDEF(BASS_ChannelRemoveFX)(DWORD handle, HFX fx); - - BOOL BASSDEF(BASS_FXSetParameters)(HFX handle, const void *params); - BOOL BASSDEF(BASS_FXGetParameters)(HFX handle, void *params); - BOOL BASSDEF(BASS_FXReset)(HFX handle); - BOOL BASSDEF(BASS_FXSetPriority)(HFX handle, int priority); - -#ifdef __cplusplus -} - -#if defined(_WIN32) && !defined(NOBASSOVERLOADS) -static inline HPLUGIN BASS_PluginLoad(const WCHAR *file, DWORD flags) -{ - return BASS_PluginLoad((const char *)file, flags | BASS_UNICODE); -} - -static inline HMUSIC BASS_MusicLoad(BOOL mem, const WCHAR *file, QWORD offset, - DWORD length, DWORD flags, DWORD freq) -{ - return BASS_MusicLoad(mem, (const void *)file, offset, length, - flags | BASS_UNICODE, freq); -} - -static inline HSAMPLE BASS_SampleLoad(BOOL mem, const WCHAR *file, QWORD offset, - DWORD length, DWORD max, DWORD flags) -{ - return BASS_SampleLoad(mem, (const void *)file, offset, length, max, - flags | BASS_UNICODE); -} - -static inline HSTREAM BASS_StreamCreateFile(BOOL mem, const WCHAR *file, - QWORD offset, QWORD length, - DWORD flags) -{ - return BASS_StreamCreateFile(mem, (const void *)file, offset, length, - flags | BASS_UNICODE); -} - -static inline HSTREAM BASS_StreamCreateURL(const WCHAR *url, DWORD offset, - DWORD flags, DOWNLOADPROC *proc, - void *user) -{ - return BASS_StreamCreateURL((const char *)url, offset, flags | BASS_UNICODE, - proc, user); -} - -static inline BOOL BASS_SetConfigPtr(DWORD option, const WCHAR *value) -{ - return BASS_SetConfigPtr(option | BASS_UNICODE, (const void *)value); -} -#endif -#endif - -#endif diff --git a/discord-rpc.h b/discord-rpc.h deleted file mode 100644 index 6795d18aa..000000000 --- a/discord-rpc.h +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once -#include - -// clang-format off - -#if defined(DISCORD_DYNAMIC_LIB) -# if defined(_WIN32) -# if defined(DISCORD_BUILDING_SDK) -# define DISCORD_EXPORT __declspec(dllexport) -# else -# define DISCORD_EXPORT __declspec(dllimport) -# endif -# else -# define DISCORD_EXPORT __attribute__((visibility("default"))) -# endif -#else -# define DISCORD_EXPORT -#endif - -// clang-format on - -#ifdef __cplusplus -extern "C" -{ -#endif - - typedef struct DiscordRichPresence - { - const char *state; /* max 128 bytes */ - const char *details; /* max 128 bytes */ - int64_t startTimestamp; - int64_t endTimestamp; - const char *largeImageKey; /* max 32 bytes */ - const char *largeImageText; /* max 128 bytes */ - const char *smallImageKey; /* max 32 bytes */ - const char *smallImageText; /* max 128 bytes */ - const char *partyId; /* max 128 bytes */ - int partySize; - int partyMax; - const char *matchSecret; /* max 128 bytes */ - const char *joinSecret; /* max 128 bytes */ - const char *spectateSecret; /* max 128 bytes */ - int8_t instance; - } DiscordRichPresence; - - typedef struct DiscordJoinRequest - { - const char *userId; - const char *username; - const char *discriminator; - const char *avatar; - } DiscordJoinRequest; - - typedef struct DiscordEventHandlers - { - void (*ready)(void); - void (*disconnected)(int errorCode, const char *message); - void (*errored)(int errorCode, const char *message); - void (*joinGame)(const char *joinSecret); - void (*spectateGame)(const char *spectateSecret); - void (*joinRequest)(const DiscordJoinRequest *request); - } DiscordEventHandlers; - -#define DISCORD_REPLY_NO 0 -#define DISCORD_REPLY_YES 1 -#define DISCORD_REPLY_IGNORE 2 - - DISCORD_EXPORT void Discord_Initialize(const char *applicationId, - DiscordEventHandlers *handlers, - int autoRegister, - const char *optionalSteamId); - DISCORD_EXPORT void Discord_Shutdown(void); - - /* checks for incoming messages, dispatches callbacks */ - DISCORD_EXPORT void Discord_RunCallbacks(void); - -/* If you disable the lib starting its own io thread, you'll need to call this - * from your own */ -#ifdef DISCORD_DISABLE_IO_THREAD - DISCORD_EXPORT void Discord_UpdateConnection(void); -#endif - - DISCORD_EXPORT void - Discord_UpdatePresence(const DiscordRichPresence *presence); - DISCORD_EXPORT void Discord_ClearPresence(void); - - DISCORD_EXPORT void Discord_Respond(const char *userid, - /* DISCORD_REPLY_ */ int reply); - -#ifdef __cplusplus -} /* extern "C" */ -#endif diff --git a/discord_register.h b/discord_register.h deleted file mode 100644 index efc2adc8a..000000000 --- a/discord_register.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#if defined(DISCORD_DYNAMIC_LIB) -#if defined(_WIN32) -#if defined(DISCORD_BUILDING_SDK) -#define DISCORD_EXPORT __declspec(dllexport) -#else -#define DISCORD_EXPORT __declspec(dllimport) -#endif -#else -#define DISCORD_EXPORT __attribute__((visibility("default"))) -#endif -#else -#define DISCORD_EXPORT -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - - DISCORD_EXPORT void Discord_Register(const char *applicationId, - const char *command); - DISCORD_EXPORT void Discord_RegisterSteamGame(const char *applicationId, - const char *steamId); - -#ifdef __cplusplus -} -#endif diff --git a/discord_rpc.h b/discord_rpc.h deleted file mode 100644 index 8dcf72c1f..000000000 --- a/discord_rpc.h +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once -#include - -// clang-format off - -#if defined(DISCORD_DYNAMIC_LIB) -# if defined(_WIN32) -# if defined(DISCORD_BUILDING_SDK) -# define DISCORD_EXPORT __declspec(dllexport) -# else -# define DISCORD_EXPORT __declspec(dllimport) -# endif -# else -# define DISCORD_EXPORT __attribute__((visibility("default"))) -# endif -#else -# define DISCORD_EXPORT -#endif - -// clang-format on - -#ifdef __cplusplus -extern "C" -{ -#endif - - typedef struct DiscordRichPresence - { - const char *state; /* max 128 bytes */ - const char *details; /* max 128 bytes */ - int64_t startTimestamp; - int64_t endTimestamp; - const char *largeImageKey; /* max 32 bytes */ - const char *largeImageText; /* max 128 bytes */ - const char *smallImageKey; /* max 32 bytes */ - const char *smallImageText; /* max 128 bytes */ - const char *partyId; /* max 128 bytes */ - int partySize; - int partyMax; - const char *matchSecret; /* max 128 bytes */ - const char *joinSecret; /* max 128 bytes */ - const char *spectateSecret; /* max 128 bytes */ - int8_t instance; - } DiscordRichPresence; - - typedef struct DiscordJoinRequest - { - const char *userId; - const char *username; - const char *discriminator; - const char *avatar; - } DiscordJoinRequest; - - typedef struct DiscordEventHandlers - { - void (*ready)(void); - void (*disconnected)(int errorCode, const char *message); - void (*errored)(int errorCode, const char *message); - void (*joinGame)(const char *joinSecret); - void (*spectateGame)(const char *spectateSecret); - void (*joinRequest)(const DiscordJoinRequest *request); - } DiscordEventHandlers; - -#define DISCORD_REPLY_NO 0 -#define DISCORD_REPLY_YES 1 -#define DISCORD_REPLY_IGNORE 2 - - DISCORD_EXPORT void Discord_Initialize(const char *applicationId, - DiscordEventHandlers *handlers, - int autoRegister, - const char *optionalSteamId); - DISCORD_EXPORT void Discord_Shutdown(void); - - /* checks for incoming messages, dispatches callbacks */ - DISCORD_EXPORT void Discord_RunCallbacks(void); - -/* If you disable the lib starting its own io thread, you'll need to call this - * from your own */ -#ifdef DISCORD_DISABLE_IO_THREAD - DISCORD_EXPORT void Discord_UpdateConnection(void); -#endif - - DISCORD_EXPORT void - Discord_UpdatePresence(const DiscordRichPresence *presence); - DISCORD_EXPORT void Discord_ClearPresence(void); - - DISCORD_EXPORT void Discord_Respond(const char *userid, - /* DISCORD_REPLY_ */ int reply); - -#ifdef __cplusplus -} /* extern "C" */ -#endif diff --git a/dronline-client.pro b/dronline-client.pro new file mode 100644 index 000000000..8490ebe37 --- /dev/null +++ b/dronline-client.pro @@ -0,0 +1,126 @@ +QT += core gui widgets uitools multimedia network + +CONFIG += c++11 + +TEMPLATE = app +VERSION = 1.0.0.0 +TARGET = dro-client + +RC_ICONS = logo.ico + +INCLUDEPATH += $$PWD/include $$PWD/3rd +DEPENDPATH += $$PWD/include + +HEADERS += \ + include/aoabstractplayer.h \ + include/aoapplication.h \ + include/aobasshandle.h \ + include/aoblipplayer.h \ + include/aobutton.h \ + include/aocharbutton.h \ + include/aocharmovie.h \ + include/aoconfig.h \ + include/aoconfigpanel.h \ + include/aoemotebutton.h \ + include/aoevidencebutton.h \ + include/aoevidencedescription.h \ + include/aoevidencedisplay.h \ + include/aoexception.h \ + include/aoguiloader.h \ + include/aoimage.h \ + include/aolabel.h \ + include/aolineedit.h \ + include/aomovie.h \ + include/aomusicplayer.h \ + include/aonotearea.h \ + include/aonotepad.h \ + include/aonotepicker.h \ + include/aopacket.h \ + include/aopixmap.h \ + include/aoscene.h \ + include/aosfxplayer.h \ + include/aoshoutplayer.h \ + include/aotextarea.h \ + include/aotimer.h \ + include/courtroom.h \ + include/datatypes.h \ + include/debug_functions.h \ + include/discord_rich_presence.h \ + include/encryption_functions.h \ + include/file_functions.h \ + include/hardware_functions.h \ + include/hex_functions.h \ + include/lobby.h \ + include/misc_functions.h \ + include/networkmanager.h + +SOURCES += \ + src/aoabstractplayer.cpp \ + src/aoapplication.cpp \ + src/aobasshandle.cpp \ + src/aoblipplayer.cpp \ + src/aobutton.cpp \ + src/aocharbutton.cpp \ + src/aocharmovie.cpp \ + src/aoconfig.cpp \ + src/aoconfigpanel.cpp \ + src/aoemotebutton.cpp \ + src/aoevidencebutton.cpp \ + src/aoevidencedescription.cpp \ + src/aoevidencedisplay.cpp \ + src/aoexception.cpp \ + src/aoguiloader.cpp \ + src/aoimage.cpp \ + src/aolabel.cpp \ + src/aolineedit.cpp \ + src/aomovie.cpp \ + src/aomusicplayer.cpp \ + src/aonotearea.cpp \ + src/aonotepad.cpp \ + src/aonotepicker.cpp \ + src/aopacket.cpp \ + src/aopixmap.cpp \ + src/aoscene.cpp \ + src/aosfxplayer.cpp \ + src/aoshoutplayer.cpp \ + src/aotextarea.cpp \ + src/aotimer.cpp \ + src/audio_functions.cpp \ + src/charselect.cpp \ + src/courtroom.cpp \ + src/courtroom_widgets.cpp \ + src/debug_functions.cpp \ + src/discord_rich_presence.cpp \ + src/emotes.cpp \ + src/encryption_functions.cpp \ + src/evidence.cpp \ + src/file_functions.cpp \ + src/hardware_functions.cpp \ + src/hex_functions.cpp \ + src/lobby.cpp \ + src/main.cpp \ + src/misc_functions.cpp \ + src/networkmanager.cpp \ + src/packet_distribution.cpp \ + src/path_functions.cpp \ + src/text_file_functions.cpp + +# 1. You need to get BASS and put the x86 bass DLL/headers in the project root folder +# AND the compilation output folder. If you want a static link, you'll probably +# need the .lib file too. MinGW-GCC is really finicky finding BASS, it seems. +# 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers +# in the same way as BASS. Discord RPC uses CMake, which does not play nicely with +# QMake, so this step must be manual. +unix:LIBS += -L$$PWD -lbass -ldiscord-rpc +win32:LIBS += -L$$PWD/3rd -lbass -ldiscord-rpc #"$$PWD/discord-rpc.dll" +android:LIBS += -L$$PWD\android\libs\armeabi-v7a\ -lbass + +ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + +RESOURCES += \ + res.qrc + +DISTFILES += + +FORMS += \ + res/ui/config_panel.ui diff --git a/aoabstractplayer.h b/include/aoabstractplayer.h similarity index 100% rename from aoabstractplayer.h rename to include/aoabstractplayer.h diff --git a/aoapplication.h b/include/aoapplication.h similarity index 100% rename from aoapplication.h rename to include/aoapplication.h diff --git a/aobasshandle.h b/include/aobasshandle.h similarity index 100% rename from aobasshandle.h rename to include/aobasshandle.h diff --git a/aoblipplayer.h b/include/aoblipplayer.h similarity index 91% rename from aoblipplayer.h rename to include/aoblipplayer.h index 020c5c2a3..45f08b624 100644 --- a/aoblipplayer.h +++ b/include/aoblipplayer.h @@ -1,11 +1,13 @@ #ifndef AOBLIPPLAYER_H #define AOBLIPPLAYER_H - +// src #include "aoapplication.h" -#include "bass.h" - +// 3rd +#include +// qt #include #include +// std #include const int BLIP_COUNT = 5; diff --git a/aobutton.h b/include/aobutton.h similarity index 100% rename from aobutton.h rename to include/aobutton.h diff --git a/aocharbutton.h b/include/aocharbutton.h similarity index 100% rename from aocharbutton.h rename to include/aocharbutton.h diff --git a/aocharmovie.h b/include/aocharmovie.h similarity index 100% rename from aocharmovie.h rename to include/aocharmovie.h diff --git a/aoconfig.h b/include/aoconfig.h similarity index 100% rename from aoconfig.h rename to include/aoconfig.h diff --git a/aoconfigpanel.h b/include/aoconfigpanel.h similarity index 100% rename from aoconfigpanel.h rename to include/aoconfigpanel.h diff --git a/aoemotebutton.h b/include/aoemotebutton.h similarity index 100% rename from aoemotebutton.h rename to include/aoemotebutton.h diff --git a/aoevidencebutton.h b/include/aoevidencebutton.h similarity index 100% rename from aoevidencebutton.h rename to include/aoevidencebutton.h diff --git a/aoevidencedescription.h b/include/aoevidencedescription.h similarity index 100% rename from aoevidencedescription.h rename to include/aoevidencedescription.h diff --git a/aoevidencedisplay.h b/include/aoevidencedisplay.h similarity index 100% rename from aoevidencedisplay.h rename to include/aoevidencedisplay.h diff --git a/aoexception.h b/include/aoexception.h similarity index 100% rename from aoexception.h rename to include/aoexception.h diff --git a/aoguiloader.h b/include/aoguiloader.h similarity index 100% rename from aoguiloader.h rename to include/aoguiloader.h diff --git a/aoimage.h b/include/aoimage.h similarity index 100% rename from aoimage.h rename to include/aoimage.h diff --git a/aolabel.h b/include/aolabel.h similarity index 100% rename from aolabel.h rename to include/aolabel.h diff --git a/aolineedit.h b/include/aolineedit.h similarity index 100% rename from aolineedit.h rename to include/aolineedit.h diff --git a/aomovie.h b/include/aomovie.h similarity index 100% rename from aomovie.h rename to include/aomovie.h diff --git a/aomusicplayer.h b/include/aomusicplayer.h similarity index 100% rename from aomusicplayer.h rename to include/aomusicplayer.h diff --git a/aonotearea.h b/include/aonotearea.h similarity index 100% rename from aonotearea.h rename to include/aonotearea.h diff --git a/aonotepad.h b/include/aonotepad.h similarity index 100% rename from aonotepad.h rename to include/aonotepad.h diff --git a/aonotepicker.h b/include/aonotepicker.h similarity index 100% rename from aonotepicker.h rename to include/aonotepicker.h diff --git a/aopacket.h b/include/aopacket.h similarity index 100% rename from aopacket.h rename to include/aopacket.h diff --git a/aopixmap.h b/include/aopixmap.h similarity index 100% rename from aopixmap.h rename to include/aopixmap.h diff --git a/aoscene.h b/include/aoscene.h similarity index 100% rename from aoscene.h rename to include/aoscene.h diff --git a/aosfxplayer.h b/include/aosfxplayer.h similarity index 100% rename from aosfxplayer.h rename to include/aosfxplayer.h diff --git a/aoshoutplayer.h b/include/aoshoutplayer.h similarity index 100% rename from aoshoutplayer.h rename to include/aoshoutplayer.h diff --git a/aotextarea.h b/include/aotextarea.h similarity index 100% rename from aotextarea.h rename to include/aotextarea.h diff --git a/aotimer.h b/include/aotimer.h similarity index 100% rename from aotimer.h rename to include/aotimer.h diff --git a/courtroom.h b/include/courtroom.h similarity index 100% rename from courtroom.h rename to include/courtroom.h diff --git a/datatypes.h b/include/datatypes.h similarity index 100% rename from datatypes.h rename to include/datatypes.h diff --git a/debug_functions.h b/include/debug_functions.h similarity index 100% rename from debug_functions.h rename to include/debug_functions.h diff --git a/discord_rich_presence.h b/include/discord_rich_presence.h similarity index 98% rename from discord_rich_presence.h rename to include/discord_rich_presence.h index 1107d4f5c..8a04e6def 100644 --- a/discord_rich_presence.h +++ b/include/discord_rich_presence.h @@ -1,7 +1,8 @@ #ifndef DISCORD_RICH_PRESENCE_H #define DISCORD_RICH_PRESENCE_H - +// 3rd #include +// std #include namespace AttorneyOnline diff --git a/encryption_functions.h b/include/encryption_functions.h similarity index 100% rename from encryption_functions.h rename to include/encryption_functions.h diff --git a/file_functions.h b/include/file_functions.h similarity index 100% rename from file_functions.h rename to include/file_functions.h diff --git a/hardware_functions.h b/include/hardware_functions.h similarity index 100% rename from hardware_functions.h rename to include/hardware_functions.h diff --git a/hex_functions.h b/include/hex_functions.h similarity index 100% rename from hex_functions.h rename to include/hex_functions.h diff --git a/lobby.h b/include/lobby.h similarity index 100% rename from lobby.h rename to include/lobby.h diff --git a/misc_functions.h b/include/misc_functions.h similarity index 100% rename from misc_functions.h rename to include/misc_functions.h diff --git a/networkmanager.h b/include/networkmanager.h similarity index 100% rename from networkmanager.h rename to include/networkmanager.h diff --git a/aoabstractplayer.cpp b/src/aoabstractplayer.cpp similarity index 100% rename from aoabstractplayer.cpp rename to src/aoabstractplayer.cpp diff --git a/aoapplication.cpp b/src/aoapplication.cpp similarity index 100% rename from aoapplication.cpp rename to src/aoapplication.cpp diff --git a/aobasshandle.cpp b/src/aobasshandle.cpp similarity index 100% rename from aobasshandle.cpp rename to src/aobasshandle.cpp diff --git a/aoblipplayer.cpp b/src/aoblipplayer.cpp similarity index 100% rename from aoblipplayer.cpp rename to src/aoblipplayer.cpp diff --git a/aobutton.cpp b/src/aobutton.cpp similarity index 100% rename from aobutton.cpp rename to src/aobutton.cpp diff --git a/aocharbutton.cpp b/src/aocharbutton.cpp similarity index 100% rename from aocharbutton.cpp rename to src/aocharbutton.cpp diff --git a/aocharmovie.cpp b/src/aocharmovie.cpp similarity index 100% rename from aocharmovie.cpp rename to src/aocharmovie.cpp diff --git a/aoconfig.cpp b/src/aoconfig.cpp similarity index 100% rename from aoconfig.cpp rename to src/aoconfig.cpp diff --git a/aoconfigpanel.cpp b/src/aoconfigpanel.cpp similarity index 100% rename from aoconfigpanel.cpp rename to src/aoconfigpanel.cpp diff --git a/aoemotebutton.cpp b/src/aoemotebutton.cpp similarity index 100% rename from aoemotebutton.cpp rename to src/aoemotebutton.cpp diff --git a/aoevidencebutton.cpp b/src/aoevidencebutton.cpp similarity index 100% rename from aoevidencebutton.cpp rename to src/aoevidencebutton.cpp diff --git a/aoevidencedescription.cpp b/src/aoevidencedescription.cpp similarity index 100% rename from aoevidencedescription.cpp rename to src/aoevidencedescription.cpp diff --git a/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp similarity index 100% rename from aoevidencedisplay.cpp rename to src/aoevidencedisplay.cpp diff --git a/aoexception.cpp b/src/aoexception.cpp similarity index 100% rename from aoexception.cpp rename to src/aoexception.cpp diff --git a/aoguiloader.cpp b/src/aoguiloader.cpp similarity index 100% rename from aoguiloader.cpp rename to src/aoguiloader.cpp diff --git a/aoimage.cpp b/src/aoimage.cpp similarity index 100% rename from aoimage.cpp rename to src/aoimage.cpp diff --git a/aolabel.cpp b/src/aolabel.cpp similarity index 100% rename from aolabel.cpp rename to src/aolabel.cpp diff --git a/aolineedit.cpp b/src/aolineedit.cpp similarity index 100% rename from aolineedit.cpp rename to src/aolineedit.cpp diff --git a/aomovie.cpp b/src/aomovie.cpp similarity index 100% rename from aomovie.cpp rename to src/aomovie.cpp diff --git a/aomusicplayer.cpp b/src/aomusicplayer.cpp similarity index 100% rename from aomusicplayer.cpp rename to src/aomusicplayer.cpp diff --git a/aonotearea.cpp b/src/aonotearea.cpp similarity index 100% rename from aonotearea.cpp rename to src/aonotearea.cpp diff --git a/aonotepad.cpp b/src/aonotepad.cpp similarity index 100% rename from aonotepad.cpp rename to src/aonotepad.cpp diff --git a/aonotepicker.cpp b/src/aonotepicker.cpp similarity index 100% rename from aonotepicker.cpp rename to src/aonotepicker.cpp diff --git a/aopacket.cpp b/src/aopacket.cpp similarity index 100% rename from aopacket.cpp rename to src/aopacket.cpp diff --git a/aopixmap.cpp b/src/aopixmap.cpp similarity index 100% rename from aopixmap.cpp rename to src/aopixmap.cpp diff --git a/aoscene.cpp b/src/aoscene.cpp similarity index 100% rename from aoscene.cpp rename to src/aoscene.cpp diff --git a/aosfxplayer.cpp b/src/aosfxplayer.cpp similarity index 100% rename from aosfxplayer.cpp rename to src/aosfxplayer.cpp diff --git a/aoshoutplayer.cpp b/src/aoshoutplayer.cpp similarity index 100% rename from aoshoutplayer.cpp rename to src/aoshoutplayer.cpp diff --git a/aotextarea.cpp b/src/aotextarea.cpp similarity index 100% rename from aotextarea.cpp rename to src/aotextarea.cpp diff --git a/aotimer.cpp b/src/aotimer.cpp similarity index 100% rename from aotimer.cpp rename to src/aotimer.cpp diff --git a/audio_functions.cpp b/src/audio_functions.cpp similarity index 100% rename from audio_functions.cpp rename to src/audio_functions.cpp diff --git a/charselect.cpp b/src/charselect.cpp similarity index 100% rename from charselect.cpp rename to src/charselect.cpp diff --git a/courtroom.cpp b/src/courtroom.cpp similarity index 100% rename from courtroom.cpp rename to src/courtroom.cpp diff --git a/courtroom_widgets.cpp b/src/courtroom_widgets.cpp similarity index 100% rename from courtroom_widgets.cpp rename to src/courtroom_widgets.cpp diff --git a/debug_functions.cpp b/src/debug_functions.cpp similarity index 100% rename from debug_functions.cpp rename to src/debug_functions.cpp diff --git a/discord_rich_presence.cpp b/src/discord_rich_presence.cpp similarity index 100% rename from discord_rich_presence.cpp rename to src/discord_rich_presence.cpp diff --git a/emotes.cpp b/src/emotes.cpp similarity index 100% rename from emotes.cpp rename to src/emotes.cpp diff --git a/encryption_functions.cpp b/src/encryption_functions.cpp similarity index 100% rename from encryption_functions.cpp rename to src/encryption_functions.cpp diff --git a/evidence.cpp b/src/evidence.cpp similarity index 100% rename from evidence.cpp rename to src/evidence.cpp diff --git a/file_functions.cpp b/src/file_functions.cpp similarity index 100% rename from file_functions.cpp rename to src/file_functions.cpp diff --git a/hardware_functions.cpp b/src/hardware_functions.cpp similarity index 100% rename from hardware_functions.cpp rename to src/hardware_functions.cpp diff --git a/hex_functions.cpp b/src/hex_functions.cpp similarity index 100% rename from hex_functions.cpp rename to src/hex_functions.cpp diff --git a/lobby.cpp b/src/lobby.cpp similarity index 100% rename from lobby.cpp rename to src/lobby.cpp diff --git a/main.cpp b/src/main.cpp similarity index 100% rename from main.cpp rename to src/main.cpp diff --git a/misc_functions.cpp b/src/misc_functions.cpp similarity index 100% rename from misc_functions.cpp rename to src/misc_functions.cpp diff --git a/networkmanager.cpp b/src/networkmanager.cpp similarity index 100% rename from networkmanager.cpp rename to src/networkmanager.cpp diff --git a/packet_distribution.cpp b/src/packet_distribution.cpp similarity index 100% rename from packet_distribution.cpp rename to src/packet_distribution.cpp diff --git a/path_functions.cpp b/src/path_functions.cpp similarity index 100% rename from path_functions.cpp rename to src/path_functions.cpp diff --git a/text_file_functions.cpp b/src/text_file_functions.cpp similarity index 100% rename from text_file_functions.cpp rename to src/text_file_functions.cpp From 5b0e5b968ebc9c30c554774a9330d7a5a51eb242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Wed, 16 Sep 2020 19:08:44 +0200 Subject: [PATCH 109/842] Update dronline-client.pro Small fix for Unix, looking for libraries inside the `3rd` folder like Windows would. --- dronline-client.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dronline-client.pro b/dronline-client.pro index 8490ebe37..ce544db55 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -111,7 +111,7 @@ SOURCES += \ # 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers # in the same way as BASS. Discord RPC uses CMake, which does not play nicely with # QMake, so this step must be manual. -unix:LIBS += -L$$PWD -lbass -ldiscord-rpc +unix:LIBS += -L$$PWD/3rd -lbass -ldiscord-rpc win32:LIBS += -L$$PWD/3rd -lbass -ldiscord-rpc #"$$PWD/discord-rpc.dll" android:LIBS += -L$$PWD\android\libs\armeabi-v7a\ -lbass From e6c6cb43707904aaa367cfacd52938c2cfa46da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Wed, 16 Sep 2020 19:24:45 +0200 Subject: [PATCH 110/842] Removal of Android Because the last thing we need is to realize we've been speaking to robots. - Removed Android-related stuff since it's never going to be used. --- dronline-client.pro | 5 +---- include/networkmanager.h | 7 ------- src/path_functions.cpp | 11 ----------- 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index ce544db55..f9bbf5722 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -112,10 +112,7 @@ SOURCES += \ # in the same way as BASS. Discord RPC uses CMake, which does not play nicely with # QMake, so this step must be manual. unix:LIBS += -L$$PWD/3rd -lbass -ldiscord-rpc -win32:LIBS += -L$$PWD/3rd -lbass -ldiscord-rpc #"$$PWD/discord-rpc.dll" -android:LIBS += -L$$PWD\android\libs\armeabi-v7a\ -lbass - -ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android +win32:LIBS += -L$$PWD/3rd -lbass -ldiscord-rpc #"$$PWD/3rd/discord-rpc.dll" RESOURCES += \ res.qrc diff --git a/include/networkmanager.h b/include/networkmanager.h index 5f5eb7a5a..9e16a3cf6 100644 --- a/include/networkmanager.h +++ b/include/networkmanager.h @@ -1,13 +1,6 @@ #ifndef NETWORKMANAGER_H #define NETWORKMANAGER_H -// Qt for Android has stubbed QDnsLookup. This is not documented in any part of -// their wiki. This prevents SRV lookup/failover behavior from functioning. -// https://bugreports.qt.io/browse/QTBUG-56143 -#ifndef ANDROID -#define MS_FAILOVER_SUPPORTED -#endif - //#define LOCAL_MS #ifdef LOCAL_MS diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 44ad14bd6..6160670c8 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -26,17 +26,6 @@ QString AOApplication::get_base_path() { #ifdef BASE_OVERRIDE base_path = base_override; -#elif defined(ANDROID) - QString sdcard_storage = getenv("SECONDARY_STORAGE"); - if (dir_exists(sdcard_storage + "/AO2/")) - { - base_path = sdcard_storage + "/AO2/"; - } - else - { - QString external_storage = getenv("EXTERNAL_STORAGE"); - base_path = external_storage + "/AO2/"; - } #else base_path = QDir::currentPath() + "/base/"; #endif From bfcffda4a79d24c534eca04033d55e05455f62c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Wed, 16 Sep 2020 23:50:10 +0200 Subject: [PATCH 111/842] Android clean up, follow up. Removed the rest of the Android files. --- android/AndroidManifest.xml | 79 ------------------------------------- android/project.properties | 1 - 2 files changed, 80 deletions(-) delete mode 100644 android/AndroidManifest.xml delete mode 100644 android/project.properties diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml deleted file mode 100644 index f458c6a78..000000000 --- a/android/AndroidManifest.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/project.properties b/android/project.properties deleted file mode 100644 index a08f37edd..000000000 --- a/android/project.properties +++ /dev/null @@ -1 +0,0 @@ -target=android-21 \ No newline at end of file From c060aadcd8a6487da62b086e569ad3b8e077c749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Thu, 17 Sep 2020 01:02:46 +0200 Subject: [PATCH 112/842] Update README.md Updated to reflect DROnline. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b034d333f..427f0a8f2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Attorney-Online-Client-Remake -This is a remake of Attorney Online made by FanatSors, licensed under GPLv3. +# Danganronpa-Online-Client +This is the official client used, it is a derivative of Attorney-Online-Client-Remake. ## Qt -This project uses Qt 5.7, which is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.txt) with [certain licensing restrictions and exceptions](https://www.qt.io/qt-licensing-terms/). To comply with licensing requirements for static linking, object code is available if you would like to relink with an alternative version of Qt, and the source code for Qt may be found at https://github.com/qt/qtbase, http://code.qt.io/cgit/, or at https://qt.io. +This project uses Qt 5.14.2, which is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.txt) with [certain licensing restrictions and exceptions](https://www.qt.io/qt-licensing-terms/). To comply with licensing requirements for static linking, object code is available if you would like to relink with an alternative version of Qt, and the source code for Qt may be found at https://github.com/qt/qtbase, http://code.qt.io/cgit/, or at https://qt.io. Copyright (C) 2016 The Qt Company Ltd. From 65a5a0d175f1f28aa80c92f2d0a0b8a818d2b757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 18 Sep 2020 16:20:16 +0200 Subject: [PATCH 113/842] CPP17, Arch, Discord, ... * Moved from CPP11 to CPP17. * Can now more easily build depending on the compiler's arch. (`x86`, `x86_64`, ...) * Updated Discord RCP to the latest version. --- .gitignore | 10 +++++----- dronline-client.pro | 9 ++++----- logo.ico => icon.ico | Bin logo.png => icon.png | Bin include/aoevidencedescription.h | 1 + include/courtroom.h | 5 +++-- include/discord_rich_presence.h | 2 +- src/discord_rich_presence.cpp | 4 ++-- 8 files changed, 16 insertions(+), 15 deletions(-) rename logo.ico => icon.ico (100%) rename logo.png => icon.png (100%) diff --git a/.gitignore b/.gitignore index 054156108..7fc49ad0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,9 @@ +3rd/ +base/ +.qmake.conf +.gitignore *~ *.db *.user *.dll -*.so -base_override.h -send-presence.exe -3rd/ -base-full/ \ No newline at end of file +*.so \ No newline at end of file diff --git a/dronline-client.pro b/dronline-client.pro index f9bbf5722..ccd49216f 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -1,14 +1,14 @@ QT += core gui widgets uitools multimedia network -CONFIG += c++11 +CONFIG += c++17 TEMPLATE = app VERSION = 1.0.0.0 TARGET = dro-client -RC_ICONS = logo.ico +RC_ICONS = icon.ico -INCLUDEPATH += $$PWD/include $$PWD/3rd +INCLUDEPATH += $$PWD/include $$PWD/3rd/include DEPENDPATH += $$PWD/include HEADERS += \ @@ -111,8 +111,7 @@ SOURCES += \ # 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers # in the same way as BASS. Discord RPC uses CMake, which does not play nicely with # QMake, so this step must be manual. -unix:LIBS += -L$$PWD/3rd -lbass -ldiscord-rpc -win32:LIBS += -L$$PWD/3rd -lbass -ldiscord-rpc #"$$PWD/3rd/discord-rpc.dll" +LIBS += -L$$PWD/3rd/$${QMAKE_HOST.arch} -lbass -ldiscord-rpc RESOURCES += \ res.qrc diff --git a/logo.ico b/icon.ico similarity index 100% rename from logo.ico rename to icon.ico diff --git a/logo.png b/icon.png similarity index 100% rename from logo.png rename to icon.png diff --git a/include/aoevidencedescription.h b/include/aoevidencedescription.h index b4d399e40..23666a6c0 100644 --- a/include/aoevidencedescription.h +++ b/include/aoevidencedescription.h @@ -6,6 +6,7 @@ class AOEvidenceDescription : public QPlainTextEdit { Q_OBJECT + public: AOEvidenceDescription(QWidget *parent); diff --git a/include/courtroom.h b/include/courtroom.h index 0de53d261..30dc84cb0 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -49,6 +49,7 @@ class AOApplication; class Courtroom : public QMainWindow { Q_OBJECT + public: explicit Courtroom(AOApplication *p_ao_app); @@ -562,8 +563,8 @@ class Courtroom : public QMainWindow QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip QVector ui_label_images; - QVector label_images = {"Pre", "Flip", "Hidden", - "Music", "SFX", "Blip"}; + QVector label_images = {"Pre", "Flip", "Hidden", + "Music", "SFX", "Blip"}; AOButton *ui_effect_flash = nullptr; AOButton *ui_effect_gloom = nullptr; diff --git a/include/discord_rich_presence.h b/include/discord_rich_presence.h index 8a04e6def..078717509 100644 --- a/include/discord_rich_presence.h +++ b/include/discord_rich_presence.h @@ -1,7 +1,7 @@ #ifndef DISCORD_RICH_PRESENCE_H #define DISCORD_RICH_PRESENCE_H // 3rd -#include +#include // std #include diff --git a/src/discord_rich_presence.cpp b/src/discord_rich_presence.cpp index 0e85fc25f..05cf53c39 100644 --- a/src/discord_rich_presence.cpp +++ b/src/discord_rich_presence.cpp @@ -33,8 +33,8 @@ void Discord::start(const char *APPLICATION_ID) DiscordEventHandlers handlers; std::memset(&handlers, 0, sizeof(handlers)); handlers = {}; - handlers.ready = [] { - qInfo() << "Discord RPC ready"; + handlers.ready = [](const DiscordUser *user) { + qInfo() << "Discord RPC ready for" << user->username; }; handlers.disconnected = [](int errorCode, const char *message) { qInfo() << "Discord RPC disconnected! " << message; From fcca77bb9fcbfd85d2706d92d99ffe8ab0468b88 Mon Sep 17 00:00:00 2001 From: Cerapter <43446478+Cerapter@users.noreply.github.com> Date: Sat, 19 Sep 2020 13:45:05 +0200 Subject: [PATCH 114/842] Add DRO devs to about window (#64) * Add DRO devs to about window * Add Ziella to devs, mention AO2 --- src/lobby.cpp | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index de9f19bec..5f52ce3be 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -7,6 +7,8 @@ #include #include +#include +#include #include #include @@ -365,21 +367,27 @@ void Lobby::on_connect_released() void Lobby::on_about_clicked() { - call_notice("Attorney Online 2 is built using Qt.\n\n" - "Lead development:\n" - "OmniTroid\n\n" - "stonedDiscord\n" - "longbyte1\n" - "Supporting development:\n" - "Fiercy\n\n" - "UI design:\n" - "Ruekasu\n" - "Draxirch\n\n" - "Special thanks:\n" - "Unishred\n" - "Argoneus\n" - "Noevain\n" - "Cronnicossy"); + const bool hasApng = QImageReader::supportedImageFormats().contains("apng"); + + QString msg = tr("

Danganronpa Online

" + "version: %1" + "

Source code: " + "" + "https://github.com/Chrezm/DRO-Client" + "

Development:
" + "Cerapter, Elf, Iuvee, Tricky Leifa, Ziella" + "

Based on Attorney Online 2:
" + "" + "https://github.com/AttorneyOnline/AO2-Client" + "

Running on Qt version %2 with the BASS audio engine.
" + "APNG plugin loaded: %3" + "

Built on %4") + .arg(ao_app->get_version_string()) + .arg(QLatin1String(QT_VERSION_STR)) + .arg(hasApng ? tr("Yes") : tr("No")) + .arg(QLatin1String(__DATE__)); + + QMessageBox::about(this, tr("About"), msg); } void Lobby::on_server_list_clicked(QModelIndex p_model) From 546a911da999555f620c1223b94e27ef0caf5cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sun, 20 Sep 2020 14:54:41 +0200 Subject: [PATCH 115/842] Update README.md Linking to the repo. --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 427f0a8f2..60e149838 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Danganronpa-Online-Client -This is the official client used, it is a derivative of Attorney-Online-Client-Remake. +This is the official client used, it is a derivative of [Attorney-Online-Client-Remake](https://github.com/AttorneyOnline/AO2-Client). ## Qt This project uses Qt 5.14.2, which is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.txt) with [certain licensing restrictions and exceptions](https://www.qt.io/qt-licensing-terms/). To comply with licensing requirements for static linking, object code is available if you would like to relink with an alternative version of Qt, and the source code for Qt may be found at https://github.com/qt/qtbase, http://code.qt.io/cgit/, or at https://qt.io. @@ -7,7 +7,6 @@ This project uses Qt 5.14.2, which is licensed under the [GNU Lesser General Pub Copyright (C) 2016 The Qt Company Ltd. ## BASS - -This project depends on the BASS shared library. Get it here: http://www.un4seen.com/ +This project uses [BASS shared library](http://www.un4seen.com/). Copyright (c) 1999-2016 Un4seen Developments Ltd. All rights reserved. From 02fba1fc00e89ee94fd91be497f4a95359ec341a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sun, 20 Sep 2020 17:17:40 +0200 Subject: [PATCH 116/842] #65 AOMovie Interjection fix * Added play_interjection --- dronline-client.pro | 4 +- include/aoapplication.h | 3 -- include/aomovie.h | 7 +++- src/aomovie.cpp | 76 ++++++++++++++++++++++++++++++++++--- src/courtroom.cpp | 7 ++-- src/file_functions.cpp | 2 - src/text_file_functions.cpp | 7 ---- 7 files changed, 82 insertions(+), 24 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index ccd49216f..f7130e1c7 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -1,6 +1,6 @@ QT += core gui widgets uitools multimedia network -CONFIG += c++17 +CONFIG += c++11 TEMPLATE = app VERSION = 1.0.0.0 @@ -111,7 +111,7 @@ SOURCES += \ # 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers # in the same way as BASS. Discord RPC uses CMake, which does not play nicely with # QMake, so this step must be manual. -LIBS += -L$$PWD/3rd/$${QMAKE_HOST.arch} -lbass -ldiscord-rpc +LIBS += -L$$PWD/3rd -lbass -ldiscord-rpc RESOURCES += \ res.qrc diff --git a/include/aoapplication.h b/include/aoapplication.h index 2410680aa..70bc37c9a 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -303,9 +303,6 @@ class AOApplication : public QApplication // Returns the value of chat from the specific p_char's ini file QString get_chat(QString p_char); - // Returns the value of shouts from the specified p_char's ini file - QString get_char_shouts(QString p_char); - // Not in use int get_text_delay(QString p_char, QString p_emote); diff --git a/include/aomovie.h b/include/aomovie.h index c890783ce..a1fc63e52 100644 --- a/include/aomovie.h +++ b/include/aomovie.h @@ -16,7 +16,12 @@ class AOMovie : public QLabel ~AOMovie(); void set_play_once(bool p_play_once); - void play(QString p_file, QString p_char = "", QString p_custom_theme = ""); + void play(QString p_file, QString p_char = ""); + + /// + /// \brief Searches and play the first interjection file it can find based on the provided character name and interjection name. + /// + void play_interjection(QString p_char_name, QString p_interjection_name); void combo_resize(int w, int h); void stop(); diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 549d368cb..1b3dc60b7 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -4,7 +4,8 @@ #include "file_functions.h" #include "misc_functions.h" -AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) +AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) + : QLabel(p_parent) { ao_app = p_ao_app; @@ -25,7 +26,7 @@ void AOMovie::set_play_once(bool p_play_once) play_once = p_play_once; } -void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) +void AOMovie::play(QString p_file, QString p_char) { m_movie->stop(); QVector f_vec; @@ -46,14 +47,13 @@ void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) QStringList f_paths{ custom_path, ao_app->get_character_path(p_char, "overlay/" + p_file), - ao_app->get_case_sensitive_path(ao_app->get_base_path() + "themes/" + - p_custom_theme + "/" + p_file), ao_app->get_theme_variant_path(p_file), ao_app->get_theme_path(p_file), ao_app->get_default_theme_path(p_file), ao_app->get_theme_variant_path("placeholder"), ao_app->get_theme_path("placeholder"), - ao_app->get_default_theme_path("placeholder")}; + ao_app->get_default_theme_path("placeholder"), + }; for (auto &f_file : f_paths) { @@ -74,6 +74,7 @@ void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) } qDebug() << file_path; + qDebug() << "playing" << file_path; m_movie->setFileName(file_path); this->show(); @@ -81,6 +82,71 @@ void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) m_movie->start(); } +void AOMovie::play_interjection(QString p_char_name, + QString p_interjection_name) +{ + m_movie->stop(); + + QString chr_interjection; + { + QString interjection_suffix = "_bubble"; + + // FIXME there is no reason custom shouldn't have a suffix + if (p_interjection_name.toLower() == "custom") + { + interjection_suffix.clear(); + } + + chr_interjection = ao_app->get_character_path( + p_char_name, p_interjection_name + interjection_suffix); + } + + QStringList possible_paths{ + chr_interjection, + ao_app->get_theme_variant_path(p_interjection_name), + ao_app->get_theme_path(p_interjection_name), + ao_app->get_default_theme_path(p_interjection_name), + }; + + QString interjection_filepath; + for (QString &i_path : possible_paths) + { + bool skip = false; + + for (QString &i_ext : QStringList{".webp", ".apng", ".gif"}) + { + QString fullpath = ao_app->get_case_sensitive_path(i_path + i_ext); + if (file_exists(fullpath)) + { + skip = true; + interjection_filepath = fullpath; + break; + } + } + + if (skip) + { + break; + } + } + + if (interjection_filepath.isEmpty()) + { + emit done(); + return; + } + + qDebug() << "playing interjection" + << (p_interjection_name.isEmpty() ? "(none)" : p_interjection_name) + << "for character" + << (p_char_name.isEmpty() ? "(none)" : p_char_name) << "at" + << (interjection_filepath.isEmpty() ? "(not found)" : interjection_filepath); + m_movie->setFileName(interjection_filepath); + this->show(); + m_movie->setScaledSize(this->size()); + m_movie->start(); +} + void AOMovie::stop() { m_movie->stop(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 5fef2cbb3..ed0545e03 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -21,7 +21,8 @@ #include #include -Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() +Courtroom::Courtroom(AOApplication *p_ao_app) + : QMainWindow() { ao_app = p_ao_app; ao_config = new AOConfig(this); @@ -830,7 +831,6 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) int 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 an objection is used if (objection_mod > 0) @@ -843,8 +843,7 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) if (objection_mod >= 1 && objection_mod <= ui_shouts.size() && ui_shouts.size() > 0) // check to prevent crashing { - ui_vp_objection->play(shout_names.at(objection_mod - 1), f_char, - f_custom_theme); + ui_vp_objection->play_interjection(f_char, shout_names.at(objection_mod - 1)); m_shouts_player->play(shout_names.at(objection_mod - 1) + ".wav", f_char); } else diff --git a/src/file_functions.cpp b/src/file_functions.cpp index c7a249c93..f2d4ef125 100644 --- a/src/file_functions.cpp +++ b/src/file_functions.cpp @@ -6,13 +6,11 @@ bool file_exists(QString file_path) { QFileInfo check_file(file_path); - return check_file.exists() && check_file.isFile(); } bool dir_exists(QString dir_path) { QDir check_dir(dir_path); - return check_dir.exists(); } diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index dbc6f189a..7a1f303f1 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -649,13 +649,6 @@ QString AOApplication::get_chat(QString p_char) return f_result.toLower(); } -QString AOApplication::get_char_shouts(QString p_char) -{ - QString f_result = read_char_ini(p_char, "shouts", "[Options]", "[Time]"); - - return f_result.toLower(); -} - int AOApplication::get_emote_number(QString p_char) { QString f_result = read_char_ini(p_char, "number", "[Emotions]", "[Offsets]"); From 92229dbff913244113a54fddb85c90c0cd39fb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sun, 20 Sep 2020 17:19:31 +0200 Subject: [PATCH 117/842] Update INCLUDEPATH to include root of `3rd` folder --- dronline-client.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dronline-client.pro b/dronline-client.pro index f7130e1c7..017bb9ab9 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -8,7 +8,7 @@ TARGET = dro-client RC_ICONS = icon.ico -INCLUDEPATH += $$PWD/include $$PWD/3rd/include +INCLUDEPATH += $$PWD/include $$PWD/3rd $$PWD/3rd/include DEPENDPATH += $$PWD/include HEADERS += \ From 2fdedfcd06d10070b0cf61722a4bb081ea29153d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sun, 20 Sep 2020 18:15:17 +0200 Subject: [PATCH 118/842] Restoring arch changes. --- dronline-client.pro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index 017bb9ab9..b54bff304 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -8,7 +8,7 @@ TARGET = dro-client RC_ICONS = icon.ico -INCLUDEPATH += $$PWD/include $$PWD/3rd $$PWD/3rd/include +INCLUDEPATH += $$PWD/include $$PWD/3rd/include DEPENDPATH += $$PWD/include HEADERS += \ @@ -111,7 +111,7 @@ SOURCES += \ # 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers # in the same way as BASS. Discord RPC uses CMake, which does not play nicely with # QMake, so this step must be manual. -LIBS += -L$$PWD/3rd -lbass -ldiscord-rpc +LIBS += -L$$PWD/3rd/$${QMAKE_HOST.arch} -lbass -ldiscord-rpc RESOURCES += \ res.qrc From b101678f12887f18c7dd9dd99f940db2a4117fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Wed, 23 Sep 2020 00:04:44 +0200 Subject: [PATCH 119/842] Update to C++17 and arch-target * Moved project to C++17 * Fixed arch --- dronline-client.pro | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index b54bff304..0941c8b6e 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -1,6 +1,6 @@ -QT += core gui widgets uitools multimedia network +QT += core gui widgets uitools network -CONFIG += c++11 +CONFIG += c++17 TEMPLATE = app VERSION = 1.0.0.0 @@ -111,7 +111,7 @@ SOURCES += \ # 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers # in the same way as BASS. Discord RPC uses CMake, which does not play nicely with # QMake, so this step must be manual. -LIBS += -L$$PWD/3rd/$${QMAKE_HOST.arch} -lbass -ldiscord-rpc +LIBS += -L$$PWD/3rd/$${QT_ARCH} -lbass -ldiscord-rpc RESOURCES += \ res.qrc From 1a3b8bc2ba8383970ad1e9f85816d64ffa41c37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 25 Sep 2020 04:28:07 +0200 Subject: [PATCH 120/842] Music records * Added music records to logs * Removed needless code duplication --- src/courtroom.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ed0545e03..50ba86b1e 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -560,15 +560,14 @@ void Courtroom::save_textlog(QString p_text) { QString f_file = ao_app->get_base_path() + icchatlogsfilename; - ao_app->append_note(p_text, f_file); + ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, f_file); } void Courtroom::append_server_chatmessage(QString p_name, QString p_message) { ui_server_chatlog->append_chatmessage(p_name, p_message); if (ao_config->log_is_recording_enabled()) - save_textlog("(OOC)[" + QTime::currentTime().toString() + "] " + p_name + - ": " + p_message); + save_textlog("(OOC)" + p_name + ": " + p_message); } void Courtroom::on_chat_return_pressed() @@ -826,8 +825,7 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) append_ic_text(f_showname, m_chatmessage[MESSAGE], false, false); if (ao_config->log_is_recording_enabled()) - save_textlog("[" + QTime::currentTime().toString() + "] " + f_showname + - ": " + m_chatmessage[MESSAGE]); + save_textlog(f_showname + ": " + m_chatmessage[MESSAGE]); int objection_mod = m_chatmessage[OBJECTION_MOD].toInt(); QString f_char = m_chatmessage[CHAR_NAME]; @@ -1738,6 +1736,8 @@ void Courtroom::handle_song(QStringList *p_contents) if (ao_app->get_music_change_log_enabled()) { append_ic_text(str_char, "has played a song: " + f_song, false, true); + if (ao_config->log_is_recording_enabled()) + save_textlog(str_char + " has played a song: " + f_song); } m_music_player->play(f_song); } From 8c015124b8767e85cfd38859e0fa2220d9012eee Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 1 Oct 2020 10:29:16 -0400 Subject: [PATCH 121/842] Streamline file lookup code --- include/aoapplication.h | 16 +++- src/aocharmovie.cpp | 13 ++- src/aoimage.cpp | 2 +- src/aomovie.cpp | 34 +++++-- src/courtroom.cpp | 68 ++++++++++++-- src/courtroom_widgets.cpp | 183 +++++++++++++++++++++++++++++------- src/path_functions.cpp | 63 ++++++++----- src/text_file_functions.cpp | 173 +++++++++++++++++++++++++++++++++- 8 files changed, 468 insertions(+), 84 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 70bc37c9a..3f9f80c1f 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -115,8 +115,8 @@ class AOApplication : public QApplication QString get_base_path(); QString get_data_path(); QString get_theme_path(QString p_file); - QString get_theme_variant_path(QString p_file); - QString get_default_theme_path(QString p_file); + // Get the theme variant + QString get_theme_variant(); QString get_character_path(QString p_character, QString p_file); // QString get_demothings_path(); QString get_sounds_path(QString p_file); @@ -125,6 +125,12 @@ class AOApplication : public QApplication QString get_default_background_path(QString p_file); QString get_evidence_path(QString p_file); + QString find_asset_path(QStringList possible_paths); + QString find_asset_path(QStringList possible_paths, + QStringList possible_exts); + QString find_theme_asset_path(QString p_file); + QString find_theme_asset_path(QString p_root, QStringList p_exts); + /** * @brief Searches for a file with any of the given extensions, and returns * the first extension that actually matches to an existing file. @@ -170,9 +176,9 @@ class AOApplication : public QApplication // variable QString get_theme(); - // Reads the theme variant from config.ini and loads it into the current theme - // variant variable - QString get_theme_variant(); + // Reads the gamemode from config.ini and loads it into the current_gamemode + // variable + QString get_gamemode(); // Returns the blip rate from config.ini int read_blip_rate(); diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 6a31bfd75..7abbfa251 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -26,7 +26,17 @@ AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, bool p_visible) { - QString target_path; + QStringList exts{".webp", ".apng", ".gif", ".png"}; + QString target_path = ao_app->find_asset_path( + { + ao_app->get_character_path(p_char, p_emote_prefix + p_emote), + ao_app->get_character_path(p_char, p_emote), + }, + exts); + if (target_path.isEmpty()) + target_path = ao_app->find_theme_asset_path("placeholder", exts); + + /* QStringList f_paths{ ao_app->get_character_path(p_char, p_emote_prefix + p_emote), // .gif ao_app->get_character_path(p_char, p_emote), // .png @@ -52,6 +62,7 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, if (found) break; } + */ show(); if (!p_visible) diff --git a/src/aoimage.cpp b/src/aoimage.cpp index 94c2bd80d..4e7f74895 100644 --- a/src/aoimage.cpp +++ b/src/aoimage.cpp @@ -24,7 +24,7 @@ void AOImage::set_image(QString p_image) void AOImage::set_image_from_path(QString p_path) { - QString default_path = ao_app->get_default_theme_path("chatmed.png"); + QString default_path = ao_app->find_theme_asset_path("chatmed.png"); QString final_path; diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 1b3dc60b7..c5e563edf 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -4,8 +4,7 @@ #include "file_functions.h" #include "misc_functions.h" -AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) - : QLabel(p_parent) +AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) { ao_app = p_ao_app; @@ -44,9 +43,21 @@ void AOMovie::play(QString p_file, QString p_char) else custom_path = ao_app->get_character_path(p_char, p_file + "_bubble"); + QStringList exts{".webp", ".apng", ".gif", ".png"}; + file_path = ao_app->find_asset_path( + { + custom_path, + ao_app->get_character_path(p_char, "overlay/" + p_file), + }, + exts); + if (file_path.isEmpty()) + { + file_path = ao_app->find_theme_asset_path(p_file, exts); + if (file_path.isEmpty()) + file_path = ao_app->find_theme_asset_path("placeholder", exts); + } + /* QStringList f_paths{ - custom_path, - ao_app->get_character_path(p_char, "overlay/" + p_file), ao_app->get_theme_variant_path(p_file), ao_app->get_theme_path(p_file), ao_app->get_default_theme_path(p_file), @@ -71,7 +82,7 @@ void AOMovie::play(QString p_file, QString p_char) if (found) break; - } + }*/ qDebug() << file_path; qDebug() << "playing" << file_path; @@ -101,6 +112,14 @@ void AOMovie::play_interjection(QString p_char_name, p_char_name, p_interjection_name + interjection_suffix); } + QStringList exts{".webp", ".apng", ".gif"}; + QString interjection_filepath = + ao_app->find_asset_path({chr_interjection}, exts); + if (interjection_filepath.isEmpty()) + interjection_filepath = + ao_app->find_theme_asset_path(p_interjection_name, exts); + + /* QStringList possible_paths{ chr_interjection, ao_app->get_theme_variant_path(p_interjection_name), @@ -128,7 +147,7 @@ void AOMovie::play_interjection(QString p_char_name, { break; } - } + }*/ if (interjection_filepath.isEmpty()) { @@ -140,7 +159,8 @@ void AOMovie::play_interjection(QString p_char_name, << (p_interjection_name.isEmpty() ? "(none)" : p_interjection_name) << "for character" << (p_char_name.isEmpty() ? "(none)" : p_char_name) << "at" - << (interjection_filepath.isEmpty() ? "(not found)" : interjection_filepath); + << (interjection_filepath.isEmpty() ? "(not found)" + : interjection_filepath); m_movie->setFileName(interjection_filepath); this->show(); m_movie->setScaledSize(this->size()); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ed0545e03..b8917ffe8 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -21,8 +21,7 @@ #include #include -Courtroom::Courtroom(AOApplication *p_ao_app) - : QMainWindow() +Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; ao_config = new AOConfig(this); @@ -113,6 +112,7 @@ void Courtroom::enter_courtroom(int p_cid) // This will also handle showing the correct shouts, effects and wtce buttons, // and cycling through them if the buttons that are supposed to be displayed // do not exist It will also take care of free blocks + set_widgets(); check_shouts(); @@ -843,7 +843,8 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) if (objection_mod >= 1 && objection_mod <= ui_shouts.size() && ui_shouts.size() > 0) // check to prevent crashing { - ui_vp_objection->play_interjection(f_char, shout_names.at(objection_mod - 1)); + ui_vp_objection->play_interjection(f_char, + shout_names.at(objection_mod - 1)); m_shouts_player->play(shout_names.at(objection_mod - 1) + ".wav", f_char); } else @@ -995,10 +996,14 @@ void Courtroom::handle_chatmessage_3() // Check for any of 9 possible ways that showname images are QVector exts = {".png", ".jpg", ".bmp"}; - // 3 places (in order) - // 1. Character folder in variant folder in theme folder - // 2. Character folder in theme folder + // 2 places (in order) + // 1. Character folder in appropriate theme folder // 3. Character folder + QString path = + ao_app->find_theme_asset_path("characters/" + f_char + "/showname"); + if (path.isEmpty()) + path = ao_app->get_character_path(f_char, "showname"); + /* QStringList directories = { ao_app->get_theme_variant_path("characters/" + f_char + "/showname"), ao_app->get_theme_path("characters/" + f_char + "/showname"), @@ -1015,7 +1020,7 @@ void Courtroom::handle_chatmessage_3() path = ao_app->get_case_sensitive_path(directory + ext); break; } - } + }*/ if (!path.isEmpty() && !chatmessage_is_empty && ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") @@ -2107,6 +2112,23 @@ void Courtroom::on_cycle_clicked() void Courtroom::cycle_shout(int p_index) { int n = ui_shouts.size(); + + // Check if any shout button is available to begin with + int i; + for (i = 0; i < n; i++) + { + if (shouts_enabled[i]) + break; + } + if (i == n) + { + call_notice( + "Fatal error: no shout button images found in current theme folder."); + exit(1); + } + + // By performing this check beforehand, the do while loop here is guaranteed + // to terminate. do { m_shout_state = (m_shout_state - p_index + n) % n; @@ -2118,6 +2140,22 @@ void Courtroom::cycle_shout(int p_index) void Courtroom::cycle_effect(int p_index) { int n = ui_effects.size(); + // Check if any effect button is available to begin with + int i; + for (i = 0; i < n; i++) + { + if (effects_enabled[i]) + break; + } + if (i == n) + { + call_notice( + "Fatal error: no effect button images found in current theme folder."); + exit(1); + } + + // By performing this check beforehand, the do while loop here is guaranteed + // to terminate. do { m_effect_current = (m_effect_current - p_index + n) % n; @@ -2129,6 +2167,22 @@ void Courtroom::cycle_effect(int p_index) void Courtroom::cycle_wtce(int p_index) { int n = ui_wtce.size(); + // Check if any splash button is available to begin with + int i; + for (i = 0; i < n; i++) + { + if (wtce_enabled[i]) + break; + } + if (i == n) + { + call_notice( + "Fatal error: no splash button images found in current theme folder."); + exit(1); + } + + // By performing this check beforehand, the do while loop here is guaranteed + // to terminate. do { m_wtce_current = (m_wtce_current - p_index + n) % n; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 8a230b816..583c1525f 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -491,6 +491,77 @@ void Courtroom::set_widget_names() void Courtroom::set_widget_layers() { + QString path = ao_app->find_theme_asset_path("courtroom_layers.ini"); + QFile layer_ini(path); + // needed to avoid cyclic parenting + QStringList recorded_widgets; + + // Given the purpose of find_theme_asset_path, this step failing would be + // weird + if (layer_ini.open(QFile::ReadOnly)) + { + QTextStream in(&layer_ini); + + // current parent's name + QString parent_name = "courtroom"; + // the courtroom is ALWAYS going to be recorded + recorded_widgets.append(parent_name); + + while (!in.atEnd()) + { + QString line = in.readLine().trimmed(); + + // skip if line is empty + if (line.isEmpty()) + continue; + + // revert to default parent if we encounter an end scope + if (line.startsWith("[\\")) + { + parent_name = "courtroom"; + } + // is this a parent? + else if (line.startsWith("[")) + { + // update the current parent + parent_name = line.remove(0, 1).chopped(1).trimmed(); + } + // if this is not a parent, it's a child + else + { + // if the child is already known, skip + if (recorded_widgets.contains(line)) + continue; + // make the child known + recorded_widgets.append(line); + + // attach the children to the parents' + QWidget *child = widget_names[line]; + // if child is null, then it does not exist + if (!child) + continue; + + QWidget *parent = widget_names[parent_name]; + // if parent is null, attach main parent + if (!parent) + parent = widget_names["courtroom"]; + + // set child to parent + bool was_visible = child->isVisible(); + child->setParent(parent); + child->raise(); + + // Readjust visibility in case this changed after the widget changed + // parent I don't know why, I don't want to know why, I shouldn't have + // to wonder why, but for whatever reason these stupid panels aren't + // laying out correctly unless we do this terribleness + if (child->isVisible() != was_visible) + child->setVisible(was_visible); + } + } + } + + /* QStringList paths{ ao_app->get_theme_variant_path("courtroom_layers.ini"), ao_app->get_theme_path("courtroom_layers.ini"), @@ -571,6 +642,8 @@ void Courtroom::set_widget_layers() break; } } + */ + // do special logic if config panel was not found in courtroom_layers. In // particular, make it visible and raise it in front of all assets. This can // help assist a theme designer who accidentally missed config_panel and would @@ -1134,8 +1207,15 @@ int Courtroom::adapt_numbered_items(QVector &item_vector, void Courtroom::check_effects() { + QStringList exts{".webp", ".gif", ".apng"}; for (int i = 0; i < ui_effects.size(); ++i) { + QString path = ao_app->find_asset_path( + {ao_app->get_character_path(current_char, effect_names.at(i))}, exts); + if (path.isEmpty()) + path = ao_app->find_theme_asset_path(effect_names.at(i), exts); + effects_enabled[i] = (!path.isEmpty()); + /* QStringList paths{ ao_app->get_character_path(current_char, effect_names.at(i) + ".webp"), ao_app->get_character_path(current_char, effect_names.at(i) + ".gif"), @@ -1156,13 +1236,23 @@ void Courtroom::check_effects() break; } } + */ } } void Courtroom::check_free_blocks() { + QStringList exts{".webp", ".gif", ".apng"}; for (int i = 0; i < ui_free_blocks.size(); ++i) { + QString path = ao_app->find_asset_path( + {ao_app->get_character_path(current_char, free_block_names.at(i))}, + exts); + if (path.isEmpty()) + path = ao_app->find_theme_asset_path(free_block_names.at(i), exts); + free_blocks_enabled[i] = (!path.isEmpty()); + + /* QStringList paths{ ao_app->get_character_path(current_char, free_block_names.at(i) + ".webp"), @@ -1185,61 +1275,82 @@ void Courtroom::check_free_blocks() break; } } + */ } } void Courtroom::check_shouts() { + QStringList exts{".webp", ".gif", ".apng"}; for (int i = 0; i < ui_shouts.size(); ++i) { - QStringList paths{ - ao_app->get_character_path(current_char, shout_names.at(i) + ".webp"), - ao_app->get_character_path(current_char, shout_names.at(i) + ".gif"), - ao_app->get_theme_variant_path(shout_names.at(i) + ".webp"), - ao_app->get_theme_variant_path(shout_names.at(i) + ".gif"), - ao_app->get_theme_variant_path(shout_names.at(i) + ".apng"), - ao_app->get_theme_path(shout_names.at(i) + ".webp"), - ao_app->get_theme_path(shout_names.at(i) + ".gif"), - ao_app->get_theme_path(shout_names.at(i) + ".apng")}; - - // Assume the shout does not exist until a matching file is found - shouts_enabled[i] = false; - for (QString path : paths) + QString path = ao_app->find_asset_path( + {ao_app->get_character_path(current_char, shout_names.at(i))}, exts); + + if (path.isEmpty()) + path = ao_app->find_theme_asset_path(shout_names.at(i), exts); + + call_notice("Final path " + path); + shouts_enabled[i] = (!path.isEmpty()); + /* + QStringList paths{ + ao_app->get_character_path(current_char, shout_names.at(i) + ".webp"), + ao_app->get_character_path(current_char, shout_names.at(i) + ".gif"), + ao_app->get_theme_variant_path(shout_names.at(i) + ".webp"), + ao_app->get_theme_variant_path(shout_names.at(i) + ".gif"), + ao_app->get_theme_variant_path(shout_names.at(i) + ".apng"), + ao_app->get_theme_path(shout_names.at(i) + ".webp"), + ao_app->get_theme_path(shout_names.at(i) + ".gif"), + ao_app->get_theme_path(shout_names.at(i) + ".apng")}; + + // Assume the shout does not exist until a matching file is found + shouts_enabled[i] = false; + for (QString path : paths) + { + if (file_exists(path)) { - if (file_exists(path)) - { - shouts_enabled[i] = true; - break; - } + shouts_enabled[i] = true; + break; } } + */ + } } void Courtroom::check_wtce() { + QStringList exts{".webp", ".gif", ".apng"}; for (int i = 0; i < ui_wtce.size(); ++i) { - QStringList paths{ - ao_app->get_character_path(current_char, wtce_names.at(i) + ".webp"), - ao_app->get_character_path(current_char, wtce_names.at(i) + ".gif"), - ao_app->get_theme_variant_path(wtce_names.at(i) + ".webp"), - ao_app->get_theme_variant_path(wtce_names.at(i) + ".gif"), - ao_app->get_theme_variant_path(wtce_names.at(i) + ".apng"), - ao_app->get_theme_path(wtce_names.at(i) + ".webp"), - ao_app->get_theme_path(wtce_names.at(i) + ".gif"), - ao_app->get_theme_path(wtce_names.at(i) + ".apng")}; - - // Assume the judge button does not exist until a matching file is found - wtce_enabled[i] = false; - for (QString path : paths) + QString path = ao_app->find_asset_path( + {ao_app->get_character_path(current_char, wtce_names.at(i))}, exts); + if (path.isEmpty()) + path = ao_app->find_theme_asset_path(wtce_names.at(i), exts); + wtce_enabled[i] = (!path.isEmpty()); + + /* + QStringList paths{ + ao_app->get_character_path(current_char, wtce_names.at(i) + ".webp"), + ao_app->get_character_path(current_char, wtce_names.at(i) + ".gif"), + ao_app->get_theme_variant_path(wtce_names.at(i) + ".webp"), + ao_app->get_theme_variant_path(wtce_names.at(i) + ".gif"), + ao_app->get_theme_variant_path(wtce_names.at(i) + ".apng"), + ao_app->get_theme_path(wtce_names.at(i) + ".webp"), + ao_app->get_theme_path(wtce_names.at(i) + ".gif"), + ao_app->get_theme_path(wtce_names.at(i) + ".apng")}; + + // Assume the judge button does not exist until a matching file is found + wtce_enabled[i] = false; + for (QString path : paths) + { + if (file_exists(path)) { - if (file_exists(path)) - { - wtce_enabled[i] = true; - break; - } + wtce_enabled[i] = true; + break; } } + */ + } } void Courtroom::delete_widget(QWidget *p_widget) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 6160670c8..9647ac483 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -44,37 +44,12 @@ QString AOApplication::get_theme_path(QString p_file) return get_case_sensitive_path(path); } -QString AOApplication::get_theme_variant_path(QString p_file) -{ - QString path = get_base_path() + "themes/" + get_theme().toLower() + - "/variants/" + get_theme_variant() + "/" + p_file; - return get_case_sensitive_path(path); -} - -QString AOApplication::get_default_theme_path(QString p_file) -{ - QString path = get_base_path() + "themes/default/" + p_file; - - return get_case_sensitive_path(path); -} - QString AOApplication::get_character_path(QString p_character, QString p_file) { QString path = get_base_path() + "characters/" + p_character + "/" + p_file; return get_case_sensitive_path(path); } -// QString AOApplication::get_demothings_path() { -// QString default_path = "misc/demothings/"; -// QString alt_path = "misc/RosterImages"; -// if (dir_exists(default_path)) -// return get_base_path() + default_path; -// else if (dir_exists(alt_path)) -// return get_base_path() + alt_path; -// else -// return get_base_path() + default_path; -//} - QString AOApplication::get_sounds_path(QString p_file) { QString path = get_base_path() + "sounds/general/" + p_file; @@ -173,3 +148,41 @@ QString AOApplication::get_case_sensitive_path(QString p_file) return file_parent_dir + "/" + file_basename; } #endif + +QString AOApplication::find_asset_path(QStringList possible_paths) +{ + return find_asset_path(possible_paths, {""}); +} + +QString AOApplication::find_asset_path(QStringList possible_roots, + QStringList possible_exts) +{ + for (QString root : possible_roots) + { + for (QString ext : possible_exts) + { + QString full_path = get_case_sensitive_path(root + ext); + qDebug() << full_path; + if (file_exists(full_path)) + return full_path; + } + } + return ""; +} + +QString AOApplication::find_theme_asset_path(QString p_file) +{ + return find_theme_asset_path(p_file, {""}); +} + +QString AOApplication::find_theme_asset_path(QString p_file, QStringList exts) +{ + QStringList paths{ + get_base_path() + "themes/" + get_theme().toLower() + "/variants" + + get_theme_variant() + "/" + p_file, + get_base_path() + "themes/" + get_theme() + "/" + p_file, + get_base_path() + "themes/default/" + p_file, + }; + + return find_asset_path(paths, exts); +} diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 7a1f303f1..6c3dea266 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -292,6 +292,39 @@ QString AOApplication::get_sfx(QString p_identifier) QString AOApplication::get_stylesheet(QString target_tag, QString p_file) { + QString path = find_theme_asset_path(p_file); + if (path.isEmpty()) + return ""; + + QFile design_ini(path); + if (!design_ini.open(QIODevice::ReadOnly)) + return ""; + + QTextStream in(&design_ini); + QString f_text; + bool tag_found = false; + + while (!in.atEnd()) + { + QString line = in.readLine(); + if (line.startsWith(target_tag, Qt::CaseInsensitive)) + { + tag_found = true; + continue; + } + + if (tag_found) + { + if ((line.startsWith("[") && line.endsWith("]"))) + break; + f_text.append(line); + } + } + + design_ini.close(); + return f_text; // This is the empty string if no appends took place + + /* QStringList paths{get_theme_variant_path(p_file), get_theme_path(p_file)}; for (QString path : paths) @@ -329,10 +362,57 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) // Default value in case everything fails, return an empty string return ""; + */ } QVector AOApplication::get_highlight_color() { + QString path = find_theme_asset_path("courtroom_config.ini"); + if (path.isEmpty()) + return QVector(); + + QFile design_ini(path); + if (!design_ini.open(QIODevice::ReadOnly)) + return QVector(); + + QTextStream in(&design_ini); + bool tag_found = false; + QVector f_vec; + + while (!in.atEnd()) + { + QString line = in.readLine(); + + if (line.startsWith("[HIGHLIGHTS]", Qt::CaseInsensitive)) + { + tag_found = true; + continue; + } + + if (tag_found) + { + if ((line.startsWith("[") && line.endsWith("]"))) + break; + // Syntax + // OpenercharCloserchar = Color, Shown + // Shown is 1 if the character should be displayed in IC, 0 otherwise. + // If not present, assume 1. + QString chars = line.split("=")[0].trimmed(); + QString chars_parameters = line.mid(line.indexOf("=") + 1); + QStringList parameters = chars_parameters.split(","); + for (int i = 0; i < parameters.size(); i++) + parameters[i] = parameters[i].trimmed(); + if (parameters.size() == 1) + parameters.append("1"); + f_vec.append({chars, parameters[0], parameters[1]}); + } + } + + design_ini.close(); + return f_vec; // Could be an empty vector if no appends were made + + /* + QString p_file = "courtroom_config.ini"; QStringList paths{get_theme_variant_path(p_file), get_theme_path(p_file)}; @@ -385,10 +465,48 @@ QVector AOApplication::get_highlight_color() // Default value in case everything fails, return an empty vector QVector f_vec; return f_vec; + */ } QString AOApplication::get_spbutton(QString p_tag, int index) { + QString path = find_theme_asset_path("courtroom_config.ini"); + if (path.isEmpty()) + return ""; + + QFile design_ini(path); + if (!design_ini.open(QIODevice::ReadOnly)) + return ""; + + QTextStream in(&design_ini); + bool tag_found = false; + QString res; + + while (!in.atEnd()) + { + QString line = in.readLine(); + + if (line.startsWith(p_tag, Qt::CaseInsensitive)) + { + tag_found = true; + continue; + } + + if (tag_found) + { + if ((line.startsWith("[") && line.endsWith("]"))) + break; + + QStringList line_contents = line.split("="); + if (line_contents.at(0).trimmed() == QString::number(index)) + res = line_contents.at(1); + } + } + + design_ini.close(); + return res; // Could be the empty string if no matches were found. + + /* QString p_file = "courtroom_config.ini"; QStringList paths{get_theme_variant_path(p_file), get_theme_path(p_file)}; @@ -432,10 +550,51 @@ QString AOApplication::get_spbutton(QString p_tag, int index) // Default value in case everything fails, return an empty string return ""; + */ } QStringList AOApplication::get_effect(int index) { + QString path = find_theme_asset_path("courtroom_config.ini"); + if (path.isEmpty()) + return QStringList(); + + QFile design_ini(path); + if (!design_ini.open(QIODevice::ReadOnly)) + return QStringList(); + + QTextStream in(&design_ini); + bool tag_found = false; + QStringList res; + + while (!in.atEnd()) + { + QString line = in.readLine(); + + if (line.startsWith("[EFFECTS]", Qt::CaseInsensitive)) + { + tag_found = true; + continue; + } + + if (tag_found) + { + if ((line.startsWith("[") && line.endsWith("]"))) + break; + + QStringList line_contents = line.split("="); + if (line_contents.at(0).trimmed() == QString::number(index)) + res = line_contents.at(1).split(","); + + if (res.size() == 1) + res.append("1"); + } + } + + design_ini.close(); + return res; + + /* QString p_file = "courtroom_config.ini"; QStringList paths{get_theme_variant_path(p_file), get_theme_path(p_file)}; @@ -483,6 +642,7 @@ QStringList AOApplication::get_effect(int index) // Default value in case everything fails, return an empty string list QStringList res; return res; + */ } QStringList AOApplication::get_sfx_list() @@ -806,8 +966,13 @@ bool AOApplication::get_blank_blip() QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) { - // Try to obtain a theme ini from either the current theme variant folder, - // the current theme folder or the default theme folder + // Try to obtain a theme ini from any of the valid files + QString path = find_theme_asset_path(p_file); + if (path.isEmpty()) + return ""; + + return read_design_ini(p_identifier, path); // Could be the empty string + /* QStringList paths{ get_theme_variant_path(p_file), get_theme_path(p_file), @@ -822,10 +987,13 @@ QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) } return ""; + */ } QString AOApplication::get_image_path(QString p_image) { + return find_theme_asset_path(p_image); + /* QString theme_variant_image_path = get_theme_variant_path(p_image); QString theme_image_path = get_theme_path(p_image); QString default_image_path = get_default_theme_path(p_image); @@ -837,4 +1005,5 @@ QString AOApplication::get_image_path(QString p_image) else if (file_exists(theme_image_path)) return theme_image_path; return default_image_path; + */ } From 783bc07d71c6d598ba07ba73bd4cba3bad2f4fab Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 1 Oct 2020 11:21:20 -0400 Subject: [PATCH 122/842] Remove old comment + Specify asset/file lookup order where relevant --- src/aocharmovie.cpp | 36 ++---- src/aomovie.cpp | 106 +++++----------- src/courtroom.cpp | 28 +---- src/courtroom_widgets.cpp | 207 +++++------------------------- src/text_file_functions.cpp | 243 ++++-------------------------------- 5 files changed, 96 insertions(+), 524 deletions(-) diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 7abbfa251..166c32a9b 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -26,6 +26,14 @@ AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, bool p_visible) { + // Asset lookup order + // 1. In the character folder, look for + // `p_emote_prefix+p_emote` + extensions in `exts` in order + // 2. In the character folder, look for + // `p_emote` + extensions in `exts` in order + // 3. In the theme folder (variant/main/default), look for + // "placeholder" + extensions in `exts` in order + QStringList exts{".webp", ".apng", ".gif", ".png"}; QString target_path = ao_app->find_asset_path( { @@ -36,34 +44,6 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, if (target_path.isEmpty()) target_path = ao_app->find_theme_asset_path("placeholder", exts); - /* - QStringList f_paths{ - ao_app->get_character_path(p_char, p_emote_prefix + p_emote), // .gif - ao_app->get_character_path(p_char, p_emote), // .png - ao_app->get_theme_variant_path("placeholder"), // .gif - ao_app->get_theme_path("placeholder"), // .gif - ao_app->get_default_theme_path("placeholder") // .gif - }; - - for (auto &f_file : f_paths) - { - bool found = false; - for (auto &ext : QStringList{".webp", ".apng", ".gif", ".png"}) - { - QString fullPath = ao_app->get_case_sensitive_path(f_file + ext); - found = file_exists(fullPath); - if (found) - { - target_path = fullPath; - break; - } - } - - if (found) - break; - } - */ - show(); if (!p_visible) hide(); diff --git a/src/aomovie.cpp b/src/aomovie.cpp index c5e563edf..217946573 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -37,17 +37,29 @@ void AOMovie::play(QString p_file, QString p_char) if (p_file.length() > 0 && p_file.at(0) == "!") p_file = p_file.remove(0, 1); - QString custom_path; - if (p_file == "custom") - custom_path = ao_app->get_character_path(p_char, p_file); + QString char_p_file; + // FIXME: When looking in the character folder, append "_bubble" except for + // custom We probably should drop this + if (p_file != "custom") + char_p_file = p_file + "_bubble"; else - custom_path = ao_app->get_character_path(p_char, p_file + "_bubble"); + char_p_file = p_file; + + // Asset lookup order + // 1. In the character folder, look for + // `char_p_file` + extensions in `exts` in order + // 2. In the character folder, look for + // `overlay/char_p_file` + extensions in `exts` in order + // 3. In the theme folder (variant/main/default), look for + // `p_file` + extensions in `exts` in order + // 4. In the theme folder (variant/main/default), look for + // "placeholder" + extensions in `exts` in order QStringList exts{".webp", ".apng", ".gif", ".png"}; file_path = ao_app->find_asset_path( { - custom_path, - ao_app->get_character_path(p_char, "overlay/" + p_file), + ao_app->get_character_path(p_char, char_p_file), + ao_app->get_character_path(p_char, "overlay/" + char_p_file), }, exts); if (file_path.isEmpty()) @@ -56,35 +68,7 @@ void AOMovie::play(QString p_file, QString p_char) if (file_path.isEmpty()) file_path = ao_app->find_theme_asset_path("placeholder", exts); } - /* - QStringList f_paths{ - ao_app->get_theme_variant_path(p_file), - ao_app->get_theme_path(p_file), - ao_app->get_default_theme_path(p_file), - ao_app->get_theme_variant_path("placeholder"), - ao_app->get_theme_path("placeholder"), - ao_app->get_default_theme_path("placeholder"), - }; - - for (auto &f_file : f_paths) - { - bool found = false; - for (auto &ext : decltype(f_vec){".webp", ".apng", ".gif", ".png"}) - { - QString fullPath = ao_app->get_case_sensitive_path(f_file + ext); - found = file_exists(fullPath); - if (found) - { - file_path = fullPath; - break; - } - } - - if (found) - break; - }*/ - qDebug() << file_path; qDebug() << "playing" << file_path; m_movie->setFileName(file_path); @@ -98,57 +82,25 @@ void AOMovie::play_interjection(QString p_char_name, { m_movie->stop(); - QString chr_interjection; - { - QString interjection_suffix = "_bubble"; - - // FIXME there is no reason custom shouldn't have a suffix - if (p_interjection_name.toLower() == "custom") - { - interjection_suffix.clear(); - } + QString p_char_interjection_name; + // FIXME: When looking in the character folder, append "_bubble" except for + // custom We probably should drop this + if (p_interjection_name.toLower() != "custom") + p_char_interjection_name = p_interjection_name + "_bubble"; + else + p_char_interjection_name = p_interjection_name; - chr_interjection = ao_app->get_character_path( - p_char_name, p_interjection_name + interjection_suffix); - } + // Asset lookup order + // 1. In the character folder, look for `p_char_interjection_name` + // 2. In the theme folder (variant/main/default), look for `p_char_name` QStringList exts{".webp", ".apng", ".gif"}; QString interjection_filepath = - ao_app->find_asset_path({chr_interjection}, exts); + ao_app->find_asset_path({p_char_interjection_name}, exts); if (interjection_filepath.isEmpty()) interjection_filepath = ao_app->find_theme_asset_path(p_interjection_name, exts); - /* - QStringList possible_paths{ - chr_interjection, - ao_app->get_theme_variant_path(p_interjection_name), - ao_app->get_theme_path(p_interjection_name), - ao_app->get_default_theme_path(p_interjection_name), - }; - - QString interjection_filepath; - for (QString &i_path : possible_paths) - { - bool skip = false; - - for (QString &i_ext : QStringList{".webp", ".apng", ".gif"}) - { - QString fullpath = ao_app->get_case_sensitive_path(i_path + i_ext); - if (file_exists(fullpath)) - { - skip = true; - interjection_filepath = fullpath; - break; - } - } - - if (skip) - { - break; - } - }*/ - if (interjection_filepath.isEmpty()) { emit done(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index b8917ffe8..fc39b61cd 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -994,33 +994,17 @@ void Courtroom::handle_chatmessage_3() ui_vp_showname_image->show(); - // Check for any of 9 possible ways that showname images are + // Asset lookup order + // 1. In the theme folder (variant/main/default), in the character folder, + // look for "showname" + extensions in `exts` in order + // 2. In the character folder, look for + // "showname" + extensions in `exts` in order + QVector exts = {".png", ".jpg", ".bmp"}; - // 2 places (in order) - // 1. Character folder in appropriate theme folder - // 3. Character folder QString path = ao_app->find_theme_asset_path("characters/" + f_char + "/showname"); if (path.isEmpty()) path = ao_app->get_character_path(f_char, "showname"); - /* - QStringList directories = { - ao_app->get_theme_variant_path("characters/" + f_char + "/showname"), - ao_app->get_theme_path("characters/" + f_char + "/showname"), - ao_app->get_character_path(f_char, "showname"), - }; - - qDebug() << directories; - QString ext, path; - for (QString directory : directories) - { - ext = ao_app->get_file_extension(directory, exts); - if (!ext.isEmpty()) - { - path = ao_app->get_case_sensitive_path(directory + ext); - break; - } - }*/ if (!path.isEmpty() && !chatmessage_is_empty && ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 583c1525f..ecdb7a3a9 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -491,6 +491,10 @@ void Courtroom::set_widget_names() void Courtroom::set_widget_layers() { + // File lookup order + // 1. In the theme folder (variant/main/default), look for + // "courtroom_layers.ini". + QString path = ao_app->find_theme_asset_path("courtroom_layers.ini"); QFile layer_ini(path); // needed to avoid cyclic parenting @@ -561,89 +565,6 @@ void Courtroom::set_widget_layers() } } - /* - QStringList paths{ - ao_app->get_theme_variant_path("courtroom_layers.ini"), - ao_app->get_theme_path("courtroom_layers.ini"), - ao_app->get_default_theme_path("courtroom_layers.ini"), - }; - - // needed to avoid cyclic parenting - QStringList recorded_widgets; - - // read the entire thing - for (QString path : paths) - { - QFile layer_ini(path); - - if (layer_ini.open(QFile::ReadOnly)) - { - QTextStream in(&layer_ini); - - // current parent's name - QString parent_name = "courtroom"; - // the courtroom is ALWAYS going to be recorded - recorded_widgets.append(parent_name); - - while (!in.atEnd()) - { - QString line = in.readLine().trimmed(); - - // skip if line is empty - if (line.isEmpty()) - continue; - - // revert to default parent if we encounter an end scope - if (line.startsWith("[\\")) - { - parent_name = "courtroom"; - } - // is this a parent? - else if (line.startsWith("[")) - { - // update the current parent - parent_name = line.remove(0, 1).chopped(1).trimmed(); - } - // if this is not a parent, it's a child - else - { - // if the child is already known, skip - if (recorded_widgets.contains(line)) - continue; - // make the child known - recorded_widgets.append(line); - - // attach the children to the parents' - QWidget *child = widget_names[line]; - // if child is null, then it does not exist - if (!child) - continue; - - QWidget *parent = widget_names[parent_name]; - // if parent is null, attach main parent - if (!parent) - parent = widget_names["courtroom"]; - - // set child to parent - bool was_visible = child->isVisible(); - child->setParent(parent); - child->raise(); - - // Readjust visibility in case this changed after the widget changed - // parent I don't know why, I don't want to know why, I shouldn't have - // to wonder why, but for whatever reason these stupid panels aren't - // laying out correctly unless we do this terribleness - if (child->isVisible() != was_visible) - child->setVisible(was_visible); - } - } - - // break the loop, we have found a proper file - break; - } - } - */ - // do special logic if config panel was not found in courtroom_layers. In // particular, make it visible and raise it in front of all assets. This can // help assist a theme designer who accidentally missed config_panel and would @@ -1207,6 +1128,13 @@ int Courtroom::adapt_numbered_items(QVector &item_vector, void Courtroom::check_effects() { + // Asset lookup order + // 1. In the character folder, look for + // `effect_names.at(i)` + extensions in `exts` in order + // 2. In the theme folder (variant/main/default), look for + // `effect_names.at(i)` + extensions in `exts` in order + // Only enable buttons where a file was found + QStringList exts{".webp", ".gif", ".apng"}; for (int i = 0; i < ui_effects.size(); ++i) { @@ -1215,33 +1143,18 @@ void Courtroom::check_effects() if (path.isEmpty()) path = ao_app->find_theme_asset_path(effect_names.at(i), exts); effects_enabled[i] = (!path.isEmpty()); - /* - QStringList paths{ - ao_app->get_character_path(current_char, effect_names.at(i) + ".webp"), - ao_app->get_character_path(current_char, effect_names.at(i) + ".gif"), - ao_app->get_theme_variant_path(effect_names.at(i) + ".webp"), - ao_app->get_theme_variant_path(effect_names.at(i) + ".gif"), - ao_app->get_theme_variant_path(effect_names.at(i) + ".apng"), - ao_app->get_theme_path(effect_names.at(i) + ".webp"), - ao_app->get_theme_path(effect_names.at(i) + ".gif"), - ao_app->get_theme_path(effect_names.at(i) + ".apng")}; - - // Assume the effect does not exist until a matching file is found - effects_enabled[i] = false; - for (QString path : paths) - { - if (file_exists(path)) - { - effects_enabled[i] = true; - break; - } - } - */ } } void Courtroom::check_free_blocks() { + // Asset lookup order + // 1. In the character folder, look for + // `free_block_names.at(i)` + extensions in `exts` in order + // 2. In the theme folder (variant/main/default), look for + // `free_block_names.at(i)` + extensions in `exts` in order + // Only enable buttons where a file was found + QStringList exts{".webp", ".gif", ".apng"}; for (int i = 0; i < ui_free_blocks.size(); ++i) { @@ -1251,36 +1164,18 @@ void Courtroom::check_free_blocks() if (path.isEmpty()) path = ao_app->find_theme_asset_path(free_block_names.at(i), exts); free_blocks_enabled[i] = (!path.isEmpty()); - - /* - QStringList paths{ - ao_app->get_character_path(current_char, - free_block_names.at(i) + ".webp"), - ao_app->get_character_path(current_char, - free_block_names.at(i) + ".gif"), - ao_app->get_theme_variant_path(free_block_names.at(i) + ".webp"), - ao_app->get_theme_variant_path(free_block_names.at(i) + ".gif"), - ao_app->get_theme_variant_path(free_block_names.at(i) + ".apng"), - ao_app->get_theme_path(free_block_names.at(i) + ".webp"), - ao_app->get_theme_path(free_block_names.at(i) + ".gif"), - ao_app->get_theme_path(free_block_names.at(i) + ".apng")}; - - // Assume the free block does not exist until a matching file is found - free_blocks_enabled[i] = false; - for (QString path : paths) - { - if (file_exists(path)) - { - free_blocks_enabled[i] = true; - break; - } - } - */ } } void Courtroom::check_shouts() { + // Asset lookup order + // 1. In the character folder, look for + // `shout_names.at(i)` + extensions in `exts` in order + // 2. In the theme folder (variant/main/default), look for + // `shout_names.at(i)` + extensions in `exts` in order + // Only enable buttons where a file was found + QStringList exts{".webp", ".gif", ".apng"}; for (int i = 0; i < ui_shouts.size(); ++i) { @@ -1292,33 +1187,18 @@ void Courtroom::check_shouts() call_notice("Final path " + path); shouts_enabled[i] = (!path.isEmpty()); - /* - QStringList paths{ - ao_app->get_character_path(current_char, shout_names.at(i) + ".webp"), - ao_app->get_character_path(current_char, shout_names.at(i) + ".gif"), - ao_app->get_theme_variant_path(shout_names.at(i) + ".webp"), - ao_app->get_theme_variant_path(shout_names.at(i) + ".gif"), - ao_app->get_theme_variant_path(shout_names.at(i) + ".apng"), - ao_app->get_theme_path(shout_names.at(i) + ".webp"), - ao_app->get_theme_path(shout_names.at(i) + ".gif"), - ao_app->get_theme_path(shout_names.at(i) + ".apng")}; - - // Assume the shout does not exist until a matching file is found - shouts_enabled[i] = false; - for (QString path : paths) - { - if (file_exists(path)) - { - shouts_enabled[i] = true; - break; - } - } - */ } } void Courtroom::check_wtce() { + // Asset lookup order + // 1. In the character folder, look for + // `wtce_names.at(i)` + extensions in `exts` in order + // 2. In the theme folder (variant/main/default), look for + // `wtce_names.at(i)` + extensions in `exts` in order + // Only enable buttons where a file was found + QStringList exts{".webp", ".gif", ".apng"}; for (int i = 0; i < ui_wtce.size(); ++i) { @@ -1327,29 +1207,6 @@ void Courtroom::check_wtce() if (path.isEmpty()) path = ao_app->find_theme_asset_path(wtce_names.at(i), exts); wtce_enabled[i] = (!path.isEmpty()); - - /* - QStringList paths{ - ao_app->get_character_path(current_char, wtce_names.at(i) + ".webp"), - ao_app->get_character_path(current_char, wtce_names.at(i) + ".gif"), - ao_app->get_theme_variant_path(wtce_names.at(i) + ".webp"), - ao_app->get_theme_variant_path(wtce_names.at(i) + ".gif"), - ao_app->get_theme_variant_path(wtce_names.at(i) + ".apng"), - ao_app->get_theme_path(wtce_names.at(i) + ".webp"), - ao_app->get_theme_path(wtce_names.at(i) + ".gif"), - ao_app->get_theme_path(wtce_names.at(i) + ".apng")}; - - // Assume the judge button does not exist until a matching file is found - wtce_enabled[i] = false; - for (QString path : paths) - { - if (file_exists(path)) - { - wtce_enabled[i] = true; - break; - } - } - */ } } diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 6c3dea266..d7ace6d33 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -292,6 +292,9 @@ QString AOApplication::get_sfx(QString p_identifier) QString AOApplication::get_stylesheet(QString target_tag, QString p_file) { + // File lookup order + // 1. In the theme folder (variant/main/default), look for `p_file`. + QString path = find_theme_asset_path(p_file); if (path.isEmpty()) return ""; @@ -323,50 +326,14 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) design_ini.close(); return f_text; // This is the empty string if no appends took place - - /* - QStringList paths{get_theme_variant_path(p_file), get_theme_path(p_file)}; - - for (QString path : paths) - { - QFile design_ini; - design_ini.setFileName(path); - if (!design_ini.open(QIODevice::ReadOnly)) - continue; - - QTextStream in(&design_ini); - QString f_text; - bool tag_found = false; - - while (!in.atEnd()) - { - QString line = in.readLine(); - if (line.startsWith(target_tag, Qt::CaseInsensitive)) - { - tag_found = true; - continue; - } - - if (tag_found) - { - if ((line.startsWith("[") && line.endsWith("]"))) - break; - f_text.append(line); - } - } - - design_ini.close(); - if (!f_text.isEmpty()) - return f_text; - } - - // Default value in case everything fails, return an empty string - return ""; - */ } QVector AOApplication::get_highlight_color() { + // File lookup order + // 1. In the theme folder (variant/main/default), look for + // "courtroom_config.ini". + QString path = find_theme_asset_path("courtroom_config.ini"); if (path.isEmpty()) return QVector(); @@ -410,66 +377,14 @@ QVector AOApplication::get_highlight_color() design_ini.close(); return f_vec; // Could be an empty vector if no appends were made - - /* - - QString p_file = "courtroom_config.ini"; - QStringList paths{get_theme_variant_path(p_file), get_theme_path(p_file)}; - - for (QString path : paths) - { - QVector f_vec; - - QFile design_ini; - design_ini.setFileName(path); - if (!design_ini.open(QIODevice::ReadOnly)) - continue; - - QTextStream in(&design_ini); - bool tag_found = false; - - while (!in.atEnd()) - { - QString line = in.readLine(); - - if (line.startsWith("[HIGHLIGHTS]", Qt::CaseInsensitive)) - { - tag_found = true; - continue; - } - - if (tag_found) - { - if ((line.startsWith("[") && line.endsWith("]"))) - break; - // Syntax - // OpenercharCloserchar = Color, Shown - // Shown is 1 if the character should be displayed in IC, 0 otherwise. - // If not present, assume 1. - QString chars = line.split("=")[0].trimmed(); - QString chars_parameters = line.mid(line.indexOf("=") + 1); - QStringList parameters = chars_parameters.split(","); - for (int i = 0; i < parameters.size(); i++) - parameters[i] = parameters[i].trimmed(); - if (parameters.size() == 1) - parameters.append("1"); - f_vec.append({chars, parameters[0], parameters[1]}); - } - } - - design_ini.close(); - if (!f_vec.isEmpty()) - return f_vec; - } - - // Default value in case everything fails, return an empty vector - QVector f_vec; - return f_vec; - */ } QString AOApplication::get_spbutton(QString p_tag, int index) { + // File lookup order + // 1. In the theme folder (variant/main/default), look for + // "courtroom_config.ini". + QString path = find_theme_asset_path("courtroom_config.ini"); if (path.isEmpty()) return ""; @@ -505,56 +420,14 @@ QString AOApplication::get_spbutton(QString p_tag, int index) design_ini.close(); return res; // Could be the empty string if no matches were found. - - /* - QString p_file = "courtroom_config.ini"; - QStringList paths{get_theme_variant_path(p_file), get_theme_path(p_file)}; - - for (QString path : paths) - { - QString res = ""; - - QFile design_ini; - design_ini.setFileName(path); - if (!design_ini.open(QIODevice::ReadOnly)) - continue; - - QTextStream in(&design_ini); - bool tag_found = false; - - while (!in.atEnd()) - { - QString line = in.readLine(); - - if (line.startsWith(p_tag, Qt::CaseInsensitive)) - { - tag_found = true; - continue; - } - - if (tag_found) - { - if ((line.startsWith("[") && line.endsWith("]"))) - break; - - QStringList line_contents = line.split("="); - if (line_contents.at(0).trimmed() == QString::number(index)) - res = line_contents.at(1); - } - } - - design_ini.close(); - if (!res.isEmpty()) - return res; - } - - // Default value in case everything fails, return an empty string - return ""; - */ } QStringList AOApplication::get_effect(int index) { + // File lookup order + // 1. In the theme folder (variant/main/default), look for + // "courtroom_config.ini". + QString path = find_theme_asset_path("courtroom_config.ini"); if (path.isEmpty()) return QStringList(); @@ -593,56 +466,6 @@ QStringList AOApplication::get_effect(int index) design_ini.close(); return res; - - /* - QString p_file = "courtroom_config.ini"; - QStringList paths{get_theme_variant_path(p_file), get_theme_path(p_file)}; - - for (QString path : paths) - { - QStringList res; - - QFile design_ini; - design_ini.setFileName(path); - if (!design_ini.open(QIODevice::ReadOnly)) - continue; - - QTextStream in(&design_ini); - bool tag_found = false; - - while (!in.atEnd()) - { - QString line = in.readLine(); - - if (line.startsWith("[EFFECTS]", Qt::CaseInsensitive)) - { - tag_found = true; - continue; - } - - if (tag_found) - { - if ((line.startsWith("[") && line.endsWith("]"))) - break; - - QStringList line_contents = line.split("="); - if (line_contents.at(0).trimmed() == QString::number(index)) - res = line_contents.at(1).split(","); - - if (res.size() == 1) - res.append("1"); - } - } - - design_ini.close(); - if (!res.isEmpty()) - return res; - } - - // Default value in case everything fails, return an empty string list - QStringList res; - return res; - */ } QStringList AOApplication::get_sfx_list() @@ -966,44 +789,20 @@ bool AOApplication::get_blank_blip() QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) { - // Try to obtain a theme ini from any of the valid files + // File lookup order + // 1. In the theme folder (variant/main/default), look for + // `p_identifier`. QString path = find_theme_asset_path(p_file); if (path.isEmpty()) return ""; return read_design_ini(p_identifier, path); // Could be the empty string - /* - QStringList paths{ - get_theme_variant_path(p_file), - get_theme_path(p_file), - get_default_theme_path(p_file), - }; - - for (QString path : paths) - { - QString f_result = read_design_ini(p_identifier, path); - if (!f_result.isEmpty()) - return f_result; - } - - return ""; - */ } QString AOApplication::get_image_path(QString p_image) { + // File lookup order + // 1. In the theme folder (variant/main/default), look for + // `p_image`. return find_theme_asset_path(p_image); - /* - QString theme_variant_image_path = get_theme_variant_path(p_image); - QString theme_image_path = get_theme_path(p_image); - QString default_image_path = get_default_theme_path(p_image); - - QString final_image_path; - - if (file_exists(theme_variant_image_path)) - return theme_variant_image_path; - else if (file_exists(theme_image_path)) - return theme_image_path; - return default_image_path; - */ } From e15ec329024a9884cb8a1f57e4fb842bfe4f98e2 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 1 Oct 2020 11:36:16 -0400 Subject: [PATCH 123/842] Remove case insensitivity+Fix interjections looking at wrong place+Remove leftover code/qDebug --- include/aoapplication.h | 4 ---- src/aomovie.cpp | 5 +++-- src/courtroom_widgets.cpp | 1 - src/path_functions.cpp | 3 +-- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 3f9f80c1f..c26dba2df 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -176,10 +176,6 @@ class AOApplication : public QApplication // variable QString get_theme(); - // Reads the gamemode from config.ini and loads it into the current_gamemode - // variable - QString get_gamemode(); - // Returns the blip rate from config.ini int read_blip_rate(); diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 217946573..10e236c19 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -95,8 +95,9 @@ void AOMovie::play_interjection(QString p_char_name, // 2. In the theme folder (variant/main/default), look for `p_char_name` QStringList exts{".webp", ".apng", ".gif"}; - QString interjection_filepath = - ao_app->find_asset_path({p_char_interjection_name}, exts); + QString interjection_filepath = ao_app->find_asset_path( + {ao_app->get_character_path(p_char_name, p_char_interjection_name)}, + exts); if (interjection_filepath.isEmpty()) interjection_filepath = ao_app->find_theme_asset_path(p_interjection_name, exts); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index ecdb7a3a9..6c214976c 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1185,7 +1185,6 @@ void Courtroom::check_shouts() if (path.isEmpty()) path = ao_app->find_theme_asset_path(shout_names.at(i), exts); - call_notice("Final path " + path); shouts_enabled[i] = (!path.isEmpty()); } } diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 9647ac483..3efba5a1d 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -162,7 +162,6 @@ QString AOApplication::find_asset_path(QStringList possible_roots, for (QString ext : possible_exts) { QString full_path = get_case_sensitive_path(root + ext); - qDebug() << full_path; if (file_exists(full_path)) return full_path; } @@ -178,7 +177,7 @@ QString AOApplication::find_theme_asset_path(QString p_file) QString AOApplication::find_theme_asset_path(QString p_file, QStringList exts) { QStringList paths{ - get_base_path() + "themes/" + get_theme().toLower() + "/variants" + + get_base_path() + "themes/" + get_theme() + "/variants/" + get_theme_variant() + "/" + p_file, get_base_path() + "themes/" + get_theme() + "/" + p_file, get_base_path() + "themes/default/" + p_file, From efed600b0218157b56c375ef179103077059db71 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 2 Oct 2020 01:27:18 -0400 Subject: [PATCH 124/842] Rename variants to gamemodes --- include/aoapplication.h | 13 +++++----- include/aoconfig.h | 6 ++--- include/aoconfigpanel.h | 8 +++--- include/aoimage.h | 1 - include/courtroom.h | 8 +++--- res/ui/config_panel.ui | 6 ++--- src/aoapplication.cpp | 10 ++++---- src/aocharmovie.cpp | 2 +- src/aoconfig.cpp | 22 ++++++++-------- src/aoconfigpanel.cpp | 50 ++++++++++++++++++++----------------- src/aomovie.cpp | 8 +++--- src/courtroom.cpp | 14 ++++++----- src/courtroom_widgets.cpp | 13 +++++----- src/packet_distribution.cpp | 4 +-- src/path_functions.cpp | 4 +-- src/text_file_functions.cpp | 16 ++++++------ 16 files changed, 94 insertions(+), 91 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index c26dba2df..eddbb2380 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -115,8 +115,7 @@ class AOApplication : public QApplication QString get_base_path(); QString get_data_path(); QString get_theme_path(QString p_file); - // Get the theme variant - QString get_theme_variant(); + QString get_gamemode(); QString get_character_path(QString p_character, QString p_file); // QString get_demothings_path(); QString get_sounds_path(QString p_file); @@ -240,8 +239,8 @@ class AOApplication : public QApplication // Overwrites config.ini with new theme void write_theme(QString theme); - // Set the theme variant - void set_theme_variant(QString m_theme_variant); + // Set the gamemode + void set_gamemode(QString m_gamemode); // Returns the contents of serverlist.txt QVector read_serverlist_txt(); @@ -249,7 +248,7 @@ class AOApplication : public QApplication // Returns the value of p_identifier in the design.ini file in p_design_path QString read_design_ini(QString p_identifier, QString p_design_path); - // Returns the value of p_identifier from p_file in either a theme variant + // Returns the value of p_identifier from p_file in either a theme gamemode // subfolder, a theme folder, or default theme folder QString read_theme_ini(QString p_identifier, QString p_file); @@ -344,7 +343,7 @@ class AOApplication : public QApplication // Returns p_char's gender QString get_gender(QString p_char); - // Get the location of p_image, which is either in a theme variant subfolder, + // Get the location of p_image, which is either in a theme gamemode subfolder, // a theme folder, or default theme folder QString get_image_path(QString p_image); @@ -365,7 +364,7 @@ private slots: void on_courtroom_destroyed(); void on_config_theme_changed(); void on_config_reload_theme_requested(); - void on_config_theme_variant_changed(); + void on_config_gamemode_changed(); public slots: void server_disconnected(); diff --git a/include/aoconfig.h b/include/aoconfig.h index d592c9bbc..e1fac51a3 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -20,7 +20,7 @@ class AOConfig : public QObject QString username(); QString callwords(); QString theme(); - QString theme_variant(); + QString gamemode(); bool always_pre_enabled(); int chat_tick_interval(); bool server_alerts_enabled(); @@ -45,7 +45,7 @@ public slots: void set_username(QString p_string); void set_callwords(QString p_string); void set_theme(QString p_string); - void set_theme_variant(QString p_string); + void set_gamemode(QString p_string); void set_server_alerts(bool p_enabled); void set_server_alerts(int p_state); void set_always_pre(bool p_enabled); @@ -73,7 +73,7 @@ public slots: void username_changed(QString); void callwords_changed(QString); void theme_changed(QString); - void theme_variant_changed(QString); + void gamemode_changed(QString); void server_alerts_changed(bool); void always_pre_changed(bool); void chat_tick_interval_changed(int); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 867620f52..7b6d92716 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -6,10 +6,10 @@ #include #include #include +#include #include #include #include -#include #include // src #include "aoconfig.h" @@ -26,7 +26,7 @@ class AOConfigPanel : public QWidget QLineEdit *w_callwords = nullptr; QComboBox *w_theme = nullptr; QPushButton *w_reload_theme = nullptr; - QComboBox *w_theme_variant = nullptr; + QComboBox *w_gamemode = nullptr; QCheckBox *w_always_pre = nullptr; QSpinBox *w_chat_tick_interval = nullptr; QCheckBox *w_server_alerts = nullptr; @@ -65,11 +65,11 @@ public slots: private: void refresh_theme_list(); - void refresh_theme_variant_list(); + void refresh_gamemode_list(); private slots: void on_reload_theme_clicked(); - void on_theme_variant_index_changed(QString p_text); + void on_gamemode_index_changed(QString p_text); void on_log_is_topdown_changed(bool p_enabled); void on_effects_value_changed(int p_num); void on_system_value_changed(int p_num); diff --git a/include/aoimage.h b/include/aoimage.h index 0f91fc638..fb59ede40 100644 --- a/include/aoimage.h +++ b/include/aoimage.h @@ -14,7 +14,6 @@ class AOImage : public QLabel AOImage(QWidget *parent, AOApplication *p_ao_app); void set_image(QString p_image); - void set_image_variant(QString p_image, QString p_variant); void set_image_from_path(QString p_path); void set_size_and_pos(QString identifier); diff --git a/include/courtroom.h b/include/courtroom.h index 30dc84cb0..961f32b63 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -225,8 +225,8 @@ class Courtroom : public QMainWindow // handle server-side clock animation and display void handle_clock(QString time); - // handle request to change theme variant - void handle_theme_variant(QString theme_variant); + // handle request to change gamemode + void handle_gamemode(QString gamemode); void play_preanim(); @@ -563,8 +563,8 @@ class Courtroom : public QMainWindow QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip QVector ui_label_images; - QVector label_images = {"Pre", "Flip", "Hidden", - "Music", "SFX", "Blip"}; + QVector label_images = {"Pre", "Flip", "Hidden", + "Music", "SFX", "Blip"}; AOButton *ui_effect_flash = nullptr; AOButton *ui_effect_gloom = nullptr; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 30947d3fd..2f30b7ad1 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -118,14 +118,14 @@ - + - Theme variant: + Gamemode: - + diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index d937e2083..b1affa13c 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -23,8 +23,8 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) config = new AOConfig(this); connect(config, SIGNAL(theme_changed(QString)), this, SLOT(on_config_theme_changed())); - connect(config, SIGNAL(theme_variant_changed(QString)), this, - SLOT(on_config_theme_variant_changed())); + connect(config, SIGNAL(gamemode_changed(QString)), this, + SLOT(on_config_gamemode_changed())); config_panel = new AOConfigPanel; connect(config_panel, SIGNAL(reload_theme()), this, @@ -118,9 +118,9 @@ QString AOApplication::get_version_string() QString::number(MINOR_VERSION); } -void AOApplication::set_theme_variant(QString p_variant) +void AOApplication::set_gamemode(QString p_gamemode) { - config->set_theme_variant(p_variant); + config->set_gamemode(p_gamemode); emit reload_theme(); } @@ -134,7 +134,7 @@ void AOApplication::on_config_reload_theme_requested() emit reload_theme(); } -void AOApplication::on_config_theme_variant_changed() +void AOApplication::on_config_gamemode_changed() { emit reload_theme(); } diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 166c32a9b..5819f9a2b 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -31,7 +31,7 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, // `p_emote_prefix+p_emote` + extensions in `exts` in order // 2. In the character folder, look for // `p_emote` + extensions in `exts` in order - // 3. In the theme folder (variant/main/default), look for + // 3. In the theme folder (gamemode/main/default), look for // "placeholder" + extensions in `exts` in order QStringList exts{".webp", ".apng", ".gif", ".png"}; diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index bf715ba23..2e7b03575 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -24,7 +24,7 @@ class AOConfigPrivate : public QObject QString username; QString callwords; QString theme; - QString theme_variant; + QString gamemode; bool always_pre; int chat_tick_interval; bool server_alerts; @@ -75,12 +75,12 @@ public slots: theme = p_string; invoke_parents("theme_changed", Q_ARG(QString, p_string)); } - void set_theme_variant(QString p_string) + void set_gamemode(QString p_string) { - if (theme_variant == p_string) + if (gamemode == p_string) return; - theme_variant = p_string; - invoke_parents("theme_variant_changed", Q_ARG(QString, p_string)); + gamemode = p_string; + invoke_parents("gamemode_changed", Q_ARG(QString, p_string)); } void set_always_pre(bool p_enabled) { @@ -185,7 +185,7 @@ public slots: username = cfg.value("username").toString(); callwords = cfg.value("callwords").toString(); theme = cfg.value("theme", "default").toString(); - theme_variant = cfg.value("theme_variant", "").toString(); + gamemode = cfg.value("gamemode", "").toString(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); server_alerts = cfg.value("server_alerts", true).toBool(); @@ -206,7 +206,7 @@ public slots: cfg.setValue("username", username); cfg.setValue("callwords", callwords); cfg.setValue("theme", theme); - cfg.setValue("theme_variant", theme_variant); + cfg.setValue("gamemode", gamemode); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("server_alerts", server_alerts); @@ -289,9 +289,9 @@ QString AOConfig::theme() return d->theme; } -QString AOConfig::theme_variant() +QString AOConfig::gamemode() { - return d->theme_variant; + return d->gamemode; } bool AOConfig::always_pre_enabled() @@ -379,9 +379,9 @@ void AOConfig::set_theme(QString p_string) d->set_theme(p_string); } -void AOConfig::set_theme_variant(QString p_string) +void AOConfig::set_gamemode(QString p_string) { - d->set_theme_variant(p_string); + d->set_gamemode(p_string); } void AOConfig::set_always_pre(int p_state) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 090aa92c7..fb33f44e8 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -22,7 +22,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) w_username = AO_GUI_WIDGET(QLineEdit, "username"); w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); w_theme = AO_GUI_WIDGET(QComboBox, "theme"); - w_theme_variant = AO_GUI_WIDGET(QComboBox, "theme_variant"); + w_gamemode = AO_GUI_WIDGET(QComboBox, "gamemode"); w_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); @@ -31,8 +31,10 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) // IC Chatlog w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); - w_log_orientation_top_down = AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); - w_log_orientation_bottom_up = AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); + w_log_orientation_top_down = + AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); + w_log_orientation_bottom_up = + AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); w_log_music = AO_GUI_WIDGET(QCheckBox, "log_music"); w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); @@ -50,7 +52,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) // themes refresh_theme_list(); - refresh_theme_variant_list(); + refresh_gamemode_list(); // input connect(m_config, SIGNAL(username_changed(QString)), w_username, @@ -59,7 +61,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(setText(QString))); connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(theme_variant_changed(QString)), w_theme_variant, + connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); @@ -69,7 +71,8 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); - connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); + connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, + SLOT(on_log_is_topdown_changed(bool))); connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, @@ -99,8 +102,8 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(set_theme(QString))); connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); - connect(w_theme_variant, SIGNAL(currentIndexChanged(QString)), this, - SLOT(on_theme_variant_index_changed(QString))); + connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, + SLOT(on_gamemode_index_changed(QString))); connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, @@ -109,7 +112,8 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(set_server_alerts(int))); connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); - connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_topdown(bool))); + connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, + SLOT(set_log_is_topdown(bool))); connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_uses_newline(int))); connect(w_log_music, SIGNAL(stateChanged(int)), m_config, @@ -141,7 +145,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) w_username->setText(m_config->username()); w_callwords->setText(m_config->callwords()); w_theme->setCurrentText(m_config->theme()); - w_theme_variant->setCurrentText(m_config->theme_variant()); + w_gamemode->setCurrentText(m_config->gamemode()); w_always_pre->setChecked(m_config->always_pre_enabled()); w_chat_tick_interval->setValue(m_config->chat_tick_interval()); w_server_alerts->setChecked(m_config->server_alerts_enabled()); @@ -185,31 +189,31 @@ void AOConfigPanel::refresh_theme_list() w_theme->blockSignals(false); } -void AOConfigPanel::refresh_theme_variant_list() +void AOConfigPanel::refresh_gamemode_list() { - const QString p_prev_text = w_theme_variant->currentText(); + const QString p_prev_text = w_gamemode->currentText(); // block signals - w_theme_variant->blockSignals(true); - w_theme_variant->clear(); + w_gamemode->blockSignals(true); + w_gamemode->clear(); - // add empty entry indicating no variant chosen - w_theme_variant->addItem(""); + // add empty entry indicating no gamemode chosen + w_gamemode->addItem(""); // themes for (QString i_folder : QDir(QDir::currentPath() + "/base/themes/" + - m_config->theme() + "/variants/") + m_config->theme() + "/gamemodes/") .entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; - w_theme_variant->addItem(i_folder, i_folder); + w_gamemode->addItem(i_folder, i_folder); } // restore previous selection - w_theme_variant->setCurrentText(p_prev_text); + w_gamemode->setCurrentText(p_prev_text); // unblock - w_theme_variant->blockSignals(false); + w_gamemode->blockSignals(false); } void AOConfigPanel::on_reload_theme_clicked() @@ -218,10 +222,10 @@ void AOConfigPanel::on_reload_theme_clicked() emit reload_theme(); } -void AOConfigPanel::on_theme_variant_index_changed(QString p_text) +void AOConfigPanel::on_gamemode_index_changed(QString p_text) { Q_UNUSED(p_text); - m_config->set_theme_variant(w_theme_variant->currentData().toString()); + m_config->set_gamemode(w_gamemode->currentData().toString()); } void AOConfigPanel::on_log_is_topdown_changed(bool p_enabled) @@ -253,5 +257,5 @@ void AOConfigPanel::on_blips_value_changed(int p_num) void AOConfigPanel::on_config_reload_theme_requested() { refresh_theme_list(); - refresh_theme_variant_list(); + refresh_gamemode_list(); } diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 10e236c19..e26a39751 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -33,7 +33,7 @@ void AOMovie::play(QString p_file, QString p_char) // Remove ! at the beginning of p_file if needed // This is an indicator that the file is not selectable in the current theme - // (variant) but is still usable by other people + // (gamemode) but is still usable by other people if (p_file.length() > 0 && p_file.at(0) == "!") p_file = p_file.remove(0, 1); @@ -50,9 +50,9 @@ void AOMovie::play(QString p_file, QString p_char) // `char_p_file` + extensions in `exts` in order // 2. In the character folder, look for // `overlay/char_p_file` + extensions in `exts` in order - // 3. In the theme folder (variant/main/default), look for + // 3. In the theme folder (gamemode/main/default), look for // `p_file` + extensions in `exts` in order - // 4. In the theme folder (variant/main/default), look for + // 4. In the theme folder (gamemode/main/default), look for // "placeholder" + extensions in `exts` in order QStringList exts{".webp", ".apng", ".gif", ".png"}; @@ -92,7 +92,7 @@ void AOMovie::play_interjection(QString p_char_name, // Asset lookup order // 1. In the character folder, look for `p_char_interjection_name` - // 2. In the theme folder (variant/main/default), look for `p_char_name` + // 2. In the theme folder (gamemode/main/default), look for `p_char_name` QStringList exts{".webp", ".apng", ".gif"}; QString interjection_filepath = ao_app->find_asset_path( diff --git a/src/courtroom.cpp b/src/courtroom.cpp index fc39b61cd..b02d14946 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -350,11 +350,12 @@ void Courtroom::handle_clock(QString time) ui_vp_clock->play(string); } -void Courtroom::handle_theme_variant(QString theme_variant) +void Courtroom::handle_gamemode(QString gamemode) { - ao_app->set_theme_variant(theme_variant); + ao_app->set_gamemode(gamemode); on_app_reload_theme_requested(); } + void Courtroom::list_music() { ui_music_list->clear(); @@ -995,16 +996,17 @@ void Courtroom::handle_chatmessage_3() ui_vp_showname_image->show(); // Asset lookup order - // 1. In the theme folder (variant/main/default), in the character folder, + // 1. In the theme folder (gamemode/main/default), in the character folder, // look for "showname" + extensions in `exts` in order // 2. In the character folder, look for // "showname" + extensions in `exts` in order - QVector exts = {".png", ".jpg", ".bmp"}; + QStringList exts = {".png", ".jpg", ".bmp"}; QString path = - ao_app->find_theme_asset_path("characters/" + f_char + "/showname"); + ao_app->find_theme_asset_path("characters/" + f_char + "/showname", exts); if (path.isEmpty()) - path = ao_app->get_character_path(f_char, "showname"); + path = ao_app->find_asset_path( + {ao_app->get_character_path(f_char, "showname")}, exts); if (!path.isEmpty() && !chatmessage_is_empty && ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 6c214976c..200d10361 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -492,7 +492,7 @@ void Courtroom::set_widget_names() void Courtroom::set_widget_layers() { // File lookup order - // 1. In the theme folder (variant/main/default), look for + // 1. In the theme folder (gamemode/main/default), look for // "courtroom_layers.ini". QString path = ao_app->find_theme_asset_path("courtroom_layers.ini"); @@ -849,7 +849,7 @@ void Courtroom::set_widgets() if (ao_app->read_theme_ini("enable_button_images", cc_config_ini) == "true") { // Set files, ask questions later - // set_image first tries the theme variant folder, then the theme folder, + // set_image first tries the gamemode folder, then the theme folder, // then falls back to the default theme ui_change_character->set_image("changecharacter.png"); if (ui_change_character->image_path.isEmpty()) @@ -1131,7 +1131,7 @@ void Courtroom::check_effects() // Asset lookup order // 1. In the character folder, look for // `effect_names.at(i)` + extensions in `exts` in order - // 2. In the theme folder (variant/main/default), look for + // 2. In the theme folder (gamemode/main/default), look for // `effect_names.at(i)` + extensions in `exts` in order // Only enable buttons where a file was found @@ -1151,7 +1151,7 @@ void Courtroom::check_free_blocks() // Asset lookup order // 1. In the character folder, look for // `free_block_names.at(i)` + extensions in `exts` in order - // 2. In the theme folder (variant/main/default), look for + // 2. In the theme folder (gamemode/main/default), look for // `free_block_names.at(i)` + extensions in `exts` in order // Only enable buttons where a file was found @@ -1172,7 +1172,7 @@ void Courtroom::check_shouts() // Asset lookup order // 1. In the character folder, look for // `shout_names.at(i)` + extensions in `exts` in order - // 2. In the theme folder (variant/main/default), look for + // 2. In the theme folder (gamemode/main/default), look for // `shout_names.at(i)` + extensions in `exts` in order // Only enable buttons where a file was found @@ -1194,7 +1194,7 @@ void Courtroom::check_wtce() // Asset lookup order // 1. In the character folder, look for // `wtce_names.at(i)` + extensions in `exts` in order - // 2. In the theme folder (variant/main/default), look for + // 2. In the theme folder (gamemode/main/default), look for // `wtce_names.at(i)` + extensions in `exts` in order // Only enable buttons where a file was found @@ -1300,7 +1300,6 @@ void Courtroom::load_free_blocks() ui_free_blocks[i - 1]->setObjectName(name); } } - qDebug() << "FREE BLOCKS HERE " << free_block_names; } void Courtroom::load_shouts() diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 8bad16914..5943619df 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -680,11 +680,11 @@ void AOApplication::server_packet_received(AOPacket *p_packet) { w_courtroom->handle_clock(f_contents.at(1)); } - else if (header == "VA") + else if (header == "GM") { if (courtroom_constructed) { - w_courtroom->handle_theme_variant(f_contents.at(0)); + w_courtroom->handle_gamemode(f_contents.at(0)); } } else if (header == "TR") diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 3efba5a1d..edc548835 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -177,8 +177,8 @@ QString AOApplication::find_theme_asset_path(QString p_file) QString AOApplication::find_theme_asset_path(QString p_file, QStringList exts) { QStringList paths{ - get_base_path() + "themes/" + get_theme() + "/variants/" + - get_theme_variant() + "/" + p_file, + get_base_path() + "themes/" + get_theme() + "/gamemodes/" + + get_gamemode() + "/" + p_file, get_base_path() + "themes/" + get_theme() + "/" + p_file, get_base_path() + "themes/default/" + p_file, }; diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index d7ace6d33..16ed4d330 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -15,9 +15,9 @@ QString AOApplication::get_theme() return config->theme(); } -QString AOApplication::get_theme_variant() +QString AOApplication::get_gamemode() { - return config->theme_variant(); + return config->gamemode(); } int AOApplication::read_blip_rate() @@ -293,7 +293,7 @@ QString AOApplication::get_sfx(QString p_identifier) QString AOApplication::get_stylesheet(QString target_tag, QString p_file) { // File lookup order - // 1. In the theme folder (variant/main/default), look for `p_file`. + // 1. In the theme folder (gamemode/main/default), look for `p_file`. QString path = find_theme_asset_path(p_file); if (path.isEmpty()) @@ -331,7 +331,7 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) QVector AOApplication::get_highlight_color() { // File lookup order - // 1. In the theme folder (variant/main/default), look for + // 1. In the theme folder (gamemode/main/default), look for // "courtroom_config.ini". QString path = find_theme_asset_path("courtroom_config.ini"); @@ -382,7 +382,7 @@ QVector AOApplication::get_highlight_color() QString AOApplication::get_spbutton(QString p_tag, int index) { // File lookup order - // 1. In the theme folder (variant/main/default), look for + // 1. In the theme folder (gamemode/main/default), look for // "courtroom_config.ini". QString path = find_theme_asset_path("courtroom_config.ini"); @@ -425,7 +425,7 @@ QString AOApplication::get_spbutton(QString p_tag, int index) QStringList AOApplication::get_effect(int index) { // File lookup order - // 1. In the theme folder (variant/main/default), look for + // 1. In the theme folder (gamemode/main/default), look for // "courtroom_config.ini". QString path = find_theme_asset_path("courtroom_config.ini"); @@ -790,7 +790,7 @@ bool AOApplication::get_blank_blip() QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) { // File lookup order - // 1. In the theme folder (variant/main/default), look for + // 1. In the theme folder (gamemode/main/default), look for // `p_identifier`. QString path = find_theme_asset_path(p_file); if (path.isEmpty()) @@ -802,7 +802,7 @@ QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) QString AOApplication::get_image_path(QString p_image) { // File lookup order - // 1. In the theme folder (variant/main/default), look for + // 1. In the theme folder (gamemode/main/default), look for // `p_image`. return find_theme_asset_path(p_image); } From d7cee61b8d96a56d1f8a5b49c61ed0f835d4aea6 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 2 Oct 2020 16:00:19 -0400 Subject: [PATCH 125/842] Add documentation+Remove leftover code --- include/aoapplication.h | 63 ++++++++++++++++++++++++++++++++++------- src/courtroom.cpp | 23 ++++++++------- 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index eddbb2380..211c82543 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -114,7 +114,6 @@ class AOApplication : public QApplication // implementation in path_functions.cpp QString get_base_path(); QString get_data_path(); - QString get_theme_path(QString p_file); QString get_gamemode(); QString get_character_path(QString p_character, QString p_file); // QString get_demothings_path(); @@ -124,23 +123,67 @@ class AOApplication : public QApplication QString get_default_background_path(QString p_file); QString get_evidence_path(QString p_file); + /** + * @brief Searches for a file in any of the given paths, and returns + * the first path that actually matches to an existing file. + * + * @param possible_paths Paths to check. Case-insensitive. + * + * @return The first case-sensitive path that corresponds to an actual file, + * or an empty string, if not one does. + */ QString find_asset_path(QStringList possible_paths); - QString find_asset_path(QStringList possible_paths, + /** + * @brief Searches for a file with any of the given root and extensions, and + * returns the first path that actually matches to an existing file. A root is + * matched to all given extensions in order before continuing to the next + * root. + * + * @param possible_roots The potential roots the filepath could have. + * Case-insensitive. + * @param possible_exts The potential extensions the filepath could have. + * Case-insensitive. + * + * @return The first case-sensitive root+extension path for which a file + * exists, or an empty string, if not one does. + */ + QString find_asset_path(QStringList possible_roots, QStringList possible_exts); + + /** + * @brief Searches for a file in the current theme folder, and returns + * the first path that actually matches to an existing file. + * If `theme_folder = get_base_path() + "themes/"` + * the paths checked are (in order) + * + * 1. theme_folder + get_gamemode() + "/" + p_file + * 2. theme_folder + p_file + * + * @param possible_paths Paths to check. Case-insensitive. + * + * @return The first case-sensitive path that corresponds to an actual file, + * or an empty string, if not one does. + */ QString find_theme_asset_path(QString p_file); - QString find_theme_asset_path(QString p_root, QStringList p_exts); /** - * @brief Searches for a file with any of the given extensions, and returns - * the first extension that actually matches to an existing file. + * @brief Searches for a root+extension path in the current theme folder, and + * returns the first path that actually matches to an existing file. + * + * If `theme_folder = get_base_path() + "themes/"`, `p_ext` + * is an extension, the paths checked are (in order) * - * @param p_file The path to the file, without extension. - * @param p_exts The potential extensions the file could have. + * 1. theme_folder + get_gamemode() + "/" + p_root + p_ext + * 2. theme_folder + p_root + p_ext * - * @return The first extension with which a file exists, or an empty string, - * if not one does. + * @param p_root The root the filepath could have. Case-insensitive. + * @param p_exts The potential extensions the filepath could have. + * Case-insensitive. + * + * @return The first case-sensitive root+extension path that corresponds to an + * actual file, or an empty string, if not one does. */ - QString get_file_extension(QString p_file, QVector p_exts); + QString find_theme_asset_path(QString p_root, QStringList p_exts); /** * @brief Returns the 'correct' path for the file given as the parameter by diff --git a/src/courtroom.cpp b/src/courtroom.cpp index b02d14946..9532c33e8 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -236,17 +236,19 @@ void Courtroom::set_scene() f_desk_image = "stand"; } - QVector exts{".webp", ".apng", ".gif", ".png"}; + QStringList exts{".webp", ".apng", ".gif", ".png"}; bool has_all_desks; - if (ao_app->get_file_extension(get_background_path("defensedesk"), exts) == - "") + if (ao_app->find_asset_path({get_background_path("defensedesk")}, exts) + .isEmpty()) has_all_desks = false; - else if (ao_app->get_file_extension(get_background_path("prosecutiondesk"), - exts) == "") + else if (ao_app + ->find_asset_path({get_background_path("prosecutiondesk")}, + exts) + .isEmpty()) has_all_desks = false; - else if (ao_app->get_file_extension(get_background_path("stand"), exts) == - "") + else if (ao_app->find_asset_path({get_background_path("stand")}, exts) + .isEmpty()) has_all_desks = false; else has_all_desks = true; @@ -1581,12 +1583,11 @@ void Courtroom::play_sfx() if (sfx_name == "1") return; - QVector extensions{"", ".wav", ".ogg", ".opus", ".mp3"}; - + QStringList exts{"", ".wav", ".ogg", ".opus", ".mp3"}; QString f_file = - ao_app->get_file_extension(ao_app->get_sounds_path(sfx_name), extensions); + ao_app->find_asset_path({ao_app->get_sounds_path(sfx_name)}, exts); - m_effects_player->play(sfx_name + f_file); + m_effects_player->play(f_file); } void Courtroom::set_text_color() From eedb669d762a2cdc0d9f49c78d2d19dd96a51846 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 2 Oct 2020 16:01:55 -0400 Subject: [PATCH 126/842] Add manual gamemode option --- include/aoapplication.h | 16 +++++--- include/aoconfig.h | 6 ++- include/aoconfigpanel.h | 5 ++- res/ui/config_panel.ui | 19 +++++++++- src/aoapplication.cpp | 16 +++++--- src/aoconfig.cpp | 74 ++++++++++++++++++++++++------------- src/aoconfigpanel.cpp | 26 +++++++++---- src/courtroom.cpp | 4 ++ src/packet_distribution.cpp | 5 +-- src/path_functions.cpp | 17 --------- 10 files changed, 121 insertions(+), 67 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 211c82543..e39ab4df4 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -114,7 +114,6 @@ class AOApplication : public QApplication // implementation in path_functions.cpp QString get_base_path(); QString get_data_path(); - QString get_gamemode(); QString get_character_path(QString p_character, QString p_file); // QString get_demothings_path(); QString get_sounds_path(QString p_file); @@ -231,16 +230,23 @@ class AOApplication : public QApplication // returns a list of call words QStringList get_callwords(); - // returns whatever preanimations should always play or not - bool get_always_pre_enabled(); + // returns the current gamemode + QString get_gamemode(); - // returns whatever the client should simulate first person dialog - bool get_first_person_enabled(); + // returns whether the player is able to change gamemodes manually while + // ignoring server orders for it + bool get_manual_gamemode_enabled(); // returns whether server alerts (ones that trigger a client alert other than // callwords) should actually tigger a server alert or not bool get_server_alerts_enabled(); + // returns whatever preanimations should always play or not + bool get_always_pre_enabled(); + + // returns whatever the client should simulate first person dialog + bool get_first_person_enabled(); + // returns if chatlog goes downward bool get_chatlog_scrolldown(); diff --git a/include/aoconfig.h b/include/aoconfig.h index e1fac51a3..145bbea08 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -21,9 +21,10 @@ class AOConfig : public QObject QString callwords(); QString theme(); QString gamemode(); + bool manual_gamemode_enabled(); + bool server_alerts_enabled(); bool always_pre_enabled(); int chat_tick_interval(); - bool server_alerts_enabled(); int log_max_lines(); bool log_is_topdown_enabled(); bool log_uses_newline_enabled(); @@ -46,6 +47,8 @@ public slots: void set_callwords(QString p_string); void set_theme(QString p_string); void set_gamemode(QString p_string); + void set_manual_gamemode(bool p_enabled); + void set_manual_gamemode(int p_state); void set_server_alerts(bool p_enabled); void set_server_alerts(int p_state); void set_always_pre(bool p_enabled); @@ -74,6 +77,7 @@ public slots: void callwords_changed(QString); void theme_changed(QString); void gamemode_changed(QString); + void manual_gamemode_changed(bool); void server_alerts_changed(bool); void always_pre_changed(bool); void chat_tick_interval_changed(int); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 7b6d92716..7277886e7 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -27,11 +27,12 @@ class AOConfigPanel : public QWidget QComboBox *w_theme = nullptr; QPushButton *w_reload_theme = nullptr; QComboBox *w_gamemode = nullptr; - QCheckBox *w_always_pre = nullptr; - QSpinBox *w_chat_tick_interval = nullptr; + QCheckBox *w_manual_gamemode = nullptr; QCheckBox *w_server_alerts = nullptr; // IC Chatlog + QCheckBox *w_always_pre = nullptr; + QSpinBox *w_chat_tick_interval = nullptr; QSpinBox *w_log_max_lines = nullptr; QCheckBox *w_log_uses_newline = nullptr; QRadioButton *w_log_orientation_top_down = nullptr; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 2f30b7ad1..3d9f1d103 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -154,11 +154,14 @@ - + Qt::Vertical + + QSizePolicy::Expanding + 20 @@ -167,6 +170,20 @@ + + + + + + + + + + + Manual gamemode: + + + diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index b1affa13c..bed21f9e3 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -166,21 +166,25 @@ void AOApplication::toggle_config_panel() } } -bool AOApplication::get_always_pre_enabled() +bool AOApplication::get_server_alerts_enabled() { - return config->always_pre_enabled(); + return config->server_alerts_enabled(); } -bool AOApplication::get_first_person_enabled() +bool AOApplication::get_manual_gamemode_enabled() { - return config->get_bool("first_person", false); + return config->manual_gamemode_enabled(); } -bool AOApplication::get_server_alerts_enabled() +bool AOApplication::get_always_pre_enabled() { - return config->server_alerts_enabled(); + return config->always_pre_enabled(); } +bool AOApplication::get_first_person_enabled() +{ + return config->get_bool("first_person", false); +} bool AOApplication::get_chatlog_scrolldown() { return config->log_is_topdown_enabled(); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 2e7b03575..410bec0f9 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -25,9 +25,10 @@ class AOConfigPrivate : public QObject QString callwords; QString theme; QString gamemode; + bool manual_gamemode; + bool server_alerts; bool always_pre; int chat_tick_interval; - bool server_alerts; int log_max_lines; bool log_is_topdown; bool log_uses_newline; @@ -82,6 +83,20 @@ public slots: gamemode = p_string; invoke_parents("gamemode_changed", Q_ARG(QString, p_string)); } + void set_manual_gamemode(bool p_enabled) + { + if (manual_gamemode == p_enabled) + return; + manual_gamemode = p_enabled; + invoke_parents("manual_gamemode_changed", Q_ARG(bool, p_enabled)); + } + void set_server_alerts(bool p_enabled) + { + if (server_alerts == p_enabled) + return; + server_alerts = p_enabled; + invoke_parents("server_alerts_changed", Q_ARG(bool, p_enabled)); + } void set_always_pre(bool p_enabled) { if (always_pre == p_enabled) @@ -96,13 +111,6 @@ public slots: chat_tick_interval = p_number; invoke_parents("chat_tick_interval_changed", Q_ARG(int, p_number)); } - void set_server_alerts(bool p_enabled) - { - if (server_alerts == p_enabled) - return; - server_alerts = p_enabled; - invoke_parents("server_alerts_changed", Q_ARG(bool, p_enabled)); - } void set_log_max_lines(int p_number) { if (log_max_lines == p_number) @@ -186,9 +194,10 @@ public slots: callwords = cfg.value("callwords").toString(); theme = cfg.value("theme", "default").toString(); gamemode = cfg.value("gamemode", "").toString(); + manual_gamemode = cfg.value("manual_gamemode", false).toBool(); + server_alerts = cfg.value("server_alerts", true).toBool(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); - server_alerts = cfg.value("server_alerts", true).toBool(); log_max_lines = cfg.value("chatlog_limit", 200).toInt(); log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); log_uses_newline = cfg.value("chatlog_newline", false).toBool(); @@ -207,9 +216,10 @@ public slots: cfg.setValue("callwords", callwords); cfg.setValue("theme", theme); cfg.setValue("gamemode", gamemode); + cfg.setValue("manual_gamemode", manual_gamemode); + cfg.setValue("server_alerts", server_alerts); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); - cfg.setValue("server_alerts", server_alerts); cfg.setValue("chatlog_limit", log_max_lines); cfg.setValue("chatlog_scrolldown", log_is_topdown); cfg.setValue("chatlog_newline", log_uses_newline); @@ -294,21 +304,25 @@ QString AOConfig::gamemode() return d->gamemode; } -bool AOConfig::always_pre_enabled() +bool AOConfig::manual_gamemode_enabled() { - return d->always_pre; + return d->manual_gamemode; } -int AOConfig::chat_tick_interval() +bool AOConfig::server_alerts_enabled() { - return d->chat_tick_interval; + return d->server_alerts; } -bool AOConfig::server_alerts_enabled() +bool AOConfig::always_pre_enabled() { - return d->server_alerts; + return d->always_pre; } +int AOConfig::chat_tick_interval() +{ + return d->chat_tick_interval; +} int AOConfig::log_max_lines() { return d->log_max_lines; @@ -384,19 +398,14 @@ void AOConfig::set_gamemode(QString p_string) d->set_gamemode(p_string); } -void AOConfig::set_always_pre(int p_state) +void AOConfig::set_manual_gamemode(int p_state) { - set_always_pre(p_state == Qt::Checked); + set_manual_gamemode(p_state == Qt::Checked); } -void AOConfig::set_always_pre(bool p_enabled) +void AOConfig::set_manual_gamemode(bool p_enabled) { - d->set_always_pre(p_enabled); -} - -void AOConfig::set_chat_tick_interval(int p_number) -{ - d->set_chat_tick_interval(p_number); + d->set_manual_gamemode(p_enabled); } void AOConfig::set_server_alerts(int p_state) @@ -409,6 +418,21 @@ void AOConfig::set_server_alerts(bool p_enabled) d->set_server_alerts(p_enabled); } +void AOConfig::set_always_pre(int p_state) +{ + set_always_pre(p_state == Qt::Checked); +} + +void AOConfig::set_always_pre(bool p_enabled) +{ + d->set_always_pre(p_enabled); +} + +void AOConfig::set_chat_tick_interval(int p_number) +{ + d->set_chat_tick_interval(p_number); +} + void AOConfig::set_log_max_lines(int p_number) { d->set_log_max_lines(p_number); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index fb33f44e8..807552b70 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -24,11 +24,12 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) w_theme = AO_GUI_WIDGET(QComboBox, "theme"); w_gamemode = AO_GUI_WIDGET(QComboBox, "gamemode"); w_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); - w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); - w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); + w_manual_gamemode = AO_GUI_WIDGET(QCheckBox, "manual_gamemode"); w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); // IC Chatlog + w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); + w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); w_log_orientation_top_down = @@ -63,12 +64,14 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, + SLOT(setChecked(bool))); connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); - connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, - SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, @@ -104,12 +107,14 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(on_reload_theme_clicked())); connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); + connect(w_manual_gamemode, SIGNAL(stateChanged(int)), m_config, + SLOT(set_manual_gamemode(int))); + connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, + SLOT(set_server_alerts(int))); connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); - connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, - SLOT(set_server_alerts(int))); connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, @@ -146,9 +151,10 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) w_callwords->setText(m_config->callwords()); w_theme->setCurrentText(m_config->theme()); w_gamemode->setCurrentText(m_config->gamemode()); + w_manual_gamemode->setChecked(m_config->manual_gamemode_enabled()); + w_server_alerts->setChecked(m_config->server_alerts_enabled()); w_always_pre->setChecked(m_config->always_pre_enabled()); w_chat_tick_interval->setValue(m_config->chat_tick_interval()); - w_server_alerts->setChecked(m_config->server_alerts_enabled()); w_log_max_lines->setValue(m_config->log_max_lines()); if (m_config->log_is_topdown_enabled()) w_log_orientation_top_down->setChecked(true); @@ -163,6 +169,12 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) w_blips->setValue(m_config->blips_volume()); w_blip_rate->setValue(m_config->blip_rate()); w_blank_blips->setChecked(m_config->blank_blips_enabled()); + + // Widget enabling connections + w_gamemode->setEnabled(m_config->manual_gamemode_enabled()); + // The manual gamemode checkbox enables browsing the gamemode combox + connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_gamemode, + SLOT(setEnabled(bool))); } void AOConfigPanel::refresh_theme_list() diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 9532c33e8..9adea248d 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -354,6 +354,10 @@ void Courtroom::handle_clock(QString time) void Courtroom::handle_gamemode(QString gamemode) { + // First check if only manual gamemode changes are allowed + // If so, ignore order. + if (ao_app->get_manual_gamemode_enabled()) + return; ao_app->set_gamemode(gamemode); on_app_reload_theme_requested(); } diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 5943619df..10bdde0b9 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -683,9 +683,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) else if (header == "GM") { if (courtroom_constructed) - { - w_courtroom->handle_gamemode(f_contents.at(0)); - } + goto end; + w_courtroom->handle_gamemode(f_contents.at(0)); } else if (header == "TR") { diff --git a/src/path_functions.cpp b/src/path_functions.cpp index edc548835..85b0d7178 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -38,12 +38,6 @@ QString AOApplication::get_data_path() return get_base_path() + "data/"; } -QString AOApplication::get_theme_path(QString p_file) -{ - QString path = get_base_path() + "themes/" + get_theme() + "/" + p_file; - return get_case_sensitive_path(path); -} - QString AOApplication::get_character_path(QString p_character, QString p_file) { QString path = get_base_path() + "characters/" + p_character + "/" + p_file; @@ -98,17 +92,6 @@ QString Courtroom::get_background_path(QString p_file) p_file; } -QString AOApplication::get_file_extension(QString p_file, - QVector p_exts) -{ - for (auto &ext : p_exts) - { - if (file_exists(get_case_sensitive_path(p_file + ext))) - return ext; - } - return ""; -} - #ifndef CASE_SENSITIVE_FILESYSTEM QString AOApplication::get_case_sensitive_path(QString p_file) { From 85bee6f017d140f2087e295553d53b0551fcc996 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 2 Oct 2020 16:41:01 -0400 Subject: [PATCH 127/842] Restore build settings --- dronline-client.pro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index 0941c8b6e..b59c3ada8 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -8,7 +8,7 @@ TARGET = dro-client RC_ICONS = icon.ico -INCLUDEPATH += $$PWD/include $$PWD/3rd/include +INCLUDEPATH += $$PWD/include $$PWD/3rd $$PWD/3rd/include DEPENDPATH += $$PWD/include HEADERS += \ @@ -111,7 +111,7 @@ SOURCES += \ # 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers # in the same way as BASS. Discord RPC uses CMake, which does not play nicely with # QMake, so this step must be manual. -LIBS += -L$$PWD/3rd/$${QT_ARCH} -lbass -ldiscord-rpc +LIBS += -L$$PWD/3rd/$${QMAKE_HOST.arch} -lbass -ldiscord-rpc RESOURCES += \ res.qrc From 78cd770f93aab1fbe0edb59f446413047df961ad Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 2 Oct 2020 17:37:25 -0400 Subject: [PATCH 128/842] Move log to own tab+Move theme/gamemode to Game tab --- res/ui/config_panel.ui | 372 ++++++++++++++++++----------------------- 1 file changed, 162 insertions(+), 210 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 3d9f1d103..2055bc039 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -19,7 +19,7 @@ 487 - 361 + 300 @@ -29,7 +29,7 @@ - 0 + 1 @@ -64,28 +64,94 @@ - + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Qt::Horizontal - + + + + Server alerts: + + + + + + + + 0 + 0 + + + + + + + + + + + + + 0 + 0 + + + + + 429 + 284 + + + + Game + + + Theme: - - + + 0 0 - + + + 6 + 0 @@ -117,93 +183,38 @@ - + Gamemode: - + - - - - Qt::Horizontal - - - - - - - Server alerts: - - - - - - - - 0 - 0 - - + + - + Manual gamemode: - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 40 - - - - - + - - - - Manual gamemode: + + + + Qt::Horizontal - - - - - - 0 - 0 - - - - - 429 - 284 - - - - Game - - - + Always-anim: @@ -213,7 +224,7 @@ - + @@ -229,21 +240,14 @@ - - - - Qt::Horizontal - - - - + Chat-tick interval: - + ms @@ -256,143 +260,104 @@ - - - - Qt::Horizontal + + + + + Log + + + + + + Max length: - - + + + + lines + + + 10000 + + + 200 + + + + + + + Newline padding: + + + + + + + + + + + + + + Music switching: + + + + + + + + + + + + - + 0 0 - Log + Orientation - + - + - Max length: + Top-down - - - - 0 - 0 - - - - line(s) - - - 0 - - - 10000 - - - 200 - - - - - - - Newline padding: - - - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - Orientation - - - - - - Top-down - - - - - - - Bottom-up - - - - - - - - - - Music switching: - - - true - - - - - - - - 0 - 0 - - - - - - - - - + - Save to disk: - - - - - - - - 0 - 0 - + Bottom-up + + + + Save to disk: + + + + + + + + + + @@ -621,19 +586,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - From eb0fe28274d2b3b71857abcfdf0ca554346ff2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 3 Oct 2020 07:44:55 +0200 Subject: [PATCH 129/842] Slight updated to the UI --- res/ui/config_panel.ui | 175 +++++++++++++++++++++++++++++------------ src/aoconfigpanel.cpp | 2 +- 2 files changed, 125 insertions(+), 52 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 2055bc039..914b350db 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 1 + 0 @@ -114,6 +114,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -191,30 +204,40 @@ - - - - - - Manual gamemode: - - + + + + + false + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Allows you to manually switch to a gamemode interface.</p></body></html> + + + Manual + + + + - - - - - - - Qt::Horizontal - + Always-anim: @@ -224,7 +247,7 @@ - + @@ -240,14 +263,14 @@ - + Chat-tick interval: - + ms @@ -260,13 +283,26 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + Log - + @@ -287,35 +323,40 @@ - - - - Newline padding: - - - - - - - - - - - - - - Music switching: - - - - - - - - - + + + + + + + 0 + 0 + + + + Extra newlines are + + + Newline + + + + + + + + 0 + 0 + + + + Music switch + + + + - + @@ -344,20 +385,39 @@ - + Save to disk: - + + + + 0 + 0 + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -586,6 +646,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 807552b70..cbaf42a56 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -22,8 +22,8 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) w_username = AO_GUI_WIDGET(QLineEdit, "username"); w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); w_theme = AO_GUI_WIDGET(QComboBox, "theme"); - w_gamemode = AO_GUI_WIDGET(QComboBox, "gamemode"); w_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); + w_gamemode = AO_GUI_WIDGET(QComboBox, "gamemode"); w_manual_gamemode = AO_GUI_WIDGET(QCheckBox, "manual_gamemode"); w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); From 9542a5a507dc4494691e52709d515d94b7c06ea6 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 3 Oct 2020 09:19:16 -0400 Subject: [PATCH 130/842] Add time of day combobox --- include/aoapplication.h | 43 +++++++++++++++------ include/aoconfig.h | 15 +++++--- include/aoconfigpanel.h | 11 ++++-- include/courtroom.h | 3 ++ res/ui/config_panel.ui | 34 ++++++++++++----- src/aoapplication.cpp | 13 +++++++ src/aocharmovie.cpp | 2 +- src/aoconfig.cpp | 38 ++++++++++++++----- src/aoconfigpanel.cpp | 76 +++++++++++++++++++++++++++++++------ src/aomovie.cpp | 12 +++--- src/courtroom.cpp | 10 ++++- src/courtroom_widgets.cpp | 14 +++---- src/packet_distribution.cpp | 6 +++ src/path_functions.cpp | 4 ++ src/text_file_functions.cpp | 18 ++++++--- 15 files changed, 226 insertions(+), 73 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index e39ab4df4..43e7d20a5 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -155,8 +155,10 @@ class AOApplication : public QApplication * If `theme_folder = get_base_path() + "themes/"` * the paths checked are (in order) * - * 1. theme_folder + get_gamemode() + "/" + p_file - * 2. theme_folder + p_file + * 1. theme_folder + get_gamemode() + "/" + get_timeofday() + "/" + p_file + * 2. theme_folder + get_gamemode() + "/" + p_file + * 3. theme_folder + get_timeofday() + "/" + p_file + * 4. theme_folder + p_file * * @param possible_paths Paths to check. Case-insensitive. * @@ -172,8 +174,11 @@ class AOApplication : public QApplication * If `theme_folder = get_base_path() + "themes/"`, `p_ext` * is an extension, the paths checked are (in order) * - * 1. theme_folder + get_gamemode() + "/" + p_root + p_ext - * 2. theme_folder + p_root + p_ext + * 1. theme_folder + get_gamemode() + "/" + get_timeofday() + "/" + p_root + + * p_ext + * 2. theme_folder + get_gamemode() + "/" + p_root + p_ext + * 3. theme_folder + get_timeofday() + "/" + p_root + p_ext + * 4. theme_folder + p_root + p_ext * * @param p_root The root the filepath could have. Case-insensitive. * @param p_exts The potential extensions the filepath could have. @@ -213,10 +218,6 @@ class AOApplication : public QApplication // Returns text from note file QString read_note(QString filename); - // Reads the theme from config.ini and loads it into the current_theme - // variable - QString get_theme(); - // Returns the blip rate from config.ini int read_blip_rate(); @@ -230,9 +231,15 @@ class AOApplication : public QApplication // returns a list of call words QStringList get_callwords(); + // returns the current theme + QString get_theme(); + // returns the current gamemode QString get_gamemode(); + // returns the current time of day + QString get_timeofday(); + // returns whether the player is able to change gamemodes manually while // ignoring server orders for it bool get_manual_gamemode_enabled(); @@ -291,14 +298,21 @@ class AOApplication : public QApplication // Set the gamemode void set_gamemode(QString m_gamemode); + // Set the time of day + void set_timeofday(QString m_timeofday); + // Returns the contents of serverlist.txt QVector read_serverlist_txt(); // Returns the value of p_identifier in the design.ini file in p_design_path QString read_design_ini(QString p_identifier, QString p_design_path); - // Returns the value of p_identifier from p_file in either a theme gamemode - // subfolder, a theme folder, or default theme folder + // Returns the value of p_identifier from p_file in either + // 1. theme gamemode + timeofday subfolder, + // 2. theme gamemode subfolder, + // 3. theme timeofday subfolder, + // 4. theme folder, + // 5. default theme folder QString read_theme_ini(QString p_identifier, QString p_file); // Helper function for returning an int in a file inside of the theme folder @@ -392,8 +406,12 @@ class AOApplication : public QApplication // Returns p_char's gender QString get_gender(QString p_char); - // Get the location of p_image, which is either in a theme gamemode subfolder, - // a theme folder, or default theme folder + // Get the location of p_image, which is either in a + // 1. theme gamemode + timeofday subfolder, + // 2. theme gamemode subfolder, + // 3. theme timeofday subfolder, + // 4. theme folder, + // 5. default theme folder QString get_image_path(QString p_image); signals: @@ -414,6 +432,7 @@ private slots: void on_config_theme_changed(); void on_config_reload_theme_requested(); void on_config_gamemode_changed(); + void on_config_timeofday_changed(); public slots: void server_disconnected(); diff --git a/include/aoconfig.h b/include/aoconfig.h index 145bbea08..ae5496afa 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -19,10 +19,11 @@ class AOConfig : public QObject // getters QString username(); QString callwords(); + bool server_alerts_enabled(); QString theme(); QString gamemode(); + QString timeofday(); bool manual_gamemode_enabled(); - bool server_alerts_enabled(); bool always_pre_enabled(); int chat_tick_interval(); int log_max_lines(); @@ -45,14 +46,15 @@ public slots: public slots: void set_username(QString p_string); void set_callwords(QString p_string); - void set_theme(QString p_string); - void set_gamemode(QString p_string); - void set_manual_gamemode(bool p_enabled); - void set_manual_gamemode(int p_state); void set_server_alerts(bool p_enabled); void set_server_alerts(int p_state); void set_always_pre(bool p_enabled); void set_always_pre(int p_state); + void set_theme(QString p_string); + void set_gamemode(QString p_string); + void set_timeofday(QString p_string); + void set_manual_gamemode(bool p_enabled); + void set_manual_gamemode(int p_state); void set_chat_tick_interval(int p_number); void set_log_max_lines(int p_number); void set_log_is_topdown(bool p_enabled); @@ -75,10 +77,11 @@ public slots: signals: void username_changed(QString); void callwords_changed(QString); + void server_alerts_changed(bool); void theme_changed(QString); void gamemode_changed(QString); + void timeofday_changed(QString); void manual_gamemode_changed(bool); - void server_alerts_changed(bool); void always_pre_changed(bool); void chat_tick_interval_changed(int); void log_max_lines_changed(int); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 7277886e7..5d4c70bb0 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -24,15 +24,18 @@ class AOConfigPanel : public QWidget // general QLineEdit *w_username = nullptr; QLineEdit *w_callwords = nullptr; + QCheckBox *w_server_alerts = nullptr; + + // game QComboBox *w_theme = nullptr; QPushButton *w_reload_theme = nullptr; QComboBox *w_gamemode = nullptr; + QComboBox *w_timeofday = nullptr; QCheckBox *w_manual_gamemode = nullptr; - QCheckBox *w_server_alerts = nullptr; - - // IC Chatlog QCheckBox *w_always_pre = nullptr; QSpinBox *w_chat_tick_interval = nullptr; + + // IC Chatlog QSpinBox *w_log_max_lines = nullptr; QCheckBox *w_log_uses_newline = nullptr; QRadioButton *w_log_orientation_top_down = nullptr; @@ -67,10 +70,12 @@ public slots: private: void refresh_theme_list(); void refresh_gamemode_list(); + void refresh_timeofday_list(); private slots: void on_reload_theme_clicked(); void on_gamemode_index_changed(QString p_text); + void on_timeofday_index_changed(QString p_text); void on_log_is_topdown_changed(bool p_enabled); void on_effects_value_changed(int p_num); void on_system_value_changed(int p_num); diff --git a/include/courtroom.h b/include/courtroom.h index 961f32b63..f4fa37c67 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -228,6 +228,9 @@ class Courtroom : public QMainWindow // handle request to change gamemode void handle_gamemode(QString gamemode); + // handle request to change time of day + void handle_timeofday(QString timeofday); + void play_preanim(); // plays the witness testimony or cross examination animation based on diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 914b350db..26026bf4a 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 0 + 1 @@ -230,14 +230,14 @@ - - - - Qt::Horizontal + + + + Time of day: - + Always-anim: @@ -247,7 +247,7 @@ - + @@ -263,14 +263,14 @@ - + Chat-tick interval: - + ms @@ -283,7 +283,7 @@ - + Qt::Vertical @@ -296,6 +296,20 @@ + + + + Qt::Horizontal + + + + + + + + + + diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index bed21f9e3..5babb583d 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -25,6 +25,8 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) SLOT(on_config_theme_changed())); connect(config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_config_gamemode_changed())); + connect(config, SIGNAL(timeofday_changed(QString)), this, + SLOT(on_config_timeofday_changed())); config_panel = new AOConfigPanel; connect(config_panel, SIGNAL(reload_theme()), this, @@ -124,6 +126,12 @@ void AOApplication::set_gamemode(QString p_gamemode) emit reload_theme(); } +void AOApplication::set_timeofday(QString p_timeofday) +{ + config->set_timeofday(p_timeofday); + emit reload_theme(); +} + void AOApplication::on_config_theme_changed() { emit reload_theme(); @@ -139,6 +147,11 @@ void AOApplication::on_config_gamemode_changed() emit reload_theme(); } +void AOApplication::on_config_timeofday_changed() +{ + emit reload_theme(); +} + void AOApplication::set_favorite_list() { favorite_list = read_serverlist_txt(); diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 5819f9a2b..b2cf334e1 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -31,7 +31,7 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, // `p_emote_prefix+p_emote` + extensions in `exts` in order // 2. In the character folder, look for // `p_emote` + extensions in `exts` in order - // 3. In the theme folder (gamemode/main/default), look for + // 3. In the theme folder (gamemode-timeofday/main/default), look for // "placeholder" + extensions in `exts` in order QStringList exts{".webp", ".apng", ".gif", ".png"}; diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 410bec0f9..ad6e63bf2 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -25,6 +25,7 @@ class AOConfigPrivate : public QObject QString callwords; QString theme; QString gamemode; + QString timeofday; bool manual_gamemode; bool server_alerts; bool always_pre; @@ -69,6 +70,13 @@ public slots: callwords = p_string; invoke_parents("callwords_changed", Q_ARG(QString, p_string)); } + void set_server_alerts(bool p_enabled) + { + if (server_alerts == p_enabled) + return; + server_alerts = p_enabled; + invoke_parents("server_alerts_changed", Q_ARG(bool, p_enabled)); + } void set_theme(QString p_string) { if (theme == p_string) @@ -83,6 +91,13 @@ public slots: gamemode = p_string; invoke_parents("gamemode_changed", Q_ARG(QString, p_string)); } + void set_timeofday(QString p_string) + { + if (timeofday == p_string) + return; + timeofday = p_string; + invoke_parents("timeofday_changed", Q_ARG(QString, p_string)); + } void set_manual_gamemode(bool p_enabled) { if (manual_gamemode == p_enabled) @@ -90,13 +105,6 @@ public slots: manual_gamemode = p_enabled; invoke_parents("manual_gamemode_changed", Q_ARG(bool, p_enabled)); } - void set_server_alerts(bool p_enabled) - { - if (server_alerts == p_enabled) - return; - server_alerts = p_enabled; - invoke_parents("server_alerts_changed", Q_ARG(bool, p_enabled)); - } void set_always_pre(bool p_enabled) { if (always_pre == p_enabled) @@ -192,10 +200,11 @@ public slots: { username = cfg.value("username").toString(); callwords = cfg.value("callwords").toString(); + server_alerts = cfg.value("server_alerts", true).toBool(); theme = cfg.value("theme", "default").toString(); gamemode = cfg.value("gamemode", "").toString(); + timeofday = cfg.value("timeofday", "").toString(); manual_gamemode = cfg.value("manual_gamemode", false).toBool(); - server_alerts = cfg.value("server_alerts", true).toBool(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); log_max_lines = cfg.value("chatlog_limit", 200).toInt(); @@ -214,10 +223,11 @@ public slots: { cfg.setValue("username", username); cfg.setValue("callwords", callwords); + cfg.setValue("server_alerts", server_alerts); cfg.setValue("theme", theme); cfg.setValue("gamemode", gamemode); + cfg.setValue("timeofday", timeofday); cfg.setValue("manual_gamemode", manual_gamemode); - cfg.setValue("server_alerts", server_alerts); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("chatlog_limit", log_max_lines); @@ -304,6 +314,11 @@ QString AOConfig::gamemode() return d->gamemode; } +QString AOConfig::timeofday() +{ + return d->timeofday; +} + bool AOConfig::manual_gamemode_enabled() { return d->manual_gamemode; @@ -398,6 +413,11 @@ void AOConfig::set_gamemode(QString p_string) d->set_gamemode(p_string); } +void AOConfig::set_timeofday(QString p_string) +{ + d->set_timeofday(p_string); +} + void AOConfig::set_manual_gamemode(int p_state) { set_manual_gamemode(p_state == Qt::Checked); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index cbaf42a56..3e011eb15 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -21,15 +21,18 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) // general w_username = AO_GUI_WIDGET(QLineEdit, "username"); w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); + w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); + + // game w_theme = AO_GUI_WIDGET(QComboBox, "theme"); w_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); w_gamemode = AO_GUI_WIDGET(QComboBox, "gamemode"); w_manual_gamemode = AO_GUI_WIDGET(QCheckBox, "manual_gamemode"); - w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); - - // IC Chatlog + w_timeofday = AO_GUI_WIDGET(QComboBox, "timeofday"); w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); + + // IC Chatlog w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); w_log_orientation_top_down = @@ -54,20 +57,23 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) // themes refresh_theme_list(); refresh_gamemode_list(); + refresh_timeofday_list(); // input connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); + connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, + SLOT(setChecked(bool))); connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, - SLOT(setChecked(bool))); + connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, + SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); connect(m_config, SIGNAL(chat_tick_interval_changed(int)), @@ -101,6 +107,8 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(set_username(QString))); connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); + connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, + SLOT(set_server_alerts(int))); connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(w_reload_theme, SIGNAL(clicked()), this, @@ -109,8 +117,8 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(on_gamemode_index_changed(QString))); connect(w_manual_gamemode, SIGNAL(stateChanged(int)), m_config, SLOT(set_manual_gamemode(int))); - connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, - SLOT(set_server_alerts(int))); + connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, + SLOT(on_timeofday_index_changed(QString))); connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, @@ -149,10 +157,11 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) // set values w_username->setText(m_config->username()); w_callwords->setText(m_config->callwords()); + w_server_alerts->setChecked(m_config->server_alerts_enabled()); w_theme->setCurrentText(m_config->theme()); w_gamemode->setCurrentText(m_config->gamemode()); w_manual_gamemode->setChecked(m_config->manual_gamemode_enabled()); - w_server_alerts->setChecked(m_config->server_alerts_enabled()); + w_timeofday->setCurrentText(m_config->timeofday()); w_always_pre->setChecked(m_config->always_pre_enabled()); w_chat_tick_interval->setValue(m_config->chat_tick_interval()); w_log_max_lines->setValue(m_config->log_max_lines()); @@ -211,10 +220,10 @@ void AOConfigPanel::refresh_gamemode_list() // add empty entry indicating no gamemode chosen w_gamemode->addItem(""); - // themes - for (QString i_folder : QDir(QDir::currentPath() + "/base/themes/" + - m_config->theme() + "/gamemodes/") - .entryList(QDir::Dirs)) + // gamemodes + QString path = + QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; + for (QString i_folder : QDir(path).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; @@ -228,6 +237,42 @@ void AOConfigPanel::refresh_gamemode_list() w_gamemode->blockSignals(false); } +void AOConfigPanel::refresh_timeofday_list() +{ + const QString p_prev_text = w_timeofday->currentText(); + + // block signals + w_timeofday->blockSignals(true); + w_timeofday->clear(); + + // add empty entry indicating no time of day chosen + w_timeofday->addItem(""); + + // decide path to look for times of day. This differs whether there is a + // gamemode chosen or not + QString path; + if (m_config->gamemode().isEmpty()) + path = + QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; + else + path = QDir::currentPath() + "/base/themes/" + m_config->theme() + + "/gamemodes/" + m_config->gamemode() + "/times/"; + + // times of day + for (QString i_folder : QDir(path).entryList(QDir::Dirs)) + { + if (i_folder == "." || i_folder == "..") + continue; + w_timeofday->addItem(i_folder, i_folder); + } + + // restore previous selection + w_timeofday->setCurrentText(p_prev_text); + + // unblock + w_timeofday->blockSignals(false); +} + void AOConfigPanel::on_reload_theme_clicked() { qDebug() << "reload theme clicked"; @@ -240,6 +285,12 @@ void AOConfigPanel::on_gamemode_index_changed(QString p_text) m_config->set_gamemode(w_gamemode->currentData().toString()); } +void AOConfigPanel::on_timeofday_index_changed(QString p_text) +{ + Q_UNUSED(p_text); + m_config->set_timeofday(w_timeofday->currentData().toString()); +} + void AOConfigPanel::on_log_is_topdown_changed(bool p_enabled) { w_log_orientation_top_down->setChecked(p_enabled); @@ -270,4 +321,5 @@ void AOConfigPanel::on_config_reload_theme_requested() { refresh_theme_list(); refresh_gamemode_list(); + refresh_timeofday_list(); } diff --git a/src/aomovie.cpp b/src/aomovie.cpp index e26a39751..b30034c8c 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -33,7 +33,7 @@ void AOMovie::play(QString p_file, QString p_char) // Remove ! at the beginning of p_file if needed // This is an indicator that the file is not selectable in the current theme - // (gamemode) but is still usable by other people + // (gamemode-timeofday) but is still usable by other people if (p_file.length() > 0 && p_file.at(0) == "!") p_file = p_file.remove(0, 1); @@ -50,9 +50,9 @@ void AOMovie::play(QString p_file, QString p_char) // `char_p_file` + extensions in `exts` in order // 2. In the character folder, look for // `overlay/char_p_file` + extensions in `exts` in order - // 3. In the theme folder (gamemode/main/default), look for + // 3. In the theme folder (gamemode-timeofday/main/default), look for // `p_file` + extensions in `exts` in order - // 4. In the theme folder (gamemode/main/default), look for + // 4. In the theme folder (gamemode-timeofday/main/default), look for // "placeholder" + extensions in `exts` in order QStringList exts{".webp", ".apng", ".gif", ".png"}; @@ -91,8 +91,10 @@ void AOMovie::play_interjection(QString p_char_name, p_char_interjection_name = p_interjection_name; // Asset lookup order - // 1. In the character folder, look for `p_char_interjection_name` - // 2. In the theme folder (gamemode/main/default), look for `p_char_name` + // 1. In the character folder, look for + // `p_char_interjection_name` + // 2. In the theme folder (gamemode-timeofday/main/default), look for + // `p_char_name` QStringList exts{".webp", ".apng", ".gif"}; QString interjection_filepath = ao_app->find_asset_path( diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 9adea248d..bddddf8d0 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -362,6 +362,12 @@ void Courtroom::handle_gamemode(QString gamemode) on_app_reload_theme_requested(); } +void Courtroom::handle_timeofday(QString timeofday) +{ + ao_app->set_timeofday(timeofday); + on_app_reload_theme_requested(); +} + void Courtroom::list_music() { ui_music_list->clear(); @@ -1002,8 +1008,8 @@ void Courtroom::handle_chatmessage_3() ui_vp_showname_image->show(); // Asset lookup order - // 1. In the theme folder (gamemode/main/default), in the character folder, - // look for "showname" + extensions in `exts` in order + // 1. In the theme folder (gamemode-timeofday/main/default), in the character + // folder, look for "showname" + extensions in `exts` in order // 2. In the character folder, look for // "showname" + extensions in `exts` in order diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 200d10361..0df616670 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -492,7 +492,7 @@ void Courtroom::set_widget_names() void Courtroom::set_widget_layers() { // File lookup order - // 1. In the theme folder (gamemode/main/default), look for + // 1. In the theme folder (gamemode-timeofday/main/default), look for // "courtroom_layers.ini". QString path = ao_app->find_theme_asset_path("courtroom_layers.ini"); @@ -849,8 +849,8 @@ void Courtroom::set_widgets() if (ao_app->read_theme_ini("enable_button_images", cc_config_ini) == "true") { // Set files, ask questions later - // set_image first tries the gamemode folder, then the theme folder, - // then falls back to the default theme + // set_image first tries the gamemode-timeofday folder, then the theme + // folder, then falls back to the default theme ui_change_character->set_image("changecharacter.png"); if (ui_change_character->image_path.isEmpty()) ui_change_character->setText("Change Character"); @@ -1131,7 +1131,7 @@ void Courtroom::check_effects() // Asset lookup order // 1. In the character folder, look for // `effect_names.at(i)` + extensions in `exts` in order - // 2. In the theme folder (gamemode/main/default), look for + // 2. In the theme folder (gamemode-timeofday/main/default), look for // `effect_names.at(i)` + extensions in `exts` in order // Only enable buttons where a file was found @@ -1151,7 +1151,7 @@ void Courtroom::check_free_blocks() // Asset lookup order // 1. In the character folder, look for // `free_block_names.at(i)` + extensions in `exts` in order - // 2. In the theme folder (gamemode/main/default), look for + // 2. In the theme folder (gamemode-timeofday/main/default), look for // `free_block_names.at(i)` + extensions in `exts` in order // Only enable buttons where a file was found @@ -1172,7 +1172,7 @@ void Courtroom::check_shouts() // Asset lookup order // 1. In the character folder, look for // `shout_names.at(i)` + extensions in `exts` in order - // 2. In the theme folder (gamemode/main/default), look for + // 2. In the theme folder (gamemode-timeofday/main/default), look for // `shout_names.at(i)` + extensions in `exts` in order // Only enable buttons where a file was found @@ -1194,7 +1194,7 @@ void Courtroom::check_wtce() // Asset lookup order // 1. In the character folder, look for // `wtce_names.at(i)` + extensions in `exts` in order - // 2. In the theme folder (gamemode/main/default), look for + // 2. In the theme folder (gamemode-timeofday/main/default), look for // `wtce_names.at(i)` + extensions in `exts` in order // Only enable buttons where a file was found diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 10bdde0b9..167e283f2 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -686,6 +686,12 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; w_courtroom->handle_gamemode(f_contents.at(0)); } + else if (header == "TD") + { + if (courtroom_constructed) + goto end; + w_courtroom->handle_timeofday(f_contents.at(0)); + } else if (header == "TR") { // Timer resume diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 85b0d7178..c20caf139 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -160,8 +160,12 @@ QString AOApplication::find_theme_asset_path(QString p_file) QString AOApplication::find_theme_asset_path(QString p_file, QStringList exts) { QStringList paths{ + get_base_path() + "themes/" + get_theme() + "/gamemodes/" + + get_gamemode() + "/times/" + get_timeofday() + "/" + p_file, get_base_path() + "themes/" + get_theme() + "/gamemodes/" + get_gamemode() + "/" + p_file, + get_base_path() + "themes/" + get_theme() + "/times/" + get_timeofday() + + "/" + p_file, get_base_path() + "themes/" + get_theme() + "/" + p_file, get_base_path() + "themes/default/" + p_file, }; diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 16ed4d330..004ef5857 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -20,6 +20,11 @@ QString AOApplication::get_gamemode() return config->gamemode(); } +QString AOApplication::get_timeofday() +{ + return config->timeofday(); +} + int AOApplication::read_blip_rate() { return config->blip_rate(); @@ -293,7 +298,8 @@ QString AOApplication::get_sfx(QString p_identifier) QString AOApplication::get_stylesheet(QString target_tag, QString p_file) { // File lookup order - // 1. In the theme folder (gamemode/main/default), look for `p_file`. + // 1. In the theme folder (gamemode-timeofday/main/default), look for + // `p_file`. QString path = find_theme_asset_path(p_file); if (path.isEmpty()) @@ -331,7 +337,7 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) QVector AOApplication::get_highlight_color() { // File lookup order - // 1. In the theme folder (gamemode/main/default), look for + // 1. In the theme folder (gamemode-timeofday/main/default), look for // "courtroom_config.ini". QString path = find_theme_asset_path("courtroom_config.ini"); @@ -382,7 +388,7 @@ QVector AOApplication::get_highlight_color() QString AOApplication::get_spbutton(QString p_tag, int index) { // File lookup order - // 1. In the theme folder (gamemode/main/default), look for + // 1. In the theme folder (gamemode-timeofday/main/default), look for // "courtroom_config.ini". QString path = find_theme_asset_path("courtroom_config.ini"); @@ -425,7 +431,7 @@ QString AOApplication::get_spbutton(QString p_tag, int index) QStringList AOApplication::get_effect(int index) { // File lookup order - // 1. In the theme folder (gamemode/main/default), look for + // 1. In the theme folder (gamemode-timeofday/main/default), look for // "courtroom_config.ini". QString path = find_theme_asset_path("courtroom_config.ini"); @@ -790,7 +796,7 @@ bool AOApplication::get_blank_blip() QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) { // File lookup order - // 1. In the theme folder (gamemode/main/default), look for + // 1. In the theme folder (gamemode-timeofday/main/default), look for // `p_identifier`. QString path = find_theme_asset_path(p_file); if (path.isEmpty()) @@ -802,7 +808,7 @@ QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) QString AOApplication::get_image_path(QString p_image) { // File lookup order - // 1. In the theme folder (gamemode/main/default), look for + // 1. In the theme folder (gamemode-timeofday/main/default), look for // `p_image`. return find_theme_asset_path(p_image); } From cb98001df30a964afd6327810545b38ece062199 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 3 Oct 2020 12:07:32 -0400 Subject: [PATCH 131/842] Add manual time of day checkbox --- include/aoapplication.h | 10 +++++--- include/aoconfig.h | 10 +++++--- include/aoconfigpanel.h | 3 ++- res/ui/config_panel.ui | 13 ++++++++++ src/aoapplication.cpp | 5 ++++ src/aoconfig.cpp | 51 +++++++++++++++++++++++++++---------- src/aoconfigpanel.cpp | 10 ++++++++ src/courtroom.cpp | 4 +++ src/packet_distribution.cpp | 2 +- 9 files changed, 87 insertions(+), 21 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 43e7d20a5..f2884768d 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -237,12 +237,16 @@ class AOApplication : public QApplication // returns the current gamemode QString get_gamemode(); + // returns whether the player is able to change gamemodes manually and + // ignore server orders for it + bool get_manual_gamemode_enabled(); + // returns the current time of day QString get_timeofday(); - // returns whether the player is able to change gamemodes manually while - // ignoring server orders for it - bool get_manual_gamemode_enabled(); + // returns whether the player is able to change time of day manually and + // ignore server orders for it + bool get_manual_timeofday_enabled(); // returns whether server alerts (ones that trigger a client alert other than // callwords) should actually tigger a server alert or not diff --git a/include/aoconfig.h b/include/aoconfig.h index ae5496afa..7babeb956 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -22,8 +22,9 @@ class AOConfig : public QObject bool server_alerts_enabled(); QString theme(); QString gamemode(); - QString timeofday(); bool manual_gamemode_enabled(); + QString timeofday(); + bool manual_timeofday_enabled(); bool always_pre_enabled(); int chat_tick_interval(); int log_max_lines(); @@ -52,9 +53,11 @@ public slots: void set_always_pre(int p_state); void set_theme(QString p_string); void set_gamemode(QString p_string); - void set_timeofday(QString p_string); void set_manual_gamemode(bool p_enabled); void set_manual_gamemode(int p_state); + void set_timeofday(QString p_string); + void set_manual_timeofday(bool p_enabled); + void set_manual_timeofday(int p_state); void set_chat_tick_interval(int p_number); void set_log_max_lines(int p_number); void set_log_is_topdown(bool p_enabled); @@ -80,8 +83,9 @@ public slots: void server_alerts_changed(bool); void theme_changed(QString); void gamemode_changed(QString); - void timeofday_changed(QString); void manual_gamemode_changed(bool); + void timeofday_changed(QString); + void manual_timeofday_changed(bool); void always_pre_changed(bool); void chat_tick_interval_changed(int); void log_max_lines_changed(int); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 5d4c70bb0..c2364742d 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -30,8 +30,9 @@ class AOConfigPanel : public QWidget QComboBox *w_theme = nullptr; QPushButton *w_reload_theme = nullptr; QComboBox *w_gamemode = nullptr; - QComboBox *w_timeofday = nullptr; QCheckBox *w_manual_gamemode = nullptr; + QComboBox *w_timeofday = nullptr; + QCheckBox *w_manual_timeofday = nullptr; QCheckBox *w_always_pre = nullptr; QSpinBox *w_chat_tick_interval = nullptr; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 26026bf4a..9999636f1 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -308,6 +308,19 @@ + + + + + 0 + 0 + + + + Manual + + + diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 5babb583d..6bd748029 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -189,6 +189,11 @@ bool AOApplication::get_manual_gamemode_enabled() return config->manual_gamemode_enabled(); } +bool AOApplication::get_manual_timeofday_enabled() +{ + return config->manual_timeofday_enabled(); +} + bool AOApplication::get_always_pre_enabled() { return config->always_pre_enabled(); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index ad6e63bf2..52d51aba9 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -25,8 +25,9 @@ class AOConfigPrivate : public QObject QString callwords; QString theme; QString gamemode; - QString timeofday; bool manual_gamemode; + QString timeofday; + bool manual_timeofday; bool server_alerts; bool always_pre; int chat_tick_interval; @@ -91,6 +92,13 @@ public slots: gamemode = p_string; invoke_parents("gamemode_changed", Q_ARG(QString, p_string)); } + void set_manual_gamemode(bool p_enabled) + { + if (manual_gamemode == p_enabled) + return; + manual_gamemode = p_enabled; + invoke_parents("manual_gamemode_changed", Q_ARG(bool, p_enabled)); + } void set_timeofday(QString p_string) { if (timeofday == p_string) @@ -98,12 +106,12 @@ public slots: timeofday = p_string; invoke_parents("timeofday_changed", Q_ARG(QString, p_string)); } - void set_manual_gamemode(bool p_enabled) + void set_manual_timeofday(bool p_enabled) { - if (manual_gamemode == p_enabled) + if (manual_timeofday == p_enabled) return; - manual_gamemode = p_enabled; - invoke_parents("manual_gamemode_changed", Q_ARG(bool, p_enabled)); + manual_timeofday = p_enabled; + invoke_parents("manual_timeofday_changed", Q_ARG(bool, p_enabled)); } void set_always_pre(bool p_enabled) { @@ -203,8 +211,9 @@ public slots: server_alerts = cfg.value("server_alerts", true).toBool(); theme = cfg.value("theme", "default").toString(); gamemode = cfg.value("gamemode", "").toString(); - timeofday = cfg.value("timeofday", "").toString(); manual_gamemode = cfg.value("manual_gamemode", false).toBool(); + timeofday = cfg.value("timeofday", "").toString(); + manual_timeofday = cfg.value("manual_timeofday", false).toBool(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); log_max_lines = cfg.value("chatlog_limit", 200).toInt(); @@ -226,8 +235,9 @@ public slots: cfg.setValue("server_alerts", server_alerts); cfg.setValue("theme", theme); cfg.setValue("gamemode", gamemode); - cfg.setValue("timeofday", timeofday); cfg.setValue("manual_gamemode", manual_gamemode); + cfg.setValue("timeofday", timeofday); + cfg.setValue("manual_timeofday", manual_timeofday); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("chatlog_limit", log_max_lines); @@ -314,14 +324,19 @@ QString AOConfig::gamemode() return d->gamemode; } +bool AOConfig::manual_gamemode_enabled() +{ + return d->manual_gamemode; +} + QString AOConfig::timeofday() { return d->timeofday; } -bool AOConfig::manual_gamemode_enabled() +bool AOConfig::manual_timeofday_enabled() { - return d->manual_gamemode; + return d->manual_timeofday; } bool AOConfig::server_alerts_enabled() @@ -413,19 +428,29 @@ void AOConfig::set_gamemode(QString p_string) d->set_gamemode(p_string); } +void AOConfig::set_manual_gamemode(int p_state) +{ + set_manual_gamemode(p_state == Qt::Checked); +} + +void AOConfig::set_manual_gamemode(bool p_enabled) +{ + d->set_manual_gamemode(p_enabled); +} + void AOConfig::set_timeofday(QString p_string) { d->set_timeofday(p_string); } -void AOConfig::set_manual_gamemode(int p_state) +void AOConfig::set_manual_timeofday(int p_state) { - set_manual_gamemode(p_state == Qt::Checked); + set_manual_timeofday(p_state == Qt::Checked); } -void AOConfig::set_manual_gamemode(bool p_enabled) +void AOConfig::set_manual_timeofday(bool p_enabled) { - d->set_manual_gamemode(p_enabled); + d->set_manual_timeofday(p_enabled); } void AOConfig::set_server_alerts(int p_state) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 3e011eb15..757158d4c 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -29,6 +29,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) w_gamemode = AO_GUI_WIDGET(QComboBox, "gamemode"); w_manual_gamemode = AO_GUI_WIDGET(QCheckBox, "manual_gamemode"); w_timeofday = AO_GUI_WIDGET(QComboBox, "timeofday"); + w_manual_timeofday = AO_GUI_WIDGET(QCheckBox, "manual_timeofday"); w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); @@ -74,6 +75,8 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(setChecked(bool))); connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, + SLOT(setChecked(bool))); connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); connect(m_config, SIGNAL(chat_tick_interval_changed(int)), @@ -119,6 +122,8 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(set_manual_gamemode(int))); connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); + connect(w_manual_timeofday, SIGNAL(stateChanged(int)), m_config, + SLOT(set_manual_timeofday(int))); connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, @@ -162,6 +167,7 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) w_gamemode->setCurrentText(m_config->gamemode()); w_manual_gamemode->setChecked(m_config->manual_gamemode_enabled()); w_timeofday->setCurrentText(m_config->timeofday()); + w_manual_timeofday->setChecked(m_config->manual_timeofday_enabled()); w_always_pre->setChecked(m_config->always_pre_enabled()); w_chat_tick_interval->setValue(m_config->chat_tick_interval()); w_log_max_lines->setValue(m_config->log_max_lines()); @@ -181,9 +187,13 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) // Widget enabling connections w_gamemode->setEnabled(m_config->manual_gamemode_enabled()); + w_timeofday->setEnabled(m_config->manual_timeofday_enabled()); // The manual gamemode checkbox enables browsing the gamemode combox + // similarly with time of day connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_gamemode, SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_timeofday, + SLOT(setEnabled(bool))); } void AOConfigPanel::refresh_theme_list() diff --git a/src/courtroom.cpp b/src/courtroom.cpp index bddddf8d0..c903e8c05 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -364,6 +364,10 @@ void Courtroom::handle_gamemode(QString gamemode) void Courtroom::handle_timeofday(QString timeofday) { + // First check if only manual time of day changes are allowed + // If so, ignore order. + if (ao_app->get_manual_timeofday_enabled()) + return; ao_app->set_timeofday(timeofday); on_app_reload_theme_requested(); } diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 167e283f2..ad844d59c 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -686,7 +686,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; w_courtroom->handle_gamemode(f_contents.at(0)); } - else if (header == "TD") + else if (header == "TOD") { if (courtroom_constructed) goto end; From e8b49a6d602512432d69e14215af042484931a91 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 3 Oct 2020 13:00:27 -0400 Subject: [PATCH 132/842] Opening the config panel refreshes theme/gamemode/timeofday comboboxes --- include/aoconfigpanel.h | 3 +++ src/aoconfigpanel.cpp | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index c2364742d..9a9675f3e 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -65,6 +65,9 @@ class AOConfigPanel : public QWidget public slots: void on_config_reload_theme_requested(); +protected: + void showEvent(QShowEvent *event); + signals: void reload_theme(); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 757158d4c..6694b126d 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -196,6 +196,19 @@ AOConfigPanel::AOConfigPanel(QWidget *p_parent) SLOT(setEnabled(bool))); } +void AOConfigPanel::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + + if (isVisible()) + { + // refresh theme, gamemode and time of day comboboxes + refresh_theme_list(); + refresh_gamemode_list(); + refresh_timeofday_list(); + } +} + void AOConfigPanel::refresh_theme_list() { const QString p_prev_text = w_theme->currentText(); From 767d76785ccf2b25b1cdbbc5e948e270747b542e Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 10 Oct 2020 11:34:38 -0400 Subject: [PATCH 133/842] Fix client rejecting GM and TOD packet if courtroom was constructed --- src/packet_distribution.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index ad844d59c..78885a734 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -682,13 +682,13 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "GM") { - if (courtroom_constructed) + if (!courtroom_constructed) goto end; w_courtroom->handle_gamemode(f_contents.at(0)); } else if (header == "TOD") { - if (courtroom_constructed) + if (!courtroom_constructed) goto end; w_courtroom->handle_timeofday(f_contents.at(0)); } From 8832c1c654104d6b8e224003c1686099881b8dfa Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 11 Oct 2020 20:31:23 -0400 Subject: [PATCH 134/842] Ignore docs folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7fc49ad0d..7a65b77d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ 3rd/ base/ +docs/ .qmake.conf .gitignore *~ From 68850c50b60a18c6ee4d650bcc45e2d757345a66 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 11 Oct 2020 20:31:35 -0400 Subject: [PATCH 135/842] Document new functions+Remove unneeded functions --- include/aoapplication.h | 142 +++++++++++++++++++++++------------- include/courtroom.h | 12 ++- src/aobutton.cpp | 5 +- src/aoevidencebutton.cpp | 2 +- src/aoevidencedisplay.cpp | 2 +- src/aoimage.cpp | 2 +- src/aolabel.cpp | 2 +- src/aoshoutplayer.cpp | 2 +- src/courtroom.cpp | 4 +- src/courtroom_widgets.cpp | 9 ++- src/text_file_functions.cpp | 39 ++-------- 11 files changed, 122 insertions(+), 99 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index f2884768d..ddd9ae192 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -124,7 +124,7 @@ class AOApplication : public QApplication /** * @brief Searches for a file in any of the given paths, and returns - * the first path that actually matches to an existing file. + * the first case-sensitive path that actually matches to an existing file. * * @param possible_paths Paths to check. Case-insensitive. * @@ -133,34 +133,36 @@ class AOApplication : public QApplication */ QString find_asset_path(QStringList possible_paths); /** - * @brief Searches for a file with any of the given root and extensions, and - * returns the first path that actually matches to an existing file. A root is - * matched to all given extensions in order before continuing to the next - * root. + * @brief Returns the first case-sensitive file that is the combination of one + * of the given root and extensions, or empty string if no combination exists. + * + * @details A root is matched to all given extensions in order before + * continuing to the next root. * * @param possible_roots The potential roots the filepath could have. * Case-insensitive. * @param possible_exts The potential extensions the filepath could have. * Case-insensitive. * - * @return The first case-sensitive root+extension path for which a file + * @return The first case-sensitive root+extension path for which a file * exists, or an empty string, if not one does. */ QString find_asset_path(QStringList possible_roots, QStringList possible_exts); /** - * @brief Searches for a file in the current theme folder, and returns - * the first path that actually matches to an existing file. - * If `theme_folder = get_base_path() + "themes/"` - * the paths checked are (in order) + * @brief Searches for a file in the current theme folder and returns the + * first case-sensitive path that matches to a file, or empty if it fails. * - * 1. theme_folder + get_gamemode() + "/" + get_timeofday() + "/" + p_file - * 2. theme_folder + get_gamemode() + "/" + p_file - * 3. theme_folder + get_timeofday() + "/" + p_file - * 4. theme_folder + p_file + * @details In the current theme folder, p_file is checked for in these paths + * 1. The current time of day folder in the current gamemode folder + * 2. The current gamemode folder + * 3. The current time of day folder + * 4. The current theme folder. + * The first path that is matched is the one that is returned. If p_file is + * not found in any path, it returns an empty string. * - * @param possible_paths Paths to check. Case-insensitive. + * @param p_file File name+extension to look for. Case-insensitive. * * @return The first case-sensitive path that corresponds to an actual file, * or an empty string, if not one does. @@ -168,19 +170,20 @@ class AOApplication : public QApplication QString find_theme_asset_path(QString p_file); /** - * @brief Searches for a root+extension path in the current theme folder, and - * returns the first path that actually matches to an existing file. + * @brief Returns the first case-sensitive file in the theme folder that is + * of the form name+extension, or empty string if it fails. * - * If `theme_folder = get_base_path() + "themes/"`, `p_ext` - * is an extension, the paths checked are (in order) + * @details The p_exts list is browsed in order. A name+extension file is + * searched in order in the following directories before checking the next + * extension: + * 1. The current time of day folder in the current gamemode folder + * 2. The current gamemode folder + * 3. The current time of day folder + * 4. The current theme folder. + * The first path that is matched is the one that is returned. If no file + * is found at all, it returns an empty string. * - * 1. theme_folder + get_gamemode() + "/" + get_timeofday() + "/" + p_root + - * p_ext - * 2. theme_folder + get_gamemode() + "/" + p_root + p_ext - * 3. theme_folder + get_timeofday() + "/" + p_root + p_ext - * 4. theme_folder + p_root + p_ext - * - * @param p_root The root the filepath could have. Case-insensitive. + * @param p_name Name of the file to look for. Case-insensitive. * @param p_exts The potential extensions the filepath could have. * Case-insensitive. * @@ -231,21 +234,50 @@ class AOApplication : public QApplication // returns a list of call words QStringList get_callwords(); - // returns the current theme + /** + * @brief Return the current theme name. + * @return Name of current theme. + */ QString get_theme(); - // returns the current gamemode + /** + * @brief Return the current gamemode. If no gamemode is set, return the + * empty string. + * + * @return Current gamemode, or empty string if not set. + */ QString get_gamemode(); - // returns whether the player is able to change gamemodes manually and - // ignore server orders for it + /** + * @brief Returns the current manual gamemode status. + * + * @details If true, a player can change gamemodes manually and their client + * will ignore orders to change gamemode from the server. If false, neither is + * possible and the client will follow orders from the server to change + * gamemode. + * + * @return Current manual gamemode status. + */ bool get_manual_gamemode_enabled(); - // returns the current time of day + /** + * @brief Returns the current time of day. If no time of day is set, return + * the empty string. + * + * @return Current gamemode, or empty string if not set. + */ QString get_timeofday(); - // returns whether the player is able to change time of day manually and - // ignore server orders for it + /** + * @brief Returns the current manual time of day status. + * + * @details If true, a player can change time of day manually and their client + * will ignore orders to change time of day from the server. If false, neither + * is possible and the client will follow orders from the server to change + * time of day. + * + * @return Current manual time of day status. + */ bool get_manual_timeofday_enabled(); // returns whether server alerts (ones that trigger a client alert other than @@ -308,20 +340,34 @@ class AOApplication : public QApplication // Returns the contents of serverlist.txt QVector read_serverlist_txt(); - // Returns the value of p_identifier in the design.ini file in p_design_path - QString read_design_ini(QString p_identifier, QString p_design_path); + /** + * @brief Reads p_path and returns the value associated with key + * p_identifier. If the file or key do not exist, return empty. + * + * @param p_identifier Key to look for. + * @param p_path Full path to ini file + * @return Value associated with key, or empty if not found. + */ + QString read_ini(QString p_identifier, QString p_path); - // Returns the value of p_identifier from p_file in either - // 1. theme gamemode + timeofday subfolder, - // 2. theme gamemode subfolder, - // 3. theme timeofday subfolder, - // 4. theme folder, - // 5. default theme folder + /** + * @brief Searches p_file in theme folder and returns the value associated + * with key p_identifier. If the file or key do not exist, return empty. + * + * @details p_file is looked for in the following directories. The earliest + * directory where it is found is the one that is considered. + * 1. The current time of day folder in the current gamemode folder + * 2. The current gamemode folder + * 3. The current time of day folder + * 4. The current theme folder. + * 5. The default theme folder. + * + * @param p_identifier Key to look for. + * @param p_file Name of file+ini to look for. + * @return Value associated with key, or empty if not found. + */ QString read_theme_ini(QString p_identifier, QString p_file); - // Helper function for returning an int in a file inside of the theme folder - int get_design_ini_value(QString p_identifier, QString p_design_file); - // Returns the coordinates of widget with p_identifier from p_file QPoint get_button_spacing(QString p_identifier, QString p_file); @@ -410,14 +456,6 @@ class AOApplication : public QApplication // Returns p_char's gender QString get_gender(QString p_char); - // Get the location of p_image, which is either in a - // 1. theme gamemode + timeofday subfolder, - // 2. theme gamemode subfolder, - // 3. theme timeofday subfolder, - // 4. theme folder, - // 5. default theme folder - QString get_image_path(QString p_image); - signals: void reload_theme(); diff --git a/include/courtroom.h b/include/courtroom.h index f4fa37c67..e1d33880f 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -225,10 +225,18 @@ class Courtroom : public QMainWindow // handle server-side clock animation and display void handle_clock(QString time); - // handle request to change gamemode + /** + * @brief Handle server request to change to given gamemode. If manual + * gamemode configuration is on, this method does nothing. + * @param gamemode Gamemode to change to. + */ void handle_gamemode(QString gamemode); - // handle request to change time of day + /** + * @brief Handle server request to change to given time of day. If manual + * time of day configuration is on, this method does nothing. + * @param timeofday Time of day to change to. + */ void handle_timeofday(QString timeofday); void play_preanim(); diff --git a/src/aobutton.cpp b/src/aobutton.cpp index 89c151055..81082331f 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -13,10 +13,11 @@ AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) void AOButton::set_image(QString p_image) { - image_path = ao_app->get_image_path(p_image); + image_path = ao_app->find_theme_asset_path(p_image); // Get the path of the found image without the extension QString image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); - QString hover_image_path = ao_app->get_image_path(image_name + "_hover.png"); + QString hover_image_path = + ao_app->find_theme_asset_path(image_name + "_hover.png"); if (file_exists(image_path)) { diff --git a/src/aoevidencebutton.cpp b/src/aoevidencebutton.cpp index d9c3dab63..c9cce7812 100644 --- a/src/aoevidencebutton.cpp +++ b/src/aoevidencebutton.cpp @@ -55,7 +55,7 @@ void AOEvidenceButton::set_image(QString p_image) void AOEvidenceButton::set_theme_image(QString p_image) { - QString path = ao_app->get_image_path(p_image); + QString path = ao_app->find_theme_asset_path(p_image); this->setText(""); this->setStyleSheet("border-image:url(\"" + path + "\")"); } diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index 8924c87b7..b640c2324 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -50,7 +50,7 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, evidence_icon->resize(icon_dimensions.width, icon_dimensions.height); evidence_icon->setPixmap(f_pixmap.scale_to_size(evidence_icon->size())); - QString f_path = ao_app->get_image_path(gif_name); + QString f_path = ao_app->find_theme_asset_path(gif_name); evidence_movie->setFileName(f_path); if (evidence_movie->frameCount() < 1) return; diff --git a/src/aoimage.cpp b/src/aoimage.cpp index 4e7f74895..e8d57397c 100644 --- a/src/aoimage.cpp +++ b/src/aoimage.cpp @@ -11,7 +11,7 @@ AOImage::AOImage(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) void AOImage::set_image(QString p_image) { - QString f_path = ao_app->get_image_path(p_image); + QString f_path = ao_app->find_theme_asset_path(p_image); AOPixmap f_pixmap(f_path); this->setPixmap(f_pixmap.scale_to_size(size())); diff --git a/src/aolabel.cpp b/src/aolabel.cpp index e6318fbea..06d098fc0 100644 --- a/src/aolabel.cpp +++ b/src/aolabel.cpp @@ -9,6 +9,6 @@ AOLabel::AOLabel(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) void AOLabel::set_image(QString p_image) { - QString f_path = ao_app->get_image_path(p_image); + QString f_path = ao_app->find_theme_asset_path(p_image); setStyleSheet("border-image:url(\"" + f_path + "\")"); } diff --git a/src/aoshoutplayer.cpp b/src/aoshoutplayer.cpp index b6a784d99..5dee583ce 100644 --- a/src/aoshoutplayer.cpp +++ b/src/aoshoutplayer.cpp @@ -13,7 +13,7 @@ void AOShoutPlayer::play(QString p_name, QString p_char) QString f_file; QString char_path = ao_app->get_character_path(p_char, p_name); - QString theme_path = ao_app->get_image_path(p_name); + QString theme_path = ao_app->find_theme_asset_path(p_name); qDebug() << char_path; qDebug() << theme_path; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index c903e8c05..63614c40f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -196,8 +196,8 @@ void Courtroom::set_scene() if (file_exists(ini_path)) { - f_background = ao_app->read_design_ini(f_side, ini_path); - f_desk_image = ao_app->read_design_ini(f_side + "_desk", ini_path); + f_background = ao_app->read_ini(f_side, ini_path); + f_desk_image = ao_app->read_ini(f_side + "_desk", ini_path); if (f_desk_mod == "0") // keeping a bit of the functionality for now { diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 0df616670..226df9509 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1238,7 +1238,7 @@ void Courtroom::load_effects() // And create new effects int effect_number = - ao_app->get_design_ini_value("effect_number", cc_config_ini); + ao_app->read_theme_ini("effect_number", cc_config_ini).toInt(); effects_enabled.resize(effect_number); ui_effects.resize(effect_number); @@ -1275,7 +1275,7 @@ void Courtroom::load_free_blocks() // And create new free block buttons int free_block_number = - ao_app->get_design_ini_value("free_block_number", cc_config_ini); + ao_app->read_theme_ini("free_block_number", cc_config_ini).toInt(); free_blocks_enabled.resize(free_block_number); ui_free_blocks.resize(free_block_number); @@ -1309,7 +1309,7 @@ void Courtroom::load_shouts() // And create new shouts int shout_number = - ao_app->get_design_ini_value("shout_number", cc_config_ini); + ao_app->read_theme_ini("shout_number", cc_config_ini).toInt(); shouts_enabled.resize(shout_number); ui_shouts.resize(shout_number); @@ -1347,7 +1347,8 @@ void Courtroom::load_wtce() delete_widget(widget); // And create new wtce buttons - int wtce_number = ao_app->get_design_ini_value("wtce_number", cc_config_ini); + int wtce_number = + ao_app->read_theme_ini("wtce_number", cc_config_ini).toInt(); wtce_enabled.resize(wtce_number); ui_wtce.resize(wtce_number); diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 004ef5857..9fc436a88 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -158,21 +158,15 @@ QVector AOApplication::read_serverlist_txt() return f_server_list; } -QString AOApplication::read_design_ini(QString p_identifier, - QString p_design_path) +QString AOApplication::read_ini(QString p_identifier, QString p_path) { - QFile design_ini; - - design_ini.setFileName(p_design_path); - - if (!design_ini.open(QIODevice::ReadOnly)) - { + QFile ini; + ini.setFileName(p_path); + if (!ini.open(QIODevice::ReadOnly)) return ""; - } - QTextStream in(&design_ini); + QTextStream in(&ini); QString result = ""; - while (!in.atEnd()) { QString f_line = in.readLine().trimmed(); @@ -184,27 +178,16 @@ QString AOApplication::read_design_ini(QString p_identifier, if (line_elements.at(0).trimmed() != p_identifier) continue; - if (line_elements.size() < 2) continue; - result = line_elements.at(1).trimmed(); break; } - design_ini.close(); - + ini.close(); return result; } -int AOApplication::get_design_ini_value(QString p_identifier, QString p_file) -{ - QString result = read_theme_ini(p_identifier, p_file); - if (result.isEmpty()) - return 0; - return result.toInt(); -} - QPoint AOApplication::get_button_spacing(QString p_identifier, QString p_file) { QPoint return_value; @@ -802,13 +785,5 @@ QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) if (path.isEmpty()) return ""; - return read_design_ini(p_identifier, path); // Could be the empty string -} - -QString AOApplication::get_image_path(QString p_image) -{ - // File lookup order - // 1. In the theme folder (gamemode-timeofday/main/default), look for - // `p_image`. - return find_theme_asset_path(p_image); + return read_ini(p_identifier, path); // Could be the empty string } From b1d60661e5438435106a2b51439320580ebd0fba Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 11 Oct 2020 20:41:23 -0400 Subject: [PATCH 136/842] Add default arguments to asset reading functions, because default is cool --- include/aoapplication.h | 33 ++------------------------------- src/path_functions.cpp | 10 ---------- 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index ddd9ae192..c18c19a52 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -122,16 +122,6 @@ class AOApplication : public QApplication QString get_default_background_path(QString p_file); QString get_evidence_path(QString p_file); - /** - * @brief Searches for a file in any of the given paths, and returns - * the first case-sensitive path that actually matches to an existing file. - * - * @param possible_paths Paths to check. Case-insensitive. - * - * @return The first case-sensitive path that corresponds to an actual file, - * or an empty string, if not one does. - */ - QString find_asset_path(QStringList possible_paths); /** * @brief Returns the first case-sensitive file that is the combination of one * of the given root and extensions, or empty string if no combination exists. @@ -148,26 +138,7 @@ class AOApplication : public QApplication * exists, or an empty string, if not one does. */ QString find_asset_path(QStringList possible_roots, - QStringList possible_exts); - - /** - * @brief Searches for a file in the current theme folder and returns the - * first case-sensitive path that matches to a file, or empty if it fails. - * - * @details In the current theme folder, p_file is checked for in these paths - * 1. The current time of day folder in the current gamemode folder - * 2. The current gamemode folder - * 3. The current time of day folder - * 4. The current theme folder. - * The first path that is matched is the one that is returned. If p_file is - * not found in any path, it returns an empty string. - * - * @param p_file File name+extension to look for. Case-insensitive. - * - * @return The first case-sensitive path that corresponds to an actual file, - * or an empty string, if not one does. - */ - QString find_theme_asset_path(QString p_file); + QStringList possible_exts = {""}); /** * @brief Returns the first case-sensitive file in the theme folder that is @@ -190,7 +161,7 @@ class AOApplication : public QApplication * @return The first case-sensitive root+extension path that corresponds to an * actual file, or an empty string, if not one does. */ - QString find_theme_asset_path(QString p_root, QStringList p_exts); + QString find_theme_asset_path(QString p_root, QStringList p_exts = {""}); /** * @brief Returns the 'correct' path for the file given as the parameter by diff --git a/src/path_functions.cpp b/src/path_functions.cpp index c20caf139..c5b8a9c99 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -132,11 +132,6 @@ QString AOApplication::get_case_sensitive_path(QString p_file) } #endif -QString AOApplication::find_asset_path(QStringList possible_paths) -{ - return find_asset_path(possible_paths, {""}); -} - QString AOApplication::find_asset_path(QStringList possible_roots, QStringList possible_exts) { @@ -152,11 +147,6 @@ QString AOApplication::find_asset_path(QStringList possible_roots, return ""; } -QString AOApplication::find_theme_asset_path(QString p_file) -{ - return find_theme_asset_path(p_file, {""}); -} - QString AOApplication::find_theme_asset_path(QString p_file, QStringList exts) { QStringList paths{ From 14f0ce47faa2b27c13b99bc4c8973705c736b5fa Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 11 Oct 2020 22:03:31 -0400 Subject: [PATCH 137/842] Buttons with no associated image now show push button with name, instead of being skipped over --- include/courtroom.h | 67 ++++++++++++++++--- src/courtroom.cpp | 133 +++++++++++++------------------------- src/courtroom_widgets.cpp | 8 +-- 3 files changed, 106 insertions(+), 102 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index e1d33880f..f9f40ee85 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -182,11 +182,29 @@ class Courtroom : public QMainWindow void move_widget(QWidget *p_widget, QString p_identifier); + /** + * @brief Show the currently selected shout button, hide the remaining ones. + * If no shouts exist, this method does nothing. + */ void set_shouts(); + + /** + * @brief Show the currently selected effect button, hide the remaining ones. + * If no effects exist, this method does nothing. + */ void set_effects(); - void set_judge_enabled(bool p_enabled); + + /** + * @brief Show the currently selected splash button, hide the remaining ones. + * If no splashes exist, this method does nothing. + */ void set_judge_wtce(); + + /** + * @brief Show all free blocks and restart their animations. + */ void set_free_blocks(); + void set_judge_enabled(bool p_enabled); // these are for OOC chat void append_server_chatmessage(QString p_name, QString p_message); @@ -714,9 +732,24 @@ private slots: void on_cycle_clicked(); - void cycle_shout(int p_index); - void cycle_effect(int p_index); - void cycle_wtce(int p_index); + /** + * @brief Selects the shout p_delta slots ahead of the current one, wrapping + * around if needed. If p_delta is negative, look -p_delta slots behind. + * @param Shout slots to advance. May be negative. + */ + void cycle_shout(int p_delta); + /** + * @brief Selects the effect p_delta slots ahead of the current one, wrapping + * around if needed. If p_delta is negative, look -p_delta slots behind. + * @param Shout slots to advance. May be negative. + */ + void cycle_effect(int p_delta); + /** + * @brief Selects the splash p_delta slots ahead of the current one, wrapping + * around if needed. If p_delta is negative, look -p_delta slots behind. + * @param Shout slots to advance. May be negative. + */ + void cycle_wtce(int p_delta); void on_add_button_clicked(); void on_delete_button_clicked(); @@ -730,17 +763,27 @@ private slots: void load_wtce(); void load_free_blocks(); /** - * @brief reset the shout button's texture to default - * DOES NOT MODIFY OBJECTION_STATE + * @brief Set the sprites of the shout buttons, and mark the currently + * selected shout as such. + * + * @details If a sprite cannot be found for a shout button, a regular + * push button is displayed for it with its shout name instead. */ - void reset_shout_buttons(); + void draw_shout_buttons(); /** * @brief a general purpose function to toggle button selection */ void on_shout_clicked(); - void reset_effect_buttons(); + /** + * @brief Set the sprites of the effect buttons, and mark the currently + * selected effect as such. + * + * @details If a sprite cannot be found for a shout button, a regular + * push button is displayed for it with its shout name instead. + */ + void draw_effect_buttons(); void on_effect_button_clicked(); void on_mute_clicked(); @@ -754,7 +797,13 @@ private slots: void on_witness_testimony_clicked(); void on_cross_examination_clicked(); - void reset_judge_wtce_buttons(); + /** + * @brief Set the sprites of the splash buttons. + * + * @details If a sprite cannot be found for a shout button, a regular + * push button is displayed for it with its shout name instead. + */ + void draw_judge_wtce_buttons(); void on_wtce_clicked(); void on_change_character_clicked(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 63614c40f..13c2a4aec 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -92,7 +92,7 @@ void Courtroom::enter_courtroom(int p_cid) m_shout_state = 0; m_wtce_current = 0; - reset_judge_wtce_buttons(); + draw_judge_wtce_buttons(); // setup chat on_chat_config_changed(); @@ -789,13 +789,13 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); m_objection_state = 0; - reset_shout_buttons(); + draw_shout_buttons(); m_effect_state = 0; - reset_effect_buttons(); + draw_effect_buttons(); m_wtce_current = 0; - reset_judge_wtce_buttons(); + draw_judge_wtce_buttons(); is_presenting_evidence = false; ui_evidence_present->set_image("present_disabled.png"); @@ -2047,13 +2047,20 @@ void Courtroom::on_area_list_double_clicked(QModelIndex p_model) ui_ic_chat_message->setFocus(); } -void Courtroom::reset_shout_buttons() +void Courtroom::draw_shout_buttons() { for (int i = 0; i < ui_shouts.size(); ++i) - ui_shouts[i]->set_image(shout_names.at(i) + ".png"); + { + QString shout_file = shout_names.at(i) + ".png"; + ui_shouts[i]->set_image(shout_file); + if (ao_app->find_theme_asset_path(shout_file).isEmpty()) + ui_shouts[i]->setText(shout_names.at(i)); + else + ui_shouts[i]->setText(""); + } - if (m_objection_state != 0 && - ui_shouts.size() > 0) // check to prevent crashing + // Mark selected button as such + if (m_objection_state != 0 && ui_shouts.size() > 0) ui_shouts.at(m_objection_state - 1) ->set_image(shout_names.at(m_objection_state - 1) + "_selected.png"); } @@ -2069,7 +2076,7 @@ void Courtroom::on_shout_clicked() else m_objection_state = f_shout_id; - reset_shout_buttons(); + draw_shout_buttons(); ui_ic_chat_message->setFocus(); } @@ -2110,98 +2117,41 @@ void Courtroom::on_cycle_clicked() ui_ic_chat_message->setFocus(); } -void Courtroom::cycle_shout(int p_index) +void Courtroom::cycle_shout(int p_delta) { int n = ui_shouts.size(); - - // Check if any shout button is available to begin with - int i; - for (i = 0; i < n; i++) - { - if (shouts_enabled[i]) - break; - } - if (i == n) - { - call_notice( - "Fatal error: no shout button images found in current theme folder."); - exit(1); - } - - // By performing this check beforehand, the do while loop here is guaranteed - // to terminate. - do - { - m_shout_state = (m_shout_state - p_index + n) % n; - } while (!shouts_enabled[m_shout_state]); - + m_shout_state = (m_shout_state - p_delta + n) % n; set_shouts(); } -void Courtroom::cycle_effect(int p_index) +void Courtroom::cycle_effect(int p_delta) { int n = ui_effects.size(); - // Check if any effect button is available to begin with - int i; - for (i = 0; i < n; i++) - { - if (effects_enabled[i]) - break; - } - if (i == n) - { - call_notice( - "Fatal error: no effect button images found in current theme folder."); - exit(1); - } - - // By performing this check beforehand, the do while loop here is guaranteed - // to terminate. - do - { - m_effect_current = (m_effect_current - p_index + n) % n; - } while (!effects_enabled[m_effect_current]); - + m_effect_current = (m_effect_current - p_delta + n) % n; set_effects(); } -void Courtroom::cycle_wtce(int p_index) +void Courtroom::cycle_wtce(int p_delta) { int n = ui_wtce.size(); - // Check if any splash button is available to begin with - int i; - for (i = 0; i < n; i++) - { - if (wtce_enabled[i]) - break; - } - if (i == n) - { - call_notice( - "Fatal error: no splash button images found in current theme folder."); - exit(1); - } - - // By performing this check beforehand, the do while loop here is guaranteed - // to terminate. - do - { - m_wtce_current = (m_wtce_current - p_index + n) % n; - } while (!wtce_enabled[m_wtce_current]); - + m_wtce_current = (m_wtce_current - p_delta + n) % n; set_judge_wtce(); } -void Courtroom::reset_effect_buttons() +void Courtroom::draw_effect_buttons() { - // effect names does not necessarily have the same size as ui effects for (int i = 0; i < effect_names.size(); ++i) { - // qDebug() << effect_names << i; - ui_effects[i]->set_image(effect_names.at(i) + ".png"); + QString effect_file = effect_names.at(i) + ".png"; + ui_effects[i]->set_image(effect_file); + if (ao_app->find_theme_asset_path(effect_file).isEmpty()) + ui_effects[i]->setText(effect_names.at(i)); + else + ui_effects[i]->setText(""); } - if (m_effect_state != 0 && ui_effects.size() > 0) // check to prevent crashing + // Mark selected button as such + if (m_effect_state != 0 && ui_effects.size() > 0) ui_effects[m_effect_state - 1]->set_image( effect_names.at(m_effect_state - 1) + "_pressed.png"); } @@ -2216,7 +2166,7 @@ void Courtroom::on_effect_button_clicked() else m_effect_state = f_effect_id; - reset_effect_buttons(); + draw_effect_buttons(); ui_ic_chat_message->setFocus(); } @@ -2297,15 +2247,22 @@ void Courtroom::on_cross_examination_clicked() ui_ic_chat_message->setFocus(); } -void Courtroom::reset_judge_wtce_buttons() // kind of an unnecessary function, - // but I added it just in case +void Courtroom::draw_judge_wtce_buttons() { - for (int i = 0; i < wtce_names.size(); - ++i) // effect names does not necessarily have the same size as ui - // effects + for (int i = 0; i < wtce_names.size(); ++i) { - ui_wtce[i]->set_image(wtce_names.at(i) + ".png"); + QString wtce_file = wtce_names.at(i) + ".png"; + ui_wtce[i]->set_image(wtce_file); + if (ao_app->find_theme_asset_path(wtce_file).isEmpty()) + ui_wtce[i]->setText(wtce_names.at(i)); + else + ui_wtce[i]->setText(""); } + + // Unlike the other reset functions, the judge buttons are of immediate + // action and thus are immediately unpressed after being pressed. + // Therefore, we do not need to handle displaying a "_selected.png" + // when appropriate, because there is no appropriate situation } void Courtroom::on_wtce_clicked() diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 226df9509..04e757544 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -745,7 +745,7 @@ void Courtroom::set_widgets() // ui_shouts[0]->show(); // ui_shouts[1]->show(); // ui_shouts[2]->show(); - reset_shout_buttons(); + draw_shout_buttons(); set_size_and_pos(ui_shout_up, "shout_up"); ui_shout_up->set_image("shoutup.png"); @@ -775,7 +775,7 @@ void Courtroom::set_widgets() { set_size_and_pos(ui_effects[i], effect_names[i]); } - reset_effect_buttons(); + draw_effect_buttons(); set_size_and_pos(ui_effect_up, "effect_up"); ui_effect_up->set_image("effectup.png"); @@ -816,9 +816,7 @@ void Courtroom::set_widgets() qDebug() << "AA: single wtce"; } set_judge_wtce(); - - // this will reset the image - reset_judge_wtce_buttons(); + draw_judge_wtce_buttons(); for (int i = 0; i < free_block_names.size(); ++i) { From 96a7da4932de6cdb94d124a15a0c778ae92fd949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Tue, 13 Oct 2020 19:19:53 +0200 Subject: [PATCH 138/842] AOApplication dependency, case-sensitive pathing * Added `AOApplication` dependency to `AOConfigPanel` because it wouldn't be AO2 without some `AOApplication` dependency :) * Added case-sensitive path checking for themes, gamemodes and times inside `AOConfigPanel` --- include/aoconfig.h | 1 + include/aoconfigpanel.h | 63 +++++++++++++++++++++++------------------ include/aoguiloader.h | 1 + res/ui/config_panel.ui | 2 +- src/aoapplication.cpp | 2 +- src/aoconfig.cpp | 1 + src/aoconfigpanel.cpp | 17 ++++++++--- src/aoguiloader.cpp | 1 + 8 files changed, 55 insertions(+), 33 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 7babeb956..91a699a97 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -1,5 +1,6 @@ #ifndef AOCONFIG_H #define AOCONFIG_H + // qt #include diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 9a9675f3e..68f3f7b33 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -1,5 +1,6 @@ #ifndef AOCONFIGPANEL_H #define AOCONFIGPANEL_H + // qt #include #include @@ -11,14 +12,49 @@ #include #include #include + // src #include "aoconfig.h" #include "aoguiloader.h" +class AOApplication; + class AOConfigPanel : public QWidget { Q_OBJECT +public: + AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent = nullptr); + +public slots: + void on_config_reload_theme_requested(); + +protected: + void showEvent(QShowEvent *event); + +signals: + void reload_theme(); + +private: + void refresh_theme_list(); + void refresh_gamemode_list(); + void refresh_timeofday_list(); + +private slots: + void on_reload_theme_clicked(); + void on_gamemode_index_changed(QString p_text); + void on_timeofday_index_changed(QString p_text); + void on_log_is_topdown_changed(bool p_enabled); + void on_effects_value_changed(int p_num); + void on_system_value_changed(int p_num); + void on_music_value_changed(int p_num); + void on_blips_value_changed(int p_num); + +private: + // FIXME This dependency shouldn't have come to exist. + AOApplication *ao_app = nullptr; + + // driver AOConfig *m_config = nullptr; // general @@ -58,33 +94,6 @@ class AOConfigPanel : public QWidget // save QPushButton *w_save = nullptr; - -public: - AOConfigPanel(QWidget *p_parent = nullptr); - -public slots: - void on_config_reload_theme_requested(); - -protected: - void showEvent(QShowEvent *event); - -signals: - void reload_theme(); - -private: - void refresh_theme_list(); - void refresh_gamemode_list(); - void refresh_timeofday_list(); - -private slots: - void on_reload_theme_clicked(); - void on_gamemode_index_changed(QString p_text); - void on_timeofday_index_changed(QString p_text); - void on_log_is_topdown_changed(bool p_enabled); - void on_effects_value_changed(int p_num); - void on_system_value_changed(int p_num); - void on_music_value_changed(int p_num); - void on_blips_value_changed(int p_num); }; #endif // AOCONFIGPANEL_H diff --git a/include/aoguiloader.h b/include/aoguiloader.h index 13bfe4e65..d0e35a985 100644 --- a/include/aoguiloader.h +++ b/include/aoguiloader.h @@ -1,5 +1,6 @@ #ifndef AOGUILOADER_H #define AOGUILOADER_H + // qt #include #include diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 9999636f1..9cf45e72f 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 1 + 0 diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 6bd748029..a2a819a78 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -28,7 +28,7 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(config, SIGNAL(timeofday_changed(QString)), this, SLOT(on_config_timeofday_changed())); - config_panel = new AOConfigPanel; + config_panel = new AOConfigPanel(this); connect(config_panel, SIGNAL(reload_theme()), this, SLOT(on_config_reload_theme_requested())); connect(this, SIGNAL(reload_theme()), config_panel, diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 52d51aba9..d47470ae9 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -1,4 +1,5 @@ #include "aoconfig.h" + // qt #include #include diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 6694b126d..eaf79f1db 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -1,11 +1,17 @@ #include "aoconfigpanel.h" + // qt #include #include -AOConfigPanel::AOConfigPanel(QWidget *p_parent) +// src +#include "aoapplication.h" // ruined + +AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) : QWidget(p_parent), m_config(new AOConfig(this)) { + ao_app = p_ao_app; + setWindowTitle(tr("Config")); setWindowFlag(Qt::WindowMinMaxButtonsHint, false); @@ -218,8 +224,9 @@ void AOConfigPanel::refresh_theme_list() w_theme->clear(); // themes + const QString path = QDir::currentPath() + "/base/themes"; for (QString i_folder : - QDir(QDir::currentPath() + "/base/themes").entryList(QDir::Dirs)) + QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; @@ -246,7 +253,8 @@ void AOConfigPanel::refresh_gamemode_list() // gamemodes QString path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; - for (QString i_folder : QDir(path).entryList(QDir::Dirs)) + for (QString i_folder : + QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; @@ -282,7 +290,8 @@ void AOConfigPanel::refresh_timeofday_list() "/gamemodes/" + m_config->gamemode() + "/times/"; // times of day - for (QString i_folder : QDir(path).entryList(QDir::Dirs)) + for (QString i_folder : + QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; diff --git a/src/aoguiloader.cpp b/src/aoguiloader.cpp index fd4099696..1db9c5b3d 100644 --- a/src/aoguiloader.cpp +++ b/src/aoguiloader.cpp @@ -1,4 +1,5 @@ #include "aoguiloader.h" + // qt #include #include From e9a3eaff6a4ec5d0141f37864780f25f9ff227b1 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 18 Oct 2020 10:53:23 -0400 Subject: [PATCH 139/842] Unselect effect/shouts on reload theme+Make the currently shown effect/shout be 0 on reload theme --- include/courtroom.h | 4 ++-- src/courtroom.cpp | 31 +++++++++++++++++-------------- src/courtroom_widgets.cpp | 2 +- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index f9f40ee85..f3ab5482e 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -406,10 +406,10 @@ class Courtroom : public QMainWindow QString current_file = ""; - int m_objection_state = 0; + int m_shout_state = 0; int m_effect_state = 0; int m_text_color = 0; - int m_shout_state = 0; + int m_shout_current = 0; int m_effect_current = 0; int m_wtce_current = 0; bool is_presenting_evidence = false; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 13c2a4aec..df72fbbf3 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -91,6 +91,9 @@ void Courtroom::enter_courtroom(int p_cid) current_evidence = 0; m_shout_state = 0; + m_shout_current = 0; + m_effect_state = 0; + m_effect_current = 0; m_wtce_current = 0; draw_judge_wtce_buttons(); @@ -116,7 +119,7 @@ void Courtroom::enter_courtroom(int p_cid) set_widgets(); check_shouts(); - if (!shouts_enabled[m_shout_state]) + if (!shouts_enabled[m_shout_current]) cycle_shout(1); check_effects(); @@ -593,7 +596,7 @@ void Courtroom::on_chat_return_pressed() if (ui_ic_chat_message->text() == "" || is_client_muted) return; - if ((anim_state < 3 || text_state < 2) && m_objection_state == 0) + if ((anim_state < 3 || text_state < 2) && m_shout_state == 0) return; // qDebug() << "prev_emote = " << prev_emote << "current_emote = " << @@ -664,7 +667,7 @@ void Courtroom::on_chat_return_pressed() int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); // needed or else legacy won't understand what we're saying - if (m_objection_state > 0) + if (m_shout_state > 0) { if (f_emote_mod == 5) f_emote_mod = 6; @@ -694,11 +697,11 @@ void Courtroom::on_chat_return_pressed() QString f_obj_state; - if (m_objection_state < 0 || - (!ao_app->custom_objection_enabled && m_objection_state > 3)) + if (m_shout_state < 0 || + (!ao_app->custom_objection_enabled && m_shout_state > 3)) f_obj_state = "0"; else - f_obj_state = QString::number(m_objection_state); + f_obj_state = QString::number(m_shout_state); packet_contents.append(f_obj_state); @@ -788,7 +791,7 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) ui_ic_chat_message->clear(); ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); - m_objection_state = 0; + m_shout_state = 0; draw_shout_buttons(); m_effect_state = 0; @@ -2060,9 +2063,9 @@ void Courtroom::draw_shout_buttons() } // Mark selected button as such - if (m_objection_state != 0 && ui_shouts.size() > 0) - ui_shouts.at(m_objection_state - 1) - ->set_image(shout_names.at(m_objection_state - 1) + "_selected.png"); + if (m_shout_state != 0 && ui_shouts.size() > 0) + ui_shouts.at(m_shout_state - 1) + ->set_image(shout_names.at(m_shout_state - 1) + "_selected.png"); } void Courtroom::on_shout_clicked() @@ -2071,10 +2074,10 @@ void Courtroom::on_shout_clicked() int f_shout_id = f_shout_button->property("shout_id").toInt(); // update based on current button selected - if (f_shout_id == m_objection_state) - m_objection_state = 0; + if (f_shout_id == m_shout_state) + m_shout_state = 0; else - m_objection_state = f_shout_id; + m_shout_state = f_shout_id; draw_shout_buttons(); @@ -2120,7 +2123,7 @@ void Courtroom::on_cycle_clicked() void Courtroom::cycle_shout(int p_delta) { int n = ui_shouts.size(); - m_shout_state = (m_shout_state - p_delta + n) % n; + m_shout_current = (m_shout_current - p_delta + n) % n; set_shouts(); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 04e757544..0c0b5d31e 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1381,7 +1381,7 @@ void Courtroom::set_shouts() for (auto &shout : ui_shouts) shout->hide(); if (ui_shouts.size() > 0) - ui_shouts[m_shout_state]->show(); // check to prevent crashing + ui_shouts[m_shout_current]->show(); // check to prevent crashing } void Courtroom::set_effects() From 1841791c088b03ad75a0410c01981ab73b21e2a0 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 24 Oct 2020 12:17:06 -0400 Subject: [PATCH 140/842] Move file extension list declarations to helper functions --- .gitignore | 3 +- include/file_functions.h | 7 ++++- src/aocharmovie.cpp | 8 ++--- src/aomovie.cpp | 16 +++++----- src/aoscene.cpp | 2 +- src/aosfxplayer.cpp | 4 ++- src/courtroom.cpp | 61 ++++++++++++++------------------------- src/courtroom_widgets.cpp | 27 +++++++++-------- src/file_functions.cpp | 15 ++++++++++ 9 files changed, 75 insertions(+), 68 deletions(-) diff --git a/.gitignore b/.gitignore index 7a65b77d9..b2fc2ea4d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ docs/ *.db *.user *.dll -*.so \ No newline at end of file +*.so +*.autosave \ No newline at end of file diff --git a/include/file_functions.h b/include/file_functions.h index af58a3ef8..75c46461c 100644 --- a/include/file_functions.h +++ b/include/file_functions.h @@ -2,9 +2,14 @@ #define FILE_FUNCTIONS_H #include +#include #include +QStringList animated_or_static_extensions(); +QStringList animated_extensions(); +QStringList audio_extensions(); + bool file_exists(QString file_path); bool dir_exists(QString file_path); -#endif // FILE_FUNCTIONS_H \ No newline at end of file +#endif // FILE_FUNCTIONS_H diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index b2cf334e1..89ee67cd9 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -34,15 +34,15 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, // 3. In the theme folder (gamemode-timeofday/main/default), look for // "placeholder" + extensions in `exts` in order - QStringList exts{".webp", ".apng", ".gif", ".png"}; QString target_path = ao_app->find_asset_path( { ao_app->get_character_path(p_char, p_emote_prefix + p_emote), ao_app->get_character_path(p_char, p_emote), }, - exts); + animated_or_static_extensions()); if (target_path.isEmpty()) - target_path = ao_app->find_theme_asset_path("placeholder", exts); + target_path = + ao_app->find_theme_asset_path("placeholder", animated_extensions()); show(); if (!p_visible) @@ -72,7 +72,7 @@ bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) { // figure out what extension the animation is using QString f_source_path = ao_app->get_character_path(p_char, p_emote); - for (QString &i_ext : QStringList{".webp", ".apng", ".gif", ".png"}) + for (QString &i_ext : animated_or_static_extensions()) { QString f_target_path = ao_app->get_case_sensitive_path(f_source_path + i_ext); diff --git a/src/aomovie.cpp b/src/aomovie.cpp index b30034c8c..ca54b9d65 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -55,18 +55,19 @@ void AOMovie::play(QString p_file, QString p_char) // 4. In the theme folder (gamemode-timeofday/main/default), look for // "placeholder" + extensions in `exts` in order - QStringList exts{".webp", ".apng", ".gif", ".png"}; file_path = ao_app->find_asset_path( { ao_app->get_character_path(p_char, char_p_file), ao_app->get_character_path(p_char, "overlay/" + char_p_file), }, - exts); + animated_or_static_extensions()); if (file_path.isEmpty()) { - file_path = ao_app->find_theme_asset_path(p_file, exts); + file_path = + ao_app->find_theme_asset_path(p_file, animated_or_static_extensions()); if (file_path.isEmpty()) - file_path = ao_app->find_theme_asset_path("placeholder", exts); + file_path = ao_app->find_theme_asset_path( + "placeholder", animated_or_static_extensions()); } qDebug() << "playing" << file_path; @@ -96,13 +97,12 @@ void AOMovie::play_interjection(QString p_char_name, // 2. In the theme folder (gamemode-timeofday/main/default), look for // `p_char_name` - QStringList exts{".webp", ".apng", ".gif"}; QString interjection_filepath = ao_app->find_asset_path( {ao_app->get_character_path(p_char_name, p_char_interjection_name)}, - exts); + animated_extensions()); if (interjection_filepath.isEmpty()) - interjection_filepath = - ao_app->find_theme_asset_path(p_interjection_name, exts); + interjection_filepath = ao_app->find_theme_asset_path( + p_interjection_name, animated_extensions()); if (interjection_filepath.isEmpty()) { diff --git a/src/aoscene.cpp b/src/aoscene.cpp index 2b816fbb6..510435d8d 100644 --- a/src/aoscene.cpp +++ b/src/aoscene.cpp @@ -20,7 +20,7 @@ void AOScene::set_image(QString p_image) // background specific path QString background_path = ao_app->get_background_path(p_image); - for (auto &ext : QVector{".webp", ".apng", ".gif", ".png"}) + for (auto &ext : animated_or_static_extensions()) { QString full_background_path = ao_app->get_case_sensitive_path(background_path + ext); diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index 1ee91e28a..360b19b98 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -1,4 +1,5 @@ #include "aosfxplayer.h" +#include "file_functions.h" #include @@ -11,7 +12,8 @@ AOSfxPlayer::AOSfxPlayer(QObject *p_parent, AOApplication *p_ao_app) void AOSfxPlayer::play(QString p_name) { - QString f_file = ao_app->get_sounds_path(p_name); + QString f_root = ao_app->get_sounds_path(p_name); + QString f_file = ao_app->find_asset_path({f_root}, audio_extensions()); try { diff --git a/src/courtroom.cpp b/src/courtroom.cpp index df72fbbf3..f4b6bb955 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -239,22 +239,18 @@ void Courtroom::set_scene() f_desk_image = "stand"; } - QStringList exts{".webp", ".apng", ".gif", ".png"}; - - bool has_all_desks; - if (ao_app->find_asset_path({get_background_path("defensedesk")}, exts) - .isEmpty()) - has_all_desks = false; - else if (ao_app - ->find_asset_path({get_background_path("prosecutiondesk")}, - exts) - .isEmpty()) - has_all_desks = false; - else if (ao_app->find_asset_path({get_background_path("stand")}, exts) - .isEmpty()) - has_all_desks = false; - else - has_all_desks = true; + bool has_all_desks = true; + QStringList alldesks{"defensedesk", "prosecutiondesk", "stand"}; + for (QString desk : alldesks) + { + QString full_path = ao_app->find_asset_path( + {get_background_path(desk)}, animated_or_static_extensions()); + if (full_path.isEmpty()) + { + has_all_desks = false; + break; + } + } if (f_desk_mod == "0" || (f_desk_mod != "1" && @@ -391,7 +387,7 @@ void Courtroom::list_music() QString i_song = music_list.at(n_song); bool found = false; - for (auto &ext : QStringList{"", ".wav", ".ogg", ".opus", ".mp3"}) + for (auto &ext : audio_extensions()) { QString r_song = i_song + ext; QString song_path = ao_app->get_music_path(r_song); @@ -492,7 +488,7 @@ void Courtroom::list_sfx() bool found = false; - for (auto &ext : QStringList{"", ".wav", ".ogg", ".opus", ".mp3"}) + for (auto &ext : audio_extensions()) { QString r_sfx = i_sfx + ext; QString sfx_path = ao_app->get_sounds_path(r_sfx); @@ -915,17 +911,7 @@ void Courtroom::handle_chatmessage_2() // handles IC QString chatbox = ao_app->get_chat(m_chatmessage[CHAR_NAME]); if (chatbox == "") - if (ao_app->read_theme_ini("daynight_theme", cc_config_ini) == "true") - { - if (current_clock < 0) - ui_vp_chatbox->set_image("chatmed.png"); - else if (current_clock >= 7 && current_clock < 22) - ui_vp_chatbox->set_image("chatmed_day.png"); - else - ui_vp_chatbox->set_image("chatmed_night.png"); - } - else - ui_vp_chatbox->set_image("chatmed.png"); + ui_vp_chatbox->set_image("chatmed.png"); else { QString chatbox_path = ao_app->get_base_path() + "misc/" + chatbox + ".png"; @@ -1020,12 +1006,11 @@ void Courtroom::handle_chatmessage_3() // 2. In the character folder, look for // "showname" + extensions in `exts` in order - QStringList exts = {".png", ".jpg", ".bmp"}; - QString path = - ao_app->find_theme_asset_path("characters/" + f_char + "/showname", exts); + QString path = ao_app->find_theme_asset_path( + "characters/" + f_char + "/showname", {".png"}); if (path.isEmpty()) path = ao_app->find_asset_path( - {ao_app->get_character_path(f_char, "showname")}, exts); + {ao_app->get_character_path(f_char, "showname")}, {".png"}); if (!path.isEmpty() && !chatmessage_is_empty && ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") @@ -1600,11 +1585,7 @@ void Courtroom::play_sfx() if (sfx_name == "1") return; - QStringList exts{"", ".wav", ".ogg", ".opus", ".mp3"}; - QString f_file = - ao_app->find_asset_path({ao_app->get_sounds_path(sfx_name)}, exts); - - m_effects_player->play(f_file); + m_effects_player->play(sfx_name); } void Courtroom::set_text_color() @@ -1699,7 +1680,7 @@ void Courtroom::handle_song(QStringList *p_contents) QString f_song = f_contents.at(0); int n_char = f_contents.at(1).toInt(); - for (auto &ext : QStringList{"", ".wav", ".ogg", ".opus", ".mp3"}) + for (auto &ext : audio_extensions()) { QString r_song = f_song + ext; QString song_path = ao_app->get_music_path(r_song); @@ -2464,7 +2445,7 @@ void Courtroom::on_sfx_list_clicked(QModelIndex p_index) bool found = false; - for (auto &ext : QStringList{"", ".wav", ".ogg", ".opus", ".mp3"}) + for (auto &ext : audio_extensions()) { QString r_sfx = sfx_names.at(current_sfx_id) + ext; QString sfx_path = ao_app->get_sounds_path(r_sfx); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 0c0b5d31e..4d76511b2 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1133,13 +1133,14 @@ void Courtroom::check_effects() // `effect_names.at(i)` + extensions in `exts` in order // Only enable buttons where a file was found - QStringList exts{".webp", ".gif", ".apng"}; for (int i = 0; i < ui_effects.size(); ++i) { QString path = ao_app->find_asset_path( - {ao_app->get_character_path(current_char, effect_names.at(i))}, exts); + {ao_app->get_character_path(current_char, effect_names.at(i))}, + animated_extensions()); if (path.isEmpty()) - path = ao_app->find_theme_asset_path(effect_names.at(i), exts); + path = ao_app->find_theme_asset_path(effect_names.at(i), + animated_extensions()); effects_enabled[i] = (!path.isEmpty()); } } @@ -1153,14 +1154,14 @@ void Courtroom::check_free_blocks() // `free_block_names.at(i)` + extensions in `exts` in order // Only enable buttons where a file was found - QStringList exts{".webp", ".gif", ".apng"}; for (int i = 0; i < ui_free_blocks.size(); ++i) { QString path = ao_app->find_asset_path( {ao_app->get_character_path(current_char, free_block_names.at(i))}, - exts); + animated_extensions()); if (path.isEmpty()) - path = ao_app->find_theme_asset_path(free_block_names.at(i), exts); + path = ao_app->find_theme_asset_path(free_block_names.at(i), + animated_extensions()); free_blocks_enabled[i] = (!path.isEmpty()); } } @@ -1174,14 +1175,15 @@ void Courtroom::check_shouts() // `shout_names.at(i)` + extensions in `exts` in order // Only enable buttons where a file was found - QStringList exts{".webp", ".gif", ".apng"}; for (int i = 0; i < ui_shouts.size(); ++i) { QString path = ao_app->find_asset_path( - {ao_app->get_character_path(current_char, shout_names.at(i))}, exts); + {ao_app->get_character_path(current_char, shout_names.at(i))}, + animated_extensions()); if (path.isEmpty()) - path = ao_app->find_theme_asset_path(shout_names.at(i), exts); + path = ao_app->find_theme_asset_path(shout_names.at(i), + animated_extensions()); shouts_enabled[i] = (!path.isEmpty()); } @@ -1196,13 +1198,14 @@ void Courtroom::check_wtce() // `wtce_names.at(i)` + extensions in `exts` in order // Only enable buttons where a file was found - QStringList exts{".webp", ".gif", ".apng"}; for (int i = 0; i < ui_wtce.size(); ++i) { QString path = ao_app->find_asset_path( - {ao_app->get_character_path(current_char, wtce_names.at(i))}, exts); + {ao_app->get_character_path(current_char, wtce_names.at(i))}, + animated_extensions()); if (path.isEmpty()) - path = ao_app->find_theme_asset_path(wtce_names.at(i), exts); + path = ao_app->find_theme_asset_path(wtce_names.at(i), + animated_extensions()); wtce_enabled[i] = (!path.isEmpty()); } } diff --git a/src/file_functions.cpp b/src/file_functions.cpp index f2d4ef125..1c893a984 100644 --- a/src/file_functions.cpp +++ b/src/file_functions.cpp @@ -3,6 +3,21 @@ #include "file_functions.h" +QStringList animated_or_static_extensions() +{ + return QStringList{".webp", ".apng", ".gif", ".png"}; +} + +QStringList animated_extensions() +{ + return QStringList{".webp", ".apng", ".gif"}; +} + +QStringList audio_extensions() +{ + return QStringList{"", ".wav", ".ogg", ".opus", ".mp3"}; +} + bool file_exists(QString file_path) { QFileInfo check_file(file_path); From 4a7553ac93b3dc97ed685c48f1d044ffa39142b1 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 24 Oct 2020 13:20:43 -0400 Subject: [PATCH 141/842] Make songs/sfx use find_asset_path; Fix SFX lists marking items as found iff file (item+2) exists --- src/courtroom.cpp | 84 ++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 49 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f4b6bb955..588f51e1b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -385,24 +385,16 @@ void Courtroom::list_music() for (int n_song = 0; n_song < music_list.size(); ++n_song) { QString i_song = music_list.at(n_song); - bool found = false; - - for (auto &ext : audio_extensions()) - { - QString r_song = i_song + ext; - QString song_path = ao_app->get_music_path(r_song); - if (file_exists(song_path)) - { - found = true; - break; - } - } if (i_song.toLower().contains(ui_music_search->text().toLower())) { ui_music_list->addItem(i_song); - if (found) + QString song_root = ao_app->get_music_path(i_song); + QString song_path = + ao_app->find_asset_path({song_root}, audio_extensions()); + + if (!song_path.isEmpty()) ui_music_list->item(n_listed_songs)->setBackground(found_brush); else ui_music_list->item(n_listed_songs)->setBackground(missing_brush); @@ -459,14 +451,29 @@ void Courtroom::list_sfx() QBrush found_brush(ao_app->get_color("found_song_color", f_file)); QBrush missing_brush(ao_app->get_color("missing_song_color", f_file)); + // Add hardcoded items + // FIXME: Rewrite ui_sfx_list->addItem("Default"); ui_sfx_list->addItem("Silence"); sfx_names.append("1"); // Default sfx_names.append("1"); // Silence - int n_listed_sfxs = 0; + QString default_sfx_root = ao_app->get_sounds_path("1"); + QString default_sfx_path = + ao_app->find_asset_path({default_sfx_root}, audio_extensions()); + if (!default_sfx_path.isEmpty()) + { + ui_sfx_list->item(0)->setBackground(found_brush); + ui_sfx_list->item(1)->setBackground(found_brush); + } + else + { + ui_sfx_list->item(0)->setBackground(missing_brush); + ui_sfx_list->item(1)->setBackground(missing_brush); + } + // Now add the other SFXs given by the character's sound.ini for (int n_sfx = 0; n_sfx < sfx_list.size(); ++n_sfx) { QStringList sfx = sfx_list.at(n_sfx).split("="); @@ -478,33 +485,21 @@ void Courtroom::list_sfx() else d_sfx = sfx.at(1).trimmed(); - // qDebug() << "isfx=" << i_sfx << "dsfx=" << d_sfx << "sfx=" << sfx; - if (i_sfx.toLower().contains(ui_sfx_search->text().toLower())) { ui_sfx_list->addItem(d_sfx); - ui_sfx_list->item(ui_sfx_list->count() - 1) - ->setStatusTip(QString::number(n_sfx + 2)); + int last_index = ui_sfx_list->count() - 1; + ui_sfx_list->item(last_index)->setStatusTip(QString::number(n_sfx + 2)); - bool found = false; + // Apply appropriate color whether SFX exists or not + QString sfx_root = ao_app->get_sounds_path(i_sfx); + QString sfx_path = + ao_app->find_asset_path({sfx_root}, audio_extensions()); - for (auto &ext : audio_extensions()) - { - QString r_sfx = i_sfx + ext; - QString sfx_path = ao_app->get_sounds_path(r_sfx); - if (file_exists(sfx_path)) - { - found = true; - break; - } - } - - if (found) - ui_sfx_list->item(n_listed_sfxs)->setBackground(found_brush); + if (!sfx_path.isEmpty()) + ui_sfx_list->item(last_index)->setBackground(found_brush); else - ui_sfx_list->item(n_listed_sfxs)->setBackground(missing_brush); - - ++n_listed_sfxs; + ui_sfx_list->item(last_index)->setBackground(missing_brush); } } } @@ -2439,24 +2434,15 @@ void Courtroom::on_sfx_list_clicked(QModelIndex p_index) QBrush found_brush(ao_app->get_color("found_song_color", design_ini)); QBrush missing_brush(ao_app->get_color("missing_song_color", design_ini)); - if (-1 != current_sfx_id) + if (current_sfx_id != -1) { QListWidgetItem *old_sfx = ui_sfx_list->item(current_sfx_id); - bool found = false; - - for (auto &ext : audio_extensions()) - { - QString r_sfx = sfx_names.at(current_sfx_id) + ext; - QString sfx_path = ao_app->get_sounds_path(r_sfx); - if (file_exists(sfx_path)) - { - found = true; - break; - } - } + // Apply appropriate color whether SFX exists or not + QString sfx_root = ao_app->get_sounds_path(sfx_names.at(current_sfx_id)); + QString sfx_path = ao_app->find_asset_path({sfx_root}, audio_extensions()); - if (found) + if (!sfx_path.isEmpty()) old_sfx->setBackground(found_brush); else old_sfx->setBackground(missing_brush); From 0d0d665bd72f057075e6d9367f98a0bfcb052c41 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 25 Oct 2020 23:45:22 -0400 Subject: [PATCH 142/842] Ignore GM and TOD packets if the theme already has associated version up --- src/courtroom.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 588f51e1b..746fef27b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -357,6 +357,9 @@ void Courtroom::handle_gamemode(QString gamemode) // If so, ignore order. if (ao_app->get_manual_gamemode_enabled()) return; + // If already in this gamemode, do nothing + if (ao_app->get_gamemode() == gamemode) + return; ao_app->set_gamemode(gamemode); on_app_reload_theme_requested(); } @@ -367,6 +370,9 @@ void Courtroom::handle_timeofday(QString timeofday) // If so, ignore order. if (ao_app->get_manual_timeofday_enabled()) return; + // If already in this time of day, do nothing + if (ao_app->get_timeofday() == timeofday) + return; ao_app->set_timeofday(timeofday); on_app_reload_theme_requested(); } From 61377351fb1f744261a7d44cd64ca09a7da9df19 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 26 Oct 2020 00:01:36 -0400 Subject: [PATCH 143/842] Reset current SFX ID after searching for sound effect. This solves a bug where if you select an SFX down enough, use the SFX searchbox to filter for specific SFXs and accidentally make the current SFX ID be out of bounds, attempting to select another SFX causes DRO to attempt to index out of bounds to deselect the old sound effect, which is a crash. --- src/courtroom.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 746fef27b..cbb5d2e0c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -449,6 +449,7 @@ void Courtroom::list_sfx() { ui_sfx_list->clear(); sfx_names.clear(); + current_sfx_id = -1; // Restart current SFX, because it may no longer be valid QString f_file = design_ini; @@ -735,14 +736,12 @@ void Courtroom::on_chat_return_pressed() f_text_color = QString::number(m_text_color); packet_contents.append(f_text_color); - prev_emote = current_emote; - { // reset states - ui_pre->setChecked(ao_config->always_pre_enabled()); - ui_sfx_list->clearSelection(); - list_sfx(); - } + // reset states + ui_pre->setChecked(ao_config->always_pre_enabled()); + ui_sfx_list->clearSelection(); + list_sfx(); ao_app->send_server_packet(new AOPacket("MS", packet_contents)); } From 25438878938f495ace0cc2c2d43b07f27b9422dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Tue, 27 Oct 2020 10:48:14 +0100 Subject: [PATCH 144/842] Removed redundant comments --- src/courtroom.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index cbb5d2e0c..3f5b11909 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -353,11 +353,9 @@ void Courtroom::handle_clock(QString time) void Courtroom::handle_gamemode(QString gamemode) { - // First check if only manual gamemode changes are allowed - // If so, ignore order. + // only manual gamemode changes are allowed if (ao_app->get_manual_gamemode_enabled()) return; - // If already in this gamemode, do nothing if (ao_app->get_gamemode() == gamemode) return; ao_app->set_gamemode(gamemode); @@ -366,11 +364,9 @@ void Courtroom::handle_gamemode(QString gamemode) void Courtroom::handle_timeofday(QString timeofday) { - // First check if only manual time of day changes are allowed - // If so, ignore order. + // only manual gamemode changes are allowed if (ao_app->get_manual_timeofday_enabled()) return; - // If already in this time of day, do nothing if (ao_app->get_timeofday() == timeofday) return; ao_app->set_timeofday(timeofday); From 85a8ba7df65a9d22e85ad0434ad68e8c795a1d85 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 30 Oct 2020 19:46:44 -0400 Subject: [PATCH 145/842] Update file and show clock only if file exists. Hide clock by if given -1 hour and file does not exist This will keep the clock showing the last shown clock image rather than show a placeholder. --- src/courtroom.cpp | 23 ++++++++++++++++++++--- src/courtroom_widgets.cpp | 2 +- src/packet_distribution.cpp | 1 + 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d1742e9ee..e8dbc6374 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -344,11 +344,27 @@ void Courtroom::handle_music_anim() void Courtroom::handle_clock(QString time) { + qDebug() << time; current_clock = time.toInt(); QString string = "hours\\" + time; // expected to be 0, 1, 2... - qDebug() << "hours:" << string; - ui_vp_clock->play(string); + qDebug() << current_clock << string + << ao_app->find_theme_asset_path(string, + animated_or_static_extensions()); + // If the new hour file exists, use it and show the clock + if (!ao_app->find_theme_asset_path(string, animated_or_static_extensions()) + .isEmpty()) + { + ui_vp_clock->show(); + ui_vp_clock->play(string); + } + // Otherwise, keep showing whatever was last shown... + // except if time is now -1. If so, that means the time is unknown, and there + // are no files for it. In that case, hide the clock and stop the movie + else if (current_clock == -1) + { + ui_vp_clock->hide(); + } } void Courtroom::handle_gamemode(QString gamemode) @@ -574,7 +590,8 @@ void Courtroom::save_textlog(QString p_text) { QString f_file = ao_app->get_base_path() + icchatlogsfilename; - ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, f_file); + ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, + f_file); } void Courtroom::append_server_chatmessage(QString p_name, QString p_message) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 4d76511b2..4755746eb 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -682,7 +682,7 @@ void Courtroom::set_widgets() ui_vp_music_display_b->show(); set_size_and_pos(ui_vp_clock, "clock"); - ui_vp_clock->show(); + ui_vp_clock->hide(); ui_ic_chat_message->setStyleSheet( "QLineEdit{background-color: rgba(100, 100, 100, 255);}"); diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 78885a734..23431fc71 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -678,6 +678,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "CL") { + qDebug() << f_contents; w_courtroom->handle_clock(f_contents.at(1)); } else if (header == "GM") From 5c94ae020be934795aa1c55c665b65a6c5855772 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 30 Oct 2020 21:07:44 -0400 Subject: [PATCH 146/842] Replace null character with empty string in IC Chatlog colon mode This fixes a bug where players were unable to fully copy the contents of the IC log if they were in colon mode at some point. That may be because Windows erroneously considers the null character as the end of the clipboard --- src/courtroom.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d1742e9ee..482d67290 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -574,7 +574,8 @@ void Courtroom::save_textlog(QString p_text) { QString f_file = ao_app->get_base_path() + icchatlogsfilename; - ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, f_file); + ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, + f_file); } void Courtroom::append_server_chatmessage(QString p_name, QString p_message) @@ -1215,6 +1216,8 @@ void Courtroom::update_ic_log(bool p_reset_log) { // move cursor cursor.movePosition(move_type); + QString record_end = (QString(QChar::LineFeed) + + (m_chatlog_newline ? QString(QChar::LineFeed) : "")); if (record->system) { @@ -1230,9 +1233,7 @@ void Courtroom::update_ic_log(bool p_reset_log) else separator = " "; cursor.insertText(record->name + separator, name_format); - cursor.insertText(record->line + QChar::LineFeed + - (m_chatlog_newline ? QChar::LineFeed : QChar()), - line_format); + cursor.insertText(record->line + record_end, line_format); } } @@ -1325,6 +1326,7 @@ void Courtroom::append_system_text(QString p_showname, QString p_line) { if (chatmessage_is_empty) return; + append_ic_text(p_showname, p_line, true, false); } From a035c34e7d7ae150001f402ab0c238f7e5fbbb3b Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 30 Oct 2020 21:18:22 -0400 Subject: [PATCH 147/842] Make system messages in newline mode show all newlines needed It was one newline short --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 482d67290..7e3229f52 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1221,7 +1221,7 @@ void Courtroom::update_ic_log(bool p_reset_log) if (record->system) { - cursor.insertText(record->line + QChar::LineFeed, system_format); + cursor.insertText(record->line + record_end, system_format); } else { From faeeeb28c88e473300d755b50346d973c4091e36 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 30 Oct 2020 21:37:51 -0400 Subject: [PATCH 148/842] Hide character emote page selector when in spectator mode --- src/emotes.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/emotes.cpp b/src/emotes.cpp index ed9319b9c..8f62860ad 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -92,14 +92,14 @@ void Courtroom::reset_emote_page() void Courtroom::set_emote_page() { + ui_emote_left->hide(); + ui_emote_right->hide(); + if (m_cid == -1) return; int total_emotes = ao_app->get_emote_number(current_char); - ui_emote_left->hide(); - ui_emote_right->hide(); - for (AOEmoteButton *i_button : ui_emote_list) { i_button->hide(); From 5f775bdaa0bab324109aebbeed263025816e39e1 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 30 Oct 2020 22:04:26 -0400 Subject: [PATCH 149/842] Fix memory leak with AOMovie not freeing up memory after movie was stopped --- src/aomovie.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/aomovie.cpp b/src/aomovie.cpp index ca54b9d65..6413b4e53 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -125,6 +125,9 @@ void AOMovie::play_interjection(QString p_char_name, void AOMovie::stop() { m_movie->stop(); + m_movie->setFileName(""); // Properly free up resources + // This fixes a memory leak for movie implementations that don't clean + // after themselves once stopped (which is.. all so far) this->hide(); } @@ -134,7 +137,6 @@ void AOMovie::frame_change(int n_frame) { // we need this or else the last frame wont show delay(m_movie->nextFrameDelay()); - this->stop(); // signal connected to courtroom object, let it figure out what to do From c7ff1e692a778c1a7e36e2697c08ab31e6c73388 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 30 Oct 2020 22:44:33 -0400 Subject: [PATCH 150/842] Assume current FL packet contents for DRO clients (it's been 3+ years) --- include/aoapplication.h | 9 ------ src/courtroom.cpp | 44 ++++++++-------------------- src/courtroom_widgets.cpp | 9 ++---- src/packet_distribution.cpp | 57 +++---------------------------------- 4 files changed, 19 insertions(+), 100 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index c18c19a52..657c1b8f4 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -48,15 +48,6 @@ class AOApplication : public QApplication /////////////////server metadata////////////////// unsigned int s_decryptor = 5; - bool encryption_needed = true; - - bool yellow_text_enabled = false; - bool prezoom_enabled = false; - bool flipping_enabled = false; - bool custom_objection_enabled = false; - bool improved_loading_enabled = false; - bool desk_mod_enabled = false; - bool evidence_enabled = false; ///////////////loading info/////////////////// diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d1742e9ee..c210302db 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -132,10 +132,7 @@ void Courtroom::enter_courtroom(int p_cid) check_free_blocks(); - if (ao_app->flipping_enabled) - ui_flip->show(); - else - ui_flip->hide(); + ui_flip->show(); list_music(); list_areas(); @@ -574,7 +571,8 @@ void Courtroom::save_textlog(QString p_text) { QString f_file = ao_app->get_base_path() + icchatlogsfilename; - ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, f_file); + ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, + f_file); } void Courtroom::append_server_chatmessage(QString p_name, QString p_message) @@ -619,13 +617,10 @@ void Courtroom::on_chat_return_pressed() QString f_desk_mod = "chat"; - if (ao_app->desk_mod_enabled) - { - f_desk_mod = - QString::number(ao_app->get_desk_mod(current_char, current_emote)); - if (f_desk_mod == "-1") - f_desk_mod = "chat"; - } + f_desk_mod = + QString::number(ao_app->get_desk_mod(current_char, current_emote)); + if (f_desk_mod == "-1") + f_desk_mod = "chat"; packet_contents.append(f_desk_mod); @@ -671,8 +666,6 @@ void Courtroom::on_chat_return_pressed() { if (f_emote_mod == 0) f_emote_mod = 1; - else if (f_emote_mod == 5 && ao_app->prezoom_enabled) - f_emote_mod = 4; } else { @@ -690,8 +683,7 @@ void Courtroom::on_chat_return_pressed() QString f_obj_state; - if (m_shout_state < 0 || - (!ao_app->custom_objection_enabled && m_shout_state > 3)) + if (m_shout_state < 0) f_obj_state = "0"; else f_obj_state = QString::number(m_shout_state); @@ -705,18 +697,7 @@ void Courtroom::on_chat_return_pressed() else packet_contents.append("0"); - QString f_flip; - - if (ao_app->flipping_enabled) - { - if (ui_flip->isChecked()) - f_flip = "1"; - else - f_flip = "0"; - } - else - f_flip = QString::number(m_cid); - + QString f_flip = ui_flip->isChecked() ? "1" : "0"; packet_contents.append(f_flip); packet_contents.append(QString::number(m_effect_state)); @@ -725,7 +706,7 @@ void Courtroom::on_chat_return_pressed() if (m_text_color < 0) f_text_color = "0"; - else if (m_text_color > 4 && !ao_app->yellow_text_enabled) + else if (m_text_color > 4) f_text_color = "0"; else f_text_color = QString::number(m_text_color); @@ -921,7 +902,7 @@ void Courtroom::handle_chatmessage_2() // handles IC int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); - if (ao_app->flipping_enabled && m_chatmessage[FLIP].toInt() == 1) + if (m_chatmessage[FLIP].toInt() == 1) ui_vp_player_char->set_mirror_enabled(true); else ui_vp_player_char->set_mirror_enabled(false); @@ -1824,8 +1805,7 @@ void Courtroom::on_ooc_return_pressed() ao_config->set_username(ooc_name); } - else if (ooc_message.startsWith("/rainbow") && ao_app->yellow_text_enabled && - !rainbow_appended) + else if (ooc_message.startsWith("/rainbow") && !rainbow_appended) { ui_text_color->addItem("Rainbow"); ui_ooc_chat_message->clear(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 4d76511b2..5ca0cf18b 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -217,12 +217,9 @@ void Courtroom::create_widgets() ui_text_color->addItem("Red"); ui_text_color->addItem("Orange"); ui_text_color->addItem("Blue"); - if (ao_app->yellow_text_enabled) - { - ui_text_color->addItem("Yellow"); - ui_text_color->addItem("Purple"); - ui_text_color->addItem("Pink"); - } + ui_text_color->addItem("Yellow"); + ui_text_color->addItem("Purple"); + ui_text_color->addItem("Pink"); ui_evidence_button = new AOButton(this, ao_app); diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 78885a734..2980a06ce 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -137,20 +137,6 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // you may ask where 322 comes from. that would be a good question. s_decryptor = fanta_decrypt(f_contents.at(0), 322).toUInt(); - // default(legacy) values - encryption_needed = true; - yellow_text_enabled = false; - prezoom_enabled = false; - flipping_enabled = false; - custom_objection_enabled = false; - improved_loading_enabled = false; - desk_mod_enabled = false; - evidence_enabled = false; - - // workaround for tsuserver4 - if (f_contents.at(0) == "NOENCRYPT") - encryption_needed = false; - QString f_hdid; f_hdid = get_hdid(); @@ -178,20 +164,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "FL") { - if (f_packet.contains("yellowtext", Qt::CaseInsensitive)) - yellow_text_enabled = true; - if (f_packet.contains("flipping", Qt::CaseInsensitive)) - flipping_enabled = true; - if (f_packet.contains("customobjections", Qt::CaseInsensitive)) - custom_objection_enabled = true; - if (f_packet.contains("fastloading", Qt::CaseInsensitive)) - improved_loading_enabled = true; - if (f_packet.contains("noencryption", Qt::CaseInsensitive)) - encryption_needed = false; - if (f_packet.contains("deskmod", Qt::CaseInsensitive)) - desk_mod_enabled = true; - if (f_packet.contains("evidence", Qt::CaseInsensitive)) - evidence_enabled = true; + // This section deliberately left empty } else if (header == "PN") { @@ -254,11 +227,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) AOPacket *f_packet; - if (improved_loading_enabled) - f_packet = new AOPacket("RC#%"); - else - f_packet = new AOPacket("askchar2#%"); - + f_packet = new AOPacket("RC#%"); send_server_packet(f_packet); QCryptographicHash hash(QCryptographicHash::Algorithm::Sha256); @@ -307,14 +276,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) (loaded_chars / static_cast(total_loading_size)) * 100; w_lobby->set_loading_value(loading_value); - if (improved_loading_enabled) - send_server_packet(new AOPacket("RE#%")); - else - { - QString next_packet_number = - QString::number(((loaded_chars - 1) / 10) + 1); - send_server_packet(new AOPacket("AN#" + next_packet_number + "#%")); - } + send_server_packet(new AOPacket("RE#%")); } else if (header == "EI") { @@ -774,18 +736,7 @@ void AOApplication::send_server_packet(AOPacket *p_packet, bool encoded) QString f_packet = p_packet->to_string(); - if (encryption_needed) - { - qDebug() << "S(e):" << f_packet; - - p_packet->encrypt_header(s_decryptor); - f_packet = p_packet->to_string(); - } - else - { - qDebug() << "S:" << f_packet; - } - + qDebug() << "S:" << f_packet; net_manager->ship_server_packet(f_packet); delete p_packet; From 7c52f246813038378458e3327519a7d1261175f8 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 30 Oct 2020 22:52:18 -0400 Subject: [PATCH 151/842] While we are it, remove fantacrypt --- dronline-client.pro | 2 - include/aoapplication.h | 4 -- include/aopacket.h | 5 --- include/encryption_functions.h | 9 ----- src/aopacket.cpp | 21 +---------- src/encryption_functions.cpp | 68 ---------------------------------- src/packet_distribution.cpp | 7 ++-- 7 files changed, 4 insertions(+), 112 deletions(-) delete mode 100644 include/encryption_functions.h delete mode 100644 src/encryption_functions.cpp diff --git a/dronline-client.pro b/dronline-client.pro index b59c3ada8..cc9bab63c 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -46,7 +46,6 @@ HEADERS += \ include/datatypes.h \ include/debug_functions.h \ include/discord_rich_presence.h \ - include/encryption_functions.h \ include/file_functions.h \ include/hardware_functions.h \ include/hex_functions.h \ @@ -92,7 +91,6 @@ SOURCES += \ src/debug_functions.cpp \ src/discord_rich_presence.cpp \ src/emotes.cpp \ - src/encryption_functions.cpp \ src/evidence.cpp \ src/file_functions.cpp \ src/hardware_functions.cpp \ diff --git a/include/aoapplication.h b/include/aoapplication.h index 657c1b8f4..2481dd790 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -45,10 +45,6 @@ class AOApplication : public QApplication void send_ms_packet(AOPacket *p_packet); void send_server_packet(AOPacket *p_packet, bool encoded = true); - /////////////////server metadata////////////////// - - unsigned int s_decryptor = 5; - ///////////////loading info/////////////////// // player number, it's hardly used but might be needed for some old servers diff --git a/include/aopacket.h b/include/aopacket.h index eefac88f2..3f5fd0d3c 100644 --- a/include/aopacket.h +++ b/include/aopacket.h @@ -20,15 +20,10 @@ class AOPacket } QString to_string(); - void encrypt_header(unsigned int p_key); - void decrypt_header(unsigned int p_key); - void net_encode(); void net_decode(); private: - bool encrypted = false; - QString m_header; QStringList m_contents; }; diff --git a/include/encryption_functions.h b/include/encryption_functions.h deleted file mode 100644 index b6ea1d708..000000000 --- a/include/encryption_functions.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef ENCRYPTION_FUNCTIONS_H -#define ENCRYPTION_FUNCTIONS_H - -#include - -QString fanta_encrypt(QString p_input, unsigned int key); -QString fanta_decrypt(QString p_input, unsigned int key); - -#endif // ENCRYPTION_FUNCTIONS_H diff --git a/src/aopacket.cpp b/src/aopacket.cpp index 5bb6bfec7..425e3c872 100644 --- a/src/aopacket.cpp +++ b/src/aopacket.cpp @@ -1,7 +1,5 @@ #include "aopacket.h" -#include "encryption_functions.h" - #include AOPacket::AOPacket(QString p_packet_string) @@ -33,24 +31,7 @@ QString AOPacket::to_string() f_string += "#%"; - if (encrypted) - return "#" + f_string; - else - return f_string; -} - -void AOPacket::encrypt_header(unsigned int p_key) -{ - m_header = fanta_encrypt(m_header, p_key); - - encrypted = true; -} - -void AOPacket::decrypt_header(unsigned int p_key) -{ - m_header = fanta_decrypt(m_header, p_key); - - encrypted = false; + return f_string; } void AOPacket::net_encode() diff --git a/src/encryption_functions.cpp b/src/encryption_functions.cpp deleted file mode 100644 index 22de1c003..000000000 --- a/src/encryption_functions.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "encryption_functions.h" - -#include "hex_functions.h" - -#include -#include -#include -#include -#include - -QString fanta_encrypt(QString temp_input, unsigned int p_key) -{ - // using standard stdlib types is actually easier here because of implicit - // char<->int conversion which in turn makes encryption arithmetic easier - - unsigned int key = p_key; - unsigned int C1 = 53761; - unsigned int C2 = 32618; - - QVector temp_result; - std::string input = temp_input.toUtf8().constData(); - - for (unsigned int pos = 0; pos < input.size(); ++pos) - { - uint_fast8_t output = input.at(pos) ^ (key >> 8) % 256; - temp_result.append(output); - key = (temp_result.at(pos) + key) * C1 + C2; - } - - std::string result = ""; - - for (uint_fast8_t i_int : temp_result) - { - result += omni::int_to_hex(i_int); - } - - QString final_result = QString::fromStdString(result); - - return final_result; -} - -QString fanta_decrypt(QString temp_input, unsigned int key) -{ - std::string input = temp_input.toUtf8().constData(); - - QVector unhexed_vector; - - for (unsigned int i = 0; i < input.length(); i += 2) - { - std::string byte = input.substr(i, 2); - unsigned int hex_int = strtoul(byte.c_str(), nullptr, 16); - unhexed_vector.append(hex_int); - } - - unsigned int C1 = 53761; - unsigned int C2 = 32618; - - std::string result = ""; - - for (int pos = 0; pos < unhexed_vector.size(); ++pos) - { - unsigned char output = unhexed_vector.at(pos) ^ (key >> 8) % 256; - result += output; - key = (unhexed_vector.at(pos) + key) * C1 + C2; - } - - return QString::fromStdString(result); -} diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 2980a06ce..f3ad365de 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -2,7 +2,6 @@ #include "courtroom.h" #include "debug_functions.h" -#include "encryption_functions.h" #include "hardware_functions.h" #include "lobby.h" #include "networkmanager.h" @@ -131,12 +130,12 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (header == "decryptor") { + // This packet is maintained as is for legacy purposes, + // even though its sole argument is no longer used for anything + // productive if (f_contents.size() == 0) goto end; - // you may ask where 322 comes from. that would be a good question. - s_decryptor = fanta_decrypt(f_contents.at(0), 322).toUInt(); - QString f_hdid; f_hdid = get_hdid(); From 401a474480d5cc44eca4fbbb850331d8f65fa043 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 30 Oct 2020 23:21:04 -0400 Subject: [PATCH 152/842] Improve launch error to include current theme. It is possible to trigger this error if the current theme folder does not exist, which can be tricky to decide has happened with an error message that does not describe it --- src/lobby.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 5f52ce3be..2231aef53 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -82,13 +82,20 @@ void Lobby::set_widgets() { qDebug() << "W: did not find lobby width or height in " << filename; - // Most common symptom of bad config files and missing assets. - call_notice("It doesn't look like your client is set up correctly " - "at " + - QDir::currentPath() + - "\n" - "Did you download all resources correctly from the DRO Discord " - "including the large 'base' folder?"); + // Most common symptom of bad config files, missing assets, or misnamed + // theme folder + call_notice( + "It doesn't look like your client is set up correctly. This can be " + "due to the following reasons: \n" + "1. Check you downloaded and extracted the resources correctly from " + "the DRO Discord including the large 'base' folder.\n" + "2. If you did, check that the base folder is in the same folder " + "where you launched Danganronpa Online from: " + + QDir::currentPath() + + "\n" + "3. If it is there, check that your current theme folder exists in " + "base/themes. According to base/config.ini, your current theme is " + + ao_app->get_theme()); this->resize(517, 666); } From 3d168b51d860c0ff0ca401ab79709cd73361912b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Tue, 3 Nov 2020 15:20:17 +0100 Subject: [PATCH 153/842] Added 3rdparty folder (future change) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b2fc2ea4d..769cd5cb0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ 3rd/ +3rdparty/ base/ docs/ .qmake.conf From d0db543cd9e90698568bb76b57f88f4874e4cb06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Tue, 3 Nov 2020 15:51:08 +0100 Subject: [PATCH 154/842] Added a FIXME reminder for the FL packet --- src/packet_distribution.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index f3ad365de..8fab70021 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -161,9 +161,10 @@ void AOApplication::server_packet_received(AOPacket *p_packet) w_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1)); } + // FIXME Should the FL packet acknowledgement removed? else if (header == "FL") { - // This section deliberately left empty + // The packet is acknowledged but is of no use to the client } else if (header == "PN") { From c823360faed54b4bccf0e0fa1374fb91c7a12873 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 3 Nov 2020 15:09:22 -0500 Subject: [PATCH 155/842] Rephrase memory hogging comment --- src/aomovie.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 6413b4e53..d5d45f1eb 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -126,8 +126,9 @@ void AOMovie::stop() { m_movie->stop(); m_movie->setFileName(""); // Properly free up resources - // This fixes a memory leak for movie implementations that don't clean - // after themselves once stopped (which is.. all so far) + // If the file is not cleared after the movie is stopped, the movie will + // continue using as much memory as it requested, which leads to invisible + // memory hogging. this->hide(); } From 8bae27ea5e0d77dd62f1b056c7d59cdbbe00a14f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 14 Nov 2020 00:26:01 -0500 Subject: [PATCH 156/842] Only hide area list on theme reload if music list was visible --- src/courtroom_widgets.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 4d76511b2..929242fa6 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -658,12 +658,12 @@ void Courtroom::set_widgets() set_size_and_pos(ui_mute_list, "mute_list"); ui_mute_list->hide(); + set_size_and_pos(ui_music_list, "music_list"); set_size_and_pos(ui_area_list, "area_list"); - ui_area_list->hide(); + if (ui_music_list->isVisible()) + ui_area_list->hide(); // ui_area_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - set_size_and_pos(ui_music_list, "music_list"); - set_size_and_pos(ui_sfx_list, "sfx_list"); set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message"); From a861705ba39297854992df56593d5009a6d7e2fb Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 14 Nov 2020 00:44:57 -0500 Subject: [PATCH 157/842] Do not log blankposts of the server in text log (used to simulate clearing of IC) --- src/courtroom.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d1742e9ee..d34909516 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -574,7 +574,8 @@ void Courtroom::save_textlog(QString p_text) { QString f_file = ao_app->get_base_path() + icchatlogsfilename; - ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, f_file); + ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, + f_file); } void Courtroom::append_server_chatmessage(QString p_name, QString p_message) @@ -836,7 +837,8 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) else append_ic_text(f_showname, m_chatmessage[MESSAGE], false, false); - if (ao_config->log_is_recording_enabled()) + if (ao_config->log_is_recording_enabled() && + (!chatmessage_is_empty || !is_system_speaking)) save_textlog(f_showname + ": " + m_chatmessage[MESSAGE]); int objection_mod = m_chatmessage[OBJECTION_MOD].toInt(); From f38bf843ab8daa7cbdd68ce7adf7ad25700438a5 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 14 Nov 2020 00:54:07 -0500 Subject: [PATCH 158/842] Fix loading bar using wrong rate when loading the music list --- src/packet_distribution.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 78885a734..3a3661b76 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -505,8 +505,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = - (loaded_chars / static_cast(total_loading_size)) * 100; + int loading_value = ((loaded_chars + loaded_evidence + loaded_music) / + static_cast(total_loading_size)) * + 100; w_lobby->set_loading_value(loading_value); } From 5a6c33684a42d163ad4e7d9e782979ede6c34bb8 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 14 Nov 2020 01:17:31 -0500 Subject: [PATCH 159/842] Implement ic_chatlog_bold properly (0 for name not bold, 1 for name bold) --- src/courtroom.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d1742e9ee..2763218a0 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -574,7 +574,8 @@ void Courtroom::save_textlog(QString p_text) { QString f_file = ao_app->get_base_path() + icchatlogsfilename; - ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, f_file); + ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, + f_file); } void Courtroom::append_server_chatmessage(QString p_name, QString p_message) @@ -1173,7 +1174,11 @@ void Courtroom::update_ic_log(bool p_reset_log) QColor not_found_color = QColor(255, 255, 255); QTextCharFormat name_format = ui_ic_chatlog->currentCharFormat(); - name_format.setFontWeight(QFont::Bold); + if (ao_app->get_font_property("ic_chatlog_bold", fonts_ini)) + name_format.setFontWeight(QFont::Bold); + else + name_format.setFontWeight(QFont::Normal); + QColor showname_color = ao_app->get_color("ic_chatlog_showname_color", fonts_ini); if (showname_color == not_found_color) From 92fd6802fd414b59acc906185e0ea289c308bc58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 14 Nov 2020 20:37:53 +0100 Subject: [PATCH 160/842] Safety curly braces. --- src/courtroom.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d34909516..dfd8c7be9 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -839,7 +839,9 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) if (ao_config->log_is_recording_enabled() && (!chatmessage_is_empty || !is_system_speaking)) + { save_textlog(f_showname + ": " + m_chatmessage[MESSAGE]); + } int objection_mod = m_chatmessage[OBJECTION_MOD].toInt(); QString f_char = m_chatmessage[CHAR_NAME]; From e0fad72da518c8105b27000ba4b3d93ab5232fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 14 Nov 2020 21:27:38 +0100 Subject: [PATCH 161/842] Added some extra checks to make sure that theme resets to `default`. --- .gitignore | 1 + src/aoconfig.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b2fc2ea4d..769cd5cb0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ 3rd/ +3rdparty/ base/ docs/ .qmake.conf diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index d47470ae9..ec39303ee 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -210,8 +210,12 @@ public slots: username = cfg.value("username").toString(); callwords = cfg.value("callwords").toString(); server_alerts = cfg.value("server_alerts", true).toBool(); - theme = cfg.value("theme", "default").toString(); - gamemode = cfg.value("gamemode", "").toString(); + + theme = cfg.value("theme").toString(); + if (theme.trimmed().isEmpty()) + theme = "default"; + + gamemode = cfg.value("gamemode").toString(); manual_gamemode = cfg.value("manual_gamemode", false).toBool(); timeofday = cfg.value("timeofday", "").toString(); manual_timeofday = cfg.value("manual_timeofday", false).toBool(); From 940c22878d9f3720954500a94a1d0a573ac1793f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 14 Nov 2020 21:51:44 +0100 Subject: [PATCH 162/842] Added `const` declaration to `record_end` as it never get modified after initialization --- .gitignore | 1 + src/courtroom.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b2fc2ea4d..769cd5cb0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ 3rd/ +3rdparty/ base/ docs/ .qmake.conf diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 7e3229f52..1a2c227fe 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1216,7 +1216,7 @@ void Courtroom::update_ic_log(bool p_reset_log) { // move cursor cursor.movePosition(move_type); - QString record_end = (QString(QChar::LineFeed) + + const QString record_end = (QString(QChar::LineFeed) + (m_chatlog_newline ? QString(QChar::LineFeed) : "")); if (record->system) From 84c682d50a0d2568d391e23f18ec4c273a904070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 14 Nov 2020 22:40:20 +0100 Subject: [PATCH 163/842] Improved clock-display handling The clock will now always check if the time requested is within a minimum and maximum range. If it fails, the current clock time will instead be set to unknow. This assume the server could potentially feed the client wrong information. --- .gitignore | 1 + src/courtroom.cpp | 32 +++++++++++++++++--------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index b2fc2ea4d..769cd5cb0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ 3rd/ +3rdparty/ base/ docs/ .qmake.conf diff --git a/src/courtroom.cpp b/src/courtroom.cpp index e8dbc6374..5d28b4c80 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -344,27 +344,29 @@ void Courtroom::handle_music_anim() void Courtroom::handle_clock(QString time) { - qDebug() << time; current_clock = time.toInt(); + if (current_clock < 0 || current_clock > 23) + current_clock = -1; + qInfo() << QString("Clock time changed to %1").arg(current_clock); - QString string = "hours\\" + time; // expected to be 0, 1, 2... - qDebug() << current_clock << string - << ao_app->find_theme_asset_path(string, - animated_or_static_extensions()); - // If the new hour file exists, use it and show the clock - if (!ao_app->find_theme_asset_path(string, animated_or_static_extensions()) - .isEmpty()) + ui_vp_clock->hide(); + + if (current_clock == -1) { - ui_vp_clock->show(); - ui_vp_clock->play(string); + qInfo() << "Unknown time; no asset to be used."; + return; } - // Otherwise, keep showing whatever was last shown... - // except if time is now -1. If so, that means the time is unknown, and there - // are no files for it. In that case, hide the clock and stop the movie - else if (current_clock == -1) + + qDebug() << "Displaying clock asset..."; + const QString asset_path = ao_app->find_theme_asset_path("hours/" + QString::number(current_clock), animated_or_static_extensions()); + if (asset_path.isEmpty()) { - ui_vp_clock->hide(); + qDebug() << "Asset not found; aborting."; + return; } + + ui_vp_clock->play(asset_path); + ui_vp_clock->show(); } void Courtroom::handle_gamemode(QString gamemode) From 2d5742f126c7595a550498ff99d794b1cdd967a2 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 14 Nov 2020 18:41:04 -0500 Subject: [PATCH 164/842] Delay resetting UI caused by sending IC message to AFTER the client receives it. This prevents the issue where a player attempts to send a message with an SFX, the server denies it (e.g. someone else talking, client sending duplicate messages) but the client already preemptively deselects the sound effect. --- src/courtroom.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 18902c053..38bd65ff2 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -355,7 +355,9 @@ void Courtroom::handle_clock(QString time) } qDebug() << "Displaying clock asset..."; - const QString asset_path = ao_app->find_theme_asset_path("hours/" + QString::number(current_clock), animated_or_static_extensions()); + const QString asset_path = + ao_app->find_theme_asset_path("hours/" + QString::number(current_clock), + animated_or_static_extensions()); if (asset_path.isEmpty()) { qDebug() << "Asset not found; aborting."; @@ -732,11 +734,6 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(f_text_color); prev_emote = current_emote; - // reset states - ui_pre->setChecked(ao_config->always_pre_enabled()); - ui_sfx_list->clearSelection(); - list_sfx(); - ao_app->send_server_packet(new AOPacket("MS", packet_contents)); } @@ -775,11 +772,16 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) m_chatmessage[MESSAGE] == " " || m_chatmessage[MESSAGE] == ""; m_msg_is_first_person = false; - // reset our ui state + // reset our ui state if client just spoke if (m_cid == f_char_id && !is_system_speaking) { ui_ic_chat_message->clear(); - ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); + + // reset states + ui_pre->setChecked(ao_config->always_pre_enabled()); + list_sfx(); + ui_sfx_list->setCurrentItem( + ui_sfx_list->item(0)); // prevents undefined errors m_shout_state = 0; draw_shout_buttons(); @@ -1221,8 +1223,9 @@ void Courtroom::update_ic_log(bool p_reset_log) { // move cursor cursor.movePosition(move_type); - const QString record_end = (QString(QChar::LineFeed) + - (m_chatlog_newline ? QString(QChar::LineFeed) : "")); + const QString record_end = + (QString(QChar::LineFeed) + + (m_chatlog_newline ? QString(QChar::LineFeed) : "")); if (record->system) { From 0b19c59b1b309c15a84defb49d69e3f86706be29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Tue, 17 Nov 2020 20:25:35 +0100 Subject: [PATCH 165/842] Modified char scaling, AOPixmap... * Renamed AOImage to AOImageDisplay (avoid confusing with QImage, AOPixmap, QPixmap) * Modified character scaling to fit based on height. --- dronline-client.pro | 2 +- include/aocharbutton.h | 4 ++-- include/aoevidencebutton.h | 6 ++--- include/{aoimage.h => aoimagedisplay.h} | 4 ++-- include/aonotearea.h | 4 ++-- include/aopixmap.h | 3 ++- include/courtroom.h | 32 ++++++++++++------------- include/lobby.h | 6 ++--- src/aocharbutton.cpp | 2 +- src/aocharmovie.cpp | 2 +- src/aoevidencebutton.cpp | 4 ++-- src/aoevidencedisplay.cpp | 2 +- src/aoimage.cpp | 4 ++-- src/aonotearea.cpp | 2 +- src/aopixmap.cpp | 25 +++++++++++-------- src/charselect.cpp | 4 ++-- src/courtroom_widgets.cpp | 22 ++++++++--------- src/evidence.cpp | 4 ++-- src/lobby.cpp | 4 ++-- 19 files changed, 71 insertions(+), 65 deletions(-) rename include/{aoimage.h => aoimagedisplay.h} (79%) diff --git a/dronline-client.pro b/dronline-client.pro index cc9bab63c..466d1b748 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -27,7 +27,7 @@ HEADERS += \ include/aoevidencedisplay.h \ include/aoexception.h \ include/aoguiloader.h \ - include/aoimage.h \ + include/aoimagedisplay.h \ include/aolabel.h \ include/aolineedit.h \ include/aomovie.h \ diff --git a/include/aocharbutton.h b/include/aocharbutton.h index 461af1283..a2b26eace 100644 --- a/include/aocharbutton.h +++ b/include/aocharbutton.h @@ -2,7 +2,7 @@ #define AOCHARBUTTON_H #include "aoapplication.h" -#include "aoimage.h" +#include "aoimagedisplay.h" #include #include @@ -25,7 +25,7 @@ class AOCharButton : public QPushButton void mouse_left(); private: - AOImage *ui_taken = nullptr; + AOImageDisplay *ui_taken = nullptr; protected: void enterEvent(QEvent *e); diff --git a/include/aoevidencebutton.h b/include/aoevidencebutton.h index 492f19e80..d203c8e7d 100644 --- a/include/aoevidencebutton.h +++ b/include/aoevidencebutton.h @@ -2,7 +2,7 @@ #define AOEVIDENCEBUTTON_H #include "aoapplication.h" -#include "aoimage.h" +#include "aoimagedisplay.h" #include #include @@ -28,8 +28,8 @@ class AOEvidenceButton : public QPushButton private: AOApplication *ao_app = nullptr; - AOImage *ui_selected; - AOImage *ui_selector; + AOImageDisplay *ui_selected; + AOImageDisplay *ui_selector; int m_id = 0; diff --git a/include/aoimage.h b/include/aoimagedisplay.h similarity index 79% rename from include/aoimage.h rename to include/aoimagedisplay.h index fb59ede40..43617bac5 100644 --- a/include/aoimage.h +++ b/include/aoimagedisplay.h @@ -8,10 +8,10 @@ #include -class AOImage : public QLabel +class AOImageDisplay : public QLabel { public: - AOImage(QWidget *parent, AOApplication *p_ao_app); + AOImageDisplay(QWidget *parent, AOApplication *p_ao_app); void set_image(QString p_image); void set_image_from_path(QString p_path); diff --git a/include/aonotearea.h b/include/aonotearea.h index f2f6ec433..dbccc23db 100644 --- a/include/aonotearea.h +++ b/include/aonotearea.h @@ -7,12 +7,12 @@ #include #include #include -#include +#include #include #include "aoapplication.h" -class AONoteArea : public AOImage +class AONoteArea : public AOImageDisplay { Q_OBJECT diff --git a/include/aopixmap.h b/include/aopixmap.h index 648405e25..c9fa24e74 100644 --- a/include/aopixmap.h +++ b/include/aopixmap.h @@ -11,7 +11,8 @@ class AOPixmap void clear(); - QPixmap scale_to_size(QSize p_size); + QPixmap scale(QSize p_size); + QPixmap scale_to_height(QSize p_size); private: QPixmap m_pixmap; diff --git a/include/courtroom.h b/include/courtroom.h index f3ab5482e..5fd0897a2 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -12,7 +12,7 @@ #include "aoevidencebutton.h" #include "aoevidencedescription.h" #include "aoevidencedisplay.h" -#include "aoimage.h" +#include "aoimagedisplay.h" #include "aolabel.h" #include "aolineedit.h" #include "aomovie.h" @@ -451,7 +451,7 @@ class Courtroom : public QMainWindow QString current_background = "gs4"; - AOImage *ui_background; + AOImageDisplay *ui_background; QWidget *ui_viewport; AOScene *ui_vp_background; @@ -467,21 +467,21 @@ class Courtroom : public QMainWindow // note that since it's hardcoded, it won't be of much use in other servers QVector rpc_char_list; - AOImage *ui_vp_notepad_image; + AOImageDisplay *ui_vp_notepad_image; QTextEdit *ui_vp_notepad; - AOImage *ui_vp_chatbox = nullptr; + AOImageDisplay *ui_vp_chatbox = nullptr; QTextEdit *ui_vp_showname = nullptr; QTextEdit *ui_vp_message = nullptr; - AOImage *ui_vp_testimony = nullptr; + AOImageDisplay *ui_vp_testimony = nullptr; AOMovie *ui_vp_effect = nullptr; AOMovie *ui_vp_wtce = nullptr; AOMovie *ui_vp_objection = nullptr; - AOImage *ui_vp_music_display_a = nullptr; - AOImage *ui_vp_music_display_b = nullptr; + AOImageDisplay *ui_vp_music_display_a = nullptr; + AOImageDisplay *ui_vp_music_display_b = nullptr; - AOImage *ui_vp_showname_image = nullptr; + AOImageDisplay *ui_vp_showname_image = nullptr; QTextEdit *ui_vp_music_name = nullptr; QPropertyAnimation *music_anim = nullptr; @@ -518,8 +518,8 @@ class Courtroom : public QMainWindow QComboBox *ui_emote_dropdown; QComboBox *ui_pos_dropdown; - AOImage *ui_defense_bar; - AOImage *ui_prosecution_bar; + AOImageDisplay *ui_defense_bar; + AOImageDisplay *ui_prosecution_bar; // buttons to cycle through shouts AOButton *ui_shout_up = nullptr; @@ -591,7 +591,7 @@ class Courtroom : public QMainWindow QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip - QVector ui_label_images; + QVector ui_label_images; QVector label_images = {"Pre", "Flip", "Hidden", "Music", "SFX", "Blip"}; @@ -608,30 +608,30 @@ class Courtroom : public QMainWindow QComboBox *ui_text_color; - AOImage *ui_muted; + AOImageDisplay *ui_muted; AOButton *ui_note_button; AOButton *ui_evidence_button; - AOImage *ui_evidence; + AOImageDisplay *ui_evidence; AOLineEdit *ui_evidence_name; QWidget *ui_evidence_buttons; QVector ui_evidence_list; AOButton *ui_evidence_left; AOButton *ui_evidence_right; AOButton *ui_evidence_present; - AOImage *ui_evidence_overlay; + AOImageDisplay *ui_evidence_overlay; AOButton *ui_evidence_delete; AOLineEdit *ui_evidence_image_name; AOButton *ui_evidence_image_button; AOButton *ui_evidence_x; AOEvidenceDescription *ui_evidence_description; - AOImage *ui_char_select_background; + AOImageDisplay *ui_char_select_background; // abstract widget to hold char buttons QWidget *ui_char_buttons = nullptr; - AOImage *ui_char_button_selector = nullptr; + AOImageDisplay *ui_char_button_selector = nullptr; QVector ui_char_button_list; AOButton *ui_back_to_lobby; diff --git a/include/lobby.h b/include/lobby.h index 4bf7fdfd6..7f5ead915 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -2,7 +2,7 @@ #define LOBBY_H #include "aobutton.h" -#include "aoimage.h" +#include "aoimagedisplay.h" #include "aopacket.h" #include "aotextarea.h" @@ -54,7 +54,7 @@ class Lobby : public QMainWindow private: AOApplication *ao_app = nullptr; - AOImage *ui_background; + AOImageDisplay *ui_background; AOButton *ui_public_servers; AOButton *ui_favorites; @@ -76,7 +76,7 @@ class Lobby : public QMainWindow QLineEdit *ui_chatname; QLineEdit *ui_chatmessage; - AOImage *ui_loading_background; + AOImageDisplay *ui_loading_background; QTextEdit *ui_loading_text; QProgressBar *ui_progress_bar; AOButton *ui_cancel; diff --git a/src/aocharbutton.cpp b/src/aocharbutton.cpp index 74d038a9d..6ae5ab970 100644 --- a/src/aocharbutton.cpp +++ b/src/aocharbutton.cpp @@ -13,7 +13,7 @@ AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, this->resize(60, 60); this->move(x_pos, y_pos); - ui_taken = new AOImage(this, ao_app); + ui_taken = new AOImageDisplay(this, ao_app); ui_taken->resize(60, 60); ui_taken->set_image("char_taken.png"); ui_taken->setAttribute(Qt::WA_TransparentForMouseEvents); diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 89ee67cd9..14e6c1bfa 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -148,7 +148,7 @@ void AOCharMovie::on_frame_changed(int p_frame_num) if (movie_frames.size() > p_frame_num) { AOPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(p_frame_num)); - this->setPixmap(f_pixmap.scale_to_size(this->size())); + this->setPixmap(f_pixmap.scale_to_height(this->size())); } // pre-anim only diff --git a/src/aoevidencebutton.cpp b/src/aoevidencebutton.cpp index c9cce7812..d1a877f12 100644 --- a/src/aoevidencebutton.cpp +++ b/src/aoevidencebutton.cpp @@ -10,14 +10,14 @@ AOEvidenceButton::AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, { ao_app = p_ao_app; - ui_selected = new AOImage(p_parent, ao_app); + ui_selected = new AOImageDisplay(p_parent, ao_app); ui_selected->resize(70, 70); ui_selected->move(p_x, p_y); ui_selected->set_image("evidence_selected.png"); ui_selected->setAttribute(Qt::WA_TransparentForMouseEvents); ui_selected->hide(); - ui_selector = new AOImage(p_parent, ao_app); + ui_selector = new AOImageDisplay(p_parent, ao_app); ui_selector->resize(71, 71); ui_selector->move(p_x - 1, p_y - 1); ui_selector->set_image("evidence_selector.png"); diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index b640c2324..2f8a142e9 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -48,7 +48,7 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, evidence_icon->move(icon_dimensions.x, icon_dimensions.y); evidence_icon->resize(icon_dimensions.width, icon_dimensions.height); - evidence_icon->setPixmap(f_pixmap.scale_to_size(evidence_icon->size())); + evidence_icon->setPixmap(f_pixmap.scale(evidence_icon->size())); QString f_path = ao_app->find_theme_asset_path(gif_name); evidence_movie->setFileName(f_path); diff --git a/src/aoimage.cpp b/src/aoimage.cpp index e8d57397c..e31eb0a7a 100644 --- a/src/aoimage.cpp +++ b/src/aoimage.cpp @@ -13,7 +13,7 @@ void AOImage::set_image(QString p_image) { QString f_path = ao_app->find_theme_asset_path(p_image); AOPixmap f_pixmap(f_path); - this->setPixmap(f_pixmap.scale_to_size(size())); + this->setPixmap(f_pixmap.scale(size())); // Store final path if the path exists if (file_exists(f_path)) @@ -34,7 +34,7 @@ void AOImage::set_image_from_path(QString p_path) final_path = default_path; AOPixmap f_pixmap(final_path); - this->setPixmap(f_pixmap.scale_to_size(size())); + this->setPixmap(f_pixmap.scale(size())); // Store final path if the path exists if (file_exists(final_path)) diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp index 3abfd27a5..5c2d4376c 100644 --- a/src/aonotearea.cpp +++ b/src/aonotearea.cpp @@ -6,7 +6,7 @@ #include AONoteArea::AONoteArea(QWidget *p_parent, AOApplication *p_ao_app) - : AOImage(p_parent, p_ao_app) + : AOImageDisplay(p_parent, p_ao_app) { ao_app = p_ao_app; } diff --git a/src/aopixmap.cpp b/src/aopixmap.cpp index 6848cd873..917910759 100644 --- a/src/aopixmap.cpp +++ b/src/aopixmap.cpp @@ -1,6 +1,7 @@ #include "aopixmap.h" -AOPixmap::AOPixmap(QPixmap p_pixmap) : m_pixmap(p_pixmap) +AOPixmap::AOPixmap(QPixmap p_pixmap) + : m_pixmap(p_pixmap) { if (m_pixmap.isNull()) { @@ -8,9 +9,9 @@ AOPixmap::AOPixmap(QPixmap p_pixmap) : m_pixmap(p_pixmap) } } -AOPixmap::AOPixmap(QString p_file_path) : AOPixmap(QPixmap(p_file_path)) -{ -} +AOPixmap::AOPixmap(QString p_file_path) + : AOPixmap(QPixmap(p_file_path)) +{} void AOPixmap::clear() { @@ -18,11 +19,15 @@ void AOPixmap::clear() m_pixmap.fill(Qt::transparent); } -QPixmap AOPixmap::scale_to_size(QSize p_size) +QPixmap AOPixmap::scale(QSize p_size) +{ + const bool f_pixmap_is_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); + const Qt::TransformationMode f_mode = f_pixmap_is_larger ? Qt::SmoothTransformation : Qt::FastTransformation; + return m_pixmap.scaled(p_size, Qt::IgnoreAspectRatio, f_mode); +} + +QPixmap AOPixmap::scale_to_height(QSize p_size) { - bool f_is_pixmap_larger = - m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); - return m_pixmap.scaled(p_size, Qt::IgnoreAspectRatio, - f_is_pixmap_larger ? Qt::SmoothTransformation - : Qt::FastTransformation); + const bool f_is_pixmap_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); + return m_pixmap.scaledToHeight(p_size.height(), f_is_pixmap_larger ? Qt::SmoothTransformation : Qt::FastTransformation); } diff --git a/src/charselect.cpp b/src/charselect.cpp index 7f096ef55..65620ebbe 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -7,11 +7,11 @@ void Courtroom::construct_char_select() { - ui_char_select_background = new AOImage(this, ao_app); + ui_char_select_background = new AOImageDisplay(this, ao_app); ui_char_buttons = new QWidget(ui_char_select_background); - ui_char_button_selector = new AOImage(ui_char_buttons, ao_app); + ui_char_button_selector = new AOImageDisplay(ui_char_buttons, ao_app); ui_char_button_selector->setAttribute(Qt::WA_TransparentForMouseEvents); ui_char_button_selector->resize(62, 62); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index bd6319a36..a809036a5 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -63,7 +63,7 @@ void Courtroom::create_widgets() connect(ao_config, SIGNAL(blips_volume_changed(int)), this, SLOT(on_config_blips_volume_changed(int))); - ui_background = new AOImage(this, ao_app); + ui_background = new AOImageDisplay(this, ao_app); ui_viewport = new QWidget(this); ui_vp_background = new AOScene(ui_viewport, ao_app); @@ -72,8 +72,8 @@ void Courtroom::create_widgets() ui_vp_player_char = new AOCharMovie(ui_viewport, ao_app); ui_vp_desk = new AOScene(ui_viewport, ao_app); - ui_vp_music_display_a = new AOImage(this, ao_app); - ui_vp_music_display_b = new AOImage(this, ao_app); + ui_vp_music_display_a = new AOImageDisplay(this, ao_app); + ui_vp_music_display_b = new AOImageDisplay(this, ao_app); ui_vp_music_area = new QWidget(ui_vp_music_display_a); ui_vp_music_name = new QTextEdit(ui_vp_music_area); ui_vp_music_name->setText("DANGANRONPA ONLINE"); @@ -88,7 +88,7 @@ void Courtroom::create_widgets() ui_vp_evidence_display = new AOEvidenceDisplay(this, ao_app); - ui_vp_chatbox = new AOImage(this, ao_app); + ui_vp_chatbox = new AOImageDisplay(this, ao_app); ui_vp_showname = new QTextEdit(ui_vp_chatbox); ui_vp_showname->setFrameStyle(QFrame::NoFrame); ui_vp_showname->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -100,9 +100,9 @@ void Courtroom::create_widgets() ui_vp_message->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_vp_message->setReadOnly(true); - ui_vp_showname_image = new AOImage(this, ao_app); + ui_vp_showname_image = new AOImageDisplay(this, ao_app); - ui_vp_testimony = new AOImage(this, ao_app); + ui_vp_testimony = new AOImageDisplay(this, ao_app); ui_vp_effect = new AOMovie(this, ao_app); ui_vp_wtce = new AOMovie(this, ao_app); ui_vp_objection = new AOMovie(this, ao_app); @@ -122,7 +122,7 @@ void Courtroom::create_widgets() ui_ic_chat_message = new QLineEdit(this); ui_ic_chat_message->setFrame(false); - ui_muted = new AOImage(ui_ic_chat_message, ao_app); + ui_muted = new AOImageDisplay(ui_ic_chat_message, ao_app); ui_muted->hide(); ui_ooc_chat_message = new QLineEdit(this); @@ -152,8 +152,8 @@ void Courtroom::create_widgets() construct_emotes(); - ui_defense_bar = new AOImage(this, ao_app); - ui_prosecution_bar = new AOImage(this, ao_app); + ui_defense_bar = new AOImageDisplay(this, ao_app); + ui_prosecution_bar = new AOImageDisplay(this, ao_app); load_shouts(); // Readds from theme, deletes old shouts if needed and creates // new ones @@ -187,7 +187,7 @@ void Courtroom::create_widgets() ui_label_images.resize(label_images.size()); for (int i = 0; i < ui_label_images.size(); ++i) { - ui_label_images[i] = new AOImage(this, ao_app); + ui_label_images[i] = new AOImageDisplay(this, ao_app); } ui_pre = new QCheckBox(this); @@ -223,7 +223,7 @@ void Courtroom::create_widgets() ui_evidence_button = new AOButton(this, ao_app); - ui_vp_notepad_image = new AOImage(this, ao_app); + ui_vp_notepad_image = new AOImageDisplay(this, ao_app); ui_vp_notepad = new QTextEdit(this); ui_vp_notepad->setFrameStyle(QFrame::NoFrame); diff --git a/src/evidence.cpp b/src/evidence.cpp index 1c0381ab1..2107fc6d1 100644 --- a/src/evidence.cpp +++ b/src/evidence.cpp @@ -5,7 +5,7 @@ void Courtroom::construct_evidence() { - ui_evidence = new AOImage(this, ao_app); + ui_evidence = new AOImageDisplay(this, ao_app); // ui_evidence_name = new QLabel(ui_evidence); ui_evidence_name = new AOLineEdit(ui_evidence); @@ -20,7 +20,7 @@ void Courtroom::construct_evidence() ui_evidence_right = new AOButton(ui_evidence, ao_app); ui_evidence_present = new AOButton(ui_evidence, ao_app); - ui_evidence_overlay = new AOImage(ui_evidence, ao_app); + ui_evidence_overlay = new AOImageDisplay(ui_evidence, ao_app); ui_evidence_delete = new AOButton(ui_evidence_overlay, ao_app); ui_evidence_image_name = new AOLineEdit(ui_evidence_overlay); diff --git a/src/lobby.cpp b/src/lobby.cpp index 2231aef53..1594fa764 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -18,7 +18,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() this->setWindowTitle("Danganronpa Online"); - ui_background = new AOImage(this, ao_app); + ui_background = new AOImageDisplay(this, ao_app); ui_public_servers = new AOButton(this, ao_app); ui_favorites = new AOButton(this, ao_app); ui_refresh = new AOButton(this, ao_app); @@ -42,7 +42,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_chatname = new QLineEdit(this); ui_chatname->setPlaceholderText("Name"); ui_chatmessage = new QLineEdit(this); - ui_loading_background = new AOImage(this, ao_app); + ui_loading_background = new AOImageDisplay(this, ao_app); ui_loading_text = new QTextEdit(ui_loading_background); ui_progress_bar = new QProgressBar(ui_loading_background); ui_progress_bar->setMinimum(0); From 069e29e569262f615be9ec07b3b0ad18c96d111e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Tue, 17 Nov 2020 21:25:14 +0100 Subject: [PATCH 166/842] Added nullptr init to pointers. --- dronline-client.pro | 2 +- include/aoblipplayer.h | 4 +- include/aocharmovie.h | 4 +- include/aoevidencebutton.h | 4 +- include/aoevidencedisplay.h | 6 +- include/aoimagedisplay.h | 9 +- include/aomovie.h | 2 +- include/aonotearea.h | 6 +- include/aonotepicker.h | 12 +- include/courtroom.h | 152 ++++++++++++------------ include/lobby.h | 36 +++--- include/networkmanager.h | 8 +- src/{aoimage.cpp => aoimagedisplay.cpp} | 8 +- src/lobby.cpp | 2 +- src/packet_distribution.cpp | 2 +- 15 files changed, 128 insertions(+), 129 deletions(-) rename src/{aoimage.cpp => aoimagedisplay.cpp} (76%) diff --git a/dronline-client.pro b/dronline-client.pro index 466d1b748..0b901ff87 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -69,7 +69,7 @@ SOURCES += \ src/aoevidencedisplay.cpp \ src/aoexception.cpp \ src/aoguiloader.cpp \ - src/aoimage.cpp \ + src/aoimagedisplay.cpp \ src/aolabel.cpp \ src/aolineedit.cpp \ src/aomovie.cpp \ diff --git a/include/aoblipplayer.h b/include/aoblipplayer.h index 45f08b624..9fbfcf803 100644 --- a/include/aoblipplayer.h +++ b/include/aoblipplayer.h @@ -24,8 +24,8 @@ class AOBlipPlayer int m_cycle = 0; private: - QWidget *m_parent; - AOApplication *ao_app; + QWidget *m_parent = nullptr; + AOApplication *ao_app = nullptr; int m_volume; HSTREAM m_stream_list[BLIP_COUNT]; diff --git a/include/aocharmovie.h b/include/aocharmovie.h index 24d4bea77..a82d4da1b 100644 --- a/include/aocharmovie.h +++ b/include/aocharmovie.h @@ -27,9 +27,9 @@ class AOCharMovie : public QLabel private: AOApplication *ao_app = nullptr; - QMovie *m_reader; + QMovie *m_reader = nullptr; QVector movie_frames; - QTimer *m_frame_timer; + QTimer *m_frame_timer = nullptr; bool m_mirror = false; bool m_play_once = false; diff --git a/include/aoevidencebutton.h b/include/aoevidencebutton.h index d203c8e7d..0aab424cc 100644 --- a/include/aoevidencebutton.h +++ b/include/aoevidencebutton.h @@ -28,8 +28,8 @@ class AOEvidenceButton : public QPushButton private: AOApplication *ao_app = nullptr; - AOImageDisplay *ui_selected; - AOImageDisplay *ui_selector; + AOImageDisplay *ui_selected = nullptr; + AOImageDisplay *ui_selector = nullptr; int m_id = 0; diff --git a/include/aoevidencedisplay.h b/include/aoevidencedisplay.h index 95d7e5b24..1cd5ed5ce 100644 --- a/include/aoevidencedisplay.h +++ b/include/aoevidencedisplay.h @@ -21,9 +21,9 @@ class AOEvidenceDisplay : public QLabel private: AOApplication *ao_app = nullptr; - QMovie *evidence_movie; - QLabel *evidence_icon; - AOSfxPlayer *sfx_player; + QMovie *evidence_movie = nullptr; + QLabel *evidence_icon = nullptr; + AOSfxPlayer *sfx_player = nullptr; private slots: void frame_change(int p_frame); diff --git a/include/aoimagedisplay.h b/include/aoimagedisplay.h index 43617bac5..9ea79ecb1 100644 --- a/include/aoimagedisplay.h +++ b/include/aoimagedisplay.h @@ -1,13 +1,12 @@ -// This class represents a static theme-dependent image - -#ifndef AOIMAGE_H -#define AOIMAGE_H +#ifndef AOIMAGEDISPLAY_H +#define AOIMAGEDISPLAY_H #include "aoapplication.h" #include "aopixmap.h" #include +// This class represents a static theme-dependent image class AOImageDisplay : public QLabel { public: @@ -21,4 +20,4 @@ class AOImageDisplay : public QLabel QString image_path = ""; }; -#endif // AOIMAGE_H +#endif // AOIMAGEDISPLAY_H diff --git a/include/aomovie.h b/include/aomovie.h index a1fc63e52..140a17422 100644 --- a/include/aomovie.h +++ b/include/aomovie.h @@ -26,7 +26,7 @@ class AOMovie : public QLabel void stop(); private: - QMovie *m_movie; + QMovie *m_movie = nullptr; AOApplication *ao_app = nullptr; bool play_once = true; diff --git a/include/aonotearea.h b/include/aonotearea.h index dbccc23db..e29bf0c8c 100644 --- a/include/aonotearea.h +++ b/include/aonotearea.h @@ -19,11 +19,11 @@ class AONoteArea : public AOImageDisplay public: AONoteArea(QWidget *p_parent, AOApplication *p_ao_app); - AOButton *add_button; - QVBoxLayout *m_layout; + AOButton *add_button = nullptr; + QVBoxLayout *m_layout = nullptr; private: - AOApplication *ao_app; + AOApplication *ao_app = nullptr; void set_layout(); }; diff --git a/include/aonotepicker.h b/include/aonotepicker.h index 393abf024..82d48c8c1 100644 --- a/include/aonotepicker.h +++ b/include/aonotepicker.h @@ -13,15 +13,15 @@ class AONotePicker : public QLabel public: AONotePicker(QWidget *p_parent, AOApplication *p_ao_app); - QLineEdit *m_line; - AOButton *m_button; - AOButton *m_delete_button; - AOButton *m_hover; - QHBoxLayout *m_layout; + QLineEdit *m_line = nullptr; + AOButton *m_button = nullptr; + AOButton *m_delete_button = nullptr; + AOButton *m_hover = nullptr; + QHBoxLayout *m_layout = nullptr; QString real_file = ""; private: - AOApplication *ao_app; + AOApplication *ao_app = nullptr; }; #endif // AONOTEPICKER_HPP diff --git a/include/courtroom.h b/include/courtroom.h index 5fd0897a2..e511a60e5 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -309,13 +309,13 @@ class Courtroom : public QMainWindow QVector area_names; QVector note_list; - QSignalMapper *char_button_mapper; + QSignalMapper *char_button_mapper = nullptr; // triggers ping_server() every 60 seconds - QTimer *keepalive_timer; + QTimer *keepalive_timer = nullptr; // maintains a timer for how fast messages tick onto screen - QTimer *chat_tick_timer; + QTimer *chat_tick_timer = nullptr; // which tick position(character in chat message) we are at int tick_pos = 0; // used to determine how often blips sound @@ -326,23 +326,23 @@ class Courtroom : public QMainWindow bool contains_add_button = false; ////////////// - QScrollArea *note_scroll_area; + QScrollArea *note_scroll_area = nullptr; // delay before chat messages starts ticking - QTimer *text_delay_timer; + QTimer *text_delay_timer = nullptr; // delay before sfx plays - QTimer *sfx_delay_timer; + QTimer *sfx_delay_timer = nullptr; // keeps track of how long realization is visible(it's just a white square and // should be visible less than a second) - QTimer *realization_timer; + QTimer *realization_timer = nullptr; // times how long the blinking testimony should be shown(green one in the // corner) - QTimer *testimony_show_timer; + QTimer *testimony_show_timer = nullptr; // times how long the blinking testimony should be hidden - QTimer *testimony_hide_timer; + QTimer *testimony_hide_timer = nullptr; // Generate a File Name based on the time you launched the client QString icchatlogsfilename = QDateTime::currentDateTime().toString( @@ -451,24 +451,24 @@ class Courtroom : public QMainWindow QString current_background = "gs4"; - AOImageDisplay *ui_background; + AOImageDisplay *ui_background = nullptr; - QWidget *ui_viewport; - AOScene *ui_vp_background; - AOMovie *ui_vp_speedlines; - AOCharMovie *ui_vp_player_char; - AOScene *ui_vp_desk; - AOEvidenceDisplay *ui_vp_evidence_display; + QWidget *ui_viewport = nullptr; + AOScene *ui_vp_background = nullptr; + AOMovie *ui_vp_speedlines = nullptr; + AOCharMovie *ui_vp_player_char = nullptr; + AOScene *ui_vp_desk = nullptr; + AOEvidenceDisplay *ui_vp_evidence_display = nullptr; - AONoteArea *ui_note_area; + AONoteArea *ui_note_area = nullptr; - // AONotepad *ui_vp_notepad; + // AONotepad *ui_vp_notepad = nullptr; // list of characters that require a second application ID // note that since it's hardcoded, it won't be of much use in other servers QVector rpc_char_list; - AOImageDisplay *ui_vp_notepad_image; - QTextEdit *ui_vp_notepad; + AOImageDisplay *ui_vp_notepad_image = nullptr; + QTextEdit *ui_vp_notepad = nullptr; AOImageDisplay *ui_vp_chatbox = nullptr; QTextEdit *ui_vp_showname = nullptr; @@ -486,40 +486,40 @@ class Courtroom : public QMainWindow QTextEdit *ui_vp_music_name = nullptr; QPropertyAnimation *music_anim = nullptr; - QWidget *ui_vp_music_area; + QWidget *ui_vp_music_area = nullptr; - AOMovie *ui_vp_clock; + AOMovie *ui_vp_clock = nullptr; QVector ui_timers; QTextEdit *ui_ic_chatlog = nullptr; record_type_array m_ic_records; - AOTextArea *ui_server_chatlog; + AOTextArea *ui_server_chatlog = nullptr; - QListWidget *ui_mute_list; - QListWidget *ui_area_list; - QListWidget *ui_music_list; - QListWidget *ui_sfx_list; + QListWidget *ui_mute_list = nullptr; + QListWidget *ui_area_list = nullptr; + QListWidget *ui_music_list = nullptr; + QListWidget *ui_sfx_list = nullptr; - QLineEdit *ui_ic_chat_message; + QLineEdit *ui_ic_chat_message = nullptr; - QLineEdit *ui_ooc_chat_message; - QLineEdit *ui_ooc_chat_name; + QLineEdit *ui_ooc_chat_message = nullptr; + QLineEdit *ui_ooc_chat_name = nullptr; - QLineEdit *ui_music_search; + QLineEdit *ui_music_search = nullptr; - QLineEdit *ui_sfx_search; + QLineEdit *ui_sfx_search = nullptr; QWidget *ui_emotes = nullptr; QVector ui_emote_list; - AOButton *ui_emote_left; - AOButton *ui_emote_right; + AOButton *ui_emote_left = nullptr; + AOButton *ui_emote_right = nullptr; - QComboBox *ui_emote_dropdown; - QComboBox *ui_pos_dropdown; + QComboBox *ui_emote_dropdown = nullptr; + QComboBox *ui_pos_dropdown = nullptr; - AOImageDisplay *ui_defense_bar; - AOImageDisplay *ui_prosecution_bar; + AOImageDisplay *ui_defense_bar = nullptr; + AOImageDisplay *ui_prosecution_bar = nullptr; // buttons to cycle through shouts AOButton *ui_shout_up = nullptr; @@ -572,22 +572,22 @@ class Courtroom : public QMainWindow // AOButton* ui_shout_cross_swords = nullptr; // 6 // AOButton* ui_shout_counter_alt = nullptr; // 7 - AOButton *ui_witness_testimony; - AOButton *ui_cross_examination; - AOButton *ui_investigation; - AOButton *ui_nonstop; + AOButton *ui_witness_testimony = nullptr; + AOButton *ui_cross_examination = nullptr; + AOButton *ui_investigation = nullptr; + AOButton *ui_nonstop = nullptr; - AOButton *ui_change_character; - AOButton *ui_call_mod; - AOButton *ui_switch_area_music; + AOButton *ui_change_character = nullptr; + AOButton *ui_call_mod = nullptr; + AOButton *ui_switch_area_music = nullptr; - AOButton *ui_config_panel; + AOButton *ui_config_panel = nullptr; - AOButton *ui_set_notes; + AOButton *ui_set_notes = nullptr; - QCheckBox *ui_pre; - QCheckBox *ui_flip; - QCheckBox *ui_hidden; + QCheckBox *ui_pre = nullptr; + QCheckBox *ui_flip = nullptr; + QCheckBox *ui_hidden = nullptr; QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip @@ -600,46 +600,46 @@ class Courtroom : public QMainWindow AOButton *ui_mute = nullptr; - AOButton *ui_defense_plus; - AOButton *ui_defense_minus; + AOButton *ui_defense_plus = nullptr; + AOButton *ui_defense_minus = nullptr; - AOButton *ui_prosecution_plus; - AOButton *ui_prosecution_minus; + AOButton *ui_prosecution_plus = nullptr; + AOButton *ui_prosecution_minus = nullptr; - QComboBox *ui_text_color; + QComboBox *ui_text_color = nullptr; - AOImageDisplay *ui_muted; + AOImageDisplay *ui_muted = nullptr; - AOButton *ui_note_button; + AOButton *ui_note_button = nullptr; - AOButton *ui_evidence_button; - AOImageDisplay *ui_evidence; - AOLineEdit *ui_evidence_name; - QWidget *ui_evidence_buttons; + AOButton *ui_evidence_button = nullptr; + AOImageDisplay *ui_evidence = nullptr; + AOLineEdit *ui_evidence_name = nullptr; + QWidget *ui_evidence_buttons = nullptr; QVector ui_evidence_list; - AOButton *ui_evidence_left; - AOButton *ui_evidence_right; - AOButton *ui_evidence_present; - AOImageDisplay *ui_evidence_overlay; - AOButton *ui_evidence_delete; - AOLineEdit *ui_evidence_image_name; - AOButton *ui_evidence_image_button; - AOButton *ui_evidence_x; - AOEvidenceDescription *ui_evidence_description; - - AOImageDisplay *ui_char_select_background; + AOButton *ui_evidence_left = nullptr; + AOButton *ui_evidence_right = nullptr; + AOButton *ui_evidence_present = nullptr; + AOImageDisplay *ui_evidence_overlay = nullptr; + AOButton *ui_evidence_delete = nullptr; + AOLineEdit *ui_evidence_image_name = nullptr; + AOButton *ui_evidence_image_button = nullptr; + AOButton *ui_evidence_x = nullptr; + AOEvidenceDescription *ui_evidence_description = nullptr; + + AOImageDisplay *ui_char_select_background = nullptr; // abstract widget to hold char buttons QWidget *ui_char_buttons = nullptr; AOImageDisplay *ui_char_button_selector = nullptr; QVector ui_char_button_list; - AOButton *ui_back_to_lobby; + AOButton *ui_back_to_lobby = nullptr; - AOButton *ui_char_select_left; - AOButton *ui_char_select_right; + AOButton *ui_char_select_left = nullptr; + AOButton *ui_char_select_right = nullptr; - AOButton *ui_spectator; + AOButton *ui_spectator = nullptr; QHash widget_names; diff --git a/include/lobby.h b/include/lobby.h index 7f5ead915..f32a3af6a 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -54,32 +54,32 @@ class Lobby : public QMainWindow private: AOApplication *ao_app = nullptr; - AOImageDisplay *ui_background; + AOImageDisplay *ui_background = nullptr; - AOButton *ui_public_servers; - AOButton *ui_favorites; + AOButton *ui_public_servers = nullptr; + AOButton *ui_favorites = nullptr; - AOButton *ui_refresh; - AOButton *ui_add_to_fav; - AOButton *ui_connect; + AOButton *ui_refresh = nullptr; + AOButton *ui_add_to_fav = nullptr; + AOButton *ui_connect = nullptr; - QTextEdit *ui_version; - AOButton *ui_about; + QTextEdit *ui_version = nullptr; + AOButton *ui_about = nullptr; - QListWidget *ui_server_list; + QListWidget *ui_server_list = nullptr; - QTextEdit *ui_player_count; - AOTextArea *ui_description; + QTextEdit *ui_player_count = nullptr; + AOTextArea *ui_description = nullptr; - AOTextArea *ui_chatbox; + AOTextArea *ui_chatbox = nullptr; - QLineEdit *ui_chatname; - QLineEdit *ui_chatmessage; + QLineEdit *ui_chatname = nullptr; + QLineEdit *ui_chatmessage = nullptr; - AOImageDisplay *ui_loading_background; - QTextEdit *ui_loading_text; - QProgressBar *ui_progress_bar; - AOButton *ui_cancel; + AOImageDisplay *ui_loading_background = nullptr; + QTextEdit *ui_loading_text = nullptr; + QProgressBar *ui_progress_bar = nullptr; + AOButton *ui_cancel = nullptr; server_type f_last_server; diff --git a/include/networkmanager.h b/include/networkmanager.h index 9e16a3cf6..217edf0ec 100644 --- a/include/networkmanager.h +++ b/include/networkmanager.h @@ -24,10 +24,10 @@ class NetworkManager : public QObject ~NetworkManager(); AOApplication *ao_app = nullptr; - QTcpSocket *ms_socket; - QTcpSocket *server_socket; - QDnsLookup *ms_dns; - QTimer *ms_reconnect_timer; + QTcpSocket *ms_socket = nullptr; + QTcpSocket *server_socket = nullptr; + QDnsLookup *ms_dns = nullptr; + QTimer *ms_reconnect_timer = nullptr; const QString ms_srv_hostname = "_aoms._tcp.aceattorneyonline.com"; #ifdef LOCAL_MS diff --git a/src/aoimage.cpp b/src/aoimagedisplay.cpp similarity index 76% rename from src/aoimage.cpp rename to src/aoimagedisplay.cpp index e31eb0a7a..7b277d09b 100644 --- a/src/aoimage.cpp +++ b/src/aoimagedisplay.cpp @@ -1,15 +1,15 @@ #include "file_functions.h" -#include "aoimage.h" +#include "aoimagedisplay.h" #include -AOImage::AOImage(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) +AOImageDisplay::AOImageDisplay(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) { ao_app = p_ao_app; } -void AOImage::set_image(QString p_image) +void AOImageDisplay::set_image(QString p_image) { QString f_path = ao_app->find_theme_asset_path(p_image); AOPixmap f_pixmap(f_path); @@ -22,7 +22,7 @@ void AOImage::set_image(QString p_image) image_path = ""; } -void AOImage::set_image_from_path(QString p_path) +void AOImageDisplay::set_image_from_path(QString p_path) { QString default_path = ao_app->find_theme_asset_path("chatmed.png"); diff --git a/src/lobby.cpp b/src/lobby.cpp index 1594fa764..55a53fb98 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -365,7 +365,7 @@ void Lobby::on_connect_released() { ui_connect->set_image("connect.png"); - AOPacket *f_packet; + AOPacket *f_packet = nullptr; f_packet = new AOPacket("askchaa#%"); diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index d6b1915ae..d09c0a0c3 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -225,7 +225,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) w_lobby->set_loading_text("Loading"); w_lobby->set_loading_value(0); - AOPacket *f_packet; + AOPacket *f_packet = nullptr; f_packet = new AOPacket("RC#%"); send_server_packet(f_packet); From 81b638add395d5ba8b49e2b59df3dabba539f477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Tue, 17 Nov 2020 21:27:29 +0100 Subject: [PATCH 167/842] Renamed files, removed QString member init. --- include/aoapplication.h | 2 +- include/aobutton.h | 2 +- include/aoimagedisplay.h | 2 +- include/aonotepicker.h | 2 +- include/courtroom.h | 8 ++++---- include/networkmanager.h | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 2481dd790..b539030a6 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -50,7 +50,7 @@ class AOApplication : public QApplication // player number, it's hardly used but might be needed for some old servers int s_pv = 0; - QString server_software = ""; + QString server_software; int char_list_size = 0; int loaded_chars = 0; diff --git a/include/aobutton.h b/include/aobutton.h index 894e23447..cd0000158 100644 --- a/include/aobutton.h +++ b/include/aobutton.h @@ -14,7 +14,7 @@ class AOButton : public QPushButton void set_image(QString p_image); AOApplication *ao_app = nullptr; - QString image_path = ""; + QString image_path; }; #endif // AOBUTTON_H diff --git a/include/aoimagedisplay.h b/include/aoimagedisplay.h index 9ea79ecb1..3007dffd3 100644 --- a/include/aoimagedisplay.h +++ b/include/aoimagedisplay.h @@ -17,7 +17,7 @@ class AOImageDisplay : public QLabel void set_size_and_pos(QString identifier); AOApplication *ao_app = nullptr; - QString image_path = ""; + QString image_path; }; #endif // AOIMAGEDISPLAY_H diff --git a/include/aonotepicker.h b/include/aonotepicker.h index 82d48c8c1..80dd8e56a 100644 --- a/include/aonotepicker.h +++ b/include/aonotepicker.h @@ -18,7 +18,7 @@ class AONotePicker : public QLabel AOButton *m_delete_button = nullptr; AOButton *m_hover = nullptr; QHBoxLayout *m_layout = nullptr; - QString real_file = ""; + QString real_file; private: AOApplication *ao_app = nullptr; diff --git a/include/courtroom.h b/include/courtroom.h index e511a60e5..d750e0115 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -366,10 +366,10 @@ class Courtroom : public QMainWindow QString m_chatmessage[chatmessage_size]; bool chatmessage_is_empty = false; - QString previous_ic_message = ""; + QString previous_ic_message; QColor m_base_string_color; - QString m_string_color = ""; + QString m_string_color; QStack m_color_stack; @@ -402,9 +402,9 @@ class Courtroom : public QMainWindow bool m_msg_is_first_person = false; // cid and this may differ in cases of ini-editing - QString current_char = ""; + QString current_char; - QString current_file = ""; + QString current_file; int m_shout_state = 0; int m_effect_state = 0; diff --git a/include/networkmanager.h b/include/networkmanager.h index 217edf0ec..2ee18a890 100644 --- a/include/networkmanager.h +++ b/include/networkmanager.h @@ -42,10 +42,10 @@ class NetworkManager : public QObject static const int ms_reconnect_delay_ms = 7000; bool ms_partial_packet = false; - QString ms_temp_packet = ""; + QString ms_temp_packet; bool partial_packet = false; - QString temp_packet = ""; + QString temp_packet; unsigned int s_decryptor = 5; From fec3fe9d9ba23eb40211baffc671be7671ee1d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Wed, 18 Nov 2020 01:40:07 +0100 Subject: [PATCH 168/842] Aligning sprites in the center. --- src/aocharmovie.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 14e6c1bfa..bec9b466f 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -18,6 +18,8 @@ AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) m_frame_timer = new QTimer(this); m_frame_timer->setSingleShot(true); + setAlignment(Qt::AlignCenter); + connect(m_reader, SIGNAL(frameChanged(int)), this, SLOT(on_frame_changed(int))); connect(m_frame_timer, SIGNAL(timeout()), this, SLOT(timer_done())); From 95e3e8c10d8138b1070c5a2d67e9793b4471995c Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 17 Nov 2020 19:53:35 -0500 Subject: [PATCH 169/842] Fix colors after blue not being selectable --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 38bd65ff2..a3c87f602 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -726,7 +726,7 @@ void Courtroom::on_chat_return_pressed() if (m_text_color < 0) f_text_color = "0"; - else if (m_text_color > 4) + else if (m_text_color > ui_text_color->count()) f_text_color = "0"; else f_text_color = QString::number(m_text_color); From 17686be2d2587abd8ca9e5b81fc1b5909ddb5c1a Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 26 Nov 2020 17:27:39 -0500 Subject: [PATCH 170/842] Delay reload theme execution if shout is playing to until after shout is done --- include/aomovie.h | 10 +++++++++- include/courtroom.h | 5 +++++ src/aomovie.cpp | 5 +++++ src/courtroom.cpp | 14 ++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/include/aomovie.h b/include/aomovie.h index 140a17422..cf49492d1 100644 --- a/include/aomovie.h +++ b/include/aomovie.h @@ -19,10 +19,18 @@ class AOMovie : public QLabel void play(QString p_file, QString p_char = ""); /// - /// \brief Searches and play the first interjection file it can find based on the provided character name and interjection name. + /// \brief Searches and play the first interjection file it can find based on + /// the provided character name and interjection name. /// void play_interjection(QString p_char_name, QString p_interjection_name); void combo_resize(int w, int h); + + /* + * @brief Returns the state of the current movie. Refer to QMovie::state() + * for more details. + * @returns Current movie status + */ + QMovie::MovieState state(); void stop(); private: diff --git a/include/courtroom.h b/include/courtroom.h index d750e0115..7ba9fd25c 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -406,6 +406,11 @@ class Courtroom : public QMainWindow QString current_file; + // if true, a reload theme order was delayed to be executed *after* a shout + // this allows reload theme orders that were received while a shout was + // playing to be executed only after the shout is done playing + bool shout_delayed_reload_theme = false; + int m_shout_state = 0; int m_effect_state = 0; int m_text_color = 0; diff --git a/src/aomovie.cpp b/src/aomovie.cpp index d5d45f1eb..419246ea4 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -151,3 +151,8 @@ void AOMovie::combo_resize(int w, int h) this->resize(f_size); m_movie->setScaledSize(f_size); } + +QMovie::MovieState AOMovie::state() +{ + return m_movie->state(); +} diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a3c87f602..4c4a4521f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -880,6 +880,12 @@ void Courtroom::handle_chatmessage_2() // handles IC qDebug() << "handle_chatmessage_2"; + if (shout_delayed_reload_theme) + { + shout_delayed_reload_theme = false; + on_app_reload_theme_requested(); + } + QString real_name = char_list.at(m_chatmessage[CHAR_ID].toInt()).name; QString f_showname; @@ -2282,6 +2288,14 @@ void Courtroom::on_change_character_clicked() void Courtroom::on_app_reload_theme_requested() { + // If an objection is playing, delay reload theme order to be executed + // after objection is done + if (ui_vp_objection->state() == QMovie::MovieState::Running) + { + shout_delayed_reload_theme = true; + return; + } + // Otherwise carry on load_shouts(); load_effects(); load_wtce(); From 16aa759233968c5ce90506b4da5be269679da122 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 26 Nov 2020 18:02:23 -0500 Subject: [PATCH 171/842] Prevent setting of animation and text status too early if reloading theme while IC message playing --- src/courtroom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 4c4a4521f..18d0d7f92 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2304,8 +2304,8 @@ void Courtroom::on_app_reload_theme_requested() // to update status on the background set_background(current_background); enter_courtroom(m_cid); - anim_state = 4; - text_state = 3; + // anim_state = 3; + // text_state = 2; } void Courtroom::on_back_to_lobby_clicked() From eb88161debcaef71ccd772f397f2a5b7895ef87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 27 Nov 2020 16:08:38 +0100 Subject: [PATCH 172/842] Fixed char movie breaking * FIXED actor movie breaking and not properly moving appearing after an animation is playing * REMOVED omnihex * RENAMED chatmessage enum fields * RENAMED emit to Q_EMIT * CHANGED app icon --- dronline-client.pro | 2 - icon-transparent.ico | Bin 0 -> 121899 bytes icon-transparent.png | Bin 0 -> 10656 bytes icon.ico | Bin 121899 -> 141734 bytes icon.png | Bin 10656 -> 37570 bytes include/aocharmovie.h | 21 +++-- include/courtroom.h | 8 +- include/datatypes.h | 54 ++++++------ include/hex_functions.h | 14 ---- src/aoabstractplayer.cpp | 2 +- src/aoapplication.cpp | 12 +-- src/aobasshandle.cpp | 4 +- src/aocharbutton.cpp | 4 +- src/aocharmovie.cpp | 122 +++++++++++---------------- src/aoconfigpanel.cpp | 2 +- src/aomovie.cpp | 7 +- src/aomusicplayer.cpp | 2 +- src/aosfxplayer.cpp | 2 +- src/aoshoutplayer.cpp | 2 +- src/courtroom.cpp | 159 +++++++++++++++++------------------- src/hex_functions.cpp | 85 ------------------- src/networkmanager.cpp | 6 +- src/packet_distribution.cpp | 4 +- 23 files changed, 192 insertions(+), 320 deletions(-) create mode 100644 icon-transparent.ico create mode 100644 icon-transparent.png delete mode 100644 include/hex_functions.h delete mode 100644 src/hex_functions.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 0b901ff87..afab0d4b6 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -48,7 +48,6 @@ HEADERS += \ include/discord_rich_presence.h \ include/file_functions.h \ include/hardware_functions.h \ - include/hex_functions.h \ include/lobby.h \ include/misc_functions.h \ include/networkmanager.h @@ -94,7 +93,6 @@ SOURCES += \ src/evidence.cpp \ src/file_functions.cpp \ src/hardware_functions.cpp \ - src/hex_functions.cpp \ src/lobby.cpp \ src/main.cpp \ src/misc_functions.cpp \ diff --git a/icon-transparent.ico b/icon-transparent.ico new file mode 100644 index 0000000000000000000000000000000000000000..856420281fba380c5c5c7bb0ebd3389c67a4c8be GIT binary patch literal 121899 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 SR=e{qoqHxE4sl6C+P?wtBGaG% literal 121899 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 M*EZ3r*K~UNKbsTIQ2+n{ literal 10656 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 movie_frames; + QMovie *m_movie = nullptr; QTimer *m_frame_timer = nullptr; + QImage m_current_frame; bool m_mirror = false; bool m_play_once = false; -signals: - void done(); + void paint_frame(); private slots: void on_frame_changed(int n_frame); - void timer_done(); + void on_timer_timeout(); }; #endif // AOCHARMOVIE_H diff --git a/include/courtroom.h b/include/courtroom.h index d750e0115..f28064f71 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -212,7 +212,7 @@ class Courtroom : public QMainWindow // these functions handle chatmessages sequentially. // The process itself is very convoluted and merits separate documentation // But the general idea is objection animation->pre animation->talking->idle - void handle_chatmessage(QStringList *p_contents); + void handle_chatmessage(QStringList p_contents); void handle_chatmessage_2(); void handle_chatmessage_3(); @@ -235,7 +235,7 @@ class Courtroom : public QMainWindow // prints who played the song to IC chat and plays said song(if found on local // filesystem) takes in a list where the first element is the song name and // the second is the char id of who played it - void handle_song(QStringList *p_contents); + void handle_song(QStringList p_contents); // animates music text void handle_music_anim(); @@ -592,8 +592,8 @@ class Courtroom : public QMainWindow QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip QVector ui_label_images; - QVector label_images = {"Pre", "Flip", "Hidden", - "Music", "SFX", "Blip"}; + QVector label_images = {"Pre", "Flip", "Hidden", + "Music", "SFX", "Blip"}; AOButton *ui_effect_flash = nullptr; AOButton *ui_effect_gloom = nullptr; diff --git a/include/datatypes.h b/include/datatypes.h index 3651ff8d6..e42acaade 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -97,37 +97,37 @@ struct pos_size_type int height = 0; }; -enum CHAT_MESSAGE +enum ChatMessage : std::int32_t { - DESK_MOD = 0, - PRE_EMOTE, - CHAR_NAME, - EMOTE, - MESSAGE, - SIDE, - SFX_NAME, - EMOTE_MOD, - CHAR_ID, - SFX_DELAY, - OBJECTION_MOD, - EVIDENCE_ID, - FLIP, - EFFECT_STATE, - TEXT_COLOR, - SHOWNAME + CMDeskModifier = 0, + CMPreAnim, + CMChrName, + CMEmote, + CMMessage, + CMPosition, + CMSoundName, + CMEmoteModifier, + CMChrId, + CMSoundDelay, + CMShoutModifier, + CMEvidenceId, + CMFlipState, + CMEffectState, + CMTextColor, + CMShowName, }; -enum COLOR +enum Color : std::int32_t { - WHITE = 0, - GREEN, - RED, - ORANGE, - BLUE, - YELLOW, - PURPLE, - PINK, - RAINBOW + CWhite, + CGreen, + CRed, + COrange, + CBlue, + CYellow, + CPurple, + CPink, + CRainbow, }; #endif // DATATYPES_H diff --git a/include/hex_functions.h b/include/hex_functions.h deleted file mode 100644 index c8388e0f0..000000000 --- a/include/hex_functions.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef HEX_OPERATIONS_H -#define HEX_OPERATIONS_H - -#include -#include -#include - -namespace omni -{ -char halfword_to_hex_char(unsigned int input); -std::string int_to_hex(unsigned int input); -} // namespace omni - -#endif // HEX_OPERATIONS_H diff --git a/src/aoabstractplayer.cpp b/src/aoabstractplayer.cpp index a2dd74acf..56b3ac9fb 100644 --- a/src/aoabstractplayer.cpp +++ b/src/aoabstractplayer.cpp @@ -14,5 +14,5 @@ void AOAbstractPlayer::set_volume(int p_volume) { m_volume = p_volume; - emit new_volume(m_volume); + Q_EMIT new_volume(m_volume); } diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index a2a819a78..37b4079ef 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -123,33 +123,33 @@ QString AOApplication::get_version_string() void AOApplication::set_gamemode(QString p_gamemode) { config->set_gamemode(p_gamemode); - emit reload_theme(); + Q_EMIT reload_theme(); } void AOApplication::set_timeofday(QString p_timeofday) { config->set_timeofday(p_timeofday); - emit reload_theme(); + Q_EMIT reload_theme(); } void AOApplication::on_config_theme_changed() { - emit reload_theme(); + Q_EMIT reload_theme(); } void AOApplication::on_config_reload_theme_requested() { - emit reload_theme(); + Q_EMIT reload_theme(); } void AOApplication::on_config_gamemode_changed() { - emit reload_theme(); + Q_EMIT reload_theme(); } void AOApplication::on_config_timeofday_changed() { - emit reload_theme(); + Q_EMIT reload_theme(); } void AOApplication::set_favorite_list() diff --git a/src/aobasshandle.cpp b/src/aobasshandle.cpp index e9725f533..23be86565 100644 --- a/src/aobasshandle.cpp +++ b/src/aobasshandle.cpp @@ -31,7 +31,7 @@ void AOBassHandle::suicide() disconnect(); // suicide note - emit body_discovery(); + Q_EMIT body_discovery(); delete this; } @@ -98,5 +98,5 @@ void AOBassHandle::sync(DWORD data) { Q_UNUSED(data) - emit stopped(); + Q_EMIT stopped(); } diff --git a/src/aocharbutton.cpp b/src/aocharbutton.cpp index 6ae5ab970..ed720f6ed 100644 --- a/src/aocharbutton.cpp +++ b/src/aocharbutton.cpp @@ -57,11 +57,11 @@ void AOCharButton::enterEvent(QEvent *e) { setFlat(false); QPushButton::enterEvent(e); - emit mouse_entered(this); + Q_EMIT mouse_entered(this); } void AOCharButton::leaveEvent(QEvent *e) { QPushButton::leaveEvent(e); - emit mouse_left(); + Q_EMIT mouse_left(); } diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index bec9b466f..9f9e08a51 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -13,20 +13,19 @@ AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) { ao_app = p_ao_app; - m_reader = new QMovie(this); + // center even images, not just text + setAlignment(Qt::AlignCenter); + + m_movie = new QMovie(this); m_frame_timer = new QTimer(this); m_frame_timer->setSingleShot(true); - setAlignment(Qt::AlignCenter); - - connect(m_reader, SIGNAL(frameChanged(int)), this, - SLOT(on_frame_changed(int))); - connect(m_frame_timer, SIGNAL(timeout()), this, SLOT(timer_done())); + connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(on_frame_changed(int))); + connect(m_frame_timer, SIGNAL(timeout()), this, SLOT(on_timer_timeout())); } -void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, - bool p_visible) +void AOCharMovie::play(QString p_chr, QString p_emote, QString p_emote_prefix, bool p_play_once) { // Asset lookup order // 1. In the character folder, look for @@ -38,46 +37,34 @@ void AOCharMovie::play(QString p_char, QString p_emote, QString p_emote_prefix, QString target_path = ao_app->find_asset_path( { - ao_app->get_character_path(p_char, p_emote_prefix + p_emote), - ao_app->get_character_path(p_char, p_emote), + ao_app->get_character_path(p_chr, p_emote_prefix + p_emote), + ao_app->get_character_path(p_chr, p_emote), }, animated_or_static_extensions()); if (target_path.isEmpty()) - target_path = - ao_app->find_theme_asset_path("placeholder", animated_extensions()); - - show(); - if (!p_visible) - hide(); - - movie_frames.clear(); - QImageReader *reader = new QImageReader(target_path); - for (int i = 0; i < reader->imageCount(); ++i) - { // optimize can be better, but I'll just keep it like that for now - QImage f_image = reader->read(); - if (m_mirror) - f_image = f_image.mirrored(true, false); - movie_frames.append(f_image); - } - delete reader; + target_path = ao_app->find_theme_asset_path("placeholder", animated_extensions()); - m_reader->stop(); - this->clear(); - m_reader->setFileName(target_path); - m_reader->start(); + stop(); + m_movie->setFileName(target_path); + m_play_once = p_play_once; + m_movie->start(); } -bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) +void AOCharMovie::play(QString p_chr, QString p_emote, bool p_play_once) { - QString f_file_path = ao_app->get_character_path(p_char, p_emote); + play(p_chr, p_emote, QString(), p_play_once); +} + +bool AOCharMovie::play_pre(QString p_chr, QString p_emote) +{ + QString f_file_path = ao_app->get_character_path(p_chr, p_emote); bool f_file_exist = false; { // figure out what extension the animation is using - QString f_source_path = ao_app->get_character_path(p_char, p_emote); + QString f_source_path = ao_app->get_character_path(p_chr, p_emote); for (QString &i_ext : animated_or_static_extensions()) { - QString f_target_path = - ao_app->get_case_sensitive_path(f_source_path + i_ext); + QString f_target_path = ao_app->get_case_sensitive_path(f_source_path + i_ext); if (file_exists(f_target_path)) { f_file_path = f_target_path; @@ -90,36 +77,22 @@ bool AOCharMovie::play_pre(QString p_char, QString p_emote, bool show) // play if it exist if (f_file_exist) { - m_reader->stop(); - this->clear(); - m_play_once = true; - m_reader->setFileName(f_file_path); - play(p_char, p_emote, "", show); + play(p_chr, p_emote, true); } return f_file_exist; } -void AOCharMovie::play_talking(QString p_char, QString p_emote, bool p_visible) +void AOCharMovie::play_talking(QString p_chr, QString p_emote) { - QString gif_path = ao_app->get_character_path(p_char, "(b)" + p_emote); - - m_reader->stop(); - this->clear(); - m_play_once = false; - m_reader->setFileName(gif_path); - play(p_char, p_emote, "(b)", p_visible); + QString gif_path = ao_app->get_character_path(p_chr, "(b)" + p_emote); + play(p_chr, p_emote, "(b)", false); } -void AOCharMovie::play_idle(QString p_char, QString p_emote, bool p_visible) +void AOCharMovie::play_idle(QString p_chr, QString p_emote) { - QString gif_path = ao_app->get_character_path(p_char, "(a)" + p_emote); - - this->clear(); - m_reader->stop(); - m_play_once = false; - m_reader->setFileName(gif_path); - play(p_char, p_emote, "(a)", p_visible); + QString gif_path = ao_app->get_character_path(p_chr, "(a)" + p_emote); + play(p_chr, p_emote, "(a)", false); } void AOCharMovie::set_mirror_enabled(bool p_enable) @@ -131,44 +104,45 @@ 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 - m_reader->stop(); + m_movie->stop(); m_frame_timer->stop(); - this->hide(); + m_current_frame = QImage(); + paint_frame(); } void AOCharMovie::combo_resize(QSize p_size) { resize(p_size); - m_reader->stop(); - m_frame_timer->stop(); - m_reader->setScaledSize(p_size); - m_reader->start(); + paint_frame(); +} + +void AOCharMovie::paint_frame() +{ + AOPixmap f_pixmap(QPixmap::fromImage(m_movie->currentImage().mirrored(m_mirror, false))); + this->setPixmap(f_pixmap.scale_to_height(this->size())); } void AOCharMovie::on_frame_changed(int p_frame_num) { - if (movie_frames.size() > p_frame_num) - { - AOPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(p_frame_num)); - this->setPixmap(f_pixmap.scale_to_height(this->size())); - } + m_current_frame = m_movie->currentImage(); + + paint_frame(); - // pre-anim only if (m_play_once) { - int f_frame_count = m_reader->frameCount(); + const int f_frame_count = m_movie->frameCount(); if (f_frame_count == 0 || p_frame_num == (f_frame_count - 1)) { - int f_frame_delay = m_reader->nextFrameDelay(); + int f_frame_delay = m_movie->nextFrameDelay(); if (f_frame_delay < 0) f_frame_delay = 0; + m_movie->stop(); m_frame_timer->start(f_frame_delay); - m_reader->stop(); } } } -void AOCharMovie::timer_done() +void AOCharMovie::on_timer_timeout() { - done(); + Q_EMIT done(); } diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index eaf79f1db..d759ae323 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -308,7 +308,7 @@ void AOConfigPanel::refresh_timeofday_list() void AOConfigPanel::on_reload_theme_clicked() { qDebug() << "reload theme clicked"; - emit reload_theme(); + Q_EMIT reload_theme(); } void AOConfigPanel::on_gamemode_index_changed(QString p_text) diff --git a/src/aomovie.cpp b/src/aomovie.cpp index d5d45f1eb..ef7244dcf 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -4,7 +4,8 @@ #include "file_functions.h" #include "misc_functions.h" -AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) +AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) + : QLabel(p_parent) { ao_app = p_ao_app; @@ -106,7 +107,7 @@ void AOMovie::play_interjection(QString p_char_name, if (interjection_filepath.isEmpty()) { - emit done(); + Q_EMIT done(); return; } @@ -141,7 +142,7 @@ void AOMovie::frame_change(int n_frame) this->stop(); // signal connected to courtroom object, let it figure out what to do - emit done(); + Q_EMIT done(); } } diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index 31969fbb5..51487d06f 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -40,5 +40,5 @@ void AOMusicPlayer::play(QString p_file) void AOMusicPlayer::stop() { - emit stopping(); + Q_EMIT stopping(); } diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index 360b19b98..28a928269 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -31,5 +31,5 @@ void AOSfxPlayer::play(QString p_name) void AOSfxPlayer::stop() { - emit stopping(); + Q_EMIT stopping(); } diff --git a/src/aoshoutplayer.cpp b/src/aoshoutplayer.cpp index 5dee583ce..cffd26ced 100644 --- a/src/aoshoutplayer.cpp +++ b/src/aoshoutplayer.cpp @@ -42,5 +42,5 @@ void AOShoutPlayer::play(QString p_name, QString p_char) void AOShoutPlayer::stop() { - emit stopping(); + Q_EMIT stopping(); } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 38bd65ff2..39d931f22 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -21,7 +21,8 @@ #include #include -Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() +Courtroom::Courtroom(AOApplication *p_ao_app) + : QMainWindow() { ao_app = p_ao_app; ao_config = new AOConfig(this); @@ -190,8 +191,8 @@ void Courtroom::set_scene() // witness is default if pos is invalid QString f_background = "witnessempty"; QString f_desk_image = "stand"; - QString f_desk_mod = m_chatmessage[DESK_MOD]; - QString f_side = m_chatmessage[SIDE]; + QString f_desk_mod = m_chatmessage[CMDeskModifier]; + QString f_side = m_chatmessage[CMPosition]; QString ini_path = ao_app->get_background_path("backgrounds.ini"); if (file_exists(ini_path)) @@ -737,26 +738,22 @@ void Courtroom::on_chat_return_pressed() ao_app->send_server_packet(new AOPacket("MS", packet_contents)); } -void Courtroom::handle_chatmessage(QStringList *p_contents) +void Courtroom::handle_chatmessage(QStringList p_contents) { - if (p_contents->size() < 15) + if (p_contents.size() < 15) return; - else if (p_contents->size() == 15) - p_contents->append(""); + else if (p_contents.size() == 15) + p_contents.append(QString()); - for (int n_string = 0; n_string < chatmessage_size; ++n_string) - { - m_chatmessage[n_string] = p_contents->at(n_string); - // qDebug() << "m_chatmessage[" << n_string << "] = " << - // m_chatmessage[n_string]; - } + for (int i = 0; i < chatmessage_size; ++i) + m_chatmessage[i] = p_contents[i]; - int f_char_id = m_chatmessage[CHAR_ID].toInt(); + int f_char_id = m_chatmessage[CMChrId].toInt(); if (f_char_id == -1) { is_system_speaking = true; - m_chatmessage[CHAR_ID] = "0"; + m_chatmessage[CMChrId] = "0"; f_char_id = 0; } else @@ -765,15 +762,15 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) if (f_char_id < 0 || f_char_id >= char_list.size()) return; - if (mute_map.value(m_chatmessage[CHAR_ID].toInt())) + if (mute_map.value(m_chatmessage[CMChrId].toInt())) return; chatmessage_is_empty = - m_chatmessage[MESSAGE] == " " || m_chatmessage[MESSAGE] == ""; + m_chatmessage[CMMessage] == " " || m_chatmessage[CMMessage] == ""; m_msg_is_first_person = false; // reset our ui state if client just spoke - if (m_cid == f_char_id && !is_system_speaking) + if (m_cid == f_char_id && is_system_speaking == false) { ui_ic_chat_message->clear(); @@ -800,22 +797,22 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) QString f_showname; qDebug() << "handle_chatmessage"; - qDebug() << m_chatmessage[SHOWNAME] + qDebug() << m_chatmessage[CMShowName] << ao_app->get_showname(char_list.at(f_char_id).name); // We actually DO wanna fail here if the showname is empty but the system is // speaking. // Having an empty showname for system is actually what we expect. - if (m_chatmessage[SHOWNAME].isEmpty() && !is_system_speaking) + if (m_chatmessage[CMShowName].isEmpty() && !is_system_speaking) { f_showname = ao_app->get_showname(char_list.at(f_char_id).name); } else { - f_showname = m_chatmessage[SHOWNAME]; + f_showname = m_chatmessage[CMShowName]; } - QString f_message = f_showname + ": " + m_chatmessage[MESSAGE] + "\n"; + QString f_message = f_showname + ": " + m_chatmessage[CMMessage] + "\n"; /* if (f_message == previous_ic_message && is_system_speaking == false) @@ -833,25 +830,25 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) ui_vp_effect->stop(); if (is_system_speaking) - append_system_text(f_showname, m_chatmessage[MESSAGE]); + append_system_text(f_showname, m_chatmessage[CMMessage]); else - append_ic_text(f_showname, m_chatmessage[MESSAGE], false, false); + append_ic_text(f_showname, m_chatmessage[CMMessage], false, false); if (ao_config->log_is_recording_enabled() && (!chatmessage_is_empty || !is_system_speaking)) { - save_textlog(f_showname + ": " + m_chatmessage[MESSAGE]); + save_textlog(f_showname + ": " + m_chatmessage[CMMessage]); } - int objection_mod = m_chatmessage[OBJECTION_MOD].toInt(); - QString f_char = m_chatmessage[CHAR_NAME]; + int objection_mod = m_chatmessage[CMShoutModifier].toInt(); + QString f_char = m_chatmessage[CMChrName]; // if an objection is used if (objection_mod > 0) { - int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); + int emote_mod = m_chatmessage[CMEmoteModifier].toInt(); if (emote_mod == 0) - m_chatmessage[EMOTE_MOD] = 1; + m_chatmessage[CMEmoteModifier] = 1; // handles cases 1-8 (5-8 are DRO only) if (objection_mod >= 1 && objection_mod <= ui_shouts.size() && @@ -880,17 +877,17 @@ void Courtroom::handle_chatmessage_2() // handles IC qDebug() << "handle_chatmessage_2"; - QString real_name = char_list.at(m_chatmessage[CHAR_ID].toInt()).name; + QString real_name = char_list.at(m_chatmessage[CMChrId].toInt()).name; QString f_showname; - if (m_chatmessage[SHOWNAME].isEmpty()) + if (m_chatmessage[CMShowName].isEmpty()) { f_showname = ao_app->get_showname(real_name); } else { - f_showname = m_chatmessage[SHOWNAME]; + f_showname = m_chatmessage[CMShowName]; } // Check if char.ini has color property, which overrides the theme's default @@ -906,7 +903,7 @@ void Courtroom::handle_chatmessage_2() // handles IC ui_vp_chatbox->hide(); ui_vp_showname_image->hide(); - QString chatbox = ao_app->get_chat(m_chatmessage[CHAR_NAME]); + QString chatbox = ao_app->get_chat(m_chatmessage[CMChrName]); if (chatbox == "") ui_vp_chatbox->set_image("chatmed.png"); @@ -916,16 +913,16 @@ void Courtroom::handle_chatmessage_2() // handles IC ui_vp_chatbox->set_image_from_path(chatbox_path); } - if (!m_msg_is_first_person) + if (m_msg_is_first_person == false) { set_scene(); } set_text_color(); - int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); + int emote_mod = m_chatmessage[CMEmoteModifier].toInt(); - if (m_chatmessage[FLIP].toInt() == 1) + if (m_chatmessage[CMFlipState].toInt() == 1) ui_vp_player_char->set_mirror_enabled(true); else ui_vp_player_char->set_mirror_enabled(false); @@ -952,8 +949,8 @@ void Courtroom::handle_chatmessage_3() start_chat_ticking(); - int f_evi_id = m_chatmessage[EVIDENCE_ID].toInt(); - QString f_side = m_chatmessage[SIDE]; + int f_evi_id = m_chatmessage[CMEvidenceId].toInt(); + QString f_side = m_chatmessage[CMPosition]; if (f_evi_id > 0 && f_evi_id <= local_evidence_list.size()) { @@ -965,11 +962,11 @@ void Courtroom::handle_chatmessage_3() ui_vp_evidence_display->show_evidence(f_image, is_left_side); } - int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); + int emote_mod = m_chatmessage[CMEmoteModifier].toInt(); if (emote_mod == 5 || emote_mod == 6) { - QString side = m_chatmessage[SIDE]; + QString side = m_chatmessage[CMPosition]; ui_vp_desk->hide(); if (side == "pro" || side == "hlp" || side == "wit") @@ -980,7 +977,7 @@ void Courtroom::handle_chatmessage_3() int f_anim_state = 0; // BLUE is from an enum in datatypes.h - bool text_is_blue = m_chatmessage[TEXT_COLOR].toInt() == BLUE; + bool text_is_blue = m_chatmessage[CMTextColor].toInt() == CBlue; if (!text_is_blue && text_state == 1) // talking @@ -993,8 +990,8 @@ void Courtroom::handle_chatmessage_3() return; ui_vp_player_char->stop(); - QString f_char = m_chatmessage[CHAR_NAME]; - QString f_emote = m_chatmessage[EMOTE]; + QString f_char = m_chatmessage[CMChrName]; + QString f_emote = m_chatmessage[CMEmote]; ui_vp_showname_image->show(); @@ -1026,23 +1023,23 @@ void Courtroom::handle_chatmessage_3() switch (f_anim_state) { case 2: - if (!m_msg_is_first_person) + if (m_msg_is_first_person == false) { - ui_vp_player_char->play_talking(f_char, f_emote, true); + ui_vp_player_char->play_talking(f_char, f_emote); } anim_state = 2; break; default: qDebug() << "W: invalid anim_state: " << f_anim_state; case 3: - if (!m_msg_is_first_person) + if (m_msg_is_first_person == false) { - ui_vp_player_char->play_idle(f_char, f_emote, true); + ui_vp_player_char->play_idle(f_char, f_emote); } anim_state = 3; } - int effect = m_chatmessage[EFFECT_STATE].toInt(); + int effect = m_chatmessage[CMEffectState].toInt(); QStringList offset = ao_app->get_effect_offset(f_char, effect); ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), @@ -1095,7 +1092,7 @@ void Courtroom::handle_chatmessage_3() ui_vp_effect->play(overlay_name, f_char); } - QString f_message = m_chatmessage[MESSAGE]; + QString f_message = m_chatmessage[CMMessage]; QStringList callwords = ao_app->get_callwords(); for (QString word : callwords) @@ -1340,24 +1337,26 @@ void Courtroom::append_system_text(QString p_showname, QString p_line) void Courtroom::play_preanim() { - QString f_char = m_chatmessage[CHAR_NAME]; - QString f_preanim = m_chatmessage[PRE_EMOTE]; + QString f_char = m_chatmessage[CMChrName]; + QString f_preanim = m_chatmessage[CMPreAnim]; // all time values in char.inis are multiplied by a constant(time_mod) to get // the actual time int text_delay = ao_app->get_text_delay(f_char, f_preanim) * time_mod; - int sfx_delay = m_chatmessage[SFX_DELAY].toInt() * 60; + int sfx_delay = m_chatmessage[CMSoundDelay].toInt() * 60; sfx_delay_timer->start(sfx_delay); // set state anim_state = 1; - if (!m_msg_is_first_person) + if (m_msg_is_first_person == false) { QString f_anim_path = ao_app->get_character_path(f_char, f_preanim); - if (ui_vp_player_char->play_pre(f_char, f_preanim, true)) + if (ui_vp_player_char->play_pre(f_char, f_preanim)) { + qDebug() << "Playing" << f_anim_path; + if (text_delay >= 0) text_delay_timer->start(text_delay); @@ -1408,7 +1407,7 @@ void Courtroom::start_chat_ticking() blip_pos = 0; chat_tick_timer->start(ao_app->get_chat_tick_interval()); - QString f_gender = ao_app->get_gender(m_chatmessage[CHAR_NAME]); + QString f_gender = ao_app->get_gender(m_chatmessage[CMChrName]); // m_blip_player->set_file(f_gender); m_blips_player->set_blips("sfx-blip" + f_gender + ".wav"); @@ -1427,7 +1426,7 @@ void Courtroom::chat_tick() else vp_message_format.setTextOutline(Qt::NoPen); - QString f_message = m_chatmessage[MESSAGE]; + QString f_message = m_chatmessage[CMMessage]; if (tick_pos >= f_message.size()) { @@ -1435,10 +1434,9 @@ void Courtroom::chat_tick() chat_tick_timer->stop(); anim_state = 3; - if (!m_msg_is_first_person) + if (m_msg_is_first_person == false) { - ui_vp_player_char->play_idle(m_chatmessage[CHAR_NAME], - m_chatmessage[EMOTE], true); + ui_vp_player_char->play_idle(m_chatmessage[CMChrName], m_chatmessage[CMEmote]); } m_string_color = ""; @@ -1451,7 +1449,7 @@ void Courtroom::chat_tick() if (f_character == " ") ui_vp_message->insertPlainText(" "); - else if (m_chatmessage[TEXT_COLOR].toInt() == RAINBOW) + else if (m_chatmessage[CMTextColor].toInt() == CRainbow) { QString html_color; @@ -1565,7 +1563,7 @@ void Courtroom::chat_tick() void Courtroom::show_testimony() { - if (!testimony_in_progress || m_chatmessage[SIDE] != "wit") + if (!testimony_in_progress || m_chatmessage[CMPosition] != "wit") return; ui_vp_testimony->show(); @@ -1585,7 +1583,7 @@ void Courtroom::hide_testimony() void Courtroom::play_sfx() { - QString sfx_name = m_chatmessage[SFX_NAME]; + QString sfx_name = m_chatmessage[CMSoundName]; if (sfx_name == "1") return; @@ -1595,40 +1593,40 @@ void Courtroom::play_sfx() void Courtroom::set_text_color() { - switch (m_chatmessage[TEXT_COLOR].toInt()) + switch (m_chatmessage[CMTextColor].toInt()) { - case GREEN: + case CGreen: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setRgb(101, 200, 86); break; - case RED: + case CRed: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setRgb(186, 21, 24); break; - case ORANGE: + case COrange: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setRgb(213, 89, 0); break; - case BLUE: + case CBlue: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setRgb(21, 136, 200); break; - case YELLOW: + case CYellow: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setRgb(231, 206, 78); break; - case PURPLE: + case CPurple: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setRgb(247, 118, 253); break; - case PINK: + case CPink: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setRgb(218, 124, 128); break; default: - qDebug() << "W: undefined text color: " << m_chatmessage[TEXT_COLOR]; + qDebug() << "W: undefined text color: " << m_chatmessage[CMTextColor]; [[fallthrough]]; - case WHITE: + case CWhite: ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setRgb(213, 213, 213); break; @@ -1673,16 +1671,13 @@ void Courtroom::set_ban(int p_cid) ao_app->destruct_courtroom(); } -void Courtroom::handle_song(QStringList *p_contents) +void Courtroom::handle_song(QStringList p_contents) { - - QStringList f_contents = *p_contents; - - if (f_contents.size() < 2) + if (p_contents.size() < 2) return; - QString f_song = f_contents.at(0); - int n_char = f_contents.at(1).toInt(); + QString f_song = p_contents.at(0); + int n_char = p_contents.at(1).toInt(); for (auto &ext : audio_extensions()) { @@ -1708,9 +1703,9 @@ void Courtroom::handle_song(QStringList *p_contents) // If there is an empty showname, the client will use instead the default // showname of the character. QString f_showname; - if (f_contents.size() == 3) + if (p_contents.size() == 3) { - f_showname = f_contents.at(2); + f_showname = p_contents.at(2); } else { @@ -2430,7 +2425,7 @@ void Courtroom::ping_server() void Courtroom::closeEvent(QCloseEvent *event) { - emit closing(); + Q_EMIT closing(); QMainWindow::closeEvent(event); } diff --git a/src/hex_functions.cpp b/src/hex_functions.cpp deleted file mode 100644 index 7e2ac941f..000000000 --- a/src/hex_functions.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// WELCOME TO THE EXTREMELY GHETTO HEX CONVERSION KLUDGE BECAUSE I COULDNT MAKE -// ANYTHING ELSE WORK - -#include "hex_functions.h" - -namespace omni -{ -char halfword_to_hex_char(unsigned int input) -{ - if (input > 127) - return 'F'; - - switch (input) - { - case 0: - return '0'; - case 1: - return '1'; - case 2: - return '2'; - case 3: - return '3'; - case 4: - return '4'; - case 5: - return '5'; - case 6: - return '6'; - case 7: - return '7'; - case 8: - return '8'; - case 9: - return '9'; - case 10: - return 'A'; - case 11: - return 'B'; - case 12: - return 'C'; - case 13: - return 'D'; - case 14: - return 'E'; - case 15: - return 'F'; - default: - return 'F'; - } -} - -std::string int_to_hex(unsigned int input) -{ - if (input > 255) - return "FF"; - - std::bitset<8> whole_byte(input); - // 240 represents 11110000, our needed bitmask - uint8_t left_mask_int = 240; - std::bitset<8> left_mask(left_mask_int); - std::bitset<8> left_halfword((whole_byte & left_mask) >> 4); - // likewise, 15 represents 00001111 - uint8_t right_mask_int = 15; - std::bitset<8> right_mask(right_mask_int); - std::bitset<8> right_halfword((whole_byte & right_mask)); - - unsigned int left = left_halfword.to_ulong(); - unsigned int right = right_halfword.to_ulong(); - - // std::cout << "now have have " << left << " and " << right << '\n'; - - char a = halfword_to_hex_char(left); - char b = halfword_to_hex_char(right); - - std::string left_string(1, a); - std::string right_string(1, b); - - std::string final_byte = left_string + right_string; - - // std::string final_byte = halfword_to_hex_char(left) + "" + - // halfword_to_hex_char(right); - - return final_byte; -} -} // namespace omni diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp index 0ddc62003..15e40a794 100644 --- a/src/networkmanager.cpp +++ b/src/networkmanager.cpp @@ -186,13 +186,13 @@ void NetworkManager::on_srv_lookup() if (!connected) connect_to_master_nosrv(); else - emit ms_connect_finished(connected, false); + Q_EMIT ms_connect_finished(connected, false); #endif } void NetworkManager::on_ms_nosrv_connect_success() { - emit ms_connect_finished(true, false); + Q_EMIT ms_connect_finished(true, false); QObject::disconnect(ms_socket, SIGNAL(connected()), this, SLOT(on_ms_nosrv_connect_success())); @@ -212,7 +212,7 @@ void NetworkManager::on_ms_socket_error(QAbstractSocket::SocketError error) this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); - emit ms_connect_finished(false, true); + Q_EMIT ms_connect_finished(false, true); ms_reconnect_timer->start(ms_reconnect_delay_ms); } diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index d09c0a0c3..03e58adf0 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -551,12 +551,12 @@ 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->handle_chatmessage(p_packet->get_contents()); } else if (header == "MC") { if (courtroom_constructed && courtroom_loaded) - w_courtroom->handle_song(&p_packet->get_contents()); + w_courtroom->handle_song(p_packet->get_contents()); } else if (header == "RT") { From 4a7462827dbed6f8a72a83b8fa163189123e7dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 27 Nov 2020 16:14:55 +0100 Subject: [PATCH 173/842] Reorganized code --- include/aomovie.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/aomovie.h b/include/aomovie.h index 140a17422..af45ff026 100644 --- a/include/aomovie.h +++ b/include/aomovie.h @@ -25,14 +25,14 @@ class AOMovie : public QLabel void combo_resize(int w, int h); void stop(); +signals: + void done(); + private: QMovie *m_movie = nullptr; AOApplication *ao_app = nullptr; bool play_once = true; -signals: - void done(); - private slots: void frame_change(int n_frame); }; From 1bfd7fad7315d4d1ec559255f3b021500958456a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 27 Nov 2020 19:18:08 +0100 Subject: [PATCH 174/842] Variable rename --- include/aocharmovie.h | 2 +- src/aocharmovie.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/aocharmovie.h b/include/aocharmovie.h index 8f5f0d235..78312acfe 100644 --- a/include/aocharmovie.h +++ b/include/aocharmovie.h @@ -21,7 +21,7 @@ class AOCharMovie : public QLabel bool play_pre(QString p_chr, QString p_emote); void play_talking(QString p_chr, QString p_emote); void play_idle(QString p_chr, QString p_emote); - void set_mirror_enabled(bool p_enable); + void set_mirror_enabled(bool p_enabled); void combo_resize(QSize p_size); void stop(); diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 9f9e08a51..7678640f1 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -95,9 +95,9 @@ void AOCharMovie::play_idle(QString p_chr, QString p_emote) play(p_chr, p_emote, "(a)", false); } -void AOCharMovie::set_mirror_enabled(bool p_enable) +void AOCharMovie::set_mirror_enabled(bool p_enabled) { - m_mirror = p_enable; + m_mirror = p_enabled; } void AOCharMovie::stop() From 341943aa35a87a39e140e46d87bebab1dd3b1a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 27 Nov 2020 19:43:24 +0100 Subject: [PATCH 175/842] Fix out of range issues * FIX out of range assert failure --- src/courtroom.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 36ddd91d5..b3209dc6a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -120,15 +120,15 @@ void Courtroom::enter_courtroom(int p_cid) set_widgets(); check_shouts(); - if (!shouts_enabled[m_shout_current]) + if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) cycle_shout(1); check_effects(); - if (!effects_enabled[m_effect_current]) + if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) cycle_effect(1); check_wtce(); - if (is_judge && !wtce_enabled[m_wtce_current]) + if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) cycle_wtce(1); check_free_blocks(); From 2bfd4f0f5b482904a8b202eac25ae041f8e55e0b Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Sun, 29 Nov 2020 15:30:21 -0500 Subject: [PATCH 176/842] Use new identifiers --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 09089970d..c7c8c276c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -883,7 +883,7 @@ void Courtroom::handle_chatmessage_2() // handles IC on_app_reload_theme_requested(); } - QString real_name = char_list.at(m_chatmessage[CHAR_ID].toInt()).name; + QString real_name = char_list.at(m_chatmessage[CMChrId].toInt()).name; QString f_showname; From 7f7671077e39c7d85423a942a67ccfbeadcf4563 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 2 Dec 2020 20:16:42 -0500 Subject: [PATCH 177/842] Make firing timer use Qt::PreciseTimer + Optimize firing timer restarts --- src/aotimer.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/aotimer.cpp b/src/aotimer.cpp index 805fa954c..daf30085a 100644 --- a/src/aotimer.cpp +++ b/src/aotimer.cpp @@ -11,6 +11,7 @@ AOTimer::AOTimer(QWidget *p_parent) : QTextEdit(p_parent) setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setReadOnly(true); + firing_timer.setTimerType(Qt::PreciseTimer); connect(&firing_timer, SIGNAL(timeout()), this, SLOT(update_time())); set_time(start_time); @@ -57,7 +58,16 @@ void AOTimer::update_time() // current timer Redraw and return old_manual_timer.perform_timestep(); redraw(); - firing_timer.start(firing_timer_length); + // As the firing timer is not single shot, it will restart automatically once + // expired. However, it will do so with its firing interval, whatever it was + // (which could be less than firing_timer_length if, say, the timer was + // paused midstep). + // The next firing_timer should take firing_timer_length miliseconds no + // matter what. However, to avoid needless computation, we only explicitly + // restart firing_timer if its length was not firing_timer_length already + // (see example above). + if (firing_timer.interval() != firing_timer_length) + firing_timer.start(firing_timer_length); } void AOTimer::set() From 16581919dfc081deaa7e219b89bed767baaae310 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 3 Dec 2020 10:08:08 -0500 Subject: [PATCH 178/842] Fix setting firing interval restarting the timer even if it was paused. --- include/aotimer.h | 2 ++ src/aotimer.cpp | 24 ++++++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/include/aotimer.h b/include/aotimer.h index 0256155c0..87ef5fdec 100644 --- a/include/aotimer.h +++ b/include/aotimer.h @@ -56,6 +56,8 @@ class AOTimer : public QTextEdit int firing_timer_length = 12; int time_spent_in_timestep = 0; + bool paused; + public slots: void update_time(); void set(); diff --git a/src/aotimer.cpp b/src/aotimer.cpp index daf30085a..9e564fc17 100644 --- a/src/aotimer.cpp +++ b/src/aotimer.cpp @@ -12,6 +12,7 @@ AOTimer::AOTimer(QWidget *p_parent) : QTextEdit(p_parent) setReadOnly(true); firing_timer.setTimerType(Qt::PreciseTimer); + firing_timer.setInterval(firing_timer_length); connect(&firing_timer, SIGNAL(timeout()), this, SLOT(update_time())); set_time(start_time); @@ -63,9 +64,10 @@ void AOTimer::update_time() // (which could be less than firing_timer_length if, say, the timer was // paused midstep). // The next firing_timer should take firing_timer_length miliseconds no - // matter what. However, to avoid needless computation, we only explicitly - // restart firing_timer if its length was not firing_timer_length already - // (see example above). + // matter what. However, manually restarting a timer is a very expensive QT + // operation, while QT's built-in automatic restart is very efficient. + // Therefore, we only restart firing_timer if its length was not + // firing_timer_length already (see example above). if (firing_timer.interval() != firing_timer_length) firing_timer.start(firing_timer_length); } @@ -77,12 +79,16 @@ void AOTimer::set() void AOTimer::resume() { - firing_timer.start(firing_timer_length); + paused = false; + firing_timer.start(); } void AOTimer::pause() { + paused = true; + int remaining = firing_timer.remainingTime(); firing_timer.stop(); + firing_timer.setInterval(remaining); } void AOTimer::redraw() @@ -122,10 +128,16 @@ void AOTimer::set_firing_interval(int new_firing_interval) // For this timestep however, the firing interval will be shorter than // firing_timer_length to account for the fact the timer may have already been // running a bit in this timestep + int this_step_firing_interval; if (new_firing_interval < time_spent_in_timestep) - firing_timer.start(0); + this_step_firing_interval = 0; else - firing_timer.start(new_firing_interval - time_spent_in_timestep); + this_step_firing_interval = new_firing_interval - time_spent_in_timestep; + + firing_timer.setInterval(this_step_firing_interval); + + if (!paused) + firing_timer.start(); } void AOTimer::set_concentrate_mode() From dc70582cd3e77f555ef5a0acdd725c995d5e4ba2 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 3 Dec 2020 10:13:36 -0500 Subject: [PATCH 179/842] Set default time to 00:00 --- include/aotimer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/aotimer.h b/include/aotimer.h index 87ef5fdec..d053bfc4e 100644 --- a/include/aotimer.h +++ b/include/aotimer.h @@ -50,7 +50,7 @@ class AOTimer : public QTextEdit ManualTimer manual_timer; QTimer firing_timer; - QTime start_time = QTime(0, 5); + QTime start_time = QTime(0, 0); // All of this is in miliseconds int manual_timer_timestep_length = -12; int firing_timer_length = 12; From 174c152b28661dc8c6ee36930b3b4344ae2d0a0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Mon, 7 Dec 2020 15:58:08 +0100 Subject: [PATCH 180/842] Removed deprecated code --- src/courtroom.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index c7c8c276c..f2475b15b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2290,6 +2290,7 @@ void Courtroom::on_app_reload_theme_requested() shout_delayed_reload_theme = true; return; } + // Otherwise carry on load_shouts(); load_effects(); @@ -2299,8 +2300,6 @@ void Courtroom::on_app_reload_theme_requested() // to update status on the background set_background(current_background); enter_courtroom(m_cid); - // anim_state = 3; - // text_state = 2; } void Courtroom::on_back_to_lobby_clicked() From 626a855a31b599ab0e76d5388f829b71c46e2c91 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 15 Dec 2020 21:26:48 -0500 Subject: [PATCH 181/842] Fix clock looking for wrong file location to play animation --- src/courtroom.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 0bfe9c1dd..329c62be5 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -21,8 +21,7 @@ #include #include -Courtroom::Courtroom(AOApplication *p_ao_app) - : QMainWindow() +Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; ao_config = new AOConfig(this); @@ -120,15 +119,18 @@ void Courtroom::enter_courtroom(int p_cid) set_widgets(); check_shouts(); - if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) + if (m_shout_current < shouts_enabled.length() && + !shouts_enabled[m_shout_current]) cycle_shout(1); check_effects(); - if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) + if (m_effect_current < effects_enabled.length() && + !effects_enabled[m_effect_current]) cycle_effect(1); check_wtce(); - if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) + if (is_judge && + (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) cycle_wtce(1); check_free_blocks(); @@ -356,16 +358,15 @@ void Courtroom::handle_clock(QString time) } qDebug() << "Displaying clock asset..."; - const QString asset_path = - ao_app->find_theme_asset_path("hours/" + QString::number(current_clock), - animated_or_static_extensions()); + QString clock_filename = "hours/" + QString::number(current_clock); + const QString asset_path = ao_app->find_theme_asset_path( + clock_filename, animated_or_static_extensions()); if (asset_path.isEmpty()) { qDebug() << "Asset not found; aborting."; return; } - - ui_vp_clock->play(asset_path); + ui_vp_clock->play(clock_filename); ui_vp_clock->show(); } @@ -1442,7 +1443,8 @@ void Courtroom::chat_tick() if (m_msg_is_first_person == false) { - ui_vp_player_char->play_idle(m_chatmessage[CMChrName], m_chatmessage[CMEmote]); + ui_vp_player_char->play_idle(m_chatmessage[CMChrName], + m_chatmessage[CMEmote]); } m_string_color = ""; From 9daca0d528b801f22a439aeff29172216432adf6 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 2 Jan 2021 11:53:56 -0300 Subject: [PATCH 182/842] Record mod calls in client log if recording is on --- src/courtroom.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 329c62be5..b3e685da7 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1808,6 +1808,8 @@ void Courtroom::mod_called(QString p_ip) { m_system_player->play(ao_app->get_sfx("mod_call")); ao_app->alert(this); + if (ao_config->log_is_recording_enabled()) + save_textlog("(OOC)(MOD CALL)" + p_ip); } } From c67fa387e2cfad533a3313adca36819a8f4d733f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 2 Jan 2021 14:52:13 -0300 Subject: [PATCH 183/842] Use "font_default" for widget font if requested font does not exist --- src/courtroom.cpp | 2 ++ src/courtroom_widgets.cpp | 11 +++++++++++ src/lobby.cpp | 9 ++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 329c62be5..2f8880a45 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -30,6 +30,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, 0, NULL); BASS_PluginLoad("bassopus.dll", BASS_UNICODE); + // ao_app->setFont(QFont(default_font)); + create_widgets(); connect_widgets(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index a809036a5..196eea5b6 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1476,8 +1476,19 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, QString class_name = widget->metaObject()->className(); int f_weight = ao_app->get_font_property(p_identifier, design_file); + + // Font priority + // 1. "font_" + p_identifier + // 2. "font_default" + // 3. System font + QFontDatabase font_database; QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); + if (!font_database.families().contains(font_name)) + { + QString default_font_name = + ao_app->get_font_name("font_default", "courtroom_fonts.ini"); + } widget->setFont(QFont(font_name, f_weight)); if (override_color.isEmpty()) diff --git a/src/lobby.cpp b/src/lobby.cpp index 55a53fb98..5e3e4183d 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -236,10 +236,16 @@ void Lobby::set_font(QWidget *widget, QString p_identifier) return; int f_weight = ao_app->get_font_property(p_identifier, design_file); - QString class_name = widget->metaObject()->className(); + // Font priority + // 1. "font_" + p_identifier + // 2. "font_default" + // 3. System font + QFontDatabase font_database; QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); + if (!font_database.families().contains(font_name)) + font_name = ao_app->get_font_name("font_default", "lobby_fonts.ini"); QFont font(font_name, f_weight); widget->setFont(font); @@ -257,6 +263,7 @@ void Lobby::set_font(QWidget *widget, QString p_identifier) if (center) is_center = "qproperty-alignment: AlignCenter;"; + QString class_name = widget->metaObject()->className(); QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: rgba(" + QString::number(f_color.red()) + ", " + QString::number(f_color.green()) + From f04946784a2013518aa98afce306e09f59a2862b Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 2 Jan 2021 14:54:22 -0300 Subject: [PATCH 184/842] Remove leftover comment --- src/courtroom.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2f8880a45..329c62be5 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -30,8 +30,6 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, 0, NULL); BASS_PluginLoad("bassopus.dll", BASS_UNICODE); - // ao_app->setFont(QFont(default_font)); - create_widgets(); connect_widgets(); From 4399c6c4d9773f053f5ab1cc0f37ba7afade6a29 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 3 Jan 2021 23:27:41 -0300 Subject: [PATCH 185/842] Implement ackMS packet recognition to reset UI after server acknowledges client sent IC message --- include/aoapplication.h | 4 +++ include/courtroom.h | 7 +++-- src/courtroom.cpp | 53 ++++++++++++++++++++++++------------- src/packet_distribution.cpp | 11 ++++++-- 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index b539030a6..5f60d0237 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -45,6 +45,10 @@ class AOApplication : public QApplication void send_ms_packet(AOPacket *p_packet); void send_server_packet(AOPacket *p_packet, bool encoded = true); + ///////////////server metadata//////////////// + + bool ackMS_enabled = false; + ///////////////loading info/////////////////// // player number, it's hardly used but might be needed for some old servers diff --git a/include/courtroom.h b/include/courtroom.h index c55077e60..a1d600996 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -209,6 +209,9 @@ class Courtroom : public QMainWindow // these are for OOC chat void append_server_chatmessage(QString p_name, QString p_message); + // handles resetting the UI after the server acknowledged the client sent an + // message. + void handle_acknowledged_ms(); // these functions handle chatmessages sequentially. // The process itself is very convoluted and merits separate documentation // But the general idea is objection animation->pre animation->talking->idle @@ -597,8 +600,8 @@ class Courtroom : public QMainWindow QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip QVector ui_label_images; - QVector label_images = {"Pre", "Flip", "Hidden", - "Music", "SFX", "Blip"}; + QVector label_images = {"Pre", "Flip", "Hidden", + "Music", "SFX", "Blip"}; AOButton *ui_effect_flash = nullptr; AOButton *ui_effect_gloom = nullptr; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 329c62be5..67d11e1b0 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -739,6 +739,29 @@ void Courtroom::on_chat_return_pressed() ao_app->send_server_packet(new AOPacket("MS", packet_contents)); } +void Courtroom::handle_acknowledged_ms() +{ + ui_ic_chat_message->clear(); + + // reset states + ui_pre->setChecked(ao_config->always_pre_enabled()); + list_sfx(); + ui_sfx_list->setCurrentItem( + ui_sfx_list->item(0)); // prevents undefined errors + + m_shout_state = 0; + draw_shout_buttons(); + + m_effect_state = 0; + draw_effect_buttons(); + + m_wtce_current = 0; + draw_judge_wtce_buttons(); + + is_presenting_evidence = false; + ui_evidence_present->set_image("present_disabled.png"); +} + void Courtroom::handle_chatmessage(QStringList p_contents) { if (p_contents.size() < 15) @@ -773,26 +796,20 @@ void Courtroom::handle_chatmessage(QStringList p_contents) // reset our ui state if client just spoke if (m_cid == f_char_id && is_system_speaking == false) { - ui_ic_chat_message->clear(); - - // reset states - ui_pre->setChecked(ao_config->always_pre_enabled()); - list_sfx(); - ui_sfx_list->setCurrentItem( - ui_sfx_list->item(0)); // prevents undefined errors - - m_shout_state = 0; - draw_shout_buttons(); - - m_effect_state = 0; - draw_effect_buttons(); - - m_wtce_current = 0; - draw_judge_wtce_buttons(); + // If the server does not have the feature of acknowledging our MS + // messages, assume in this if that the message is proof the server + // acknowledged our message. It is not quite the same, as it is + // possible the server crafted a message with the same char_id + // as the client, but the client did not send that message, but it is + // the best we can do. + if (!ao_app->ackMS_enabled) + handle_acknowledged_ms(); - is_presenting_evidence = false; - ui_evidence_present->set_image("present_disabled.png"); + // If the server does have the feature of acknowledging our MS messages, + // it will have sent an ackMS packet prior to the MS one, so chat would + // have been cleared and thus we need not do anything else. + // Also update first person mode status m_msg_is_first_person = ao_app->get_first_person_enabled(); } diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 03e58adf0..f42ad54c6 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -139,6 +139,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QString f_hdid; f_hdid = get_hdid(); + ackMS_enabled = false; + AOPacket *hi_packet = new AOPacket("HI#" + f_hdid + "#%"); send_server_packet(hi_packet); } @@ -161,10 +163,10 @@ void AOApplication::server_packet_received(AOPacket *p_packet) w_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1)); } - // FIXME Should the FL packet acknowledgement removed? else if (header == "FL") { - // The packet is acknowledged but is of no use to the client + if (f_packet.contains("ackMS", Qt::CaseInsensitive)) + ackMS_enabled = true; } else if (header == "PN") { @@ -553,6 +555,11 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (courtroom_constructed && courtroom_loaded) w_courtroom->handle_chatmessage(p_packet->get_contents()); } + else if (header == "ackMS") + { + if (courtroom_constructed && courtroom_loaded) + w_courtroom->handle_acknowledged_ms(); + } else if (header == "MC") { if (courtroom_constructed && courtroom_loaded) From 3ba910b71fc40342bc20ac6fb98b2712b04ab144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Thu, 21 Jan 2021 11:24:28 +0100 Subject: [PATCH 186/842] Added macro identification * Added macro identification blocks that will be required to get removed in the future once 1.0.0 is released --- dronline-client.pro | 2 ++ include/aoapplication.h | 2 ++ src/courtroom.cpp | 9 +++++++-- src/packet_distribution.cpp | 4 ++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index afab0d4b6..f950aecb0 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -11,6 +11,8 @@ RC_ICONS = icon.ico INCLUDEPATH += $$PWD/include $$PWD/3rd $$PWD/3rd/include DEPENDPATH += $$PWD/include +DEFINES += DRO_ACKMS + HEADERS += \ include/aoabstractplayer.h \ include/aoapplication.h \ diff --git a/include/aoapplication.h b/include/aoapplication.h index 5f60d0237..8d63eded8 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -45,9 +45,11 @@ class AOApplication : public QApplication void send_ms_packet(AOPacket *p_packet); void send_server_packet(AOPacket *p_packet, bool encoded = true); +#ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release ///////////////server metadata//////////////// bool ackMS_enabled = false; +#endif ///////////////loading info/////////////////// diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 67d11e1b0..a5994faf1 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -21,7 +21,8 @@ #include #include -Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() +Courtroom::Courtroom(AOApplication *p_ao_app) + : QMainWindow() { ao_app = p_ao_app; ao_config = new AOConfig(this); @@ -796,6 +797,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) // reset our ui state if client just spoke if (m_cid == f_char_id && is_system_speaking == false) { +#ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release // If the server does not have the feature of acknowledging our MS // messages, assume in this if that the message is proof the server // acknowledged our message. It is not quite the same, as it is @@ -803,13 +805,16 @@ void Courtroom::handle_chatmessage(QStringList p_contents) // as the client, but the client did not send that message, but it is // the best we can do. if (!ao_app->ackMS_enabled) + { handle_acknowledged_ms(); + } // If the server does have the feature of acknowledging our MS messages, // it will have sent an ackMS packet prior to the MS one, so chat would // have been cleared and thus we need not do anything else. +#endif - // Also update first person mode status + // update first person mode status m_msg_is_first_person = ao_app->get_first_person_enabled(); } diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index f42ad54c6..5d9552b22 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -139,7 +139,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QString f_hdid; f_hdid = get_hdid(); +#ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release ackMS_enabled = false; +#endif AOPacket *hi_packet = new AOPacket("HI#" + f_hdid + "#%"); send_server_packet(hi_packet); @@ -165,8 +167,10 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "FL") { +#ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release if (f_packet.contains("ackMS", Qt::CaseInsensitive)) ackMS_enabled = true; +#endif } else if (header == "PN") { From 0559721722a56bf38bb0af97b942d15002c49832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sat, 23 Jan 2021 12:12:08 +0100 Subject: [PATCH 187/842] Update courtroom_widgets.cpp Fixed variable being useless. --- src/courtroom_widgets.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 196eea5b6..54287120e 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1482,12 +1482,10 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, // 2. "font_default" // 3. System font QFontDatabase font_database; - QString font_name = - ao_app->get_font_name("font_" + p_identifier, design_file); + QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); if (!font_database.families().contains(font_name)) { - QString default_font_name = - ao_app->get_font_name("font_default", "courtroom_fonts.ini"); + font_name = ao_app->get_font_name("font_default", "courtroom_fonts.ini"); } widget->setFont(QFont(font_name, f_weight)); From 55e74a996c33dc78e18cea4fb44b9ff61ce85d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sun, 24 Jan 2021 10:28:26 +0100 Subject: [PATCH 188/842] Introduced Autosave on ConfigPanel * If enabled, autosave will automatically save config settings upon exiting (shutting down) the client. - This allows the user to be aware of whatever the config settings will be saved or not automatically as well as providing visual feedback. --- include/aoconfig.h | 4 + include/aoconfigpanel.h | 8 +- res/ui/config_panel.ui | 17 ++++ src/aoconfig.cpp | 40 +++++++-- src/aoconfigpanel.cpp | 188 +++++++++++++++------------------------- 5 files changed, 129 insertions(+), 128 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 91a699a97..5aa90d016 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -18,6 +18,7 @@ class AOConfig : public QObject int get_number(QString p_name, int p_default = 0); // getters + bool autosave(); QString username(); QString callwords(); bool server_alerts_enabled(); @@ -46,6 +47,8 @@ public slots: // setters public slots: + void set_autosave(bool p_enabled); + void set_autosave(int p_state); void set_username(QString p_string); void set_callwords(QString p_string); void set_server_alerts(bool p_enabled); @@ -79,6 +82,7 @@ public slots: // signals signals: + void autosave_changed(bool); void username_changed(QString); void callwords_changed(QString); void server_alerts_changed(bool); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 68f3f7b33..8eb3df530 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -57,6 +57,11 @@ private slots: // driver AOConfig *m_config = nullptr; + // behaviour + QPushButton *w_save = nullptr; + QPushButton *w_close = nullptr; + QCheckBox *w_autosave = nullptr; + // general QLineEdit *w_username = nullptr; QLineEdit *w_callwords = nullptr; @@ -91,9 +96,6 @@ private slots: QLabel *w_blips_value = nullptr; QSpinBox *w_blip_rate = nullptr; QCheckBox *w_blank_blips = nullptr; - - // save - QPushButton *w_save = nullptr; }; #endif // AOCONFIGPANEL_H diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 9cf45e72f..a3005f514 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -692,6 +692,16 @@ + + + + + + + Autosave + + + @@ -712,6 +722,13 @@ + + + + &Close + + + diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index ec39303ee..7069cc147 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -22,6 +22,7 @@ class AOConfigPrivate : public QObject QVector parents; // data + bool autosave; QString username; QString callwords; QString theme; @@ -45,19 +46,27 @@ class AOConfigPrivate : public QObject bool blank_blips; public: - AOConfigPrivate() - : QObject(qApp), - cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat) + AOConfigPrivate() : QObject(qApp), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat) { read_file(); } ~AOConfigPrivate() { - save_file(); + if (autosave) + { + save_file(); + } } // setters public slots: + void set_autosave(bool p_enabled) + { + if (autosave == p_enabled) + return; + autosave = p_enabled; + invoke_parents("autosave_changed", Q_ARG(bool, p_enabled)); + } void set_username(QString p_string) { if (username == p_string) @@ -207,6 +216,7 @@ public slots: } void read_file() { + autosave = cfg.value("autosave", true).toBool(); username = cfg.value("username").toString(); callwords = cfg.value("callwords").toString(); server_alerts = cfg.value("server_alerts", true).toBool(); @@ -235,6 +245,7 @@ public slots: } void save_file() { + cfg.setValue("autosave", autosave); cfg.setValue("username", username); cfg.setValue("callwords", callwords); cfg.setValue("server_alerts", server_alerts); @@ -260,13 +271,11 @@ public slots: } private: - void invoke_parents(QString p_method_name, - QGenericArgument p_arg1 = QGenericArgument(nullptr)) + void invoke_parents(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)) { for (QObject *i_parent : parents) { - QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), - p_arg1); + QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), p_arg1); } } }; @@ -309,6 +318,11 @@ int AOConfig::get_number(QString p_name, int p_default) return d->cfg.value(p_name, p_default).toInt(); } +bool AOConfig::autosave() +{ + return d->autosave; +} + QString AOConfig::username() { return d->username; @@ -413,6 +427,16 @@ bool AOConfig::blank_blips_enabled() return d->blank_blips; } +void AOConfig::set_autosave(bool p_enabled) +{ + d->set_autosave(p_enabled); +} + +void AOConfig::set_autosave(int p_state) +{ + set_autosave(p_state == Qt::Checked); +} + void AOConfig::set_username(QString p_string) { d->set_username(p_string); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index d759ae323..264d7fe8d 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -7,8 +7,7 @@ // src #include "aoapplication.h" // ruined -AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) - : QWidget(p_parent), m_config(new AOConfig(this)) +AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) : QWidget(p_parent), m_config(new AOConfig(this)) { ao_app = p_ao_app; @@ -21,8 +20,10 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // tab setFocusProxy(AO_GUI_WIDGET(QTabWidget, "tab_widget")); - // save + // behaviour w_save = AO_GUI_WIDGET(QPushButton, "save"); + w_close = AO_GUI_WIDGET(QPushButton, "close"); + w_autosave = AO_GUI_WIDGET(QCheckBox, "autosave"); // general w_username = AO_GUI_WIDGET(QLineEdit, "username"); @@ -42,10 +43,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // IC Chatlog w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); - w_log_orientation_top_down = - AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); - w_log_orientation_bottom_up = - AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); + w_log_orientation_top_down = AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); + w_log_orientation_bottom_up = AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); w_log_music = AO_GUI_WIDGET(QCheckBox, "log_music"); w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); @@ -67,105 +66,62 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) refresh_timeofday_list(); // input - connect(m_config, SIGNAL(username_changed(QString)), w_username, - SLOT(setText(QString))); - connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, - SLOT(setText(QString))); - connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(theme_changed(QString)), w_theme, - SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, - SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, - SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(chat_tick_interval_changed(int)), - w_chat_tick_interval, SLOT(setValue(int))); - connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, - SLOT(setValue(int))); - connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, - SLOT(on_log_is_topdown_changed(bool))); - connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(effects_volume_changed(int)), w_effects, - SLOT(setValue(int))); - connect(m_config, SIGNAL(system_volume_changed(int)), w_system, - SLOT(setValue(int))); - connect(m_config, SIGNAL(music_volume_changed(int)), w_music, - SLOT(setValue(int))); - connect(m_config, SIGNAL(blips_volume_changed(int)), w_blips, - SLOT(setValue(int))); - connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, - SLOT(setValue(int))); - connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, - SLOT(setChecked(bool))); + connect(m_config, SIGNAL(autosave_changed(bool)), w_autosave, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); + connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); + connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); + connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); + connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); + connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(effects_volume_changed(int)), w_effects, SLOT(setValue(int))); + connect(m_config, SIGNAL(system_volume_changed(int)), w_system, SLOT(setValue(int))); + connect(m_config, SIGNAL(music_volume_changed(int)), w_music, SLOT(setValue(int))); + connect(m_config, SIGNAL(blips_volume_changed(int)), w_blips, SLOT(setValue(int))); + connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, SLOT(setValue(int))); + connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, SLOT(setChecked(bool))); // output + connect(w_close, SIGNAL(clicked()), this, SLOT(close())); connect(w_save, SIGNAL(clicked()), m_config, SLOT(save_file())); - connect(w_username, SIGNAL(textEdited(QString)), m_config, - SLOT(set_username(QString))); - connect(w_callwords, SIGNAL(textEdited(QString)), m_config, - SLOT(set_callwords(QString))); - connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, - SLOT(set_server_alerts(int))); - connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, - SLOT(set_theme(QString))); - connect(w_reload_theme, SIGNAL(clicked()), this, - SLOT(on_reload_theme_clicked())); - connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, - SLOT(on_gamemode_index_changed(QString))); - connect(w_manual_gamemode, SIGNAL(stateChanged(int)), m_config, - SLOT(set_manual_gamemode(int))); - connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, - SLOT(on_timeofday_index_changed(QString))); - connect(w_manual_timeofday, SIGNAL(stateChanged(int)), m_config, - SLOT(set_manual_timeofday(int))); - connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, - SLOT(set_always_pre(int))); - connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, - SLOT(set_chat_tick_interval(int))); - connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, - SLOT(set_log_max_lines(int))); - connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, - SLOT(set_log_is_topdown(bool))); - connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, - SLOT(set_log_uses_newline(int))); - connect(w_log_music, SIGNAL(stateChanged(int)), m_config, - SLOT(set_log_music(int))); - connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, - SLOT(set_log_is_recording(int))); - connect(w_effects, SIGNAL(valueChanged(int)), m_config, - SLOT(set_effects_volume(int))); - connect(w_effects, SIGNAL(valueChanged(int)), this, - SLOT(on_effects_value_changed(int))); - connect(w_system, SIGNAL(valueChanged(int)), m_config, - SLOT(set_system_volume(int))); - connect(w_system, SIGNAL(valueChanged(int)), this, - SLOT(on_system_value_changed(int))); - connect(w_music, SIGNAL(valueChanged(int)), m_config, - SLOT(set_music_volume(int))); - connect(w_music, SIGNAL(valueChanged(int)), this, - SLOT(on_music_value_changed(int))); - connect(w_blips, SIGNAL(valueChanged(int)), m_config, - SLOT(set_blips_volume(int))); - connect(w_blips, SIGNAL(valueChanged(int)), this, - SLOT(on_blips_value_changed(int))); - connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, - SLOT(set_blip_rate(int))); - connect(w_blank_blips, SIGNAL(stateChanged(int)), m_config, - SLOT(set_blank_blips(int))); + connect(w_autosave, SIGNAL(stateChanged(int)), m_config, SLOT(set_autosave(int))); + connect(w_username, SIGNAL(textEdited(QString)), m_config, SLOT(set_username(QString))); + connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); + connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, SLOT(set_server_alerts(int))); + connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); + connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); + connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); + connect(w_manual_gamemode, SIGNAL(stateChanged(int)), m_config, SLOT(set_manual_gamemode(int))); + connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); + connect(w_manual_timeofday, SIGNAL(stateChanged(int)), m_config, SLOT(set_manual_timeofday(int))); + connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); + connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); + connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); + connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_topdown(bool))); + connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_uses_newline(int))); + connect(w_log_music, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_music(int))); + connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_is_recording(int))); + connect(w_effects, SIGNAL(valueChanged(int)), m_config, SLOT(set_effects_volume(int))); + connect(w_effects, SIGNAL(valueChanged(int)), this, SLOT(on_effects_value_changed(int))); + connect(w_system, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_volume(int))); + connect(w_system, SIGNAL(valueChanged(int)), this, SLOT(on_system_value_changed(int))); + connect(w_music, SIGNAL(valueChanged(int)), m_config, SLOT(set_music_volume(int))); + connect(w_music, SIGNAL(valueChanged(int)), this, SLOT(on_music_value_changed(int))); + connect(w_blips, SIGNAL(valueChanged(int)), m_config, SLOT(set_blips_volume(int))); + connect(w_blips, SIGNAL(valueChanged(int)), this, SLOT(on_blips_value_changed(int))); + connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_rate(int))); + connect(w_blank_blips, SIGNAL(stateChanged(int)), m_config, SLOT(set_blank_blips(int))); // set values + w_autosave->setChecked(m_config->autosave()); w_username->setText(m_config->username()); w_callwords->setText(m_config->callwords()); w_server_alerts->setChecked(m_config->server_alerts_enabled()); @@ -177,10 +133,16 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_always_pre->setChecked(m_config->always_pre_enabled()); w_chat_tick_interval->setValue(m_config->chat_tick_interval()); w_log_max_lines->setValue(m_config->log_max_lines()); + if (m_config->log_is_topdown_enabled()) + { w_log_orientation_top_down->setChecked(true); + } else + { w_log_orientation_bottom_up->setChecked(true); + } + w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); w_log_music->setChecked(m_config->log_music_enabled()); w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); @@ -196,10 +158,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_timeofday->setEnabled(m_config->manual_timeofday_enabled()); // The manual gamemode checkbox enables browsing the gamemode combox // similarly with time of day - connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_gamemode, - SLOT(setEnabled(bool))); - connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_timeofday, - SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_gamemode, SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_timeofday, SLOT(setEnabled(bool))); } void AOConfigPanel::showEvent(QShowEvent *event) @@ -225,8 +185,7 @@ void AOConfigPanel::refresh_theme_list() // themes const QString path = QDir::currentPath() + "/base/themes"; - for (QString i_folder : - QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; @@ -251,10 +210,8 @@ void AOConfigPanel::refresh_gamemode_list() // add empty entry indicating no gamemode chosen w_gamemode->addItem(""); // gamemodes - QString path = - QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; - for (QString i_folder : - QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + QString path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; + for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; @@ -283,15 +240,12 @@ void AOConfigPanel::refresh_timeofday_list() // gamemode chosen or not QString path; if (m_config->gamemode().isEmpty()) - path = - QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; + path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; else - path = QDir::currentPath() + "/base/themes/" + m_config->theme() + - "/gamemodes/" + m_config->gamemode() + "/times/"; + path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/" + m_config->gamemode() + "/times/"; // times of day - for (QString i_folder : - QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; From 63ffcca404a3018d2100bebf5c8d321a79051a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Wed, 27 Jan 2021 13:16:49 +0100 Subject: [PATCH 189/842] Implementation of #88 --- include/aoapplication.h | 6 +-- include/aoemotebutton.h | 28 ++++++-------- src/aoemotebutton.cpp | 81 +++++++++++++++++++++++++---------------- src/emotes.cpp | 27 ++++---------- 4 files changed, 72 insertions(+), 70 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 8d63eded8..dfc07bdfb 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -130,8 +130,7 @@ class AOApplication : public QApplication * @return The first case-sensitive root+extension path for which a file * exists, or an empty string, if not one does. */ - QString find_asset_path(QStringList possible_roots, - QStringList possible_exts = {""}); + QString find_asset_path(QStringList possible_roots, QStringList possible_exts = {""}); /** * @brief Returns the first case-sensitive file in the theme folder that is @@ -351,8 +350,7 @@ class AOApplication : public QApplication QString get_sfx(QString p_identifier); // Returns the value of p_search_line within target_tag and terminator_tag - QString read_char_ini(QString p_char, QString p_search_line, - QString target_tag, QString terminator_tag); + QString read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag); // Returns the text between target_tag and terminator_tag in p_file QString get_stylesheet(QString target_tag, QString p_file); diff --git a/include/aoemotebutton.h b/include/aoemotebutton.h index 4aa85a579..69f136099 100644 --- a/include/aoemotebutton.h +++ b/include/aoemotebutton.h @@ -1,10 +1,13 @@ #ifndef AOEMOTEBUTTON_H #define AOEMOTEBUTTON_H -#include - +// src #include "aoapplication.h" +// qt +#include +#include + class AOEmoteButton : public QPushButton { Q_OBJECT @@ -12,24 +15,17 @@ class AOEmoteButton : public QPushButton public: AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y); - void set_image(QString p_char, int p_emote, QString suffix); + int get_emote_number(); + void set_emote_number(int p_emote_number); + void set_image(QString p_chr, int p_emote_number, bool p_enabled); - void set_id(int p_id) - { - m_id = p_id; - } - int get_id() - { - return m_id; - } +signals: + void emote_clicked(int p_emote_number); private: AOApplication *ao_app = nullptr; - - int m_id = 0; - -signals: - void emote_clicked(int p_id); + QLabel *w_selected = nullptr; + int m_emote_number = 0; private slots: void on_clicked(); diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index 4b5879b11..00e4f4be0 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -3,54 +3,73 @@ #include "file_functions.h" #include -AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, - int p_x, int p_y) - : QPushButton(p_parent) +AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) { ao_app = p_ao_app; this->move(p_x, p_y); this->resize(40, 40); + w_selected = new QLabel(this); + w_selected->resize(size()); + w_selected->setAttribute(Qt::WA_TransparentForMouseEvents); + w_selected->hide(); + connect(this, SIGNAL(clicked()), this, SLOT(on_clicked())); } -void AOEmoteButton::set_image(QString p_char, int p_emote, QString suffix) +void AOEmoteButton::set_emote_number(int p_emote_number) { - QString emotion_number = QString::number(p_emote + 1); - QString image_path = ao_app->get_character_path( - p_char, "emotions/ao2/button" + emotion_number + suffix); - QString alt_path = ao_app->get_character_path( - p_char, "emotions/button" + emotion_number + suffix); - QString hover_path = ao_app->get_character_path( - p_char, "emotions/hovers/button" + emotion_number + "_hover" + suffix); - - if (file_exists(image_path)) - { - this->setText(""); - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - } - else if (file_exists(alt_path)) + m_emote_number = p_emote_number; +} + +int AOEmoteButton::get_emote_number() +{ + return m_emote_number; +} + +void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) +{ + // In the land of AO2, cohesion and consistency is unknown to the world for the sake of lazy-users convenience! + const int true_emote_number = p_emote_number + 1; + + QString texture_path = ao_app->get_character_path(p_chr, QString("emotions/button%1_off.png").arg(true_emote_number)); + + // reset states + w_selected->hide(); + + // nested ifs are okay + if (p_enabled) { - this->setText(""); - if (file_exists(hover_path)) + const QString enabled_texture_path = ao_app->get_character_path(p_chr, QString("emotions/button%1_on.png").arg(true_emote_number)); + + if (file_exists(enabled_texture_path)) { - this->setStyleSheet("QPushButton {border-image:url(\"" + alt_path + - "\");}" - "QPushButton:hover {border-image:url(\"" + - hover_path + "\");}"); + texture_path = enabled_texture_path; } else - this->setStyleSheet("border-image:url(\"" + alt_path + "\")"); - } - else - { - this->setText(ao_app->get_emote_comment(p_char, p_emote)); - this->setStyleSheet("border-image:url(\"\")"); + { + const QString selected_texture_path = ao_app->get_character_path(p_chr, "emotions/selected.png"); + + if (file_exists(selected_texture_path)) + { + w_selected->setStyleSheet(QString("border-image: url(%1)").arg(selected_texture_path)); + } + else + { + w_selected->setStyleSheet("background-color: rgba(0, 0, 0, 127)"); + } + + w_selected->show(); + } } + + const bool texture_exist = file_exists(texture_path); + setText(texture_exist ? QString() : ao_app->get_emote_comment(p_chr, p_emote_number)); + setStyleSheet(texture_exist ? QString("border-image: url(%1)").arg(texture_path) : QString()); } void AOEmoteButton::on_clicked() { - emote_clicked(m_id); + emote_clicked(m_emote_number); } diff --git a/src/emotes.cpp b/src/emotes.cpp index 8f62860ad..dcdf31254 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -32,8 +32,7 @@ void Courtroom::reconstruct_emotes() // resize and move set_size_and_pos(ui_emotes, "emotes"); - QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", - "courtroom_design.ini"); + QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", "courtroom_design.ini"); const int button_width = 40; int x_spacing = f_spacing.x(); @@ -43,10 +42,8 @@ void Courtroom::reconstruct_emotes() int y_spacing = f_spacing.y(); int y_mod_count = 0; - emote_columns = - ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; - emote_rows = - ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; + emote_columns = ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; + emote_rows = ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; max_emotes_on_page = emote_columns * emote_rows; @@ -59,10 +56,9 @@ void Courtroom::reconstruct_emotes() ui_emote_list.append(f_emote); - f_emote->set_id(n); + f_emote->set_emote_number(n); - connect(f_emote, SIGNAL(emote_clicked(int)), this, - SLOT(on_emote_clicked(int))); + connect(f_emote, SIGNAL(emote_clicked(int)), this, SLOT(on_emote_clicked(int))); ++x_mod_count; @@ -130,12 +126,7 @@ void Courtroom::set_emote_page() { int n_real_emote = n_emote + current_emote_page * max_emotes_on_page; AOEmoteButton *f_emote = ui_emote_list.at(n_emote); - - if (n_real_emote == current_emote) - f_emote->set_image(current_char, n_real_emote, "_on.png"); - else - f_emote->set_image(current_char, n_real_emote, "_off.png"); - + f_emote->set_image(current_char, n_real_emote, n_real_emote == current_emote); f_emote->show(); } } @@ -161,16 +152,14 @@ void Courtroom::select_emote(int p_id) int max = (max_emotes_on_page - 1) + current_emote_page * max_emotes_on_page; if (current_emote >= min && current_emote <= max) - ui_emote_list.at(current_emote % max_emotes_on_page) - ->set_image(current_char, current_emote, "_off.png"); + ui_emote_list.at(current_emote % max_emotes_on_page)->set_image(current_char, current_emote, false); int old_emote = current_emote; current_emote = p_id; if (current_emote >= min && current_emote <= max) - ui_emote_list.at(current_emote % max_emotes_on_page) - ->set_image(current_char, current_emote, "_on.png"); + ui_emote_list.at(current_emote % max_emotes_on_page)->set_image(current_char, current_emote, true); int emote_mod = ao_app->get_emote_mod(current_char, current_emote); From 8a25c9917da19ef662ebc7da9b259d620a1272b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Wed, 27 Jan 2021 13:45:32 +0100 Subject: [PATCH 190/842] Changed half-transparent background color to linear gradient --- src/aoemotebutton.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index 00e4f4be0..6d1bb9064 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -57,7 +57,8 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) } else { - w_selected->setStyleSheet("background-color: rgba(0, 0, 0, 127)"); + w_selected->setStyleSheet( + "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 0, 0), stop:1 rgba(0, 0, 0, 127)); }"); } w_selected->show(); @@ -66,7 +67,7 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) const bool texture_exist = file_exists(texture_path); setText(texture_exist ? QString() : ao_app->get_emote_comment(p_chr, p_emote_number)); - setStyleSheet(texture_exist ? QString("border-image: url(%1)").arg(texture_path) : QString()); + setStyleSheet(texture_exist ? QString("%1 { border-image: url(%2); }").arg(metaObject()->className()).arg(texture_path) : QString()); } void AOEmoteButton::on_clicked() From 8f75d2bd7951c1f0fac8df9ad1699574f98ffb55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sun, 31 Jan 2021 17:46:23 +0100 Subject: [PATCH 191/842] Reverted ColumnLimit to 80 from 150 --- include/aoapplication.h | 6 ++++-- src/aobasshandle.cpp | 19 ++++++------------- src/aoemotebutton.cpp | 30 +++++++++++++++++++++--------- src/emotes.cpp | 21 ++++++++++++++------- 4 files changed, 45 insertions(+), 31 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index dfc07bdfb..8d63eded8 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -130,7 +130,8 @@ class AOApplication : public QApplication * @return The first case-sensitive root+extension path for which a file * exists, or an empty string, if not one does. */ - QString find_asset_path(QStringList possible_roots, QStringList possible_exts = {""}); + QString find_asset_path(QStringList possible_roots, + QStringList possible_exts = {""}); /** * @brief Returns the first case-sensitive file in the theme folder that is @@ -350,7 +351,8 @@ class AOApplication : public QApplication QString get_sfx(QString p_identifier); // Returns the value of p_search_line within target_tag and terminator_tag - QString read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag); + QString read_char_ini(QString p_char, QString p_search_line, + QString target_tag, QString terminator_tag); // Returns the text between target_tag and terminator_tag in p_file QString get_stylesheet(QString target_tag, QString p_file); diff --git a/src/aobasshandle.cpp b/src/aobasshandle.cpp index 23be86565..0de42721a 100644 --- a/src/aobasshandle.cpp +++ b/src/aobasshandle.cpp @@ -1,12 +1,9 @@ #include "aobasshandle.h" AOBassHandle::AOBassHandle(QObject *p_parent) : QObject(p_parent) -{ -} +{} -AOBassHandle::AOBassHandle(QString p_file, bool p_suicide, - QObject *p_parent) noexcept(false) - : QObject(p_parent) +AOBassHandle::AOBassHandle(QString p_file, bool p_suicide, QObject *p_parent) noexcept(false) : QObject(p_parent) { set_file(p_file, p_suicide); } @@ -49,14 +46,11 @@ void AOBassHandle::set_file(QString p_file, bool p_suicide) noexcept(false) m_file = p_file; // create a handle based on the file - m_handle = BASS_StreamCreateFile(FALSE, m_file.utf16(), 0, 0, - BASS_UNICODE | BASS_ASYNCFILE); + m_handle = BASS_StreamCreateFile(FALSE, m_file.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); if (!m_handle) - throw AOException( - QString("%1 could not be initialized to play").arg(p_file)); + throw AOException(QString("%1 could not be initialized to play").arg(p_file)); - m_sync = BASS_ChannelSetSync(m_handle, BASS_SYNC_END, 0, - &AOBassHandle::static_sync, this); + m_sync = BASS_ChannelSetSync(m_handle, BASS_SYNC_END, 0, &AOBassHandle::static_sync, this); if (!m_sync) { // free stream since we can't sync @@ -85,8 +79,7 @@ void AOBassHandle::stop() BASS_ChannelStop(m_handle); } -void CALLBACK AOBassHandle::static_sync(HSYNC handle, DWORD channel, DWORD data, - void *user) +void CALLBACK AOBassHandle::static_sync(HSYNC handle, DWORD channel, DWORD data, void *user) { if (auto self = static_cast(user)) { diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index 6d1bb9064..11bc75ef6 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -3,7 +3,9 @@ #include "file_functions.h" #include -AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) +AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, + int p_x, int p_y) + : QPushButton(p_parent) { ao_app = p_ao_app; @@ -30,10 +32,12 @@ int AOEmoteButton::get_emote_number() void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) { - // In the land of AO2, cohesion and consistency is unknown to the world for the sake of lazy-users convenience! + // In the land of AO2, cohesion and consistency is unknown to the world for + // the sake of lazy-users convenience! const int true_emote_number = p_emote_number + 1; - QString texture_path = ao_app->get_character_path(p_chr, QString("emotions/button%1_off.png").arg(true_emote_number)); + QString texture_path = ao_app->get_character_path( + p_chr, QString("emotions/button%1_off.png").arg(true_emote_number)); // reset states w_selected->hide(); @@ -41,7 +45,8 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) // nested ifs are okay if (p_enabled) { - const QString enabled_texture_path = ao_app->get_character_path(p_chr, QString("emotions/button%1_on.png").arg(true_emote_number)); + const QString enabled_texture_path = ao_app->get_character_path( + p_chr, QString("emotions/button%1_on.png").arg(true_emote_number)); if (file_exists(enabled_texture_path)) { @@ -49,16 +54,19 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) } else { - const QString selected_texture_path = ao_app->get_character_path(p_chr, "emotions/selected.png"); + const QString selected_texture_path = + ao_app->get_character_path(p_chr, "emotions/selected.png"); if (file_exists(selected_texture_path)) { - w_selected->setStyleSheet(QString("border-image: url(%1)").arg(selected_texture_path)); + w_selected->setStyleSheet( + QString("border-image: url(%1)").arg(selected_texture_path)); } else { w_selected->setStyleSheet( - "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 0, 0), stop:1 rgba(0, 0, 0, 127)); }"); + "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, " + "y2:1, stop:0 rgba(0, 0, 0, 0), stop:1 rgba(0, 0, 0, 127)); }"); } w_selected->show(); @@ -66,8 +74,12 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) } const bool texture_exist = file_exists(texture_path); - setText(texture_exist ? QString() : ao_app->get_emote_comment(p_chr, p_emote_number)); - setStyleSheet(texture_exist ? QString("%1 { border-image: url(%2); }").arg(metaObject()->className()).arg(texture_path) : QString()); + setText(texture_exist ? QString() + : ao_app->get_emote_comment(p_chr, p_emote_number)); + setStyleSheet(texture_exist ? QString("%1 { border-image: url(%2); }") + .arg(metaObject()->className()) + .arg(texture_path) + : QString()); } void AOEmoteButton::on_clicked() diff --git a/src/emotes.cpp b/src/emotes.cpp index dcdf31254..406692a7f 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -32,7 +32,8 @@ void Courtroom::reconstruct_emotes() // resize and move set_size_and_pos(ui_emotes, "emotes"); - QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", "courtroom_design.ini"); + QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", + "courtroom_design.ini"); const int button_width = 40; int x_spacing = f_spacing.x(); @@ -42,8 +43,10 @@ void Courtroom::reconstruct_emotes() int y_spacing = f_spacing.y(); int y_mod_count = 0; - emote_columns = ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; - emote_rows = ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; + emote_columns = + ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; + emote_rows = + ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; max_emotes_on_page = emote_columns * emote_rows; @@ -58,7 +61,8 @@ void Courtroom::reconstruct_emotes() f_emote->set_emote_number(n); - connect(f_emote, SIGNAL(emote_clicked(int)), this, SLOT(on_emote_clicked(int))); + connect(f_emote, SIGNAL(emote_clicked(int)), this, + SLOT(on_emote_clicked(int))); ++x_mod_count; @@ -126,7 +130,8 @@ void Courtroom::set_emote_page() { int n_real_emote = n_emote + current_emote_page * max_emotes_on_page; AOEmoteButton *f_emote = ui_emote_list.at(n_emote); - f_emote->set_image(current_char, n_real_emote, n_real_emote == current_emote); + f_emote->set_image(current_char, n_real_emote, + n_real_emote == current_emote); f_emote->show(); } } @@ -152,14 +157,16 @@ void Courtroom::select_emote(int p_id) int max = (max_emotes_on_page - 1) + current_emote_page * max_emotes_on_page; if (current_emote >= min && current_emote <= max) - ui_emote_list.at(current_emote % max_emotes_on_page)->set_image(current_char, current_emote, false); + ui_emote_list.at(current_emote % max_emotes_on_page) + ->set_image(current_char, current_emote, false); int old_emote = current_emote; current_emote = p_id; if (current_emote >= min && current_emote <= max) - ui_emote_list.at(current_emote % max_emotes_on_page)->set_image(current_char, current_emote, true); + ui_emote_list.at(current_emote % max_emotes_on_page) + ->set_image(current_char, current_emote, true); int emote_mod = ao_app->get_emote_mod(current_char, current_emote); From df24305c628bc38f7d258aeaaecf4ad9aecdbb77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sun, 31 Jan 2021 17:49:14 +0100 Subject: [PATCH 192/842] Reverted ColumnLimit to 80 (was 150) --- src/aoconfig.cpp | 10 ++- src/aoconfigpanel.cpp | 180 ++++++++++++++++++++++++++++-------------- 2 files changed, 127 insertions(+), 63 deletions(-) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 7069cc147..9148fd380 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -46,7 +46,9 @@ class AOConfigPrivate : public QObject bool blank_blips; public: - AOConfigPrivate() : QObject(qApp), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat) + AOConfigPrivate() + : QObject(qApp), + cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat) { read_file(); } @@ -271,11 +273,13 @@ public slots: } private: - void invoke_parents(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)) + void invoke_parents(QString p_method_name, + QGenericArgument p_arg1 = QGenericArgument(nullptr)) { for (QObject *i_parent : parents) { - QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), p_arg1); + QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), + p_arg1); } } }; diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 264d7fe8d..e40d7dcb2 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -7,7 +7,8 @@ // src #include "aoapplication.h" // ruined -AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) : QWidget(p_parent), m_config(new AOConfig(this)) +AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) + : QWidget(p_parent), m_config(new AOConfig(this)) { ao_app = p_ao_app; @@ -43,8 +44,10 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) : QWidg // IC Chatlog w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); - w_log_orientation_top_down = AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); - w_log_orientation_bottom_up = AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); + w_log_orientation_top_down = + AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); + w_log_orientation_bottom_up = + AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); w_log_music = AO_GUI_WIDGET(QCheckBox, "log_music"); w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); @@ -66,59 +69,108 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) : QWidg refresh_timeofday_list(); // input - connect(m_config, SIGNAL(autosave_changed(bool)), w_autosave, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); - connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); - connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); - connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); - connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); - connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(effects_volume_changed(int)), w_effects, SLOT(setValue(int))); - connect(m_config, SIGNAL(system_volume_changed(int)), w_system, SLOT(setValue(int))); - connect(m_config, SIGNAL(music_volume_changed(int)), w_music, SLOT(setValue(int))); - connect(m_config, SIGNAL(blips_volume_changed(int)), w_blips, SLOT(setValue(int))); - connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, SLOT(setValue(int))); - connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(autosave_changed(bool)), w_autosave, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(username_changed(QString)), w_username, + SLOT(setText(QString))); + connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, + SLOT(setText(QString))); + connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(theme_changed(QString)), w_theme, + SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, + SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, + SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(chat_tick_interval_changed(int)), + w_chat_tick_interval, SLOT(setValue(int))); + connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, + SLOT(setValue(int))); + connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, + SLOT(on_log_is_topdown_changed(bool))); + connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(effects_volume_changed(int)), w_effects, + SLOT(setValue(int))); + connect(m_config, SIGNAL(system_volume_changed(int)), w_system, + SLOT(setValue(int))); + connect(m_config, SIGNAL(music_volume_changed(int)), w_music, + SLOT(setValue(int))); + connect(m_config, SIGNAL(blips_volume_changed(int)), w_blips, + SLOT(setValue(int))); + connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, + SLOT(setValue(int))); + connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, + SLOT(setChecked(bool))); // output connect(w_close, SIGNAL(clicked()), this, SLOT(close())); connect(w_save, SIGNAL(clicked()), m_config, SLOT(save_file())); - connect(w_autosave, SIGNAL(stateChanged(int)), m_config, SLOT(set_autosave(int))); - connect(w_username, SIGNAL(textEdited(QString)), m_config, SLOT(set_username(QString))); - connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); - connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, SLOT(set_server_alerts(int))); - connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); - connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); - connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); - connect(w_manual_gamemode, SIGNAL(stateChanged(int)), m_config, SLOT(set_manual_gamemode(int))); - connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); - connect(w_manual_timeofday, SIGNAL(stateChanged(int)), m_config, SLOT(set_manual_timeofday(int))); - connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); - connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); - connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); - connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_topdown(bool))); - connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_uses_newline(int))); - connect(w_log_music, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_music(int))); - connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_is_recording(int))); - connect(w_effects, SIGNAL(valueChanged(int)), m_config, SLOT(set_effects_volume(int))); - connect(w_effects, SIGNAL(valueChanged(int)), this, SLOT(on_effects_value_changed(int))); - connect(w_system, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_volume(int))); - connect(w_system, SIGNAL(valueChanged(int)), this, SLOT(on_system_value_changed(int))); - connect(w_music, SIGNAL(valueChanged(int)), m_config, SLOT(set_music_volume(int))); - connect(w_music, SIGNAL(valueChanged(int)), this, SLOT(on_music_value_changed(int))); - connect(w_blips, SIGNAL(valueChanged(int)), m_config, SLOT(set_blips_volume(int))); - connect(w_blips, SIGNAL(valueChanged(int)), this, SLOT(on_blips_value_changed(int))); - connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_rate(int))); - connect(w_blank_blips, SIGNAL(stateChanged(int)), m_config, SLOT(set_blank_blips(int))); + connect(w_autosave, SIGNAL(stateChanged(int)), m_config, + SLOT(set_autosave(int))); + connect(w_username, SIGNAL(textEdited(QString)), m_config, + SLOT(set_username(QString))); + connect(w_callwords, SIGNAL(textEdited(QString)), m_config, + SLOT(set_callwords(QString))); + connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, + SLOT(set_server_alerts(int))); + connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, + SLOT(set_theme(QString))); + connect(w_reload_theme, SIGNAL(clicked()), this, + SLOT(on_reload_theme_clicked())); + connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, + SLOT(on_gamemode_index_changed(QString))); + connect(w_manual_gamemode, SIGNAL(stateChanged(int)), m_config, + SLOT(set_manual_gamemode(int))); + connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, + SLOT(on_timeofday_index_changed(QString))); + connect(w_manual_timeofday, SIGNAL(stateChanged(int)), m_config, + SLOT(set_manual_timeofday(int))); + connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, + SLOT(set_always_pre(int))); + connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, + SLOT(set_chat_tick_interval(int))); + connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, + SLOT(set_log_max_lines(int))); + connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, + SLOT(set_log_is_topdown(bool))); + connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, + SLOT(set_log_uses_newline(int))); + connect(w_log_music, SIGNAL(stateChanged(int)), m_config, + SLOT(set_log_music(int))); + connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, + SLOT(set_log_is_recording(int))); + connect(w_effects, SIGNAL(valueChanged(int)), m_config, + SLOT(set_effects_volume(int))); + connect(w_effects, SIGNAL(valueChanged(int)), this, + SLOT(on_effects_value_changed(int))); + connect(w_system, SIGNAL(valueChanged(int)), m_config, + SLOT(set_system_volume(int))); + connect(w_system, SIGNAL(valueChanged(int)), this, + SLOT(on_system_value_changed(int))); + connect(w_music, SIGNAL(valueChanged(int)), m_config, + SLOT(set_music_volume(int))); + connect(w_music, SIGNAL(valueChanged(int)), this, + SLOT(on_music_value_changed(int))); + connect(w_blips, SIGNAL(valueChanged(int)), m_config, + SLOT(set_blips_volume(int))); + connect(w_blips, SIGNAL(valueChanged(int)), this, + SLOT(on_blips_value_changed(int))); + connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, + SLOT(set_blip_rate(int))); + connect(w_blank_blips, SIGNAL(stateChanged(int)), m_config, + SLOT(set_blank_blips(int))); // set values w_autosave->setChecked(m_config->autosave()); @@ -158,8 +210,10 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) : QWidg w_timeofday->setEnabled(m_config->manual_timeofday_enabled()); // The manual gamemode checkbox enables browsing the gamemode combox // similarly with time of day - connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_gamemode, SLOT(setEnabled(bool))); - connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_timeofday, SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_gamemode, + SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_timeofday, + SLOT(setEnabled(bool))); } void AOConfigPanel::showEvent(QShowEvent *event) @@ -185,7 +239,8 @@ void AOConfigPanel::refresh_theme_list() // themes const QString path = QDir::currentPath() + "/base/themes"; - for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + for (QString i_folder : + QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; @@ -210,8 +265,10 @@ void AOConfigPanel::refresh_gamemode_list() // add empty entry indicating no gamemode chosen w_gamemode->addItem(""); // gamemodes - QString path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; - for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + QString path = + QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; + for (QString i_folder : + QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; @@ -240,12 +297,15 @@ void AOConfigPanel::refresh_timeofday_list() // gamemode chosen or not QString path; if (m_config->gamemode().isEmpty()) - path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; + path = + QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; else - path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/" + m_config->gamemode() + "/times/"; + path = QDir::currentPath() + "/base/themes/" + m_config->theme() + + "/gamemodes/" + m_config->gamemode() + "/times/"; // times of day - for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + for (QString i_folder : + QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; From 605f71215ebfdde94060f7798a17d81e8ed97849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Thu, 11 Feb 2021 10:58:31 +0100 Subject: [PATCH 193/842] Replaced audio engine * Added master volume * Added option: disable background audio Allow the program to temporarily disable sounds when out of focus. * Modified the different audio players to use the new DRAudio. * Repurposed AOAbstractPlayer; is now an all-around AOObject that requires AOApplication. --- dronline-client.pro | 16 ++- include/aoabstractplayer.h | 34 ----- include/aoblipplayer.h | 33 ++--- include/aoconfig.h | 21 ++- include/aoconfigpanel.h | 16 ++- include/aomusicplayer.h | 19 ++- include/aoobject.h | 14 ++ include/aosfxplayer.h | 12 +- include/aoshoutplayer.h | 7 +- include/aosystemplayer.h | 13 ++ include/courtroom.h | 40 ++---- include/draudio.h | 25 ++++ include/draudioengine.h | 35 +++++ include/draudioerror.h | 15 ++ include/draudiostream.h | 55 ++++++++ include/draudiostreamfamily.h | 66 +++++++++ res/ui/config_panel.ui | 101 +++++++++++--- src/aoabstractplayer.cpp | 18 --- src/aoblipplayer.cpp | 45 ++---- src/aoconfig.cpp | 135 ++++++++++++++---- src/aoconfigpanel.cpp | 212 ++++++++++++---------------- src/aoevidencedisplay.cpp | 15 +- src/aomusicplayer.cpp | 38 ++--- src/aoobject.cpp | 4 + src/aosfxplayer.cpp | 30 +--- src/aoshoutplayer.cpp | 37 ++--- src/aosystemplayer.cpp | 17 +++ src/audio_functions.cpp | 48 +------ src/courtroom.cpp | 216 ++++++++++------------------- src/courtroom_widgets.cpp | 252 ++++++++++++---------------------- src/draudio.cpp | 95 +++++++++++++ src/draudioengine.cpp | 131 ++++++++++++++++++ src/draudioerror.cpp | 12 ++ src/draudiostream.cpp | 116 ++++++++++++++++ src/draudiostreamfamily.cpp | 164 ++++++++++++++++++++++ src/text_file_functions.cpp | 4 +- 36 files changed, 1316 insertions(+), 795 deletions(-) delete mode 100644 include/aoabstractplayer.h create mode 100644 include/aoobject.h create mode 100644 include/aosystemplayer.h create mode 100644 include/draudio.h create mode 100644 include/draudioengine.h create mode 100644 include/draudioerror.h create mode 100644 include/draudiostream.h create mode 100644 include/draudiostreamfamily.h delete mode 100644 src/aoabstractplayer.cpp create mode 100644 src/aoobject.cpp create mode 100644 src/aosystemplayer.cpp create mode 100644 src/draudio.cpp create mode 100644 src/draudioengine.cpp create mode 100644 src/draudioerror.cpp create mode 100644 src/draudiostream.cpp create mode 100644 src/draudiostreamfamily.cpp diff --git a/dronline-client.pro b/dronline-client.pro index f950aecb0..dc5717a38 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -14,7 +14,6 @@ DEPENDPATH += $$PWD/include DEFINES += DRO_ACKMS HEADERS += \ - include/aoabstractplayer.h \ include/aoapplication.h \ include/aobasshandle.h \ include/aoblipplayer.h \ @@ -37,17 +36,24 @@ HEADERS += \ include/aonotearea.h \ include/aonotepad.h \ include/aonotepicker.h \ + include/aoobject.h \ include/aopacket.h \ include/aopixmap.h \ include/aoscene.h \ include/aosfxplayer.h \ include/aoshoutplayer.h \ + include/aosystemplayer.h \ include/aotextarea.h \ include/aotimer.h \ include/courtroom.h \ include/datatypes.h \ include/debug_functions.h \ include/discord_rich_presence.h \ + include/draudio.h \ + include/draudioengine.h \ + include/draudioerror.h \ + include/draudiostream.h \ + include/draudiostreamfamily.h \ include/file_functions.h \ include/hardware_functions.h \ include/lobby.h \ @@ -55,7 +61,6 @@ HEADERS += \ include/networkmanager.h SOURCES += \ - src/aoabstractplayer.cpp \ src/aoapplication.cpp \ src/aobasshandle.cpp \ src/aoblipplayer.cpp \ @@ -78,11 +83,13 @@ SOURCES += \ src/aonotearea.cpp \ src/aonotepad.cpp \ src/aonotepicker.cpp \ + src/aoobject.cpp \ src/aopacket.cpp \ src/aopixmap.cpp \ src/aoscene.cpp \ src/aosfxplayer.cpp \ src/aoshoutplayer.cpp \ + src/aosystemplayer.cpp \ src/aotextarea.cpp \ src/aotimer.cpp \ src/audio_functions.cpp \ @@ -91,6 +98,11 @@ SOURCES += \ src/courtroom_widgets.cpp \ src/debug_functions.cpp \ src/discord_rich_presence.cpp \ + src/draudio.cpp \ + src/draudioengine.cpp \ + src/draudioerror.cpp \ + src/draudiostream.cpp \ + src/draudiostreamfamily.cpp \ src/emotes.cpp \ src/evidence.cpp \ src/file_functions.cpp \ diff --git a/include/aoabstractplayer.h b/include/aoabstractplayer.h deleted file mode 100644 index 849c09a2c..000000000 --- a/include/aoabstractplayer.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef AOABSTRACTPLAYER_HPP -#define AOABSTRACTPLAYER_HPP - -#include -#include - -#include "aoapplication.h" -#include "aobasshandle.h" - -class AOAbstractPlayer : public QObject -{ - Q_OBJECT - -public: - AOAbstractPlayer(QObject *p_parent, AOApplication *p_ao_app); - - int get_volume(); - -public slots: - void set_volume(int p_volume); - -signals: - void starting(); - void stopping(); - void new_volume(int p_volume); - -protected: - AOApplication *ao_app = nullptr; - -private: - int m_volume = 0; -}; - -#endif // AOABSTRACTPLAYER_HPP diff --git a/include/aoblipplayer.h b/include/aoblipplayer.h index 9fbfcf803..8dbc01254 100644 --- a/include/aoblipplayer.h +++ b/include/aoblipplayer.h @@ -1,34 +1,35 @@ -#ifndef AOBLIPPLAYER_H -#define AOBLIPPLAYER_H -// src +#pragma once + +// pdir #include "aoapplication.h" +#include "aoobject.h" +#include "draudioengine.h" + // 3rd #include + // qt #include #include + // std -#include +#include const int BLIP_COUNT = 5; -class AOBlipPlayer +class AOBlipPlayer : public AOObject { + Q_OBJECT + public: - AOBlipPlayer(QWidget *parent, AOApplication *p_ao_app); + AOBlipPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); +public slots: void set_blips(QString p_sfx); void blip_tick(); - void set_volume(int p_volume); - - int m_cycle = 0; private: - QWidget *m_parent = nullptr; - AOApplication *ao_app = nullptr; - - int m_volume; - HSTREAM m_stream_list[BLIP_COUNT]; + DRAudioEngine::family_ptr m_family; + std::optional m_blip; + std::optional m_file; }; - -#endif // AOBLIPPLAYER_H diff --git a/include/aoconfig.h b/include/aoconfig.h index 5aa90d016..18acc1bbd 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -34,10 +34,14 @@ class AOConfig : public QObject bool log_uses_newline_enabled(); bool log_music_enabled(); bool log_is_recording_enabled(); - int effects_volume(); + + // audio + int master_volume(); + bool disable_background_audio(); int system_volume(); + int effect_volume(); int music_volume(); - int blips_volume(); + int blip_volume(); int blip_rate(); bool blank_blips_enabled(); @@ -72,10 +76,13 @@ public slots: void set_log_music(int p_state); void set_log_is_recording(bool p_enabled); void set_log_is_recording(int p_state); - void set_effects_volume(int p_number); + void set_disable_background_audio(bool p_enabled); + void set_disable_background_audio(int p_state); + void set_master_volume(int p_number); void set_system_volume(int p_number); + void set_effect_volume(int p_number); void set_music_volume(int p_number); - void set_blips_volume(int p_number); + void set_blip_volume(int p_number); void set_blip_rate(int p_number); void set_blank_blips(bool p_enabled); void set_blank_blips(int p_state); @@ -98,10 +105,12 @@ public slots: void log_uses_newline_changed(bool); void log_music_changed(bool); void log_is_recording_changed(bool); - void effects_volume_changed(int); + void master_volume_changed(int); + void disable_background_audio_changed(bool); void system_volume_changed(int); + void effect_volume_changed(int); void music_volume_changed(int); - void blips_volume_changed(int); + void blip_volume_changed(int); void blip_rate_changed(int); void blank_blips_changed(bool); }; diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 8eb3df530..60fd9b7e0 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -45,10 +45,11 @@ private slots: void on_gamemode_index_changed(QString p_text); void on_timeofday_index_changed(QString p_text); void on_log_is_topdown_changed(bool p_enabled); - void on_effects_value_changed(int p_num); + void on_master_value_changed(int p_num); void on_system_value_changed(int p_num); + void on_effect_value_changed(int p_num); void on_music_value_changed(int p_num); - void on_blips_value_changed(int p_num); + void on_blip_value_changed(int p_num); private: // FIXME This dependency shouldn't have come to exist. @@ -86,14 +87,17 @@ private slots: QCheckBox *w_log_is_recording = nullptr; // audio - QSlider *w_effects = nullptr; - QLabel *w_effects_value = nullptr; + QSlider *w_master = nullptr; + QLabel *w_master_value = nullptr; + QCheckBox *w_disable_background_audio = nullptr; QSlider *w_system = nullptr; QLabel *w_system_value = nullptr; + QSlider *w_effect = nullptr; + QLabel *w_effect_value = nullptr; QSlider *w_music = nullptr; QLabel *w_music_value = nullptr; - QSlider *w_blips = nullptr; - QLabel *w_blips_value = nullptr; + QSlider *w_blip = nullptr; + QLabel *w_blip_value = nullptr; QSpinBox *w_blip_rate = nullptr; QCheckBox *w_blank_blips = nullptr; }; diff --git a/include/aomusicplayer.h b/include/aomusicplayer.h index b1c0a2614..bf548b192 100644 --- a/include/aomusicplayer.h +++ b/include/aomusicplayer.h @@ -1,21 +1,20 @@ -#ifndef AOMUSICPLAYER_H -#define AOMUSICPLAYER_H +#pragma once -#include "aoabstractplayer.h" +#include "aoobject.h" +#include "draudioengine.h" -class AOMusicPlayer : public AOAbstractPlayer +class AOMusicPlayer : public AOObject { Q_OBJECT public: - AOMusicPlayer(QObject *p_parent, AOApplication *p_ao_app); + AOMusicPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); - void play(QString p_file); +public slots: + void play(QString p_song); void stop(); private: - AOBassHandle *m_handle = nullptr; - QString m_file; + DRAudioEngine::family_ptr m_family; + QString m_song; }; - -#endif // AOMUSICPLAYER_H diff --git a/include/aoobject.h b/include/aoobject.h new file mode 100644 index 000000000..a95421514 --- /dev/null +++ b/include/aoobject.h @@ -0,0 +1,14 @@ +#pragma once + +#include "aoapplication.h" + +class AOObject : public QObject +{ + Q_OBJECT + +public: + AOObject(AOApplication *p_ao_app, QObject *p_parent = nullptr); + +protected: + AOApplication *ao_app = nullptr; +}; diff --git a/include/aosfxplayer.h b/include/aosfxplayer.h index b83345b3c..b390f9679 100644 --- a/include/aosfxplayer.h +++ b/include/aosfxplayer.h @@ -1,17 +1,13 @@ -#ifndef AOSFXPLAYER_H -#define AOSFXPLAYER_H +#pragma once -#include "aoabstractplayer.h" +#include "aoobject.h" -class AOSfxPlayer : public AOAbstractPlayer +class AOSfxPlayer : public AOObject { Q_OBJECT public: - AOSfxPlayer(QObject *p_parent, AOApplication *p_ao_app); + AOSfxPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); void play(QString p_file); - void stop(); }; - -#endif // AOSFXPLAYER_H diff --git a/include/aoshoutplayer.h b/include/aoshoutplayer.h index 15f04b4d2..ad7e62c9f 100644 --- a/include/aoshoutplayer.h +++ b/include/aoshoutplayer.h @@ -1,17 +1,16 @@ #ifndef AOSHOUTPLAYER_HPP #define AOSHOUTPLAYER_HPP -#include "aoabstractplayer.h" +#include "aoobject.h" -class AOShoutPlayer : public AOAbstractPlayer +class AOShoutPlayer : public AOObject { Q_OBJECT public: - AOShoutPlayer(QObject *p_parent, AOApplication *p_ao_app); + AOShoutPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); void play(QString p_name, QString p_char); - void stop(); }; #endif // AOSHOUTPLAYER_HPP diff --git a/include/aosystemplayer.h b/include/aosystemplayer.h new file mode 100644 index 000000000..ea0b18479 --- /dev/null +++ b/include/aosystemplayer.h @@ -0,0 +1,13 @@ +#pragma once + +#include "aoobject.h" + +class AOSystemPlayer : public AOObject +{ + Q_OBJECT + +public: + AOSystemPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); + + void play(QString p_file); +}; diff --git a/include/courtroom.h b/include/courtroom.h index a1d600996..2146fd85c 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -23,9 +23,11 @@ #include "aoscene.h" #include "aosfxplayer.h" #include "aoshoutplayer.h" +#include "aosystemplayer.h" #include "aotextarea.h" #include "aotimer.h" #include "datatypes.h" +#include "draudioengine.h" #include #include @@ -99,8 +101,7 @@ class Courtroom : public QMainWindow // sets font properties for QTextEdit (same as above but also text outline) void set_qtextedit_font(QTextEdit *widget, QString p_identifier); // same as second set_font but for qtextedit - void set_qtextedit_font(QTextEdit *widget, QString p_identifier, - QString override_color); + void set_qtextedit_font(QTextEdit *widget, QString p_identifier, QString override_color); // helper function that calls above function on the relevant widgets void set_fonts(); @@ -224,8 +225,7 @@ class Courtroom : public QMainWindow // selected // or the user isn't already scrolled to the top void update_ic_log(bool p_reset_log); - void append_ic_text(QString p_name, QString p_line, bool p_system, - bool p_music); + void append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music); /** * @brief Appends a message arriving from system to the IC chatlog. @@ -285,8 +285,7 @@ class Courtroom : public QMainWindow void pause_timer(int timer_id); template - int adapt_numbered_items(QVector &item_vector, - QString config_item_number, QString item_name); + int adapt_numbered_items(QVector &item_vector, QString config_item_number, QString item_name); signals: void closing(); @@ -348,8 +347,7 @@ class Courtroom : public QMainWindow QTimer *testimony_hide_timer = nullptr; // Generate a File Name based on the time you launched the client - QString icchatlogsfilename = QDateTime::currentDateTime().toString( - "'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); + QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); // configuration files locations QString rpc_ini = "configs/rpccharlist.ini"; @@ -600,8 +598,7 @@ class Courtroom : public QMainWindow QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip QVector ui_label_images; - QVector label_images = {"Pre", "Flip", "Hidden", - "Music", "SFX", "Blip"}; + QVector label_images = {"Pre", "Flip", "Hidden", "Music", "SFX", "Blip"}; AOButton *ui_effect_flash = nullptr; AOButton *ui_effect_gloom = nullptr; @@ -656,11 +653,8 @@ class Courtroom : public QMainWindow void set_widget_names(); void reset_widget_names(); void insert_widget_name(QString p_widget_name, QWidget *p_widget); - void insert_widget_names(QVector &p_widget_names, - QVector &p_widgets); - template - void insert_widget_names(QVector &p_widget_names, - QVector &p_widgets); + void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); + template void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); void set_widget_layers(); void construct_char_select(); @@ -860,33 +854,21 @@ private slots: public slots: void set_audio_mute_enabled(bool p_enabled); - void set_effects_volume(int p_volume); - void set_system_volume(int p_volume); - void set_music_volume(int p_volume); - void set_blips_volume(int p_volume); private: bool m_audio_mute = false; AOSfxPlayer *m_effects_player = nullptr; AOShoutPlayer *m_shouts_player = nullptr; - AOSfxPlayer *m_system_player = nullptr; + AOSystemPlayer *m_system_player = nullptr; AOMusicPlayer *m_music_player = nullptr; AOBlipPlayer *m_blips_player = nullptr; -private slots: - void on_config_effects_volume_changed(int p_volume); - void on_config_system_volume_changed(int p_volume); - void on_config_music_volume_changed(int p_volume); - void on_config_blips_volume_changed(int p_volume); - // QWidget interface protected: void closeEvent(QCloseEvent *event) override; }; -template -void Courtroom::insert_widget_names(QVector &p_widget_names, - QVector &p_widgets) +template void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) { QVector widgets; diff --git a/include/draudio.h b/include/draudio.h new file mode 100644 index 000000000..a6b20c48c --- /dev/null +++ b/include/draudio.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +namespace DRAudio +{ +enum class Family +{ + FSystem, + FEffect, + FMusic, + FBlip, +}; + +enum Option +{ + OSuppressed = 0x1, +}; +Q_DECLARE_FLAGS(Options, Option) + +QString get_bass_error(int p_error_code); +QString get_last_bass_error(); +} // namespace DRAudio +Q_DECLARE_METATYPE(DRAudio::Options) diff --git a/include/draudioengine.h b/include/draudioengine.h new file mode 100644 index 000000000..47f2bf7d8 --- /dev/null +++ b/include/draudioengine.h @@ -0,0 +1,35 @@ +#pragma once + +#include "draudiostreamfamily.h" + +class DRAudioEngine : public QObject +{ + Q_OBJECT + +public: + using family_ptr = QSharedPointer; + + DRAudioEngine(QObject *p_parent = nullptr); + ~DRAudioEngine(); + + static family_ptr get_family(DRAudio::Family p_family); + static QList get_family_list(); + static std::int32_t get_volume(); + static DRAudio::Options get_options(); + // option getter + static bool is_suppressed(); + +public slots: + void set_volume(std::int32_t p_volume); + void set_options(DRAudio::Options p_options); + // option setter + void set_suppressed(bool p_enabled); + +signals: + void volume_changed(std::int32_t); + void options_changed(DRAudio::Options); + +private: + void adjust_options(); + void adjust_volume(); +}; diff --git a/include/draudioerror.h b/include/draudioerror.h new file mode 100644 index 000000000..96b227d3c --- /dev/null +++ b/include/draudioerror.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +class DRAudioError +{ +public: + DRAudioError(); + DRAudioError(QString p_error); + + QString get_error(); + +private: + QString m_error; +}; diff --git a/include/draudiostream.h b/include/draudiostream.h new file mode 100644 index 000000000..54305f523 --- /dev/null +++ b/include/draudiostream.h @@ -0,0 +1,55 @@ +#pragma once + +#include "draudio.h" +#include "draudioerror.h" + +#include + +#include + +#include +#include +#include + +class DRAudioStreamFamily; + +class DRAudioStream : public QObject +{ + Q_OBJECT + +public: + ~DRAudioStream(); + + DRAudio::Family get_family(); + std::optional get_file(); + + // state + bool is_playing(); + +public slots: + void play(); + void stop(); + + std::optional set_file(QString m_file); + void set_volume(std::int32_t p_volume); + +signals: + void file_changed(QString p_file); + void finished(); + +public: + static void CALLBACK sync_on_end(HSYNC hsync, DWORD ch, DWORD data, void *userdata); + +private: + friend class DRAudioStreamFamily; + + DRAudioStream(DRAudio::Family p_family); + + // static method + DRAudio::Family m_family; + std::optional m_file; + + // bass + std::optional m_hstream; + std::stack m_hsync_stack; +}; diff --git a/include/draudiostreamfamily.h b/include/draudiostreamfamily.h new file mode 100644 index 000000000..43a661693 --- /dev/null +++ b/include/draudiostreamfamily.h @@ -0,0 +1,66 @@ +#pragma once + +#include "draudiostream.h" + +#include +#include +#include + +#include +#include + +class DRAudioEngine; + +class DRAudioStreamFamily : public QObject +{ + Q_OBJECT + +public: + using stream_ptr = QSharedPointer; + + std::optional create_stream(QString p_file); + std::optional play_stream(QString p_file); + QVector get_stream_list(); + + std::int32_t get_volume(); + std::int32_t get_capacity(); + DRAudio::Options get_options(); + // options getter + bool is_suppressed(); + + using iterator = QVector::iterator; + iterator begin(); + iterator end(); + +public slots: + void set_volume(std::int32_t p_volume); + void set_capacity(std::int32_t p_capacity); + void set_options(DRAudio::Options p_options); + // options setter + void set_suppressed(bool p_enabled); + +signals: + void volume_changed(std::int32_t); + void capacity_changed(std::int32_t); + void options_changed(DRAudio::Options); + +private: + friend class DRAudioEngine; + + DRAudioStreamFamily(DRAudio::Family p_family); + + std::int32_t calculate_volume(); + + void adjust_capacity(); + void adjust_options(); + void adjust_volume(); + + DRAudio::Family m_family; + std::int32_t m_volume = 0; + std::int32_t m_capacity = 0; + DRAudio::Options m_options; + QVector> m_stream_list; + +private slots: + void on_stream_stopped(); +}; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index a3005f514..ef0a04f09 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 0 + 3 @@ -453,13 +453,76 @@ + + + Master: + + + + + + + + + + 0 + 0 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Disable Background Audio + + + + + + + Qt::Horizontal + + + + System: - + @@ -501,14 +564,14 @@ - + Music: - + @@ -550,17 +613,17 @@ - - + + Effects: - + - + 0 @@ -576,7 +639,7 @@ - + 40 @@ -593,17 +656,17 @@ - - + + Blips: - + - + 0 @@ -619,7 +682,7 @@ - + 40 @@ -636,14 +699,14 @@ - + Blip rate: - + 1 @@ -656,14 +719,14 @@ - + Blank blips: - + @@ -673,7 +736,7 @@ - + Qt::Vertical diff --git a/src/aoabstractplayer.cpp b/src/aoabstractplayer.cpp deleted file mode 100644 index 56b3ac9fb..000000000 --- a/src/aoabstractplayer.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "aoabstractplayer.h" - -AOAbstractPlayer::AOAbstractPlayer(QObject *p_parent, AOApplication *p_ao_app) - : QObject(p_parent), ao_app(p_ao_app) -{ -} - -int AOAbstractPlayer::get_volume() -{ - return m_volume; -} - -void AOAbstractPlayer::set_volume(int p_volume) -{ - m_volume = p_volume; - - Q_EMIT new_volume(m_volume); -} diff --git a/src/aoblipplayer.cpp b/src/aoblipplayer.cpp index 9326b967d..8285043a0 100644 --- a/src/aoblipplayer.cpp +++ b/src/aoblipplayer.cpp @@ -1,46 +1,23 @@ #include "aoblipplayer.h" -AOBlipPlayer::AOBlipPlayer(QWidget *parent, AOApplication *p_ao_app) +AOBlipPlayer::AOBlipPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) { - m_parent = parent; - ao_app = p_ao_app; + m_family = DRAudioEngine::get_family(DRAudio::Family::FBlip); + m_family->set_capacity(BLIP_COUNT); } -void AOBlipPlayer::set_blips(QString p_sfx) +void AOBlipPlayer::set_blips(QString p_blip) { - QString f_path = ao_app->get_sounds_path(p_sfx); + if (m_blip.has_value() && m_blip.value() == p_blip) + return; - for (int n_stream = 0; n_stream < BLIP_COUNT; ++n_stream) - { - BASS_StreamFree(m_stream_list[n_stream]); - - m_stream_list[n_stream] = BASS_StreamCreateFile( - FALSE, f_path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); - } - - set_volume(m_volume); + m_blip = p_blip; + m_file = ao_app->get_sounds_path(m_blip.value()); } void AOBlipPlayer::blip_tick() { - int f_cycle = m_cycle++; - - if (m_cycle == BLIP_COUNT) - m_cycle = 0; - - HSTREAM f_stream = m_stream_list[f_cycle]; - - BASS_ChannelPlay(f_stream, false); -} - -void AOBlipPlayer::set_volume(int p_value) -{ - m_volume = p_value; - - float volume = p_value / 100.0f; - - for (int n_stream = 0; n_stream < BLIP_COUNT; ++n_stream) - { - BASS_ChannelSetAttribute(m_stream_list[n_stream], BASS_ATTRIB_VOL, volume); - } + if (!m_file.has_value()) + return; + m_family->play_stream(m_file.value()); } diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 9148fd380..cfaa1a85e 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -1,11 +1,16 @@ #include "aoconfig.h" +#include "draudioengine.h" + // qt #include #include +#include #include #include +#include + /*! We have to suffer through a lot of boilerplate code but hey, when has ao2 ever cared? @@ -38,18 +43,28 @@ class AOConfigPrivate : public QObject bool log_uses_newline; bool log_music; bool log_is_recording; - int effects_volume; + + int master_volume; + bool disable_background_audio; + int effect_volume; int system_volume; int music_volume; - int blips_volume; + int blip_volume; int blip_rate; bool blank_blips; + // audio sync + DRAudioEngine *audio_engine = nullptr; + public: - AOConfigPrivate() - : QObject(qApp), - cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat) + AOConfigPrivate(QObject *p_parent = nullptr) + : QObject(p_parent), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat), + audio_engine(new DRAudioEngine(this)) { + Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); + connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, + SLOT(on_application_state_changed(Qt::ApplicationState))); + read_file(); } ~AOConfigPrivate() @@ -174,33 +189,52 @@ public slots: log_is_recording = p_enabled; invoke_parents("log_is_recording_changed", Q_ARG(bool, p_enabled)); } - void set_effects_volume(int p_number) + void set_master_volume(int p_number) + { + if (master_volume == p_number) + return; + master_volume = p_number; + audio_engine->set_volume(p_number); + invoke_parents("master_volume_changed", Q_ARG(int, p_number)); + } + void set_disable_background_audio(bool p_enabled) { - if (effects_volume == p_number) + if (disable_background_audio == p_enabled) return; - effects_volume = p_number; - invoke_parents("effects_volume_changed", Q_ARG(int, p_number)); + disable_background_audio = p_enabled; + invoke_parents("disable_background_audio_changed", Q_ARG(bool, p_enabled)); } void set_system_volume(int p_number) { if (system_volume == p_number) return; system_volume = p_number; + audio_engine->get_family(DRAudio::Family::FSystem)->set_volume(p_number); invoke_parents("system_volume_changed", Q_ARG(int, p_number)); } + void set_effects_volume(int p_number) + { + if (effect_volume == p_number) + return; + effect_volume = p_number; + audio_engine->get_family(DRAudio::Family::FEffect)->set_volume(p_number); + invoke_parents("effect_volume_changed", Q_ARG(int, p_number)); + } void set_music_volume(int p_number) { if (music_volume == p_number) return; music_volume = p_number; + audio_engine->get_family(DRAudio::Family::FMusic)->set_volume(p_number); invoke_parents("music_volume_changed", Q_ARG(int, p_number)); } void set_blips_volume(int p_number) { - if (blips_volume == p_number) + if (blip_volume == p_number) return; - blips_volume = p_number; - invoke_parents("blips_volume_changed", Q_ARG(int, p_number)); + blip_volume = p_number; + audio_engine->get_family(DRAudio::Family::FBlip)->set_volume(p_number); + invoke_parents("blip_volume_changed", Q_ARG(int, p_number)); } void set_blip_rate(int p_number) { @@ -238,12 +272,22 @@ public slots: log_uses_newline = cfg.value("chatlog_newline", false).toBool(); log_music = cfg.value("music_change_log", true).toBool(); log_is_recording = cfg.value("enable_logging", true).toBool(); - effects_volume = cfg.value("default_sfx", 50).toInt(); + + disable_background_audio = cfg.value("disable_background_audio").toBool(); + master_volume = cfg.value("default_master", 50).toInt(); system_volume = cfg.value("default_system", 50).toInt(); + effect_volume = cfg.value("default_sfx", 50).toInt(); music_volume = cfg.value("default_music", 50).toInt(); - blips_volume = cfg.value("default_blip", 50).toInt(); + blip_volume = cfg.value("default_blip", 50).toInt(); blip_rate = cfg.value("blip_rate", 1000000000).toInt(); blank_blips = cfg.value("blank_blips").toBool(); + + // audio update + audio_engine->set_volume(master_volume); + audio_engine->get_family(DRAudio::Family::FSystem)->set_volume(system_volume); + audio_engine->get_family(DRAudio::Family::FEffect)->set_volume(effect_volume); + audio_engine->get_family(DRAudio::Family::FMusic)->set_volume(music_volume); + audio_engine->get_family(DRAudio::Family::FBlip)->set_volume(blip_volume); } void save_file() { @@ -263,38 +307,48 @@ public slots: cfg.setValue("chatlog_newline", log_uses_newline); cfg.setValue("music_change_log", log_music); cfg.setValue("enable_logging", log_is_recording); - cfg.setValue("default_sfx", effects_volume); + cfg.setValue("default_master", master_volume); + cfg.setValue("disable_background_audio", disable_background_audio); + cfg.setValue("default_sfx", effect_volume); cfg.setValue("default_system", system_volume); cfg.setValue("default_music", music_volume); - cfg.setValue("default_blip", blips_volume); + cfg.setValue("default_blip", blip_volume); cfg.setValue("blip_rate", blip_rate); cfg.setValue("blank_blips", blank_blips); cfg.sync(); } private: - void invoke_parents(QString p_method_name, - QGenericArgument p_arg1 = QGenericArgument(nullptr)) + void invoke_parents(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)) { for (QObject *i_parent : parents) { - QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), - p_arg1); + QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), p_arg1); } } + +private slots: + void on_application_state_changed(Qt::ApplicationState p_state) + { + audio_engine->set_suppressed(disable_background_audio && p_state != Qt::ApplicationActive); + } }; /*! * private classes are cool */ -static AOConfigPrivate *d = nullptr; +namespace +{ +static QPointer d; +} AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) { // init if not created yet if (d == nullptr) { - d = new AOConfigPrivate; + Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); + d = new AOConfigPrivate(qApp); } // ao2 is the pinnacle of thread security @@ -400,25 +454,33 @@ bool AOConfig::log_is_recording_enabled() { return d->log_is_recording; } +int AOConfig::master_volume() +{ + return d->master_volume; +} -int AOConfig::effects_volume() +bool AOConfig::disable_background_audio() { - return d->effects_volume; + return d->disable_background_audio; } int AOConfig::system_volume() { return d->system_volume; } +int AOConfig::effect_volume() +{ + return d->effect_volume; +} int AOConfig::music_volume() { return d->music_volume; } -int AOConfig::blips_volume() +int AOConfig::blip_volume() { - return d->blips_volume; + return d->blip_volume; } int AOConfig::blip_rate() @@ -556,9 +618,19 @@ void AOConfig::set_log_is_recording(int p_state) set_log_is_recording(p_state == Qt::Checked); } -void AOConfig::set_effects_volume(int p_number) +void AOConfig::set_disable_background_audio(bool p_enabled) { - d->set_effects_volume(p_number); + d->set_disable_background_audio(p_enabled); +} + +void AOConfig::set_disable_background_audio(int p_state) +{ + set_disable_background_audio(p_state == Qt::Checked); +} + +void AOConfig::set_master_volume(int p_number) +{ + d->set_master_volume(p_number); } void AOConfig::set_system_volume(int p_number) @@ -566,12 +638,17 @@ void AOConfig::set_system_volume(int p_number) d->set_system_volume(p_number); } +void AOConfig::set_effect_volume(int p_number) +{ + d->set_effects_volume(p_number); +} + void AOConfig::set_music_volume(int p_number) { d->set_music_volume(p_number); } -void AOConfig::set_blips_volume(int p_number) +void AOConfig::set_blip_volume(int p_number) { d->set_blips_volume(p_number); } diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index e40d7dcb2..c7c1a59cd 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -44,22 +44,23 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // IC Chatlog w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); - w_log_orientation_top_down = - AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); - w_log_orientation_bottom_up = - AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); + w_log_orientation_top_down = AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); + w_log_orientation_bottom_up = AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); w_log_music = AO_GUI_WIDGET(QCheckBox, "log_music"); w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); // audio - w_effects = AO_GUI_WIDGET(QSlider, "effects"); - w_effects_value = AO_GUI_WIDGET(QLabel, "effects_value"); + w_master = AO_GUI_WIDGET(QSlider, "master"); + w_master_value = AO_GUI_WIDGET(QLabel, "master_value"); + w_disable_background_audio = AO_GUI_WIDGET(QCheckBox, "disable_background_audio"); w_system = AO_GUI_WIDGET(QSlider, "system"); w_system_value = AO_GUI_WIDGET(QLabel, "system_value"); + w_effect = AO_GUI_WIDGET(QSlider, "effect"); + w_effect_value = AO_GUI_WIDGET(QLabel, "effect_value"); w_music = AO_GUI_WIDGET(QSlider, "music"); w_music_value = AO_GUI_WIDGET(QLabel, "music_value"); - w_blips = AO_GUI_WIDGET(QSlider, "blips"); - w_blips_value = AO_GUI_WIDGET(QLabel, "blips_value"); + w_blip = AO_GUI_WIDGET(QSlider, "blip"); + w_blip_value = AO_GUI_WIDGET(QLabel, "blip_value"); w_blip_rate = AO_GUI_WIDGET(QSpinBox, "blip_rate"); w_blank_blips = AO_GUI_WIDGET(QCheckBox, "blank_blips"); @@ -69,108 +70,64 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) refresh_timeofday_list(); // input - connect(m_config, SIGNAL(autosave_changed(bool)), w_autosave, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(username_changed(QString)), w_username, - SLOT(setText(QString))); - connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, - SLOT(setText(QString))); - connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(theme_changed(QString)), w_theme, - SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, - SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, - SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(chat_tick_interval_changed(int)), - w_chat_tick_interval, SLOT(setValue(int))); - connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, - SLOT(setValue(int))); - connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, - SLOT(on_log_is_topdown_changed(bool))); - connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(effects_volume_changed(int)), w_effects, - SLOT(setValue(int))); - connect(m_config, SIGNAL(system_volume_changed(int)), w_system, - SLOT(setValue(int))); - connect(m_config, SIGNAL(music_volume_changed(int)), w_music, - SLOT(setValue(int))); - connect(m_config, SIGNAL(blips_volume_changed(int)), w_blips, - SLOT(setValue(int))); - connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, - SLOT(setValue(int))); - connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, - SLOT(setChecked(bool))); + connect(m_config, SIGNAL(autosave_changed(bool)), w_autosave, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); + connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); + connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); + connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); + connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); + connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(master_volume_changed(int)), w_master, SLOT(setValue(int))); + connect(m_config, SIGNAL(disable_background_audio_changed(bool)), w_disable_background_audio, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(system_volume_changed(int)), w_system, SLOT(setValue(int))); + connect(m_config, SIGNAL(effect_volume_changed(int)), w_effect, SLOT(setValue(int))); + connect(m_config, SIGNAL(music_volume_changed(int)), w_music, SLOT(setValue(int))); + connect(m_config, SIGNAL(blip_volume_changed(int)), w_blip, SLOT(setValue(int))); + connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, SLOT(setValue(int))); + connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, SLOT(setChecked(bool))); // output connect(w_close, SIGNAL(clicked()), this, SLOT(close())); connect(w_save, SIGNAL(clicked()), m_config, SLOT(save_file())); - connect(w_autosave, SIGNAL(stateChanged(int)), m_config, - SLOT(set_autosave(int))); - connect(w_username, SIGNAL(textEdited(QString)), m_config, - SLOT(set_username(QString))); - connect(w_callwords, SIGNAL(textEdited(QString)), m_config, - SLOT(set_callwords(QString))); - connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, - SLOT(set_server_alerts(int))); - connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, - SLOT(set_theme(QString))); - connect(w_reload_theme, SIGNAL(clicked()), this, - SLOT(on_reload_theme_clicked())); - connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, - SLOT(on_gamemode_index_changed(QString))); - connect(w_manual_gamemode, SIGNAL(stateChanged(int)), m_config, - SLOT(set_manual_gamemode(int))); - connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, - SLOT(on_timeofday_index_changed(QString))); - connect(w_manual_timeofday, SIGNAL(stateChanged(int)), m_config, - SLOT(set_manual_timeofday(int))); - connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, - SLOT(set_always_pre(int))); - connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, - SLOT(set_chat_tick_interval(int))); - connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, - SLOT(set_log_max_lines(int))); - connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, - SLOT(set_log_is_topdown(bool))); - connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, - SLOT(set_log_uses_newline(int))); - connect(w_log_music, SIGNAL(stateChanged(int)), m_config, - SLOT(set_log_music(int))); - connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, - SLOT(set_log_is_recording(int))); - connect(w_effects, SIGNAL(valueChanged(int)), m_config, - SLOT(set_effects_volume(int))); - connect(w_effects, SIGNAL(valueChanged(int)), this, - SLOT(on_effects_value_changed(int))); - connect(w_system, SIGNAL(valueChanged(int)), m_config, - SLOT(set_system_volume(int))); - connect(w_system, SIGNAL(valueChanged(int)), this, - SLOT(on_system_value_changed(int))); - connect(w_music, SIGNAL(valueChanged(int)), m_config, - SLOT(set_music_volume(int))); - connect(w_music, SIGNAL(valueChanged(int)), this, - SLOT(on_music_value_changed(int))); - connect(w_blips, SIGNAL(valueChanged(int)), m_config, - SLOT(set_blips_volume(int))); - connect(w_blips, SIGNAL(valueChanged(int)), this, - SLOT(on_blips_value_changed(int))); - connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, - SLOT(set_blip_rate(int))); - connect(w_blank_blips, SIGNAL(stateChanged(int)), m_config, - SLOT(set_blank_blips(int))); + connect(w_autosave, SIGNAL(stateChanged(int)), m_config, SLOT(set_autosave(int))); + connect(w_username, SIGNAL(textEdited(QString)), m_config, SLOT(set_username(QString))); + connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); + connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, SLOT(set_server_alerts(int))); + connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); + connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); + connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); + connect(w_manual_gamemode, SIGNAL(stateChanged(int)), m_config, SLOT(set_manual_gamemode(int))); + connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); + connect(w_manual_timeofday, SIGNAL(stateChanged(int)), m_config, SLOT(set_manual_timeofday(int))); + connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); + connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); + connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); + connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_topdown(bool))); + connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_uses_newline(int))); + connect(w_log_music, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_music(int))); + connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_is_recording(int))); + connect(w_disable_background_audio, SIGNAL(stateChanged(int)), m_config, SLOT(set_disable_background_audio(int))); + connect(w_master, SIGNAL(valueChanged(int)), m_config, SLOT(set_master_volume(int))); + connect(w_master, SIGNAL(valueChanged(int)), this, SLOT(on_master_value_changed(int))); + connect(w_system, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_volume(int))); + connect(w_system, SIGNAL(valueChanged(int)), this, SLOT(on_system_value_changed(int))); + connect(w_effect, SIGNAL(valueChanged(int)), m_config, SLOT(set_effect_volume(int))); + connect(w_effect, SIGNAL(valueChanged(int)), this, SLOT(on_effect_value_changed(int))); + connect(w_music, SIGNAL(valueChanged(int)), m_config, SLOT(set_music_volume(int))); + connect(w_music, SIGNAL(valueChanged(int)), this, SLOT(on_music_value_changed(int))); + connect(w_blip, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_volume(int))); + connect(w_blip, SIGNAL(valueChanged(int)), this, SLOT(on_blip_value_changed(int))); + connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_rate(int))); + connect(w_blank_blips, SIGNAL(stateChanged(int)), m_config, SLOT(set_blank_blips(int))); // set values w_autosave->setChecked(m_config->autosave()); @@ -198,10 +155,12 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); w_log_music->setChecked(m_config->log_music_enabled()); w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); - w_effects->setValue(m_config->effects_volume()); + w_master->setValue(m_config->master_volume()); + w_disable_background_audio->setChecked(m_config->disable_background_audio()); w_system->setValue(m_config->system_volume()); + w_effect->setValue(m_config->effect_volume()); w_music->setValue(m_config->music_volume()); - w_blips->setValue(m_config->blips_volume()); + w_blip->setValue(m_config->blip_volume()); w_blip_rate->setValue(m_config->blip_rate()); w_blank_blips->setChecked(m_config->blank_blips_enabled()); @@ -210,10 +169,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_timeofday->setEnabled(m_config->manual_timeofday_enabled()); // The manual gamemode checkbox enables browsing the gamemode combox // similarly with time of day - connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_gamemode, - SLOT(setEnabled(bool))); - connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_timeofday, - SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_gamemode, SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_timeofday, SLOT(setEnabled(bool))); } void AOConfigPanel::showEvent(QShowEvent *event) @@ -239,8 +196,7 @@ void AOConfigPanel::refresh_theme_list() // themes const QString path = QDir::currentPath() + "/base/themes"; - for (QString i_folder : - QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; @@ -265,10 +221,8 @@ void AOConfigPanel::refresh_gamemode_list() // add empty entry indicating no gamemode chosen w_gamemode->addItem(""); // gamemodes - QString path = - QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; - for (QString i_folder : - QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + QString path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; + for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; @@ -297,15 +251,12 @@ void AOConfigPanel::refresh_timeofday_list() // gamemode chosen or not QString path; if (m_config->gamemode().isEmpty()) - path = - QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; + path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; else - path = QDir::currentPath() + "/base/themes/" + m_config->theme() + - "/gamemodes/" + m_config->gamemode() + "/times/"; + path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/" + m_config->gamemode() + "/times/"; // times of day - for (QString i_folder : - QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; @@ -343,9 +294,9 @@ void AOConfigPanel::on_log_is_topdown_changed(bool p_enabled) w_log_orientation_bottom_up->setChecked(!p_enabled); } -void AOConfigPanel::on_effects_value_changed(int p_num) +void AOConfigPanel::on_master_value_changed(int p_num) { - w_effects_value->setText(QString::number(p_num) + "%"); + w_master_value->setText(QString::number(p_num) + "%"); } void AOConfigPanel::on_system_value_changed(int p_num) @@ -353,14 +304,19 @@ void AOConfigPanel::on_system_value_changed(int p_num) w_system_value->setText(QString::number(p_num) + "%"); } +void AOConfigPanel::on_effect_value_changed(int p_num) +{ + w_effect_value->setText(QString::number(p_num) + "%"); +} + void AOConfigPanel::on_music_value_changed(int p_num) { w_music_value->setText(QString::number(p_num) + "%"); } -void AOConfigPanel::on_blips_value_changed(int p_num) +void AOConfigPanel::on_blip_value_changed(int p_num) { - w_blips_value->setText(QString::number(p_num) + "%"); + w_blip_value->setText(QString::number(p_num) + "%"); } void AOConfigPanel::on_config_reload_theme_requested() diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index 2f8a142e9..35887f620 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -6,21 +6,18 @@ #include "file_functions.h" #include "misc_functions.h" -AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) - : QLabel(p_parent) +AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) { ao_app = p_ao_app; evidence_movie = new QMovie(this); evidence_icon = new QLabel(this); - sfx_player = new AOSfxPlayer(this, ao_app); + sfx_player = new AOSfxPlayer(ao_app, this); - connect(evidence_movie, SIGNAL(frameChanged(int)), this, - SLOT(frame_change(int))); + connect(evidence_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); } -void AOEvidenceDisplay::show_evidence(QString p_evidence_image, - bool is_left_side) +void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_side) { this->reset(); @@ -43,8 +40,7 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, gif_name = "evidence_appear_right.gif"; } - pos_size_type icon_dimensions = - ao_app->get_element_dimensions(icon_identifier, "courtroom_design.ini"); + pos_size_type icon_dimensions = ao_app->get_element_dimensions(icon_identifier, "courtroom_design.ini"); evidence_icon->move(icon_dimensions.x, icon_dimensions.y); evidence_icon->resize(icon_dimensions.width, icon_dimensions.height); @@ -77,7 +73,6 @@ void AOEvidenceDisplay::frame_change(int p_frame) void AOEvidenceDisplay::reset() { - sfx_player->stop(); evidence_movie->stop(); evidence_icon->hide(); this->clear(); diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index 51487d06f..ab1f27d8f 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -1,44 +1,22 @@ #include "aomusicplayer.h" -#include - #include -AOMusicPlayer::AOMusicPlayer(QObject *p_parent, AOApplication *p_ao_app) - : AOAbstractPlayer(p_parent, p_ao_app) +AOMusicPlayer::AOMusicPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) { + m_family = DRAudioEngine::get_family(DRAudio::Family::FMusic); + m_family->set_capacity(1); // a single song is needed } -void AOMusicPlayer::play(QString p_file) +void AOMusicPlayer::play(QString p_song) { - QString f_file = ao_app->get_music_path(p_file); - stop(); - - m_file = f_file; - - try - { // create new song - AOBassHandle *handle = new AOBassHandle(m_file, false, this); - connect(this, &AOMusicPlayer::new_volume, handle, - &AOBassHandle::set_volume); - connect(this, &AOMusicPlayer::stopping, handle, &AOBassHandle::stop); - - // delete previous - if (m_handle) - delete m_handle; - - m_handle = handle; - m_handle->set_volume(get_volume()); - m_handle->play(); - } - catch (const std::exception &e_exception) - { - qDebug() << e_exception.what(); - } + m_song = p_song; + m_family->play_stream(ao_app->get_music_path(p_song)); } void AOMusicPlayer::stop() { - Q_EMIT stopping(); + for (auto &song : m_family->get_stream_list()) + song->stop(); } diff --git a/src/aoobject.cpp b/src/aoobject.cpp new file mode 100644 index 000000000..f7a8389a2 --- /dev/null +++ b/src/aoobject.cpp @@ -0,0 +1,4 @@ +#include "aoobject.h" + +AOObject::AOObject(AOApplication *p_ao_app, QObject *p_parent) : QObject(p_parent), ao_app(p_ao_app) +{} diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index 28a928269..b6f3a1400 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -1,35 +1,17 @@ #include "aosfxplayer.h" + +#include "draudioengine.h" #include "file_functions.h" #include #include -AOSfxPlayer::AOSfxPlayer(QObject *p_parent, AOApplication *p_ao_app) - : AOAbstractPlayer(p_parent, p_ao_app) -{ -} +AOSfxPlayer::AOSfxPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) +{} void AOSfxPlayer::play(QString p_name) { - QString f_root = ao_app->get_sounds_path(p_name); - QString f_file = ao_app->find_asset_path({f_root}, audio_extensions()); - - try - { - AOBassHandle *handle = new AOBassHandle(f_file, true, this); - connect(this, &AOSfxPlayer::new_volume, handle, &AOBassHandle::set_volume); - connect(this, &AOSfxPlayer::stopping, handle, &AOBassHandle::stop); - handle->set_volume(get_volume()); - handle->play(); - } - catch (const std::exception &e_exception) - { - qDebug() << e_exception.what(); - } -} - -void AOSfxPlayer::stop() -{ - Q_EMIT stopping(); + const QString file = ao_app->find_asset_path({ao_app->get_sounds_path(p_name)}, audio_extensions()); + DRAudioEngine::get_family(DRAudio::Family::FEffect)->play_stream(file); } diff --git a/src/aoshoutplayer.cpp b/src/aoshoutplayer.cpp index cffd26ced..152881043 100644 --- a/src/aoshoutplayer.cpp +++ b/src/aoshoutplayer.cpp @@ -1,16 +1,16 @@ #include "aoshoutplayer.h" + +#include "draudioengine.h" #include "file_functions.h" #include -AOShoutPlayer::AOShoutPlayer(QObject *p_parent, AOApplication *p_ao_app) - : AOAbstractPlayer(p_parent, p_ao_app) -{ -} +AOShoutPlayer::AOShoutPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) +{} void AOShoutPlayer::play(QString p_name, QString p_char) { - QString f_file; + QString file; QString char_path = ao_app->get_character_path(p_char, p_name); QString theme_path = ao_app->find_theme_asset_path(p_name); @@ -19,28 +19,11 @@ void AOShoutPlayer::play(QString p_name, QString p_char) qDebug() << theme_path; if (file_exists(char_path)) - f_file = char_path; + file = char_path; else if (file_exists(theme_path)) - f_file = theme_path; - else - f_file = ""; - - try - { - AOBassHandle *handle = new AOBassHandle(f_file, true, this); - connect(this, &AOShoutPlayer::new_volume, handle, - &AOBassHandle::set_volume); - connect(this, &AOShoutPlayer::stopping, handle, &AOBassHandle::stop); - handle->set_volume(get_volume()); - handle->play(); - } - catch (const std::exception &e_exception) - { - qDebug() << e_exception.what(); - } -} + file = theme_path; -void AOShoutPlayer::stop() -{ - Q_EMIT stopping(); + if (file.isNull()) + return; + DRAudioEngine::get_family(DRAudio::Family::FEffect)->play_stream(file); } diff --git a/src/aosystemplayer.cpp b/src/aosystemplayer.cpp new file mode 100644 index 000000000..b70cb801a --- /dev/null +++ b/src/aosystemplayer.cpp @@ -0,0 +1,17 @@ +#include "aosystemplayer.h" + +#include "draudioengine.h" +#include "file_functions.h" + +#include + +#include + +AOSystemPlayer::AOSystemPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) +{} + +void AOSystemPlayer::play(QString p_name) +{ + const QString file = ao_app->find_asset_path({ao_app->get_sounds_path(p_name)}, audio_extensions()); + DRAudioEngine::get_family(DRAudio::Family::FEffect)->play_stream(file); +} diff --git a/src/audio_functions.cpp b/src/audio_functions.cpp index 3e7824c20..84bd8ce3f 100644 --- a/src/audio_functions.cpp +++ b/src/audio_functions.cpp @@ -11,49 +11,7 @@ void Courtroom::set_audio_mute_enabled(bool p_enabled) return; m_audio_mute = p_enabled; - // restore volume - set_effects_volume(ao_config->effects_volume()); - set_music_volume(ao_config->music_volume()); - set_blips_volume(ao_config->blips_volume()); -} - -void Courtroom::set_effects_volume(int p_volume) -{ - m_effects_player->set_volume(is_audio_muted() ? 0 : p_volume); - m_shouts_player->set_volume(is_audio_muted() ? 0 : p_volume); -} - -void Courtroom::set_system_volume(int p_volume) -{ - m_system_player->set_volume(p_volume); -} - -void Courtroom::set_music_volume(int p_volume) -{ - m_music_player->set_volume(is_audio_muted() ? 0 : p_volume); -} - -void Courtroom::set_blips_volume(int p_volume) -{ - m_blips_player->set_volume(is_audio_muted() ? 0 : p_volume); -} - -void Courtroom::on_config_effects_volume_changed(int p_volume) -{ - set_effects_volume(p_volume); -} - -void Courtroom::on_config_system_volume_changed(int p_volume) -{ - set_system_volume(p_volume); -} - -void Courtroom::on_config_music_volume_changed(int p_volume) -{ - set_music_volume(p_volume); -} - -void Courtroom::on_config_blips_volume_changed(int p_volume) -{ - set_blips_volume(p_volume); + // suppress audio + for (auto &family : DRAudioEngine::get_family_list()) + family->set_suppressed(m_audio_mute); } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 0060f4de3..b12846c92 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -21,16 +21,11 @@ #include #include -Courtroom::Courtroom(AOApplication *p_ao_app) - : QMainWindow() +Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; ao_config = new AOConfig(this); - // initializing sound device - BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, 0, NULL); - BASS_PluginLoad("bassopus.dll", BASS_UNICODE); - create_widgets(); connect_widgets(); @@ -58,8 +53,7 @@ void Courtroom::enter_courtroom(int p_cid) f_char = ao_app->get_char_name(char_list.at(m_cid).name); QString r_char = f_char; // regex for removing non letter (except _) characters - QRegularExpression re( - QString::fromUtf8("[-`~!@#$%^&*()—+=|:;<>«»,.?/{}\'\"\\[\\]]")); + QRegularExpression re(QString::fromUtf8("[-`~!@#$%^&*()—+=|:;<>«»,.?/{}\'\"\\[\\]]")); r_char.remove(re); if (!rpc_char_list.contains(f_char.toLower())) @@ -120,18 +114,15 @@ void Courtroom::enter_courtroom(int p_cid) set_widgets(); check_shouts(); - if (m_shout_current < shouts_enabled.length() && - !shouts_enabled[m_shout_current]) + if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) cycle_shout(1); check_effects(); - if (m_effect_current < effects_enabled.length() && - !effects_enabled[m_effect_current]) + if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) cycle_effect(1); check_wtce(); - if (is_judge && - (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) + if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) cycle_wtce(1); check_free_blocks(); @@ -142,8 +133,7 @@ void Courtroom::enter_courtroom(int p_cid) list_areas(); list_sfx(); - ui_sfx_list->setCurrentItem( - ui_sfx_list->item(0)); // prevents undefined errors + ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors // unmute audio set_audio_mute_enabled(false); @@ -244,8 +234,7 @@ void Courtroom::set_scene() QStringList alldesks{"defensedesk", "prosecutiondesk", "stand"}; for (QString desk : alldesks) { - QString full_path = ao_app->find_asset_path( - {get_background_path(desk)}, animated_or_static_extensions()); + QString full_path = ao_app->find_asset_path({get_background_path(desk)}, animated_or_static_extensions()); if (full_path.isEmpty()) { has_all_desks = false; @@ -253,9 +242,7 @@ void Courtroom::set_scene() } } - if (f_desk_mod == "0" || - (f_desk_mod != "1" && - (f_side == "jud" || f_side == "hld" || f_side == "hlp"))) + if (f_desk_mod == "0" || (f_desk_mod != "1" && (f_side == "jud" || f_side == "hld" || f_side == "hlp"))) ui_vp_desk->hide(); else if (!has_all_desks) ui_vp_desk->hide(); @@ -296,8 +283,7 @@ void Courtroom::set_taken(int n_char, bool p_taken) { if (n_char >= char_list.size()) { - qDebug() - << "W: set_taken attempted to set an index bigger than char_list size"; + qDebug() << "W: set_taken attempted to set an index bigger than char_list size"; return; } @@ -322,24 +308,20 @@ void Courtroom::handle_music_anim() QString file_b = fonts_ini; pos_size_type res_a = ao_app->get_element_dimensions("music_name", file_a); pos_size_type res_b = ao_app->get_element_dimensions("music_area", file_a); - float speed = - static_cast(ao_app->get_font_property("music_name_speed", file_b)); + float speed = static_cast(ao_app->get_font_property("music_name_speed", file_b)); QFont f_font = ui_vp_music_name->font(); QFontMetrics fm(f_font); int dist; - if (ao_app->read_theme_ini("enable_const_music_speed", cc_config_ini) == - "true") + if (ao_app->read_theme_ini("enable_const_music_speed", cc_config_ini) == "true") dist = res_b.width; else dist = fm.width(ui_vp_music_name->toPlainText()); int time = static_cast(1000000 * dist / speed); music_anim->setLoopCount(-1); music_anim->setDuration(time); - music_anim->setStartValue( - QRect(res_b.width + res_b.x, res_a.y, res_a.width, res_a.height)); - music_anim->setEndValue( - QRect(-dist + res_a.x, res_a.y, res_a.width, res_a.height)); + music_anim->setStartValue(QRect(res_b.width + res_b.x, res_a.y, res_a.width, res_a.height)); + music_anim->setEndValue(QRect(-dist + res_a.x, res_a.y, res_a.width, res_a.height)); music_anim->start(); } @@ -360,8 +342,7 @@ void Courtroom::handle_clock(QString time) qDebug() << "Displaying clock asset..."; QString clock_filename = "hours/" + QString::number(current_clock); - const QString asset_path = ao_app->find_theme_asset_path( - clock_filename, animated_or_static_extensions()); + const QString asset_path = ao_app->find_theme_asset_path(clock_filename, animated_or_static_extensions()); if (asset_path.isEmpty()) { qDebug() << "Asset not found; aborting."; @@ -413,8 +394,7 @@ void Courtroom::list_music() ui_music_list->addItem(i_song); QString song_root = ao_app->get_music_path(i_song); - QString song_path = - ao_app->find_asset_path({song_root}, audio_extensions()); + QString song_path = ao_app->find_asset_path({song_root}, audio_extensions()); if (!song_path.isEmpty()) ui_music_list->item(n_listed_songs)->setBackground(found_brush); @@ -483,8 +463,7 @@ void Courtroom::list_sfx() sfx_names.append("1"); // Silence QString default_sfx_root = ao_app->get_sounds_path("1"); - QString default_sfx_path = - ao_app->find_asset_path({default_sfx_root}, audio_extensions()); + QString default_sfx_path = ao_app->find_asset_path({default_sfx_root}, audio_extensions()); if (!default_sfx_path.isEmpty()) { ui_sfx_list->item(0)->setBackground(found_brush); @@ -516,8 +495,7 @@ void Courtroom::list_sfx() // Apply appropriate color whether SFX exists or not QString sfx_root = ao_app->get_sounds_path(i_sfx); - QString sfx_path = - ao_app->find_asset_path({sfx_root}, audio_extensions()); + QString sfx_path = ao_app->find_asset_path({sfx_root}, audio_extensions()); if (!sfx_path.isEmpty()) ui_sfx_list->item(last_index)->setBackground(found_brush); @@ -563,8 +541,7 @@ void Courtroom::list_note_files() while (f_index >= f_layout->count()) on_add_button_clicked(); - AONotePicker *f_notepicker = - static_cast(f_layout->itemAt(f_index)->widget()); + AONotePicker *f_notepicker = static_cast(f_layout->itemAt(f_index)->widget()); f_notepicker->m_line->setText(f_filename); f_notepicker->real_file = f_filestring; } @@ -594,8 +571,7 @@ void Courtroom::save_textlog(QString p_text) { QString f_file = ao_app->get_base_path() + icchatlogsfilename; - ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, - f_file); + ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, f_file); } void Courtroom::append_server_chatmessage(QString p_name, QString p_message) @@ -640,8 +616,7 @@ void Courtroom::on_chat_return_pressed() QString f_desk_mod = "chat"; - f_desk_mod = - QString::number(ao_app->get_desk_mod(current_char, current_emote)); + f_desk_mod = QString::number(ao_app->get_desk_mod(current_char, current_emote)); if (f_desk_mod == "-1") f_desk_mod = "chat"; @@ -701,8 +676,7 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(QString::number(f_emote_mod)); packet_contents.append(QString::number(m_cid)); - packet_contents.append( - QString::number(ao_app->get_sfx_delay(current_char, current_emote))); + packet_contents.append(QString::number(ao_app->get_sfx_delay(current_char, current_emote))); QString f_obj_state; @@ -747,8 +721,7 @@ void Courtroom::handle_acknowledged_ms() // reset states ui_pre->setChecked(ao_config->always_pre_enabled()); list_sfx(); - ui_sfx_list->setCurrentItem( - ui_sfx_list->item(0)); // prevents undefined errors + ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors m_shout_state = 0; draw_shout_buttons(); @@ -790,8 +763,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) if (mute_map.value(m_chatmessage[CMChrId].toInt())) return; - chatmessage_is_empty = - m_chatmessage[CMMessage] == " " || m_chatmessage[CMMessage] == ""; + chatmessage_is_empty = m_chatmessage[CMMessage] == " " || m_chatmessage[CMMessage] == ""; m_msg_is_first_person = false; // reset our ui state if client just spoke @@ -820,8 +792,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) QString f_showname; qDebug() << "handle_chatmessage"; - qDebug() << m_chatmessage[CMShowName] - << ao_app->get_showname(char_list.at(f_char_id).name); + qDebug() << m_chatmessage[CMShowName] << ao_app->get_showname(char_list.at(f_char_id).name); // We actually DO wanna fail here if the showname is empty but the system is // speaking. @@ -857,8 +828,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) else append_ic_text(f_showname, m_chatmessage[CMMessage], false, false); - if (ao_config->log_is_recording_enabled() && - (!chatmessage_is_empty || !is_system_speaking)) + if (ao_config->log_is_recording_enabled() && (!chatmessage_is_empty || !is_system_speaking)) { save_textlog(f_showname + ": " + m_chatmessage[CMMessage]); } @@ -874,11 +844,9 @@ void Courtroom::handle_chatmessage(QStringList p_contents) m_chatmessage[CMEmoteModifier] = 1; // handles cases 1-8 (5-8 are DRO only) - if (objection_mod >= 1 && objection_mod <= ui_shouts.size() && - ui_shouts.size() > 0) // check to prevent crashing + if (objection_mod >= 1 && objection_mod <= ui_shouts.size() && ui_shouts.size() > 0) // check to prevent crashing { - ui_vp_objection->play_interjection(f_char, - shout_names.at(objection_mod - 1)); + ui_vp_objection->play_interjection(f_char, shout_names.at(objection_mod - 1)); m_shouts_player->play(shout_names.at(objection_mod - 1) + ".wav", f_char); } else @@ -921,8 +889,7 @@ void Courtroom::handle_chatmessage_2() // handles IC // Check if char.ini has color property, which overrides the theme's default // showname color - QString f_color = - ao_app->read_char_ini(real_name, "color", "[Options]", "[Time]"); + QString f_color = ao_app->read_char_ini(real_name, "color", "[Options]", "[Time]"); set_qtextedit_font(ui_vp_showname, "showname", f_color); ui_vp_showname->setText(f_showname); @@ -986,8 +953,7 @@ void Courtroom::handle_chatmessage_3() // shifted by 1 because 0 is no evidence per legacy standards QString f_image = local_evidence_list.at(f_evi_id - 1).image; // 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"); + bool is_left_side = !(f_side == "def" || f_side == "hlp" || f_side == "jud"); ui_vp_evidence_display->show_evidence(f_image, is_left_side); } @@ -1030,11 +996,9 @@ void Courtroom::handle_chatmessage_3() // 2. In the character folder, look for // "showname" + extensions in `exts` in order - QString path = ao_app->find_theme_asset_path( - "characters/" + f_char + "/showname", {".png"}); + QString path = ao_app->find_theme_asset_path("characters/" + f_char + "/showname", {".png"}); if (path.isEmpty()) - path = ao_app->find_asset_path( - {ao_app->get_character_path(f_char, "showname")}, {".png"}); + path = ao_app->find_asset_path({ao_app->get_character_path(f_char, "showname")}, {".png"}); if (!path.isEmpty() && !chatmessage_is_empty && ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") @@ -1071,15 +1035,13 @@ void Courtroom::handle_chatmessage_3() int effect = m_chatmessage[CMEffectState].toInt(); QStringList offset = ao_app->get_effect_offset(f_char, effect); - ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), - ui_viewport->y() + offset.at(1).toInt()); + ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), ui_viewport->y() + offset.at(1).toInt()); QStringList overlay = ao_app->get_overlay(f_char, effect); QString overlay_name = overlay.at(0); QString overlay_sfx = overlay.at(1); - bool do_it = - ao_app->read_theme_ini("non_vanilla_effects", cc_config_ini) == "true"; + bool do_it = ao_app->read_theme_ini("non_vanilla_effects", cc_config_ini) == "true"; if (effect == 1 && !do_it) { @@ -1102,8 +1064,7 @@ void Courtroom::handle_chatmessage_3() // overlay_name = "effect_gloom"; // ui_vp_effect->play(overlay_name, f_char); // } - else if (do_it && effect > 0 && effect <= ui_effects.size() && - effect_names.size() > 0) // check to prevent crashing + else if (do_it && effect > 0 && effect <= ui_effects.size() && effect_names.size() > 0) // check to prevent crashing { QString s_eff = effect_names.at(effect - 1); QStringList f_eff = ao_app->get_effect(effect); @@ -1131,10 +1092,8 @@ void Courtroom::handle_chatmessage_3() m_system_player->play(ao_app->get_sfx("word_call")); ao_app->alert(this); ui_server_chatlog->append_chatmessage( - "CLIENT", "[" + QTime::currentTime().toString("HH:mm") + "] " + - ui_vp_showname->toPlainText() + - " has called you via your callword \"" + word + - "\": \"" + f_message + "\""); + "CLIENT", "[" + QTime::currentTime().toString("HH:mm") + "] " + ui_vp_showname->toPlainText() + + " has called you via your callword \"" + word + "\": \"" + f_message + "\""); break; } } @@ -1147,12 +1106,9 @@ void Courtroom::on_chat_config_changed() int chatlog_limit = ao_config->log_max_lines(); // default chatlog_limit? - chatlog_limit = chatlog_limit <= 0 - ? 200 - : chatlog_limit; // TODO declare the default somewhere so - // it's not a magic number - if (chatlog_limit < - m_chatlog_limit) // only update if we need to chop away records + chatlog_limit = chatlog_limit <= 0 ? 200 : chatlog_limit; // TODO declare the default somewhere so + // it's not a magic number + if (chatlog_limit < m_chatlog_limit) // only update if we need to chop away records chatlog_changed = true; m_chatlog_limit = chatlog_limit; @@ -1208,16 +1164,14 @@ void Courtroom::update_ic_log(bool p_reset_log) else name_format.setFontWeight(QFont::Normal); - QColor showname_color = - ao_app->get_color("ic_chatlog_showname_color", fonts_ini); + QColor showname_color = ao_app->get_color("ic_chatlog_showname_color", fonts_ini); if (showname_color == not_found_color) showname_color = default_color; name_format.setForeground(showname_color); QTextCharFormat line_format = ui_ic_chatlog->currentCharFormat(); line_format.setFontWeight(QFont::Normal); - QColor message_color = - ao_app->get_color("ic_chatlog_message_color", fonts_ini); + QColor message_color = ao_app->get_color("ic_chatlog_message_color", fonts_ini); if (message_color == not_found_color) message_color = default_color; line_format.setForeground(message_color); @@ -1235,23 +1189,19 @@ void Courtroom::update_ic_log(bool p_reset_log) // cache previous values const QTextCursor prev_cursor = ui_ic_chatlog->textCursor(); const int scroll_pos = vscrollbar->value(); - const bool is_scrolled = m_chatlog_scrolldown - ? scroll_pos == vscrollbar->maximum() - : scroll_pos == vscrollbar->minimum(); + const bool is_scrolled = + m_chatlog_scrolldown ? scroll_pos == vscrollbar->maximum() : scroll_pos == vscrollbar->minimum(); // recover cursor QTextCursor cursor = ui_ic_chatlog->textCursor(); // figure out if we need to move up or down - const QTextCursor::MoveOperation move_type = - m_chatlog_scrolldown ? QTextCursor::End : QTextCursor::Start; + const QTextCursor::MoveOperation move_type = m_chatlog_scrolldown ? QTextCursor::End : QTextCursor::Start; for (record_type_ptr record : records_to_add) { // move cursor cursor.movePosition(move_type); - const QString record_end = - (QString(QChar::LineFeed) + - (m_chatlog_newline ? QString(QChar::LineFeed) : "")); + const QString record_end = (QString(QChar::LineFeed) + (m_chatlog_newline ? QString(QChar::LineFeed) : "")); if (record->system) { @@ -1285,11 +1235,9 @@ void Courtroom::update_ic_log(bool p_reset_log) int blocks_to_delete = ui_ic_chatlog->document()->blockCount() - block_count; // the orientation at which we need to delete from - const QTextCursor::MoveOperation start_location = - m_chatlog_scrolldown ? QTextCursor::Start : QTextCursor::End; + const QTextCursor::MoveOperation start_location = m_chatlog_scrolldown ? QTextCursor::Start : QTextCursor::End; const QTextCursor::MoveOperation block_orientation = - m_chatlog_scrolldown ? QTextCursor::NextBlock - : QTextCursor::PreviousBlock; + m_chatlog_scrolldown ? QTextCursor::NextBlock : QTextCursor::PreviousBlock; /* Blocks appear like this * textQChar(0x2029) @@ -1340,17 +1288,14 @@ void Courtroom::update_ic_log(bool p_reset_log) else { ui_ic_chatlog->moveCursor(move_type); - vscrollbar->setValue(m_chatlog_scrolldown ? vscrollbar->maximum() - : vscrollbar->minimum()); + vscrollbar->setValue(m_chatlog_scrolldown ? vscrollbar->maximum() : vscrollbar->minimum()); } } -void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, - bool p_music) +void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music) { // record new entry - m_ic_records.append( - std::make_shared(p_name, p_line, "", p_system, p_music)); + m_ic_records.append(std::make_shared(p_name, p_line, "", p_system, p_music)); // update update_ic_log(false); @@ -1465,8 +1410,7 @@ void Courtroom::chat_tick() if (m_msg_is_first_person == false) { - ui_vp_player_char->play_idle(m_chatmessage[CMChrName], - m_chatmessage[CMEmote]); + ui_vp_player_char->play_idle(m_chatmessage[CMChrName], m_chatmessage[CMEmote]); } m_string_color = ""; @@ -1510,8 +1454,7 @@ void Courtroom::chat_tick() ui_vp_message->textCursor().insertText(f_character, vp_message_format); } - else if (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == - "true") + else if (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "true") { bool highlight_found = false; bool render_character = true; @@ -1777,8 +1720,7 @@ void Courtroom::handle_wtce(QString p_wtce) QString sfx_file = cc_sounds_ini; int index = p_wtce.at(p_wtce.size() - 1).digitValue(); - if (index > 0 && index < wtce_names.size() + 1 && - wtce_names.size() > 0) // check to prevent crash + if (index > 0 && index < wtce_names.size() + 1 && wtce_names.size() > 0) // check to prevent crash { p_wtce.chop(1); // looking for the 'testimony' part if (p_wtce == "testimony") @@ -1807,8 +1749,7 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) } else if (p_bar == 2) { - ui_prosecution_bar->set_image("prosecutionbar" + QString::number(p_state) + - ".png"); + ui_prosecution_bar->set_image("prosecutionbar" + QString::number(p_state) + ".png"); prosecution_bar_state = p_state; } } @@ -1851,10 +1792,9 @@ void Courtroom::on_ooc_return_pressed() QString name; do { - ooc_name = QInputDialog::getText( - this, "Enter a name", - "You must have a name to talk in OOC chat. Enter a name: ", - QLineEdit::Normal, "user", &ok); + ooc_name = QInputDialog::getText(this, "Enter a name", + "You must have a name to talk in OOC chat. Enter a name: ", QLineEdit::Normal, + "user", &ok); } while (ok && ooc_name.isEmpty()); if (!ok) return; @@ -1995,8 +1935,7 @@ void Courtroom::on_pos_dropdown_changed(int p_index) set_judge_enabled(f_pos == "jud"); - ao_app->send_server_packet( - new AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); + ao_app->send_server_packet(new AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); // Uncomment later and remove above // Will only work in TSDR 4.3+ servers // ao_app->send_server_packet(new AOPacket("SP#" + f_pos + "#%")); @@ -2045,9 +1984,7 @@ void Courtroom::on_music_list_double_clicked(QModelIndex p_model) QString p_song = ui_music_list->item(p_model.row())->text(); - ao_app->send_server_packet( - new AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%"), - false); + ao_app->send_server_packet(new AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%"), false); ui_ic_chat_message->setFocus(); } @@ -2056,9 +1993,7 @@ void Courtroom::on_area_list_double_clicked(QModelIndex p_model) { QString p_area = ui_area_list->item(p_model.row())->text(); - ao_app->send_server_packet( - new AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%"), - false); + ao_app->send_server_packet(new AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%"), false); ui_ic_chat_message->setFocus(); } @@ -2077,8 +2012,7 @@ void Courtroom::draw_shout_buttons() // Mark selected button as such if (m_shout_state != 0 && ui_shouts.size() > 0) - ui_shouts.at(m_shout_state - 1) - ->set_image(shout_names.at(m_shout_state - 1) + "_selected.png"); + ui_shouts.at(m_shout_state - 1)->set_image(shout_names.at(m_shout_state - 1) + "_selected.png"); } void Courtroom::on_shout_clicked() @@ -2168,8 +2102,7 @@ void Courtroom::draw_effect_buttons() // Mark selected button as such if (m_effect_state != 0 && ui_effects.size() > 0) - ui_effects[m_effect_state - 1]->set_image( - effect_names.at(m_effect_state - 1) + "_pressed.png"); + ui_effects[m_effect_state - 1]->set_image(effect_names.at(m_effect_state - 1) + "_pressed.png"); } void Courtroom::on_effect_button_clicked() @@ -2206,8 +2139,7 @@ void Courtroom::on_defense_minus_clicked() int f_state = defense_bar_state - 1; if (f_state >= 0) - ao_app->send_server_packet( - new AOPacket("HP#1#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(new AOPacket("HP#1#" + QString::number(f_state) + "#%")); } void Courtroom::on_defense_plus_clicked() @@ -2215,8 +2147,7 @@ void Courtroom::on_defense_plus_clicked() int f_state = defense_bar_state + 1; if (f_state <= 10) - ao_app->send_server_packet( - new AOPacket("HP#1#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(new AOPacket("HP#1#" + QString::number(f_state) + "#%")); } void Courtroom::on_prosecution_minus_clicked() @@ -2224,8 +2155,7 @@ void Courtroom::on_prosecution_minus_clicked() int f_state = prosecution_bar_state - 1; if (f_state >= 0) - ao_app->send_server_packet( - new AOPacket("HP#2#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(new AOPacket("HP#2#" + QString::number(f_state) + "#%")); } void Courtroom::on_prosecution_plus_clicked() @@ -2233,8 +2163,7 @@ void Courtroom::on_prosecution_plus_clicked() int f_state = prosecution_bar_state + 1; if (f_state <= 10) - ao_app->send_server_packet( - new AOPacket("HP#2#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(new AOPacket("HP#2#" + QString::number(f_state) + "#%")); } void Courtroom::on_text_color_changed(int p_color) @@ -2353,8 +2282,7 @@ void Courtroom::on_char_select_right_clicked() void Courtroom::on_spectator_clicked() { - QString content = - "CC#" + QString::number(ao_app->s_pv) + "#-1#" + get_hdid() + "#%"; + QString content = "CC#" + QString::number(ao_app->s_pv) + "#-1#" + get_hdid() + "#%"; ao_app->send_server_packet(new AOPacket(content)); enter_courtroom(-1); @@ -2368,9 +2296,7 @@ void Courtroom::on_call_mod_clicked() QMessageBox::StandardButton reply; QString warning = "Are you sure you want to call a mod? They will get angry " "at you if the reason is no good."; - reply = - QMessageBox::warning(this, "Warning", warning, - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + reply = QMessageBox::warning(this, "Warning", warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (reply == QMessageBox::Yes) { @@ -2458,8 +2384,7 @@ void Courtroom::on_note_text_changed() void Courtroom::ping_server() { - ao_app->send_server_packet( - new AOPacket("CH#" + QString::number(m_cid) + "#%")); + ao_app->send_server_packet(new AOPacket("CH#" + QString::number(m_cid) + "#%")); } void Courtroom::closeEvent(QCloseEvent *event) @@ -2503,8 +2428,7 @@ void Courtroom::on_sfx_list_clicked(QModelIndex p_index) // This is just the reverse of the above, basically. We set the colour, and we // set the brush to have that colour. - selected_col.setHslF(selected_col.hueF(), selected_col.saturationF(), - final_lightness); + selected_col.setHslF(selected_col.hueF(), selected_col.saturationF(), final_lightness); selected_brush.setColor(selected_col); // Finally, we set the selected SFX's background to be the lightened-up brush. diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 54287120e..bb2728ec4 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -44,24 +44,11 @@ void Courtroom::create_widgets() char_button_mapper = new QSignalMapper(this); - m_effects_player = new AOSfxPlayer(this, ao_app); - m_effects_player->set_volume(ao_config->effects_volume()); - m_shouts_player = new AOShoutPlayer(this, ao_app); - m_shouts_player->set_volume(ao_config->effects_volume()); - connect(ao_config, SIGNAL(effects_volume_changed(int)), this, - SLOT(on_config_effects_volume_changed(int))); - m_system_player = new AOSfxPlayer(this, ao_app); - m_system_player->set_volume(ao_config->system_volume()); - connect(ao_config, SIGNAL(system_volume_changed(int)), this, - SLOT(on_config_system_volume_changed(int))); - m_music_player = new AOMusicPlayer(this, ao_app); - m_music_player->set_volume(ao_config->music_volume()); - connect(ao_config, SIGNAL(music_volume_changed(int)), this, - SLOT(on_config_music_volume_changed(int))); - m_blips_player = new AOBlipPlayer(this, ao_app); - m_blips_player->set_volume(ao_config->blips_volume()); - connect(ao_config, SIGNAL(blips_volume_changed(int)), this, - SLOT(on_config_blips_volume_changed(int))); + m_system_player = new AOSystemPlayer(ao_app, this); + m_effects_player = new AOSfxPlayer(ao_app, this); + m_shouts_player = new AOShoutPlayer(ao_app, this); + m_music_player = new AOMusicPlayer(ao_app, this); + m_blips_player = new AOBlipPlayer(ao_app, this); ui_background = new AOImageDisplay(this, ao_app); @@ -241,56 +228,40 @@ void Courtroom::connect_widgets() { connect(keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); - connect(ao_app, SIGNAL(reload_theme()), this, - SLOT(on_app_reload_theme_requested())); + connect(ao_app, SIGNAL(reload_theme()), this, SLOT(on_app_reload_theme_requested())); connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); - connect(text_delay_timer, SIGNAL(timeout()), this, - SLOT(start_chat_ticking())); + connect(text_delay_timer, SIGNAL(timeout()), this, SLOT(start_chat_ticking())); connect(sfx_delay_timer, SIGNAL(timeout()), this, SLOT(play_sfx())); connect(chat_tick_timer, SIGNAL(timeout()), this, SLOT(chat_tick())); connect(realization_timer, SIGNAL(timeout()), this, SLOT(realization_done())); - connect(testimony_show_timer, SIGNAL(timeout()), this, - SLOT(hide_testimony())); - connect(testimony_hide_timer, SIGNAL(timeout()), this, - SLOT(show_testimony())); + connect(testimony_show_timer, SIGNAL(timeout()), this, SLOT(hide_testimony())); + connect(testimony_hide_timer, SIGNAL(timeout()), this, SLOT(show_testimony())); - connect(ui_emote_left, SIGNAL(clicked()), this, - SLOT(on_emote_left_clicked())); - connect(ui_emote_right, SIGNAL(clicked()), this, - SLOT(on_emote_right_clicked())); + connect(ui_emote_left, SIGNAL(clicked()), this, SLOT(on_emote_left_clicked())); + connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); - connect(ui_emote_dropdown, SIGNAL(activated(int)), this, - SLOT(on_emote_dropdown_changed(int))); - connect(ui_pos_dropdown, SIGNAL(activated(int)), this, - SLOT(on_pos_dropdown_changed(int))); + connect(ui_emote_dropdown, SIGNAL(activated(int)), this, SLOT(on_emote_dropdown_changed(int))); + connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); connect(ui_mute_list, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(on_mute_list_item_changed(QListWidgetItem *))); - connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, - SLOT(on_chat_return_pressed())); - - connect(ui_ooc_chat_name, SIGNAL(textEdited(QString)), ao_config, - SLOT(set_username(QString))); - connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, - SLOT(setText(QString))); - connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, - SLOT(on_ooc_return_pressed())); - - connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, - SLOT(on_music_list_clicked())); - connect(ui_area_list, SIGNAL(clicked(QModelIndex)), this, - SLOT(on_area_list_clicked())); - connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, - SLOT(on_music_list_double_clicked(QModelIndex))); - connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, - SLOT(on_area_list_double_clicked(QModelIndex))); + connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_chat_return_pressed())); + + connect(ui_ooc_chat_name, SIGNAL(textEdited(QString)), ao_config, SLOT(set_username(QString))); + connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); + connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); + + connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_music_list_clicked())); + connect(ui_area_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_area_list_clicked())); + connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_music_list_double_clicked(QModelIndex))); + connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_area_list_double_clicked(QModelIndex))); // connect events for shout/effect/wtce buttons happen in load_shouts(), // load_effects(), load_wtce() @@ -298,67 +269,47 @@ void Courtroom::connect_widgets() connect(ui_shout_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_effect_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - connect(ui_effect_down, SIGNAL(clicked(bool)), this, - SLOT(on_cycle_clicked())); + connect(ui_effect_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_wtce_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_wtce_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_mute, SIGNAL(clicked()), this, SLOT(on_mute_clicked())); - connect(ui_defense_minus, SIGNAL(clicked()), this, - SLOT(on_defense_minus_clicked())); - connect(ui_defense_plus, SIGNAL(clicked()), this, - SLOT(on_defense_plus_clicked())); - connect(ui_prosecution_minus, SIGNAL(clicked()), this, - SLOT(on_prosecution_minus_clicked())); - connect(ui_prosecution_plus, SIGNAL(clicked()), this, - SLOT(on_prosecution_plus_clicked())); - - connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, - SLOT(on_text_color_changed(int))); - - connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, - SLOT(on_chat_config_changed())); - connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, - SLOT(on_chat_config_changed())); - connect(ao_config, SIGNAL(log_uses_newline_changed(bool)), this, - SLOT(on_chat_config_changed())); - - connect(ui_music_search, SIGNAL(textChanged(QString)), this, - SLOT(on_music_search_edited(QString))); - connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, - SLOT(on_sfx_search_edited(QString))); - - connect(ui_change_character, SIGNAL(clicked()), this, - SLOT(on_change_character_clicked())); + connect(ui_defense_minus, SIGNAL(clicked()), this, SLOT(on_defense_minus_clicked())); + connect(ui_defense_plus, SIGNAL(clicked()), this, SLOT(on_defense_plus_clicked())); + connect(ui_prosecution_minus, SIGNAL(clicked()), this, SLOT(on_prosecution_minus_clicked())); + connect(ui_prosecution_plus, SIGNAL(clicked()), this, SLOT(on_prosecution_plus_clicked())); + + connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, SLOT(on_text_color_changed(int))); + + connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_uses_newline_changed(bool)), this, SLOT(on_chat_config_changed())); + + connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); + connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited(QString))); + + connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); - connect(ui_switch_area_music, SIGNAL(clicked()), this, - SLOT(on_switch_area_music_clicked())); + connect(ui_switch_area_music, SIGNAL(clicked()), this, SLOT(on_switch_area_music_clicked())); - connect(ui_config_panel, SIGNAL(clicked()), this, - SLOT(on_config_panel_clicked())); - connect(ui_note_button, SIGNAL(clicked()), this, - SLOT(on_note_button_clicked())); + connect(ui_config_panel, SIGNAL(clicked()), this, SLOT(on_config_panel_clicked())); + connect(ui_note_button, SIGNAL(clicked()), this, SLOT(on_note_button_clicked())); - connect(ui_vp_notepad, SIGNAL(textChanged()), this, - SLOT(on_note_text_changed())); + connect(ui_vp_notepad, SIGNAL(textChanged()), this, SLOT(on_note_text_changed())); connect(ui_pre, SIGNAL(clicked()), this, SLOT(on_pre_clicked())); connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); - connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, - SLOT(on_sfx_list_clicked(QModelIndex))); + connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_sfx_list_clicked(QModelIndex))); - connect(ui_evidence_button, SIGNAL(clicked()), this, - SLOT(on_evidence_button_clicked())); + connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); - connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, - SLOT(on_add_button_clicked())); - connect(ui_set_notes, SIGNAL(clicked(bool)), this, - SLOT(on_set_notes_clicked())); + connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, SLOT(on_add_button_clicked())); + connect(ui_set_notes, SIGNAL(clicked(bool)), this, SLOT(on_set_notes_clicked())); } void Courtroom::reset_widget_names() @@ -457,8 +408,7 @@ void Courtroom::insert_widget_name(QString p_widget_name, QWidget *p_widget) p_widget->setObjectName(p_widget_name); } -void Courtroom::insert_widget_names(QVector &p_widget_names, - QVector &p_widgets) +void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) { for (int i = 0; i < p_widgets.length(); ++i) insert_widget_name(p_widget_names[i], p_widgets[i]); @@ -577,8 +527,7 @@ void Courtroom::set_widget_layers() void Courtroom::set_widgets() { QString filename = design_ini; - pos_size_type f_courtroom = - ao_app->get_element_dimensions("courtroom", filename); + pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", filename); if (f_courtroom.width < 0 || f_courtroom.height < 0) { @@ -681,8 +630,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_vp_clock, "clock"); ui_vp_clock->hide(); - ui_ic_chat_message->setStyleSheet( - "QLineEdit{background-color: rgba(100, 100, 100, 255);}"); + ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); ui_vp_chatbox->set_image("chatmed.png"); ui_vp_chatbox->hide(); @@ -718,12 +666,10 @@ void Courtroom::set_widgets() set_size_and_pos(ui_pos_dropdown, "pos_dropdown"); set_size_and_pos(ui_defense_bar, "defense_bar"); - ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + - ".png"); + ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + ".png"); set_size_and_pos(ui_prosecution_bar, "prosecution_bar"); - ui_prosecution_bar->set_image( - "prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); + ui_prosecution_bar->set_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); // set_size_and_pos(ui_shouts[0], "hold_it"); // ui_shouts[0]->show(); @@ -752,8 +698,7 @@ void Courtroom::set_widgets() ui_shout_down->hide(); // courtroom_config.ini necessary + check for crash - if (ao_app->read_theme_ini("enable_single_shout", cc_config_ini) == "true" && - ui_shouts.size() > 0) + if (ao_app->read_theme_ini("enable_single_shout", cc_config_ini) == "true" && ui_shouts.size() > 0) { for (auto &shout : ui_shouts) move_widget(shout, "bullet"); @@ -805,8 +750,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_wtce[i], wtce_names[i]); } - if (ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == - "true") // courtroom_config.ini necessary + if (ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true") // courtroom_config.ini necessary { for (auto &wtce : ui_wtce) move_widget(wtce, "wtce"); @@ -872,8 +816,7 @@ void Courtroom::set_widgets() // behavior will occur if the button is hidden due to 'config_panel' not being // found in courtroom_design.ini This is to assist with people who switch to // incompatible and/or smaller themes and have the button disappear - if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || - !ui_config_panel->isVisible()) + if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || !ui_config_panel->isVisible()) { ui_config_panel->setVisible(true); ui_config_panel->move(0, 0); @@ -1013,8 +956,7 @@ void Courtroom::set_widgets() note_scroll_area->setWidget(ui_note_area); ui_note_area->set_image("note_area.png"); ui_note_area->add_button->set_image("add_button.png"); - ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, - QSizePolicy::Fixed); + ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); ui_note_area->setLayout(ui_note_area->m_layout); ui_note_area->show(); note_scroll_area->hide(); @@ -1036,8 +978,7 @@ void Courtroom::set_size_and_pos(QWidget *p_widget, QString p_identifier) { QString filename = design_ini; - pos_size_type design_ini_result = - ao_app->get_element_dimensions(p_identifier, filename); + pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); if (design_ini_result.width < 0 || design_ini_result.height < 0) { @@ -1055,8 +996,7 @@ void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) { QString filename = design_ini; - pos_size_type design_ini_result = - ao_app->get_element_dimensions(p_identifier, filename); + pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); if (design_ini_result.width < 0 || design_ini_result.height < 0) { @@ -1070,15 +1010,12 @@ void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) } template -int Courtroom::adapt_numbered_items(QVector &item_vector, - QString config_item_number, - QString item_name) +int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_item_number, QString item_name) { // &item_vector must be a vector of size at least 1! // Redraw the new correct number of items. - int new_item_number = - ao_app->read_theme_ini(config_item_number, cc_config_ini).toInt(); + int new_item_number = ao_app->read_theme_ini(config_item_number, cc_config_ini).toInt(); int current_item_number = item_vector.size(); // Note we use the fact that, if config_item_number is not there, // read_theme_ini returns an empty string, which .toInt() would fail to @@ -1132,12 +1069,10 @@ void Courtroom::check_effects() for (int i = 0; i < ui_effects.size(); ++i) { - QString path = ao_app->find_asset_path( - {ao_app->get_character_path(current_char, effect_names.at(i))}, - animated_extensions()); + QString path = + ao_app->find_asset_path({ao_app->get_character_path(current_char, effect_names.at(i))}, animated_extensions()); if (path.isEmpty()) - path = ao_app->find_theme_asset_path(effect_names.at(i), - animated_extensions()); + path = ao_app->find_theme_asset_path(effect_names.at(i), animated_extensions()); effects_enabled[i] = (!path.isEmpty()); } } @@ -1153,12 +1088,10 @@ void Courtroom::check_free_blocks() for (int i = 0; i < ui_free_blocks.size(); ++i) { - QString path = ao_app->find_asset_path( - {ao_app->get_character_path(current_char, free_block_names.at(i))}, - animated_extensions()); - if (path.isEmpty()) - path = ao_app->find_theme_asset_path(free_block_names.at(i), + QString path = ao_app->find_asset_path({ao_app->get_character_path(current_char, free_block_names.at(i))}, animated_extensions()); + if (path.isEmpty()) + path = ao_app->find_theme_asset_path(free_block_names.at(i), animated_extensions()); free_blocks_enabled[i] = (!path.isEmpty()); } } @@ -1174,13 +1107,11 @@ void Courtroom::check_shouts() for (int i = 0; i < ui_shouts.size(); ++i) { - QString path = ao_app->find_asset_path( - {ao_app->get_character_path(current_char, shout_names.at(i))}, - animated_extensions()); + QString path = + ao_app->find_asset_path({ao_app->get_character_path(current_char, shout_names.at(i))}, animated_extensions()); if (path.isEmpty()) - path = ao_app->find_theme_asset_path(shout_names.at(i), - animated_extensions()); + path = ao_app->find_theme_asset_path(shout_names.at(i), animated_extensions()); shouts_enabled[i] = (!path.isEmpty()); } @@ -1197,12 +1128,10 @@ void Courtroom::check_wtce() for (int i = 0; i < ui_wtce.size(); ++i) { - QString path = ao_app->find_asset_path( - {ao_app->get_character_path(current_char, wtce_names.at(i))}, - animated_extensions()); + QString path = + ao_app->find_asset_path({ao_app->get_character_path(current_char, wtce_names.at(i))}, animated_extensions()); if (path.isEmpty()) - path = ao_app->find_theme_asset_path(wtce_names.at(i), - animated_extensions()); + path = ao_app->find_theme_asset_path(wtce_names.at(i), animated_extensions()); wtce_enabled[i] = (!path.isEmpty()); } } @@ -1220,8 +1149,7 @@ void Courtroom::delete_widget(QWidget *p_widget) grand_parent = this; // set new parent - for (QWidget *child : - p_widget->findChildren(QString(), Qt::FindDirectChildrenOnly)) + for (QWidget *child : p_widget->findChildren(QString(), Qt::FindDirectChildrenOnly)) child->setParent(grand_parent); // delete widget @@ -1235,8 +1163,7 @@ void Courtroom::load_effects() delete_widget(widget); // And create new effects - int effect_number = - ao_app->read_theme_ini("effect_number", cc_config_ini).toInt(); + int effect_number = ao_app->read_theme_ini("effect_number", cc_config_ini).toInt(); effects_enabled.resize(effect_number); ui_effects.resize(effect_number); @@ -1250,8 +1177,7 @@ void Courtroom::load_effects() // And connect their actions for (auto &effect : ui_effects) - connect(effect, SIGNAL(clicked(bool)), this, - SLOT(on_effect_button_clicked())); + connect(effect, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked())); // And add names effect_names.clear(); @@ -1272,8 +1198,7 @@ void Courtroom::load_free_blocks() delete_widget(widget); // And create new free block buttons - int free_block_number = - ao_app->read_theme_ini("free_block_number", cc_config_ini).toInt(); + int free_block_number = ao_app->read_theme_ini("free_block_number", cc_config_ini).toInt(); free_blocks_enabled.resize(free_block_number); ui_free_blocks.resize(free_block_number); @@ -1289,8 +1214,7 @@ void Courtroom::load_free_blocks() free_block_names.clear(); for (int i = 1; i <= ui_free_blocks.size(); ++i) { - QString name = - "free_block_" + ao_app->get_spbutton("[FREE BLOCKS]", i).trimmed(); + QString name = "free_block_" + ao_app->get_spbutton("[FREE BLOCKS]", i).trimmed(); if (!name.isEmpty()) { free_block_names.append(name); @@ -1306,8 +1230,7 @@ void Courtroom::load_shouts() delete_widget(widget); // And create new shouts - int shout_number = - ao_app->read_theme_ini("shout_number", cc_config_ini).toInt(); + int shout_number = ao_app->read_theme_ini("shout_number", cc_config_ini).toInt(); shouts_enabled.resize(shout_number); ui_shouts.resize(shout_number); @@ -1345,8 +1268,7 @@ void Courtroom::load_wtce() delete_widget(widget); // And create new wtce buttons - int wtce_number = - ao_app->read_theme_ini("wtce_number", cc_config_ini).toInt(); + int wtce_number = ao_app->read_theme_ini("wtce_number", cc_config_ini).toInt(); wtce_enabled.resize(wtce_number); ui_wtce.resize(wtce_number); @@ -1414,8 +1336,7 @@ void Courtroom::set_judge_wtce() wtce->hide(); // check if we use a single wtce or multiple - const bool is_single_wtce = - ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true"; + const bool is_single_wtce = ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true"; // update visibility for next/previous ui_wtce_up->setVisible(is_judge && is_single_wtce); @@ -1469,8 +1390,7 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier) set_font(widget, p_identifier, ""); } -void Courtroom::set_font(QWidget *widget, QString p_identifier, - QString override_color) +void Courtroom::set_font(QWidget *widget, QString p_identifier, QString override_color) { QString design_file = fonts_ini; QString class_name = widget->metaObject()->className(); @@ -1491,8 +1411,7 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, if (override_color.isEmpty()) { - QString color = - ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); + QString color = ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); if (color.isEmpty()) color = "255, 255, 255"; override_color = "rgba(" + color + ", 255)"; @@ -1501,9 +1420,7 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, int bold = ao_app->get_font_property(p_identifier + "_bold", design_file); QString is_bold = (bold == 1 ? "bold" : ""); - QString style_sheet_string = class_name + - " { background-color: rgba(0, 0, 0, 0);\n" + - "color: " + override_color + + QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: " + override_color + ";\n" "font: " + is_bold + "; }"; @@ -1515,8 +1432,7 @@ void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier) set_qtextedit_font(widget, p_identifier, ""); } -void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier, - QString override_color) +void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier, QString override_color) { set_font(widget, p_identifier, override_color); diff --git a/src/draudio.cpp b/src/draudio.cpp new file mode 100644 index 000000000..299134dbb --- /dev/null +++ b/src/draudio.cpp @@ -0,0 +1,95 @@ +#include "draudio.h" + +#include + +QString DRAudio::get_bass_error(int32_t p_error_code) +{ + switch (p_error_code) + { + case BASS_OK: + return "all is OK"; + case BASS_ERROR_MEM: + return "memory error"; + case BASS_ERROR_FILEOPEN: + return "can't open the file"; + case BASS_ERROR_DRIVER: + return "can't find a free/valid driver"; + case BASS_ERROR_BUFLOST: + return "the sample buffer was lost"; + case BASS_ERROR_HANDLE: + return "invalid handle"; + case BASS_ERROR_FORMAT: + return "unsupported sample format"; + case BASS_ERROR_POSITION: + return "invalid position"; + case BASS_ERROR_INIT: + return "BASS_Init has not been successfully called"; + case BASS_ERROR_START: + return "BASS_Start has not been successfully called"; + case BASS_ERROR_SSL: + return "SSL/HTTPS support isn't available"; + case BASS_ERROR_ALREADY: + return "already initialized/paused/whatever"; + case BASS_ERROR_NOTAUDIO: + return "file does not contain audio"; + case BASS_ERROR_NOCHAN: + return "can't get a free channel"; + case BASS_ERROR_ILLTYPE: + return "an illegal type was specified"; + case BASS_ERROR_ILLPARAM: + return "an illegal parameter was specified"; + case BASS_ERROR_NO3D: + return "no 3D support"; + case BASS_ERROR_NOEAX: + return "no EAX support"; + case BASS_ERROR_DEVICE: + return "illegal device number"; + case BASS_ERROR_NOPLAY: + return "not playing"; + case BASS_ERROR_FREQ: + return "illegal sample rate"; + case BASS_ERROR_NOTFILE: + return "the stream is not a file stream"; + case BASS_ERROR_NOHW: + return "no hardware voices available"; + case BASS_ERROR_EMPTY: + return "the MOD music has no sequence data"; + case BASS_ERROR_NONET: + return "no internet connection could be opened"; + case BASS_ERROR_CREATE: + return "couldn't create the file"; + case BASS_ERROR_NOFX: + return "effects are not available"; + case BASS_ERROR_NOTAVAIL: + return "requested data/action is not available"; + case BASS_ERROR_DECODE: + return "the channel is/isn't a \"decoding channel\""; + case BASS_ERROR_DX: + return "a sufficient DirectX version is not installed"; + case BASS_ERROR_TIMEOUT: + return "connection timedout"; + case BASS_ERROR_FILEFORM: + return "unsupported file format"; + case BASS_ERROR_SPEAKER: + return "unavailable speaker"; + case BASS_ERROR_VERSION: + return "invalid BASS version (used by add-ons)"; + case BASS_ERROR_CODEC: + return "codec is not available/supported"; + case BASS_ERROR_ENDED: + return "the channel/file has ended"; + case BASS_ERROR_BUSY: + return "the device is busy"; + case BASS_ERROR_UNSTREAMABLE: + return "unstreamable file"; + case BASS_ERROR_UNKNOWN: + return "some other mystery problem"; + default: + return "unknown error"; + } +} + +QString DRAudio::get_last_bass_error() +{ + return get_bass_error(BASS_ErrorGetCode()); +} diff --git a/src/draudioengine.cpp b/src/draudioengine.cpp new file mode 100644 index 000000000..88fe89562 --- /dev/null +++ b/src/draudioengine.cpp @@ -0,0 +1,131 @@ +#include "draudioengine.h" + +#include +#include +#include +#include + +class DRAudioEnginePrivate : public QObject +{ + Q_OBJECT + +public: + DRAudioEnginePrivate(QObject *parent = nullptr) : QObject(parent) + {} + + void invoke_signal(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)) + { + for (QObject *i_parent : parents) + { + QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), p_arg1); + } + } + +private: + friend class DRAudioEngine; + + QObjectList parents; + std::int32_t volume = 0; + DRAudio::Options options; + QMap family_map; +}; + +namespace +{ +static QPointer d; +} + +DRAudioEngine::DRAudioEngine(QObject *p_parent) : QObject(p_parent) +{ + if (BOOL r = BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, 0, NULL); r == FALSE) + qWarning() << DRAudioError(QString("failed to init: %1").arg(DRAudio::get_last_bass_error())).get_error(); + + if (d == nullptr) + { + Q_ASSERT_X(qApp, "initialization", "QApplication is required"); + d = new DRAudioEnginePrivate(qApp); + + // there's plenty of way of optimizing this + d->family_map.insert(DRAudio::Family::FSystem, family_ptr(new DRAudioStreamFamily(DRAudio::Family::FSystem))); + d->family_map.insert(DRAudio::Family::FEffect, family_ptr(new DRAudioStreamFamily(DRAudio::Family::FEffect))); + d->family_map.insert(DRAudio::Family::FMusic, family_ptr(new DRAudioStreamFamily(DRAudio::Family::FMusic))); + d->family_map.insert(DRAudio::Family::FBlip, family_ptr(new DRAudioStreamFamily(DRAudio::Family::FBlip))); + } + + d->parents.append(this); +} + +DRAudioEngine::~DRAudioEngine() +{ + d->parents.removeAll(this); + + BASS_Free(); +} + +DRAudioEngine::family_ptr DRAudioEngine::get_family(DRAudio::Family p_family) +{ + return d->family_map.value(p_family); +} + +QList DRAudioEngine::get_family_list() +{ + return d->family_map.values(); +} + +int32_t DRAudioEngine::get_volume() +{ + return d->volume; +} + +DRAudio::Options DRAudioEngine::get_options() +{ + return d->options; +} + +bool DRAudioEngine::is_suppressed() +{ + return d->options.testFlag(DRAudio::OSuppressed); +} + +void DRAudioEngine::set_volume(int32_t p_volume) +{ + p_volume = std::clamp(p_volume, 0, 100); + + if (d->volume == p_volume) + return; + + d->volume = p_volume; + adjust_volume(); + Q_EMIT d->invoke_signal("volume_changed", Q_ARG(std::int32_t, d->volume)); +} + +void DRAudioEngine::set_options(DRAudio::Options p_options) +{ + if (d->options == p_options) + return; + + d->options = p_options; + adjust_options(); + Q_EMIT d->invoke_signal("options_changed", Q_ARG(DRAudio::Options, d->options)); +} + +void DRAudioEngine::set_suppressed(bool p_enabled) +{ + DRAudio::Options options = d->options; + options.setFlag(DRAudio::OSuppressed, p_enabled); + set_options(options); +} + +void DRAudioEngine::adjust_options() +{ + adjust_volume(); +} + +void DRAudioEngine::adjust_volume() +{ + for (auto &i_group : d->family_map.values()) + i_group->adjust_volume(); +} + +// needed by moc +#include "draudioengine.moc" diff --git a/src/draudioerror.cpp b/src/draudioerror.cpp new file mode 100644 index 000000000..56007f91d --- /dev/null +++ b/src/draudioerror.cpp @@ -0,0 +1,12 @@ +#include "draudioerror.h" + +DRAudioError::DRAudioError() +{} + +DRAudioError::DRAudioError(QString p_error) : m_error(QString("[bass]%1").arg(p_error)) +{} + +QString DRAudioError::get_error() +{ + return m_error; +} diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp new file mode 100644 index 000000000..7cbea28a9 --- /dev/null +++ b/src/draudiostream.cpp @@ -0,0 +1,116 @@ +#include "draudiostream.h" + +#include "draudioengine.h" +#include "draudiostreamfamily.h" + +#include +#include + +DRAudioStream::DRAudioStream(DRAudio::Family p_family) : m_family(p_family) +{} + +DRAudioStream::~DRAudioStream() +{ + if (m_hstream.has_value()) + { + const HSTREAM hstream = m_hstream.value(); + + while (!m_hsync_stack.empty()) + { + BASS_ChannelRemoveSync(hstream, m_hsync_stack.top()); + m_hsync_stack.pop(); + } + + BASS_StreamFree(hstream); + } +} + +DRAudio::Family DRAudioStream::get_family() +{ + return m_family; +} + +std::optional DRAudioStream::get_file() +{ + return m_file; +} + +bool DRAudioStream::is_playing() +{ + if (!m_hstream) + return false; + return BASS_ChannelIsActive(m_hstream.value()) == BASS_ACTIVE_PLAYING; +} + +void DRAudioStream::play() +{ + if (!m_hstream) + return; + const BOOL result = BASS_ChannelPlay(m_hstream.value(), FALSE); + if (result == FALSE) + { + qWarning() << DRAudioError( + QString("failed to play file %1: %2").arg(m_file.value()).arg(DRAudio::get_last_bass_error())) + .get_error(); + Q_EMIT finished(); + } +} + +void DRAudioStream::stop() +{ + if (!m_hstream) + return; + BASS_ChannelStop(m_hstream.value()); + Q_EMIT finished(); +} + +std::optional DRAudioStream::set_file(QString p_file) +{ + if (m_file.has_value()) + return DRAudioError("file already set"); + + const QFileInfo file(p_file); + if (!file.exists()) + return DRAudioError("file does not exist"); + + if (m_file == p_file) + return std::nullopt; + + m_file = p_file; + + HSTREAM stream_handle = BASS_StreamCreateFile(FALSE, m_file->utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); + if (stream_handle == 0) + return DRAudioError( + QString("failed to create stream for file %1: %2").arg(p_file).arg(DRAudio::get_last_bass_error())); + m_hstream = stream_handle; + + // bass events + HSYNC sync_handle = BASS_ChannelSetSync(stream_handle, BASS_SYNC_END, 0, &DRAudioStream::sync_on_end, this); + if (sync_handle == 0) + return DRAudioError( + QString("failed to create sync for file %1: %2").arg(p_file).arg(DRAudio::get_last_bass_error())); + + Q_EMIT file_changed(p_file); + return std::nullopt; +} + +void DRAudioStream::set_volume(int32_t p_volume) +{ + if (!m_hstream) + return; + BASS_ChannelSetAttribute(m_hstream.value(), BASS_ATTRIB_VOL, float(p_volume) * 0.01f); +} + +void DRAudioStream::sync_on_end(HSYNC hsync, DWORD ch, DWORD data, void *userdata) +{ + Q_UNUSED(hsync) + Q_UNUSED(ch) + Q_UNUSED(data) + + /* + * BASS only provides what we fed it when we first created our synch even if + * the pointer we provided get deleted mid-way through the program lifetime + */ + DRAudioStream *self = static_cast(userdata); + Q_EMIT self->finished(); +} diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp new file mode 100644 index 000000000..3be523458 --- /dev/null +++ b/src/draudiostreamfamily.cpp @@ -0,0 +1,164 @@ +#include "draudiostreamfamily.h" + +#include "draudioengine.h" + +#include + +DRAudioStreamFamily::DRAudioStreamFamily(DRAudio::Family p_family) : m_family(p_family) +{} + +int32_t DRAudioStreamFamily::get_capacity() +{ + return m_capacity; +} + +DRAudio::Options DRAudioStreamFamily::get_options() +{ + return m_options; +} + +bool DRAudioStreamFamily::is_suppressed() +{ + return m_options.testFlag(DRAudio::OSuppressed); +} + +DRAudioStreamFamily::iterator DRAudioStreamFamily::begin() +{ + return m_stream_list.begin(); +} + +DRAudioStreamFamily::iterator DRAudioStreamFamily::end() +{ + return m_stream_list.end(); +} + +void DRAudioStreamFamily::set_capacity(int32_t p_capacity) +{ + p_capacity = std::max(p_capacity, 0); + + if (m_capacity == p_capacity) + return; + + m_capacity = p_capacity; + adjust_capacity(); + Q_EMIT capacity_changed(m_capacity); +} + +void DRAudioStreamFamily::set_options(DRAudio::Options p_options) +{ + if (m_options == p_options) + return; + + m_options = p_options; + adjust_options(); + Q_EMIT options_changed(m_options); +} + +void DRAudioStreamFamily::set_volume(int32_t p_volume) +{ + p_volume = std::clamp(p_volume, 0, 100); + + if (m_volume == p_volume) + return; + + m_volume = p_volume; + adjust_volume(); + Q_EMIT volume_changed(m_volume); +} + +void DRAudioStreamFamily::set_suppressed(bool p_enabled) +{ + DRAudio::Options options = m_options; + options.setFlag(DRAudio::OSuppressed, p_enabled); + set_options(options); +} + +std::optional DRAudioStreamFamily::create_stream(QString p_file) +{ + stream_ptr stream(new DRAudioStream(m_family)); + + if (auto err = stream->set_file(p_file); err) + { + qWarning() << err->get_error(); + return std::nullopt; + } + + m_stream_list.append(stream); + adjust_capacity(); + + stream->set_volume(calculate_volume()); + connect(stream.get(), SIGNAL(finished()), this, SLOT(on_stream_stopped())); + + return stream; +} + +std::optional DRAudioStreamFamily::play_stream(QString p_file) +{ + std::optional r_stream = create_stream(p_file); + if (r_stream.has_value()) + { + auto stream = r_stream.value(); + qWarning() << "playing" << stream->get_file().value(); + stream->play(); + } + return r_stream; +} + +QVector DRAudioStreamFamily::get_stream_list() +{ + return m_stream_list; +} + +int32_t DRAudioStreamFamily::calculate_volume() +{ + float volume = float(m_volume) * 0.01f; + + if (is_suppressed() || DRAudioEngine::is_suppressed()) + { + volume = 0; + } + else + { + // master volume adjustment + volume = volume * (float(DRAudioEngine::get_volume()) * 0.01f); + } + + return volume * 100.f; +} + +void DRAudioStreamFamily::adjust_capacity() +{ + if (m_capacity == 0) + return; + while (m_capacity < m_stream_list.length()) + m_stream_list.removeFirst(); +} + +void DRAudioStreamFamily::adjust_options() +{ + // suppressed + adjust_volume(); +} + +void DRAudioStreamFamily::adjust_volume() +{ + const float volume = calculate_volume(); + for (auto &stream : m_stream_list) + stream->set_volume(volume); +} + +void DRAudioStreamFamily::on_stream_stopped() +{ + DRAudioStream *invoker = dynamic_cast(sender()); + if (invoker == nullptr) + return; + + decltype(m_stream_list) new_stream_list; + for (auto &i_stream : m_stream_list) + { + if (i_stream.get() == invoker) + continue; + new_stream_list.append(std::move(i_stream)); + } + m_stream_list = std::move(new_stream_list); +} diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 9fc436a88..6e1308c1c 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -42,12 +42,12 @@ int AOApplication::get_default_music() int AOApplication::get_default_sfx() { - return config->effects_volume(); + return config->effect_volume(); } int AOApplication::get_default_blip() { - return config->blips_volume(); + return config->blip_volume(); } QStringList AOApplication::get_callwords() From b31060e470e8ccf3576b024452f604745c7bb198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Thu, 11 Feb 2021 12:01:56 +0100 Subject: [PATCH 194/842] Added tooltip for disable background audio checkbox --- res/ui/config_panel.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index ef0a04f09..c28a158d7 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -503,6 +503,9 @@ + + Allow the program to temporarily disable sounds when out of focus. + Disable Background Audio From a22780bf2d5150ff57eb5b539079a117ef2ef314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Thu, 11 Feb 2021 22:45:56 +0100 Subject: [PATCH 195/842] Fixed config_panel defaulting to audio tab --- res/ui/config_panel.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index c28a158d7..4c5ca3998 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 3 + 0 From 8ac02f8db3863acefd81c66e0238a15f20147717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Thu, 11 Feb 2021 23:35:37 +0100 Subject: [PATCH 196/842] Tweaked slider size to be slightly taller by default --- res/ui/config_panel.ui | 100 ++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 47 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 4c5ca3998..cac45a09c 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -116,9 +116,6 @@ - - Qt::Vertical - 20 @@ -130,18 +127,6 @@ - - - 0 - 0 - - - - - 429 - 284 - - Game @@ -285,9 +270,6 @@ - - Qt::Vertical - 20 @@ -433,10 +415,7 @@ - - - Qt::Vertical - + 20 @@ -469,6 +448,12 @@ 0 + + + 0 + 20 + + 100 @@ -501,7 +486,7 @@ - + Allow the program to temporarily disable sounds when out of focus. @@ -511,21 +496,14 @@ - - - - Qt::Horizontal - - - - + System: - + @@ -535,6 +513,12 @@ 0 + + + 0 + 20 + + 100 @@ -567,14 +551,14 @@ - + Music: - + @@ -584,6 +568,12 @@ 0 + + + 0 + 20 + + 100 @@ -616,14 +606,14 @@ - + Effects: - + @@ -633,6 +623,12 @@ 0 + + + 0 + 20 + + 100 @@ -659,14 +655,14 @@ - + Blips: - + @@ -676,6 +672,12 @@ 0 + + + 0 + 20 + + 100 @@ -702,14 +704,14 @@ - + Blip rate: - + 1 @@ -722,14 +724,14 @@ - + Blank blips: - + @@ -739,11 +741,8 @@ - - - - Qt::Vertical - + + 20 @@ -752,6 +751,13 @@ + + + + Qt::Horizontal + + + From 9838f9ac36b419e41d0743225b413a935f63eaf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 12 Feb 2021 01:12:52 +0100 Subject: [PATCH 197/842] Formatting --- .clang-format | 3 +- include/aoapplication.h | 6 +- include/aobasshandle.h | 6 +- include/aoevidencebutton.h | 3 +- include/aotextarea.h | 3 +- include/courtroom.h | 26 ++-- include/datatypes.h | 3 +- src/aoabstractplayer.cpp | 3 +- src/aoapplication.cpp | 30 ++--- src/aobasshandle.cpp | 3 +- src/aoblipplayer.cpp | 3 +- src/aobutton.cpp | 6 +- src/aocharbutton.cpp | 4 +- src/aocharmovie.cpp | 3 +- src/aoconfig.cpp | 10 +- src/aoconfigpanel.cpp | 177 +++++++++---------------- src/aoemotebutton.cpp | 32 ++--- src/aoevidencebutton.cpp | 4 +- src/aoevidencedescription.cpp | 3 +- src/aoevidencedisplay.cpp | 12 +- src/aomovie.cpp | 27 ++-- src/aomusicplayer.cpp | 6 +- src/aonotearea.cpp | 15 +-- src/aonotepad.cpp | 3 +- src/aonotepicker.cpp | 9 +- src/aopacket.cpp | 10 +- src/aopixmap.cpp | 12 +- src/aoscene.cpp | 6 +- src/aosfxplayer.cpp | 3 +- src/aoshoutplayer.cpp | 6 +- src/aotextarea.cpp | 9 +- src/aotimer.cpp | 3 +- src/charselect.cpp | 42 ++---- src/courtroom.cpp | 212 ++++++++++-------------------- src/courtroom_widgets.cpp | 241 ++++++++++++---------------------- src/discord_rich_presence.cpp | 6 +- src/emotes.cpp | 21 +-- src/evidence.cpp | 65 +++------ src/hardware_functions.cpp | 3 +- src/lobby.cpp | 70 ++++------ src/networkmanager.cpp | 51 +++---- src/packet_distribution.cpp | 79 ++++------- src/path_functions.cpp | 25 ++-- src/text_file_functions.cpp | 50 +++---- 44 files changed, 448 insertions(+), 866 deletions(-) diff --git a/.clang-format b/.clang-format index 2788dd839..403e745ff 100644 --- a/.clang-format +++ b/.clang-format @@ -6,4 +6,5 @@ AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: None AllowShortLoopsOnASingleLine: false -AllowShortEnumsOnASingleLine: false \ No newline at end of file +AllowShortEnumsOnASingleLine: false +ColumnLimit: 120 \ No newline at end of file diff --git a/include/aoapplication.h b/include/aoapplication.h index 8d63eded8..dfc07bdfb 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -130,8 +130,7 @@ class AOApplication : public QApplication * @return The first case-sensitive root+extension path for which a file * exists, or an empty string, if not one does. */ - QString find_asset_path(QStringList possible_roots, - QStringList possible_exts = {""}); + QString find_asset_path(QStringList possible_roots, QStringList possible_exts = {""}); /** * @brief Returns the first case-sensitive file in the theme folder that is @@ -351,8 +350,7 @@ class AOApplication : public QApplication QString get_sfx(QString p_identifier); // Returns the value of p_search_line within target_tag and terminator_tag - QString read_char_ini(QString p_char, QString p_search_line, - QString target_tag, QString terminator_tag); + QString read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag); // Returns the text between target_tag and terminator_tag in p_file QString get_stylesheet(QString target_tag, QString p_file); diff --git a/include/aobasshandle.h b/include/aobasshandle.h index 554f9289a..16678c4b5 100644 --- a/include/aobasshandle.h +++ b/include/aobasshandle.h @@ -19,16 +19,14 @@ class AOBassHandle : public QObject public: AOBassHandle(QObject *p_parent = nullptr); - AOBassHandle(QString p_file, bool p_suicide, - QObject *p_parent = nullptr) noexcept(false); + AOBassHandle(QString p_file, bool p_suicide, QObject *p_parent = nullptr) noexcept(false); ~AOBassHandle(); QString get_file(); void set_file(QString p_file, bool p_suicide = false) noexcept(false); // static - static void CALLBACK static_sync(HSYNC handle, DWORD channel, DWORD data, - void *user); + static void CALLBACK static_sync(HSYNC handle, DWORD channel, DWORD data, void *user); public slots: void play(); diff --git a/include/aoevidencebutton.h b/include/aoevidencebutton.h index 0aab424cc..5e06bb891 100644 --- a/include/aoevidencebutton.h +++ b/include/aoevidencebutton.h @@ -12,8 +12,7 @@ class AOEvidenceButton : public QPushButton Q_OBJECT public: - AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, - int p_y); + AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y); void reset(); void set_image(QString p_image); diff --git a/include/aotextarea.h b/include/aotextarea.h index 7fcd853a4..32635fdb0 100644 --- a/include/aotextarea.h +++ b/include/aotextarea.h @@ -14,8 +14,7 @@ class AOTextArea : public QTextBrowser private: const QRegExp omnis_dank_url_regex = QRegExp("\\b(https?://\\S+\\.\\S+)\\b"); - void auto_scroll(QTextCursor old_cursor, int scrollbar_value, - bool is_scrolled_down); + void auto_scroll(QTextCursor old_cursor, int scrollbar_value, bool is_scrolled_down); }; #endif // AOTEXTAREA_H diff --git a/include/courtroom.h b/include/courtroom.h index a1d600996..d801830f4 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -99,8 +99,7 @@ class Courtroom : public QMainWindow // sets font properties for QTextEdit (same as above but also text outline) void set_qtextedit_font(QTextEdit *widget, QString p_identifier); // same as second set_font but for qtextedit - void set_qtextedit_font(QTextEdit *widget, QString p_identifier, - QString override_color); + void set_qtextedit_font(QTextEdit *widget, QString p_identifier, QString override_color); // helper function that calls above function on the relevant widgets void set_fonts(); @@ -224,8 +223,7 @@ class Courtroom : public QMainWindow // selected // or the user isn't already scrolled to the top void update_ic_log(bool p_reset_log); - void append_ic_text(QString p_name, QString p_line, bool p_system, - bool p_music); + void append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music); /** * @brief Appends a message arriving from system to the IC chatlog. @@ -285,8 +283,7 @@ class Courtroom : public QMainWindow void pause_timer(int timer_id); template - int adapt_numbered_items(QVector &item_vector, - QString config_item_number, QString item_name); + int adapt_numbered_items(QVector &item_vector, QString config_item_number, QString item_name); signals: void closing(); @@ -348,8 +345,7 @@ class Courtroom : public QMainWindow QTimer *testimony_hide_timer = nullptr; // Generate a File Name based on the time you launched the client - QString icchatlogsfilename = QDateTime::currentDateTime().toString( - "'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); + QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); // configuration files locations QString rpc_ini = "configs/rpccharlist.ini"; @@ -600,8 +596,7 @@ class Courtroom : public QMainWindow QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip QVector ui_label_images; - QVector label_images = {"Pre", "Flip", "Hidden", - "Music", "SFX", "Blip"}; + QVector label_images = {"Pre", "Flip", "Hidden", "Music", "SFX", "Blip"}; AOButton *ui_effect_flash = nullptr; AOButton *ui_effect_gloom = nullptr; @@ -656,11 +651,8 @@ class Courtroom : public QMainWindow void set_widget_names(); void reset_widget_names(); void insert_widget_name(QString p_widget_name, QWidget *p_widget); - void insert_widget_names(QVector &p_widget_names, - QVector &p_widgets); - template - void insert_widget_names(QVector &p_widget_names, - QVector &p_widgets); + void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); + template void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); void set_widget_layers(); void construct_char_select(); @@ -884,9 +876,7 @@ private slots: void closeEvent(QCloseEvent *event) override; }; -template -void Courtroom::insert_widget_names(QVector &p_widget_names, - QVector &p_widgets) +template void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) { QVector widgets; diff --git a/include/datatypes.h b/include/datatypes.h index e42acaade..d47e1f649 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -13,8 +13,7 @@ struct record_type bool music = false; record_type() = default; - record_type(QString p_name, QString p_line, QString p_color, bool p_is_system, - bool p_is_music) + record_type(QString p_name, QString p_line, QString p_color, bool p_is_system, bool p_is_music) : name(p_name), line(p_line), system(p_is_system), music(p_is_music) { Q_UNUSED(p_color); diff --git a/src/aoabstractplayer.cpp b/src/aoabstractplayer.cpp index 56b3ac9fb..d50e30b20 100644 --- a/src/aoabstractplayer.cpp +++ b/src/aoabstractplayer.cpp @@ -1,7 +1,6 @@ #include "aoabstractplayer.h" -AOAbstractPlayer::AOAbstractPlayer(QObject *p_parent, AOApplication *p_ao_app) - : QObject(p_parent), ao_app(p_ao_app) +AOAbstractPlayer::AOAbstractPlayer(QObject *p_parent, AOApplication *p_ao_app) : QObject(p_parent), ao_app(p_ao_app) { } diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 37b4079ef..e09031f15 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -17,22 +17,16 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) discord = new AttorneyOnline::Discord(); net_manager = new NetworkManager(this); - connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), - SLOT(ms_connect_finished(bool, bool))); + connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), SLOT(ms_connect_finished(bool, bool))); config = new AOConfig(this); - connect(config, SIGNAL(theme_changed(QString)), this, - SLOT(on_config_theme_changed())); - connect(config, SIGNAL(gamemode_changed(QString)), this, - SLOT(on_config_gamemode_changed())); - connect(config, SIGNAL(timeofday_changed(QString)), this, - SLOT(on_config_timeofday_changed())); + connect(config, SIGNAL(theme_changed(QString)), this, SLOT(on_config_theme_changed())); + connect(config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_config_gamemode_changed())); + connect(config, SIGNAL(timeofday_changed(QString)), this, SLOT(on_config_timeofday_changed())); config_panel = new AOConfigPanel(this); - connect(config_panel, SIGNAL(reload_theme()), this, - SLOT(on_config_reload_theme_requested())); - connect(this, SIGNAL(reload_theme()), config_panel, - SLOT(on_config_reload_theme_requested())); + connect(config_panel, SIGNAL(reload_theme()), this, SLOT(on_config_reload_theme_requested())); + connect(this, SIGNAL(reload_theme()), config_panel, SLOT(on_config_reload_theme_requested())); config_panel->hide(); } @@ -87,8 +81,7 @@ void AOApplication::construct_courtroom() w_courtroom = new Courtroom(this); connect(w_courtroom, SIGNAL(closing()), this, SLOT(on_courtroom_closing())); - connect(w_courtroom, SIGNAL(destroyed()), this, - SLOT(on_courtroom_destroyed())); + connect(w_courtroom, SIGNAL(destroyed()), this, SLOT(on_courtroom_destroyed())); courtroom_constructed = true; QRect screenGeometry = QApplication::desktop()->screenGeometry(); @@ -116,8 +109,7 @@ void AOApplication::destruct_courtroom() QString AOApplication::get_version_string() { - return QString::number(RELEASE) + "." + QString::number(MAJOR_VERSION) + "." + - QString::number(MINOR_VERSION); + return QString::number(RELEASE) + "." + QString::number(MAJOR_VERSION) + "." + QString::number(MINOR_VERSION); } void AOApplication::set_gamemode(QString p_gamemode) @@ -278,10 +270,8 @@ void AOApplication::ms_connect_finished(bool connected, bool will_retry) } else if (will_retry) { - w_lobby->append_error( - "Error connecting to master server. Will try again in " + - QString::number(net_manager->ms_reconnect_delay_ms / 1000.f) + - " seconds."); + w_lobby->append_error("Error connecting to master server. Will try again in " + + QString::number(net_manager->ms_reconnect_delay_ms / 1000.f) + " seconds."); } else { diff --git a/src/aobasshandle.cpp b/src/aobasshandle.cpp index 0de42721a..16a4d2379 100644 --- a/src/aobasshandle.cpp +++ b/src/aobasshandle.cpp @@ -1,7 +1,8 @@ #include "aobasshandle.h" AOBassHandle::AOBassHandle(QObject *p_parent) : QObject(p_parent) -{} +{ +} AOBassHandle::AOBassHandle(QString p_file, bool p_suicide, QObject *p_parent) noexcept(false) : QObject(p_parent) { diff --git a/src/aoblipplayer.cpp b/src/aoblipplayer.cpp index 9326b967d..b0f090ca6 100644 --- a/src/aoblipplayer.cpp +++ b/src/aoblipplayer.cpp @@ -14,8 +14,7 @@ void AOBlipPlayer::set_blips(QString p_sfx) { BASS_StreamFree(m_stream_list[n_stream]); - m_stream_list[n_stream] = BASS_StreamCreateFile( - FALSE, f_path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); + m_stream_list[n_stream] = BASS_StreamCreateFile(FALSE, f_path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); } set_volume(m_volume); diff --git a/src/aobutton.cpp b/src/aobutton.cpp index 81082331f..c2d3b26b3 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -5,8 +5,7 @@ #include -AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) - : QPushButton(parent) +AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) : QPushButton(parent) { ao_app = p_ao_app; } @@ -16,8 +15,7 @@ void AOButton::set_image(QString p_image) image_path = ao_app->find_theme_asset_path(p_image); // Get the path of the found image without the extension QString image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); - QString hover_image_path = - ao_app->find_theme_asset_path(image_name + "_hover.png"); + QString hover_image_path = ao_app->find_theme_asset_path(image_name + "_hover.png"); if (file_exists(image_path)) { diff --git a/src/aocharbutton.cpp b/src/aocharbutton.cpp index ed720f6ed..74dbdf231 100644 --- a/src/aocharbutton.cpp +++ b/src/aocharbutton.cpp @@ -4,9 +4,7 @@ #include -AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, - int y_pos) - : QPushButton(parent) +AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos) : QPushButton(parent) { ao_app = p_ao_app; diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 7678640f1..ca6724284 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -8,8 +8,7 @@ #include #include -AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) - : QLabel(p_parent) +AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) { ao_app = p_ao_app; diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 9148fd380..7069cc147 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -46,9 +46,7 @@ class AOConfigPrivate : public QObject bool blank_blips; public: - AOConfigPrivate() - : QObject(qApp), - cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat) + AOConfigPrivate() : QObject(qApp), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat) { read_file(); } @@ -273,13 +271,11 @@ public slots: } private: - void invoke_parents(QString p_method_name, - QGenericArgument p_arg1 = QGenericArgument(nullptr)) + void invoke_parents(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)) { for (QObject *i_parent : parents) { - QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), - p_arg1); + QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), p_arg1); } } }; diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index e40d7dcb2..029b6410c 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -44,10 +44,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // IC Chatlog w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); - w_log_orientation_top_down = - AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); - w_log_orientation_bottom_up = - AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); + w_log_orientation_top_down = AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); + w_log_orientation_bottom_up = AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); w_log_music = AO_GUI_WIDGET(QCheckBox, "log_music"); w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); @@ -69,108 +67,59 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) refresh_timeofday_list(); // input - connect(m_config, SIGNAL(autosave_changed(bool)), w_autosave, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(username_changed(QString)), w_username, - SLOT(setText(QString))); - connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, - SLOT(setText(QString))); - connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(theme_changed(QString)), w_theme, - SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, - SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, - SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(chat_tick_interval_changed(int)), - w_chat_tick_interval, SLOT(setValue(int))); - connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, - SLOT(setValue(int))); - connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, - SLOT(on_log_is_topdown_changed(bool))); - connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(effects_volume_changed(int)), w_effects, - SLOT(setValue(int))); - connect(m_config, SIGNAL(system_volume_changed(int)), w_system, - SLOT(setValue(int))); - connect(m_config, SIGNAL(music_volume_changed(int)), w_music, - SLOT(setValue(int))); - connect(m_config, SIGNAL(blips_volume_changed(int)), w_blips, - SLOT(setValue(int))); - connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, - SLOT(setValue(int))); - connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, - SLOT(setChecked(bool))); + connect(m_config, SIGNAL(autosave_changed(bool)), w_autosave, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); + connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); + connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); + connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); + connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); + connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(effects_volume_changed(int)), w_effects, SLOT(setValue(int))); + connect(m_config, SIGNAL(system_volume_changed(int)), w_system, SLOT(setValue(int))); + connect(m_config, SIGNAL(music_volume_changed(int)), w_music, SLOT(setValue(int))); + connect(m_config, SIGNAL(blips_volume_changed(int)), w_blips, SLOT(setValue(int))); + connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, SLOT(setValue(int))); + connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, SLOT(setChecked(bool))); // output connect(w_close, SIGNAL(clicked()), this, SLOT(close())); connect(w_save, SIGNAL(clicked()), m_config, SLOT(save_file())); - connect(w_autosave, SIGNAL(stateChanged(int)), m_config, - SLOT(set_autosave(int))); - connect(w_username, SIGNAL(textEdited(QString)), m_config, - SLOT(set_username(QString))); - connect(w_callwords, SIGNAL(textEdited(QString)), m_config, - SLOT(set_callwords(QString))); - connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, - SLOT(set_server_alerts(int))); - connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, - SLOT(set_theme(QString))); - connect(w_reload_theme, SIGNAL(clicked()), this, - SLOT(on_reload_theme_clicked())); - connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, - SLOT(on_gamemode_index_changed(QString))); - connect(w_manual_gamemode, SIGNAL(stateChanged(int)), m_config, - SLOT(set_manual_gamemode(int))); - connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, - SLOT(on_timeofday_index_changed(QString))); - connect(w_manual_timeofday, SIGNAL(stateChanged(int)), m_config, - SLOT(set_manual_timeofday(int))); - connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, - SLOT(set_always_pre(int))); - connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, - SLOT(set_chat_tick_interval(int))); - connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, - SLOT(set_log_max_lines(int))); - connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, - SLOT(set_log_is_topdown(bool))); - connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, - SLOT(set_log_uses_newline(int))); - connect(w_log_music, SIGNAL(stateChanged(int)), m_config, - SLOT(set_log_music(int))); - connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, - SLOT(set_log_is_recording(int))); - connect(w_effects, SIGNAL(valueChanged(int)), m_config, - SLOT(set_effects_volume(int))); - connect(w_effects, SIGNAL(valueChanged(int)), this, - SLOT(on_effects_value_changed(int))); - connect(w_system, SIGNAL(valueChanged(int)), m_config, - SLOT(set_system_volume(int))); - connect(w_system, SIGNAL(valueChanged(int)), this, - SLOT(on_system_value_changed(int))); - connect(w_music, SIGNAL(valueChanged(int)), m_config, - SLOT(set_music_volume(int))); - connect(w_music, SIGNAL(valueChanged(int)), this, - SLOT(on_music_value_changed(int))); - connect(w_blips, SIGNAL(valueChanged(int)), m_config, - SLOT(set_blips_volume(int))); - connect(w_blips, SIGNAL(valueChanged(int)), this, - SLOT(on_blips_value_changed(int))); - connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, - SLOT(set_blip_rate(int))); - connect(w_blank_blips, SIGNAL(stateChanged(int)), m_config, - SLOT(set_blank_blips(int))); + connect(w_autosave, SIGNAL(stateChanged(int)), m_config, SLOT(set_autosave(int))); + connect(w_username, SIGNAL(textEdited(QString)), m_config, SLOT(set_username(QString))); + connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); + connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, SLOT(set_server_alerts(int))); + connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); + connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); + connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); + connect(w_manual_gamemode, SIGNAL(stateChanged(int)), m_config, SLOT(set_manual_gamemode(int))); + connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); + connect(w_manual_timeofday, SIGNAL(stateChanged(int)), m_config, SLOT(set_manual_timeofday(int))); + connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); + connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); + connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); + connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_topdown(bool))); + connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_uses_newline(int))); + connect(w_log_music, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_music(int))); + connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_is_recording(int))); + connect(w_effects, SIGNAL(valueChanged(int)), m_config, SLOT(set_effects_volume(int))); + connect(w_effects, SIGNAL(valueChanged(int)), this, SLOT(on_effects_value_changed(int))); + connect(w_system, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_volume(int))); + connect(w_system, SIGNAL(valueChanged(int)), this, SLOT(on_system_value_changed(int))); + connect(w_music, SIGNAL(valueChanged(int)), m_config, SLOT(set_music_volume(int))); + connect(w_music, SIGNAL(valueChanged(int)), this, SLOT(on_music_value_changed(int))); + connect(w_blips, SIGNAL(valueChanged(int)), m_config, SLOT(set_blips_volume(int))); + connect(w_blips, SIGNAL(valueChanged(int)), this, SLOT(on_blips_value_changed(int))); + connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_rate(int))); + connect(w_blank_blips, SIGNAL(stateChanged(int)), m_config, SLOT(set_blank_blips(int))); // set values w_autosave->setChecked(m_config->autosave()); @@ -210,10 +159,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_timeofday->setEnabled(m_config->manual_timeofday_enabled()); // The manual gamemode checkbox enables browsing the gamemode combox // similarly with time of day - connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_gamemode, - SLOT(setEnabled(bool))); - connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_timeofday, - SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_gamemode, SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_timeofday, SLOT(setEnabled(bool))); } void AOConfigPanel::showEvent(QShowEvent *event) @@ -239,8 +186,7 @@ void AOConfigPanel::refresh_theme_list() // themes const QString path = QDir::currentPath() + "/base/themes"; - for (QString i_folder : - QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; @@ -265,10 +211,8 @@ void AOConfigPanel::refresh_gamemode_list() // add empty entry indicating no gamemode chosen w_gamemode->addItem(""); // gamemodes - QString path = - QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; - for (QString i_folder : - QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + QString path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; + for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; @@ -297,15 +241,12 @@ void AOConfigPanel::refresh_timeofday_list() // gamemode chosen or not QString path; if (m_config->gamemode().isEmpty()) - path = - QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; + path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; else - path = QDir::currentPath() + "/base/themes/" + m_config->theme() + - "/gamemodes/" + m_config->gamemode() + "/times/"; + path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/" + m_config->gamemode() + "/times/"; // times of day - for (QString i_folder : - QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index 11bc75ef6..1d3c435ea 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -3,9 +3,7 @@ #include "file_functions.h" #include -AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, - int p_x, int p_y) - : QPushButton(p_parent) +AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) { ao_app = p_ao_app; @@ -36,8 +34,7 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) // the sake of lazy-users convenience! const int true_emote_number = p_emote_number + 1; - QString texture_path = ao_app->get_character_path( - p_chr, QString("emotions/button%1_off.png").arg(true_emote_number)); + QString texture_path = ao_app->get_character_path(p_chr, QString("emotions/button%1_off.png").arg(true_emote_number)); // reset states w_selected->hide(); @@ -45,8 +42,8 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) // nested ifs are okay if (p_enabled) { - const QString enabled_texture_path = ao_app->get_character_path( - p_chr, QString("emotions/button%1_on.png").arg(true_emote_number)); + const QString enabled_texture_path = + ao_app->get_character_path(p_chr, QString("emotions/button%1_on.png").arg(true_emote_number)); if (file_exists(enabled_texture_path)) { @@ -54,19 +51,16 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) } else { - const QString selected_texture_path = - ao_app->get_character_path(p_chr, "emotions/selected.png"); + const QString selected_texture_path = ao_app->get_character_path(p_chr, "emotions/selected.png"); if (file_exists(selected_texture_path)) { - w_selected->setStyleSheet( - QString("border-image: url(%1)").arg(selected_texture_path)); + w_selected->setStyleSheet(QString("border-image: url(%1)").arg(selected_texture_path)); } else { - w_selected->setStyleSheet( - "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, " - "y2:1, stop:0 rgba(0, 0, 0, 0), stop:1 rgba(0, 0, 0, 127)); }"); + w_selected->setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, " + "y2:1, stop:0 rgba(0, 0, 0, 0), stop:1 rgba(0, 0, 0, 127)); }"); } w_selected->show(); @@ -74,12 +68,10 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) } const bool texture_exist = file_exists(texture_path); - setText(texture_exist ? QString() - : ao_app->get_emote_comment(p_chr, p_emote_number)); - setStyleSheet(texture_exist ? QString("%1 { border-image: url(%2); }") - .arg(metaObject()->className()) - .arg(texture_path) - : QString()); + setText(texture_exist ? QString() : ao_app->get_emote_comment(p_chr, p_emote_number)); + setStyleSheet(texture_exist + ? QString("%1 { border-image: url(%2); }").arg(metaObject()->className()).arg(texture_path) + : QString()); } void AOEmoteButton::on_clicked() diff --git a/src/aoevidencebutton.cpp b/src/aoevidencebutton.cpp index d1a877f12..f15988d27 100644 --- a/src/aoevidencebutton.cpp +++ b/src/aoevidencebutton.cpp @@ -4,9 +4,7 @@ #include -AOEvidenceButton::AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, - int p_x, int p_y) - : QPushButton(p_parent) +AOEvidenceButton::AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) { ao_app = p_ao_app; diff --git a/src/aoevidencedescription.cpp b/src/aoevidencedescription.cpp index d5bb0a1a5..0413e8a7e 100644 --- a/src/aoevidencedescription.cpp +++ b/src/aoevidencedescription.cpp @@ -1,7 +1,6 @@ #include "aoevidencedescription.h" -AOEvidenceDescription::AOEvidenceDescription(QWidget *parent) - : QPlainTextEdit(parent) +AOEvidenceDescription::AOEvidenceDescription(QWidget *parent) : QPlainTextEdit(parent) { this->setReadOnly(true); diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index 2f8a142e9..5b164c227 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -6,8 +6,7 @@ #include "file_functions.h" #include "misc_functions.h" -AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) - : QLabel(p_parent) +AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) { ao_app = p_ao_app; @@ -15,12 +14,10 @@ AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) evidence_icon = new QLabel(this); sfx_player = new AOSfxPlayer(this, ao_app); - connect(evidence_movie, SIGNAL(frameChanged(int)), this, - SLOT(frame_change(int))); + connect(evidence_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); } -void AOEvidenceDisplay::show_evidence(QString p_evidence_image, - bool is_left_side) +void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_side) { this->reset(); @@ -43,8 +40,7 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, gif_name = "evidence_appear_right.gif"; } - pos_size_type icon_dimensions = - ao_app->get_element_dimensions(icon_identifier, "courtroom_design.ini"); + pos_size_type icon_dimensions = ao_app->get_element_dimensions(icon_identifier, "courtroom_design.ini"); evidence_icon->move(icon_dimensions.x, icon_dimensions.y); evidence_icon->resize(icon_dimensions.width, icon_dimensions.height); diff --git a/src/aomovie.cpp b/src/aomovie.cpp index d4c63c3fb..cb21adeca 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -4,8 +4,7 @@ #include "file_functions.h" #include "misc_functions.h" -AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) - : QLabel(p_parent) +AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) { ao_app = p_ao_app; @@ -64,11 +63,9 @@ void AOMovie::play(QString p_file, QString p_char) animated_or_static_extensions()); if (file_path.isEmpty()) { - file_path = - ao_app->find_theme_asset_path(p_file, animated_or_static_extensions()); + file_path = ao_app->find_theme_asset_path(p_file, animated_or_static_extensions()); if (file_path.isEmpty()) - file_path = ao_app->find_theme_asset_path( - "placeholder", animated_or_static_extensions()); + file_path = ao_app->find_theme_asset_path("placeholder", animated_or_static_extensions()); } qDebug() << "playing" << file_path; @@ -79,8 +76,7 @@ void AOMovie::play(QString p_file, QString p_char) m_movie->start(); } -void AOMovie::play_interjection(QString p_char_name, - QString p_interjection_name) +void AOMovie::play_interjection(QString p_char_name, QString p_interjection_name) { m_movie->stop(); @@ -99,11 +95,9 @@ void AOMovie::play_interjection(QString p_char_name, // `p_char_name` QString interjection_filepath = ao_app->find_asset_path( - {ao_app->get_character_path(p_char_name, p_char_interjection_name)}, - animated_extensions()); + {ao_app->get_character_path(p_char_name, p_char_interjection_name)}, animated_extensions()); if (interjection_filepath.isEmpty()) - interjection_filepath = ao_app->find_theme_asset_path( - p_interjection_name, animated_extensions()); + interjection_filepath = ao_app->find_theme_asset_path(p_interjection_name, animated_extensions()); if (interjection_filepath.isEmpty()) { @@ -111,12 +105,9 @@ void AOMovie::play_interjection(QString p_char_name, return; } - qDebug() << "playing interjection" - << (p_interjection_name.isEmpty() ? "(none)" : p_interjection_name) - << "for character" - << (p_char_name.isEmpty() ? "(none)" : p_char_name) << "at" - << (interjection_filepath.isEmpty() ? "(not found)" - : interjection_filepath); + qDebug() << "playing interjection" << (p_interjection_name.isEmpty() ? "(none)" : p_interjection_name) + << "for character" << (p_char_name.isEmpty() ? "(none)" : p_char_name) << "at" + << (interjection_filepath.isEmpty() ? "(not found)" : interjection_filepath); m_movie->setFileName(interjection_filepath); this->show(); m_movie->setScaledSize(this->size()); diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index 51487d06f..5ae5a1720 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -4,8 +4,7 @@ #include -AOMusicPlayer::AOMusicPlayer(QObject *p_parent, AOApplication *p_ao_app) - : AOAbstractPlayer(p_parent, p_ao_app) +AOMusicPlayer::AOMusicPlayer(QObject *p_parent, AOApplication *p_ao_app) : AOAbstractPlayer(p_parent, p_ao_app) { } @@ -20,8 +19,7 @@ void AOMusicPlayer::play(QString p_file) try { // create new song AOBassHandle *handle = new AOBassHandle(m_file, false, this); - connect(this, &AOMusicPlayer::new_volume, handle, - &AOBassHandle::set_volume); + connect(this, &AOMusicPlayer::new_volume, handle, &AOBassHandle::set_volume); connect(this, &AOMusicPlayer::stopping, handle, &AOBassHandle::stop); // delete previous diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp index 5c2d4376c..bc93a8ad9 100644 --- a/src/aonotearea.cpp +++ b/src/aonotearea.cpp @@ -5,8 +5,7 @@ #include -AONoteArea::AONoteArea(QWidget *p_parent, AOApplication *p_ao_app) - : AOImageDisplay(p_parent, p_ao_app) +AONoteArea::AONoteArea(QWidget *p_parent, AOApplication *p_ao_app) : AOImageDisplay(p_parent, p_ao_app) { ao_app = p_ao_app; } @@ -54,10 +53,8 @@ void Courtroom::on_add_button_clicked() set_dropdown(f_line, "[LINE EDIT]"); - connect(f_button, SIGNAL(clicked(bool)), this, - SLOT(on_set_file_button_clicked())); - connect(f_delete, SIGNAL(clicked(bool)), this, - SLOT(on_delete_button_clicked())); + connect(f_button, SIGNAL(clicked(bool)), this, SLOT(on_set_file_button_clicked())); + connect(f_delete, SIGNAL(clicked(bool)), this, SLOT(on_delete_button_clicked())); connect(f_hover, SIGNAL(clicked(bool)), this, SLOT(on_file_selected())); } @@ -78,13 +75,11 @@ void Courtroom::set_note_files() for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) { - AONotePicker *f_notepicker = static_cast( - ui_note_area->m_layout->itemAt(i)->widget()); + AONotePicker *f_notepicker = static_cast(ui_note_area->m_layout->itemAt(i)->widget()); QString f_filestring = f_notepicker->real_file; QString f_filename = f_notepicker->m_line->text(); - t += - QString::number(i) + " = " + f_filestring + " = " + f_filename + "\n\n"; + t += QString::number(i) + " = " + f_filestring + " = " + f_filename + "\n\n"; } config_file.close(); diff --git a/src/aonotepad.cpp b/src/aonotepad.cpp index 6af28cce5..4723bee0f 100644 --- a/src/aonotepad.cpp +++ b/src/aonotepad.cpp @@ -1,6 +1,5 @@ #include "aonotepad.h" -AONotepad::AONotepad(QWidget *p_parent, AOApplication *p_ao_app) - : QTextEdit(p_parent), ao_app(p_ao_app) +AONotepad::AONotepad(QWidget *p_parent, AOApplication *p_ao_app) : QTextEdit(p_parent), ao_app(p_ao_app) { } diff --git a/src/aonotepicker.cpp b/src/aonotepicker.cpp index 0aa90e84f..738308c69 100644 --- a/src/aonotepicker.cpp +++ b/src/aonotepicker.cpp @@ -6,8 +6,7 @@ #include #include -AONotePicker::AONotePicker(QWidget *p_parent, AOApplication *p_ao_app) - : QLabel(p_parent) +AONotePicker::AONotePicker(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) { ao_app = p_ao_app; } @@ -16,8 +15,7 @@ void Courtroom::on_file_selected() { for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) { - AONotePicker *f_notepicker = static_cast( - ui_note_area->m_layout->itemAt(i)->widget()); + AONotePicker *f_notepicker = static_cast(ui_note_area->m_layout->itemAt(i)->widget()); f_notepicker->m_hover->set_image("note_select.png"); } @@ -46,8 +44,7 @@ void Courtroom::on_set_file_button_clicked() { AOButton *f_button = static_cast(sender()); AONotePicker *f_notepicker = static_cast(f_button->parent()); - QString f_filename = QFileDialog::getOpenFileName( - this, "Open File", QDir::currentPath(), "Text files (*.txt)"); + QString f_filename = QFileDialog::getOpenFileName(this, "Open File", QDir::currentPath(), "Text files (*.txt)"); if (f_filename != "") { diff --git a/src/aopacket.cpp b/src/aopacket.cpp index 425e3c872..833d1da58 100644 --- a/src/aopacket.cpp +++ b/src/aopacket.cpp @@ -39,10 +39,7 @@ void AOPacket::net_encode() for (int n_element = 0; n_element < m_contents.size(); ++n_element) { QString f_element = m_contents.at(n_element); - f_element.replace("#", "") - .replace("%", "") - .replace("$", "") - .replace("&", ""); + f_element.replace("#", "").replace("%", "").replace("$", "").replace("&", ""); m_contents.removeAt(n_element); m_contents.insert(n_element, f_element); @@ -54,10 +51,7 @@ void AOPacket::net_decode() for (int n_element = 0; n_element < m_contents.size(); ++n_element) { QString f_element = m_contents.at(n_element); - f_element.replace("", "#") - .replace("", "%") - .replace("", "$") - .replace("", "&"); + f_element.replace("", "#").replace("", "%").replace("", "$").replace("", "&"); m_contents.removeAt(n_element); m_contents.insert(n_element, f_element); diff --git a/src/aopixmap.cpp b/src/aopixmap.cpp index 917910759..cde27e318 100644 --- a/src/aopixmap.cpp +++ b/src/aopixmap.cpp @@ -1,7 +1,6 @@ #include "aopixmap.h" -AOPixmap::AOPixmap(QPixmap p_pixmap) - : m_pixmap(p_pixmap) +AOPixmap::AOPixmap(QPixmap p_pixmap) : m_pixmap(p_pixmap) { if (m_pixmap.isNull()) { @@ -9,9 +8,9 @@ AOPixmap::AOPixmap(QPixmap p_pixmap) } } -AOPixmap::AOPixmap(QString p_file_path) - : AOPixmap(QPixmap(p_file_path)) -{} +AOPixmap::AOPixmap(QString p_file_path) : AOPixmap(QPixmap(p_file_path)) +{ +} void AOPixmap::clear() { @@ -29,5 +28,6 @@ QPixmap AOPixmap::scale(QSize p_size) QPixmap AOPixmap::scale_to_height(QSize p_size) { const bool f_is_pixmap_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); - return m_pixmap.scaledToHeight(p_size.height(), f_is_pixmap_larger ? Qt::SmoothTransformation : Qt::FastTransformation); + return m_pixmap.scaledToHeight(p_size.height(), + f_is_pixmap_larger ? Qt::SmoothTransformation : Qt::FastTransformation); } diff --git a/src/aoscene.cpp b/src/aoscene.cpp index 510435d8d..23c925378 100644 --- a/src/aoscene.cpp +++ b/src/aoscene.cpp @@ -6,8 +6,7 @@ #include #include -AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) - : QLabel(parent), ao_app(p_ao_app) +AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent), ao_app(p_ao_app) { m_reader = new QMovie(this); setMovie(m_reader); @@ -22,8 +21,7 @@ void AOScene::set_image(QString p_image) for (auto &ext : animated_or_static_extensions()) { - QString full_background_path = - ao_app->get_case_sensitive_path(background_path + ext); + QString full_background_path = ao_app->get_case_sensitive_path(background_path + ext); if (file_exists(full_background_path)) { diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index 28a928269..26ea7906c 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -5,8 +5,7 @@ #include -AOSfxPlayer::AOSfxPlayer(QObject *p_parent, AOApplication *p_ao_app) - : AOAbstractPlayer(p_parent, p_ao_app) +AOSfxPlayer::AOSfxPlayer(QObject *p_parent, AOApplication *p_ao_app) : AOAbstractPlayer(p_parent, p_ao_app) { } diff --git a/src/aoshoutplayer.cpp b/src/aoshoutplayer.cpp index cffd26ced..073a9ae09 100644 --- a/src/aoshoutplayer.cpp +++ b/src/aoshoutplayer.cpp @@ -3,8 +3,7 @@ #include -AOShoutPlayer::AOShoutPlayer(QObject *p_parent, AOApplication *p_ao_app) - : AOAbstractPlayer(p_parent, p_ao_app) +AOShoutPlayer::AOShoutPlayer(QObject *p_parent, AOApplication *p_ao_app) : AOAbstractPlayer(p_parent, p_ao_app) { } @@ -28,8 +27,7 @@ void AOShoutPlayer::play(QString p_name, QString p_char) try { AOBassHandle *handle = new AOBassHandle(f_file, true, this); - connect(this, &AOShoutPlayer::new_volume, handle, - &AOBassHandle::set_volume); + connect(this, &AOShoutPlayer::new_volume, handle, &AOBassHandle::set_volume); connect(this, &AOShoutPlayer::stopping, handle, &AOBassHandle::stop); handle->set_volume(get_volume()); handle->play(); diff --git a/src/aotextarea.cpp b/src/aotextarea.cpp index e9b30cbfd..c1821fd36 100644 --- a/src/aotextarea.cpp +++ b/src/aotextarea.cpp @@ -13,8 +13,7 @@ void AOTextArea::append_chatmessage(QString p_name, QString p_message) { const QTextCursor old_cursor = this->textCursor(); const int old_scrollbar_value = this->verticalScrollBar()->value(); - const bool is_scrolled_down = - old_scrollbar_value == this->verticalScrollBar()->maximum(); + const bool is_scrolled_down = old_scrollbar_value == this->verticalScrollBar()->maximum(); this->moveCursor(QTextCursor::End); @@ -35,8 +34,7 @@ void AOTextArea::append_error(QString p_message) { const QTextCursor old_cursor = this->textCursor(); const int old_scrollbar_value = this->verticalScrollBar()->value(); - const bool is_scrolled_down = - old_scrollbar_value == this->verticalScrollBar()->maximum(); + const bool is_scrolled_down = old_scrollbar_value == this->verticalScrollBar()->maximum(); this->moveCursor(QTextCursor::End); @@ -51,8 +49,7 @@ void AOTextArea::append_error(QString p_message) this->auto_scroll(old_cursor, old_scrollbar_value, is_scrolled_down); } -void AOTextArea::auto_scroll(QTextCursor old_cursor, int old_scrollbar_value, - bool is_scrolled_down) +void AOTextArea::auto_scroll(QTextCursor old_cursor, int old_scrollbar_value, bool is_scrolled_down) { if (old_cursor.hasSelection() || !is_scrolled_down) { diff --git a/src/aotimer.cpp b/src/aotimer.cpp index 9e564fc17..7bed24191 100644 --- a/src/aotimer.cpp +++ b/src/aotimer.cpp @@ -122,8 +122,7 @@ void AOTimer::set_firing_interval(int new_firing_interval) */ // Update time spent so far and new future firing interval - time_spent_in_timestep += - (firing_timer_length - firing_timer.remainingTime()); + time_spent_in_timestep += (firing_timer_length - firing_timer.remainingTime()); firing_timer_length = new_firing_interval; // For this timestep however, the firing interval will be shorter than // firing_timer_length to account for the fact the timer may have already been diff --git a/src/charselect.cpp b/src/charselect.cpp index 65620ebbe..77d08e070 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -23,15 +23,11 @@ void Courtroom::construct_char_select() ui_spectator = new AOButton(ui_char_select_background, ao_app); ui_spectator->setText("Spectator"); - connect(char_button_mapper, SIGNAL(mapped(int)), this, - SLOT(char_clicked(int))); - connect(ui_back_to_lobby, SIGNAL(clicked()), this, - SLOT(on_back_to_lobby_clicked())); + connect(char_button_mapper, SIGNAL(mapped(int)), this, SLOT(char_clicked(int))); + connect(ui_back_to_lobby, SIGNAL(clicked()), this, SLOT(on_back_to_lobby_clicked())); - connect(ui_char_select_left, SIGNAL(clicked()), this, - SLOT(on_char_select_left_clicked())); - connect(ui_char_select_right, SIGNAL(clicked()), this, - SLOT(on_char_select_right_clicked())); + connect(ui_char_select_left, SIGNAL(clicked()), this, SLOT(on_char_select_left_clicked())); + connect(ui_char_select_right, SIGNAL(clicked()), this, SLOT(on_char_select_right_clicked())); connect(ui_spectator, SIGNAL(clicked()), this, SLOT(on_spectator_clicked())); @@ -43,8 +39,7 @@ void Courtroom::reconstruct_char_select() while (!ui_char_button_list.isEmpty()) delete ui_char_button_list.takeLast(); - QPoint f_spacing = - ao_app->get_button_spacing("char_button_spacing", "courtroom_design.ini"); + QPoint f_spacing = ao_app->get_button_spacing("char_button_spacing", "courtroom_design.ini"); const int button_width = 60; int x_spacing = f_spacing.x(); @@ -56,12 +51,8 @@ void Courtroom::reconstruct_char_select() set_size_and_pos(ui_char_buttons, "char_buttons"); - char_columns = - ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + - 1; - char_rows = ((ui_char_buttons->height() - button_height) / - (y_spacing + button_height)) + - 1; + char_columns = ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + 1; + char_rows = ((ui_char_buttons->height() - button_height) / (y_spacing + button_height)) + 1; max_chars_on_page = char_columns * char_rows; @@ -70,16 +61,14 @@ void Courtroom::reconstruct_char_select() int x_pos = (button_width + x_spacing) * x_mod_count; int y_pos = (button_height + y_spacing) * y_mod_count; - AOCharButton *button = - new AOCharButton(ui_char_buttons, ao_app, x_pos, y_pos); + AOCharButton *button = new AOCharButton(ui_char_buttons, ao_app, x_pos, y_pos); ui_char_button_list.append(button); connect(button, SIGNAL(clicked()), char_button_mapper, SLOT(map())); char_button_mapper->setMapping(button, n); // mouse events - connect(button, SIGNAL(mouse_entered(AOCharButton *)), this, - SLOT(char_mouse_entered(AOCharButton *))); + connect(button, SIGNAL(mouse_entered(AOCharButton *)), this, SLOT(char_mouse_entered(AOCharButton *))); connect(button, SIGNAL(mouse_left()), this, SLOT(char_mouse_left())); ++x_mod_count; @@ -106,13 +95,11 @@ void Courtroom::set_char_select() { QString filename = "courtroom_design.ini"; - pos_size_type f_charselect = - ao_app->get_element_dimensions("char_select", filename); + pos_size_type f_charselect = ao_app->get_element_dimensions("char_select", filename); if (f_charselect.width < 0 || f_charselect.height < 0) { - qDebug() - << "W: did not find courtroom width or height in courtroom_design.ini!"; + qDebug() << "W: did not find courtroom width or height in courtroom_design.ini!"; this->resize(714, 668); } else @@ -177,8 +164,7 @@ void Courtroom::char_clicked(int n_char) { int n_real_char = n_char + current_char_page * max_chars_on_page; - QString char_ini_path = - ao_app->get_character_path(char_list.at(n_real_char).name, "char.ini"); + QString char_ini_path = ao_app->get_character_path(char_list.at(n_real_char).name, "char.ini"); qDebug() << "char_ini_path" << char_ini_path; if (!file_exists(char_ini_path)) @@ -194,8 +180,8 @@ void Courtroom::char_clicked(int n_char) } else { - QString content = "CC#" + QString::number(ao_app->s_pv) + "#" + - QString::number(n_real_char) + "#" + get_hdid() + "#%"; + QString content = + "CC#" + QString::number(ao_app->s_pv) + "#" + QString::number(n_real_char) + "#" + get_hdid() + "#%"; ao_app->send_server_packet(new AOPacket(content)); } } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 0060f4de3..73f60d463 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -21,8 +21,7 @@ #include #include -Courtroom::Courtroom(AOApplication *p_ao_app) - : QMainWindow() +Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; ao_config = new AOConfig(this); @@ -58,8 +57,7 @@ void Courtroom::enter_courtroom(int p_cid) f_char = ao_app->get_char_name(char_list.at(m_cid).name); QString r_char = f_char; // regex for removing non letter (except _) characters - QRegularExpression re( - QString::fromUtf8("[-`~!@#$%^&*()—+=|:;<>«»,.?/{}\'\"\\[\\]]")); + QRegularExpression re(QString::fromUtf8("[-`~!@#$%^&*()—+=|:;<>«»,.?/{}\'\"\\[\\]]")); r_char.remove(re); if (!rpc_char_list.contains(f_char.toLower())) @@ -120,18 +118,15 @@ void Courtroom::enter_courtroom(int p_cid) set_widgets(); check_shouts(); - if (m_shout_current < shouts_enabled.length() && - !shouts_enabled[m_shout_current]) + if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) cycle_shout(1); check_effects(); - if (m_effect_current < effects_enabled.length() && - !effects_enabled[m_effect_current]) + if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) cycle_effect(1); check_wtce(); - if (is_judge && - (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) + if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) cycle_wtce(1); check_free_blocks(); @@ -142,8 +137,7 @@ void Courtroom::enter_courtroom(int p_cid) list_areas(); list_sfx(); - ui_sfx_list->setCurrentItem( - ui_sfx_list->item(0)); // prevents undefined errors + ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors // unmute audio set_audio_mute_enabled(false); @@ -244,8 +238,7 @@ void Courtroom::set_scene() QStringList alldesks{"defensedesk", "prosecutiondesk", "stand"}; for (QString desk : alldesks) { - QString full_path = ao_app->find_asset_path( - {get_background_path(desk)}, animated_or_static_extensions()); + QString full_path = ao_app->find_asset_path({get_background_path(desk)}, animated_or_static_extensions()); if (full_path.isEmpty()) { has_all_desks = false; @@ -253,9 +246,7 @@ void Courtroom::set_scene() } } - if (f_desk_mod == "0" || - (f_desk_mod != "1" && - (f_side == "jud" || f_side == "hld" || f_side == "hlp"))) + if (f_desk_mod == "0" || (f_desk_mod != "1" && (f_side == "jud" || f_side == "hld" || f_side == "hlp"))) ui_vp_desk->hide(); else if (!has_all_desks) ui_vp_desk->hide(); @@ -296,8 +287,7 @@ void Courtroom::set_taken(int n_char, bool p_taken) { if (n_char >= char_list.size()) { - qDebug() - << "W: set_taken attempted to set an index bigger than char_list size"; + qDebug() << "W: set_taken attempted to set an index bigger than char_list size"; return; } @@ -322,24 +312,20 @@ void Courtroom::handle_music_anim() QString file_b = fonts_ini; pos_size_type res_a = ao_app->get_element_dimensions("music_name", file_a); pos_size_type res_b = ao_app->get_element_dimensions("music_area", file_a); - float speed = - static_cast(ao_app->get_font_property("music_name_speed", file_b)); + float speed = static_cast(ao_app->get_font_property("music_name_speed", file_b)); QFont f_font = ui_vp_music_name->font(); QFontMetrics fm(f_font); int dist; - if (ao_app->read_theme_ini("enable_const_music_speed", cc_config_ini) == - "true") + if (ao_app->read_theme_ini("enable_const_music_speed", cc_config_ini) == "true") dist = res_b.width; else dist = fm.width(ui_vp_music_name->toPlainText()); int time = static_cast(1000000 * dist / speed); music_anim->setLoopCount(-1); music_anim->setDuration(time); - music_anim->setStartValue( - QRect(res_b.width + res_b.x, res_a.y, res_a.width, res_a.height)); - music_anim->setEndValue( - QRect(-dist + res_a.x, res_a.y, res_a.width, res_a.height)); + music_anim->setStartValue(QRect(res_b.width + res_b.x, res_a.y, res_a.width, res_a.height)); + music_anim->setEndValue(QRect(-dist + res_a.x, res_a.y, res_a.width, res_a.height)); music_anim->start(); } @@ -360,8 +346,7 @@ void Courtroom::handle_clock(QString time) qDebug() << "Displaying clock asset..."; QString clock_filename = "hours/" + QString::number(current_clock); - const QString asset_path = ao_app->find_theme_asset_path( - clock_filename, animated_or_static_extensions()); + const QString asset_path = ao_app->find_theme_asset_path(clock_filename, animated_or_static_extensions()); if (asset_path.isEmpty()) { qDebug() << "Asset not found; aborting."; @@ -413,8 +398,7 @@ void Courtroom::list_music() ui_music_list->addItem(i_song); QString song_root = ao_app->get_music_path(i_song); - QString song_path = - ao_app->find_asset_path({song_root}, audio_extensions()); + QString song_path = ao_app->find_asset_path({song_root}, audio_extensions()); if (!song_path.isEmpty()) ui_music_list->item(n_listed_songs)->setBackground(found_brush); @@ -483,8 +467,7 @@ void Courtroom::list_sfx() sfx_names.append("1"); // Silence QString default_sfx_root = ao_app->get_sounds_path("1"); - QString default_sfx_path = - ao_app->find_asset_path({default_sfx_root}, audio_extensions()); + QString default_sfx_path = ao_app->find_asset_path({default_sfx_root}, audio_extensions()); if (!default_sfx_path.isEmpty()) { ui_sfx_list->item(0)->setBackground(found_brush); @@ -516,8 +499,7 @@ void Courtroom::list_sfx() // Apply appropriate color whether SFX exists or not QString sfx_root = ao_app->get_sounds_path(i_sfx); - QString sfx_path = - ao_app->find_asset_path({sfx_root}, audio_extensions()); + QString sfx_path = ao_app->find_asset_path({sfx_root}, audio_extensions()); if (!sfx_path.isEmpty()) ui_sfx_list->item(last_index)->setBackground(found_brush); @@ -563,8 +545,7 @@ void Courtroom::list_note_files() while (f_index >= f_layout->count()) on_add_button_clicked(); - AONotePicker *f_notepicker = - static_cast(f_layout->itemAt(f_index)->widget()); + AONotePicker *f_notepicker = static_cast(f_layout->itemAt(f_index)->widget()); f_notepicker->m_line->setText(f_filename); f_notepicker->real_file = f_filestring; } @@ -594,8 +575,7 @@ void Courtroom::save_textlog(QString p_text) { QString f_file = ao_app->get_base_path() + icchatlogsfilename; - ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, - f_file); + ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, f_file); } void Courtroom::append_server_chatmessage(QString p_name, QString p_message) @@ -640,8 +620,7 @@ void Courtroom::on_chat_return_pressed() QString f_desk_mod = "chat"; - f_desk_mod = - QString::number(ao_app->get_desk_mod(current_char, current_emote)); + f_desk_mod = QString::number(ao_app->get_desk_mod(current_char, current_emote)); if (f_desk_mod == "-1") f_desk_mod = "chat"; @@ -701,8 +680,7 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(QString::number(f_emote_mod)); packet_contents.append(QString::number(m_cid)); - packet_contents.append( - QString::number(ao_app->get_sfx_delay(current_char, current_emote))); + packet_contents.append(QString::number(ao_app->get_sfx_delay(current_char, current_emote))); QString f_obj_state; @@ -747,8 +725,7 @@ void Courtroom::handle_acknowledged_ms() // reset states ui_pre->setChecked(ao_config->always_pre_enabled()); list_sfx(); - ui_sfx_list->setCurrentItem( - ui_sfx_list->item(0)); // prevents undefined errors + ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors m_shout_state = 0; draw_shout_buttons(); @@ -790,8 +767,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) if (mute_map.value(m_chatmessage[CMChrId].toInt())) return; - chatmessage_is_empty = - m_chatmessage[CMMessage] == " " || m_chatmessage[CMMessage] == ""; + chatmessage_is_empty = m_chatmessage[CMMessage] == " " || m_chatmessage[CMMessage] == ""; m_msg_is_first_person = false; // reset our ui state if client just spoke @@ -820,8 +796,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) QString f_showname; qDebug() << "handle_chatmessage"; - qDebug() << m_chatmessage[CMShowName] - << ao_app->get_showname(char_list.at(f_char_id).name); + qDebug() << m_chatmessage[CMShowName] << ao_app->get_showname(char_list.at(f_char_id).name); // We actually DO wanna fail here if the showname is empty but the system is // speaking. @@ -857,8 +832,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) else append_ic_text(f_showname, m_chatmessage[CMMessage], false, false); - if (ao_config->log_is_recording_enabled() && - (!chatmessage_is_empty || !is_system_speaking)) + if (ao_config->log_is_recording_enabled() && (!chatmessage_is_empty || !is_system_speaking)) { save_textlog(f_showname + ": " + m_chatmessage[CMMessage]); } @@ -874,11 +848,9 @@ void Courtroom::handle_chatmessage(QStringList p_contents) m_chatmessage[CMEmoteModifier] = 1; // handles cases 1-8 (5-8 are DRO only) - if (objection_mod >= 1 && objection_mod <= ui_shouts.size() && - ui_shouts.size() > 0) // check to prevent crashing + if (objection_mod >= 1 && objection_mod <= ui_shouts.size() && ui_shouts.size() > 0) // check to prevent crashing { - ui_vp_objection->play_interjection(f_char, - shout_names.at(objection_mod - 1)); + ui_vp_objection->play_interjection(f_char, shout_names.at(objection_mod - 1)); m_shouts_player->play(shout_names.at(objection_mod - 1) + ".wav", f_char); } else @@ -921,8 +893,7 @@ void Courtroom::handle_chatmessage_2() // handles IC // Check if char.ini has color property, which overrides the theme's default // showname color - QString f_color = - ao_app->read_char_ini(real_name, "color", "[Options]", "[Time]"); + QString f_color = ao_app->read_char_ini(real_name, "color", "[Options]", "[Time]"); set_qtextedit_font(ui_vp_showname, "showname", f_color); ui_vp_showname->setText(f_showname); @@ -986,8 +957,7 @@ void Courtroom::handle_chatmessage_3() // shifted by 1 because 0 is no evidence per legacy standards QString f_image = local_evidence_list.at(f_evi_id - 1).image; // 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"); + bool is_left_side = !(f_side == "def" || f_side == "hlp" || f_side == "jud"); ui_vp_evidence_display->show_evidence(f_image, is_left_side); } @@ -1030,11 +1000,9 @@ void Courtroom::handle_chatmessage_3() // 2. In the character folder, look for // "showname" + extensions in `exts` in order - QString path = ao_app->find_theme_asset_path( - "characters/" + f_char + "/showname", {".png"}); + QString path = ao_app->find_theme_asset_path("characters/" + f_char + "/showname", {".png"}); if (path.isEmpty()) - path = ao_app->find_asset_path( - {ao_app->get_character_path(f_char, "showname")}, {".png"}); + path = ao_app->find_asset_path({ao_app->get_character_path(f_char, "showname")}, {".png"}); if (!path.isEmpty() && !chatmessage_is_empty && ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") @@ -1071,15 +1039,13 @@ void Courtroom::handle_chatmessage_3() int effect = m_chatmessage[CMEffectState].toInt(); QStringList offset = ao_app->get_effect_offset(f_char, effect); - ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), - ui_viewport->y() + offset.at(1).toInt()); + ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), ui_viewport->y() + offset.at(1).toInt()); QStringList overlay = ao_app->get_overlay(f_char, effect); QString overlay_name = overlay.at(0); QString overlay_sfx = overlay.at(1); - bool do_it = - ao_app->read_theme_ini("non_vanilla_effects", cc_config_ini) == "true"; + bool do_it = ao_app->read_theme_ini("non_vanilla_effects", cc_config_ini) == "true"; if (effect == 1 && !do_it) { @@ -1102,8 +1068,7 @@ void Courtroom::handle_chatmessage_3() // overlay_name = "effect_gloom"; // ui_vp_effect->play(overlay_name, f_char); // } - else if (do_it && effect > 0 && effect <= ui_effects.size() && - effect_names.size() > 0) // check to prevent crashing + else if (do_it && effect > 0 && effect <= ui_effects.size() && effect_names.size() > 0) // check to prevent crashing { QString s_eff = effect_names.at(effect - 1); QStringList f_eff = ao_app->get_effect(effect); @@ -1131,10 +1096,8 @@ void Courtroom::handle_chatmessage_3() m_system_player->play(ao_app->get_sfx("word_call")); ao_app->alert(this); ui_server_chatlog->append_chatmessage( - "CLIENT", "[" + QTime::currentTime().toString("HH:mm") + "] " + - ui_vp_showname->toPlainText() + - " has called you via your callword \"" + word + - "\": \"" + f_message + "\""); + "CLIENT", "[" + QTime::currentTime().toString("HH:mm") + "] " + ui_vp_showname->toPlainText() + + " has called you via your callword \"" + word + "\": \"" + f_message + "\""); break; } } @@ -1147,12 +1110,9 @@ void Courtroom::on_chat_config_changed() int chatlog_limit = ao_config->log_max_lines(); // default chatlog_limit? - chatlog_limit = chatlog_limit <= 0 - ? 200 - : chatlog_limit; // TODO declare the default somewhere so - // it's not a magic number - if (chatlog_limit < - m_chatlog_limit) // only update if we need to chop away records + chatlog_limit = chatlog_limit <= 0 ? 200 : chatlog_limit; // TODO declare the default somewhere so + // it's not a magic number + if (chatlog_limit < m_chatlog_limit) // only update if we need to chop away records chatlog_changed = true; m_chatlog_limit = chatlog_limit; @@ -1208,16 +1168,14 @@ void Courtroom::update_ic_log(bool p_reset_log) else name_format.setFontWeight(QFont::Normal); - QColor showname_color = - ao_app->get_color("ic_chatlog_showname_color", fonts_ini); + QColor showname_color = ao_app->get_color("ic_chatlog_showname_color", fonts_ini); if (showname_color == not_found_color) showname_color = default_color; name_format.setForeground(showname_color); QTextCharFormat line_format = ui_ic_chatlog->currentCharFormat(); line_format.setFontWeight(QFont::Normal); - QColor message_color = - ao_app->get_color("ic_chatlog_message_color", fonts_ini); + QColor message_color = ao_app->get_color("ic_chatlog_message_color", fonts_ini); if (message_color == not_found_color) message_color = default_color; line_format.setForeground(message_color); @@ -1235,23 +1193,19 @@ void Courtroom::update_ic_log(bool p_reset_log) // cache previous values const QTextCursor prev_cursor = ui_ic_chatlog->textCursor(); const int scroll_pos = vscrollbar->value(); - const bool is_scrolled = m_chatlog_scrolldown - ? scroll_pos == vscrollbar->maximum() - : scroll_pos == vscrollbar->minimum(); + const bool is_scrolled = + m_chatlog_scrolldown ? scroll_pos == vscrollbar->maximum() : scroll_pos == vscrollbar->minimum(); // recover cursor QTextCursor cursor = ui_ic_chatlog->textCursor(); // figure out if we need to move up or down - const QTextCursor::MoveOperation move_type = - m_chatlog_scrolldown ? QTextCursor::End : QTextCursor::Start; + const QTextCursor::MoveOperation move_type = m_chatlog_scrolldown ? QTextCursor::End : QTextCursor::Start; for (record_type_ptr record : records_to_add) { // move cursor cursor.movePosition(move_type); - const QString record_end = - (QString(QChar::LineFeed) + - (m_chatlog_newline ? QString(QChar::LineFeed) : "")); + const QString record_end = (QString(QChar::LineFeed) + (m_chatlog_newline ? QString(QChar::LineFeed) : "")); if (record->system) { @@ -1285,11 +1239,9 @@ void Courtroom::update_ic_log(bool p_reset_log) int blocks_to_delete = ui_ic_chatlog->document()->blockCount() - block_count; // the orientation at which we need to delete from - const QTextCursor::MoveOperation start_location = - m_chatlog_scrolldown ? QTextCursor::Start : QTextCursor::End; + const QTextCursor::MoveOperation start_location = m_chatlog_scrolldown ? QTextCursor::Start : QTextCursor::End; const QTextCursor::MoveOperation block_orientation = - m_chatlog_scrolldown ? QTextCursor::NextBlock - : QTextCursor::PreviousBlock; + m_chatlog_scrolldown ? QTextCursor::NextBlock : QTextCursor::PreviousBlock; /* Blocks appear like this * textQChar(0x2029) @@ -1340,17 +1292,14 @@ void Courtroom::update_ic_log(bool p_reset_log) else { ui_ic_chatlog->moveCursor(move_type); - vscrollbar->setValue(m_chatlog_scrolldown ? vscrollbar->maximum() - : vscrollbar->minimum()); + vscrollbar->setValue(m_chatlog_scrolldown ? vscrollbar->maximum() : vscrollbar->minimum()); } } -void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, - bool p_music) +void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music) { // record new entry - m_ic_records.append( - std::make_shared(p_name, p_line, "", p_system, p_music)); + m_ic_records.append(std::make_shared(p_name, p_line, "", p_system, p_music)); // update update_ic_log(false); @@ -1465,8 +1414,7 @@ void Courtroom::chat_tick() if (m_msg_is_first_person == false) { - ui_vp_player_char->play_idle(m_chatmessage[CMChrName], - m_chatmessage[CMEmote]); + ui_vp_player_char->play_idle(m_chatmessage[CMChrName], m_chatmessage[CMEmote]); } m_string_color = ""; @@ -1510,8 +1458,7 @@ void Courtroom::chat_tick() ui_vp_message->textCursor().insertText(f_character, vp_message_format); } - else if (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == - "true") + else if (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "true") { bool highlight_found = false; bool render_character = true; @@ -1777,8 +1724,7 @@ void Courtroom::handle_wtce(QString p_wtce) QString sfx_file = cc_sounds_ini; int index = p_wtce.at(p_wtce.size() - 1).digitValue(); - if (index > 0 && index < wtce_names.size() + 1 && - wtce_names.size() > 0) // check to prevent crash + if (index > 0 && index < wtce_names.size() + 1 && wtce_names.size() > 0) // check to prevent crash { p_wtce.chop(1); // looking for the 'testimony' part if (p_wtce == "testimony") @@ -1807,8 +1753,7 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) } else if (p_bar == 2) { - ui_prosecution_bar->set_image("prosecutionbar" + QString::number(p_state) + - ".png"); + ui_prosecution_bar->set_image("prosecutionbar" + QString::number(p_state) + ".png"); prosecution_bar_state = p_state; } } @@ -1851,10 +1796,9 @@ void Courtroom::on_ooc_return_pressed() QString name; do { - ooc_name = QInputDialog::getText( - this, "Enter a name", - "You must have a name to talk in OOC chat. Enter a name: ", - QLineEdit::Normal, "user", &ok); + ooc_name = QInputDialog::getText(this, "Enter a name", + "You must have a name to talk in OOC chat. Enter a name: ", QLineEdit::Normal, + "user", &ok); } while (ok && ooc_name.isEmpty()); if (!ok) return; @@ -1995,8 +1939,7 @@ void Courtroom::on_pos_dropdown_changed(int p_index) set_judge_enabled(f_pos == "jud"); - ao_app->send_server_packet( - new AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); + ao_app->send_server_packet(new AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); // Uncomment later and remove above // Will only work in TSDR 4.3+ servers // ao_app->send_server_packet(new AOPacket("SP#" + f_pos + "#%")); @@ -2045,9 +1988,7 @@ void Courtroom::on_music_list_double_clicked(QModelIndex p_model) QString p_song = ui_music_list->item(p_model.row())->text(); - ao_app->send_server_packet( - new AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%"), - false); + ao_app->send_server_packet(new AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%"), false); ui_ic_chat_message->setFocus(); } @@ -2056,9 +1997,7 @@ void Courtroom::on_area_list_double_clicked(QModelIndex p_model) { QString p_area = ui_area_list->item(p_model.row())->text(); - ao_app->send_server_packet( - new AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%"), - false); + ao_app->send_server_packet(new AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%"), false); ui_ic_chat_message->setFocus(); } @@ -2077,8 +2016,7 @@ void Courtroom::draw_shout_buttons() // Mark selected button as such if (m_shout_state != 0 && ui_shouts.size() > 0) - ui_shouts.at(m_shout_state - 1) - ->set_image(shout_names.at(m_shout_state - 1) + "_selected.png"); + ui_shouts.at(m_shout_state - 1)->set_image(shout_names.at(m_shout_state - 1) + "_selected.png"); } void Courtroom::on_shout_clicked() @@ -2168,8 +2106,7 @@ void Courtroom::draw_effect_buttons() // Mark selected button as such if (m_effect_state != 0 && ui_effects.size() > 0) - ui_effects[m_effect_state - 1]->set_image( - effect_names.at(m_effect_state - 1) + "_pressed.png"); + ui_effects[m_effect_state - 1]->set_image(effect_names.at(m_effect_state - 1) + "_pressed.png"); } void Courtroom::on_effect_button_clicked() @@ -2206,8 +2143,7 @@ void Courtroom::on_defense_minus_clicked() int f_state = defense_bar_state - 1; if (f_state >= 0) - ao_app->send_server_packet( - new AOPacket("HP#1#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(new AOPacket("HP#1#" + QString::number(f_state) + "#%")); } void Courtroom::on_defense_plus_clicked() @@ -2215,8 +2151,7 @@ void Courtroom::on_defense_plus_clicked() int f_state = defense_bar_state + 1; if (f_state <= 10) - ao_app->send_server_packet( - new AOPacket("HP#1#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(new AOPacket("HP#1#" + QString::number(f_state) + "#%")); } void Courtroom::on_prosecution_minus_clicked() @@ -2224,8 +2159,7 @@ void Courtroom::on_prosecution_minus_clicked() int f_state = prosecution_bar_state - 1; if (f_state >= 0) - ao_app->send_server_packet( - new AOPacket("HP#2#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(new AOPacket("HP#2#" + QString::number(f_state) + "#%")); } void Courtroom::on_prosecution_plus_clicked() @@ -2233,8 +2167,7 @@ void Courtroom::on_prosecution_plus_clicked() int f_state = prosecution_bar_state + 1; if (f_state <= 10) - ao_app->send_server_packet( - new AOPacket("HP#2#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(new AOPacket("HP#2#" + QString::number(f_state) + "#%")); } void Courtroom::on_text_color_changed(int p_color) @@ -2353,8 +2286,7 @@ void Courtroom::on_char_select_right_clicked() void Courtroom::on_spectator_clicked() { - QString content = - "CC#" + QString::number(ao_app->s_pv) + "#-1#" + get_hdid() + "#%"; + QString content = "CC#" + QString::number(ao_app->s_pv) + "#-1#" + get_hdid() + "#%"; ao_app->send_server_packet(new AOPacket(content)); enter_courtroom(-1); @@ -2368,9 +2300,7 @@ void Courtroom::on_call_mod_clicked() QMessageBox::StandardButton reply; QString warning = "Are you sure you want to call a mod? They will get angry " "at you if the reason is no good."; - reply = - QMessageBox::warning(this, "Warning", warning, - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + reply = QMessageBox::warning(this, "Warning", warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (reply == QMessageBox::Yes) { @@ -2458,8 +2388,7 @@ void Courtroom::on_note_text_changed() void Courtroom::ping_server() { - ao_app->send_server_packet( - new AOPacket("CH#" + QString::number(m_cid) + "#%")); + ao_app->send_server_packet(new AOPacket("CH#" + QString::number(m_cid) + "#%")); } void Courtroom::closeEvent(QCloseEvent *event) @@ -2503,8 +2432,7 @@ void Courtroom::on_sfx_list_clicked(QModelIndex p_index) // This is just the reverse of the above, basically. We set the colour, and we // set the brush to have that colour. - selected_col.setHslF(selected_col.hueF(), selected_col.saturationF(), - final_lightness); + selected_col.setHslF(selected_col.hueF(), selected_col.saturationF(), final_lightness); selected_brush.setColor(selected_col); // Finally, we set the selected SFX's background to be the lightened-up brush. diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 54287120e..4240dc001 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -48,20 +48,16 @@ void Courtroom::create_widgets() m_effects_player->set_volume(ao_config->effects_volume()); m_shouts_player = new AOShoutPlayer(this, ao_app); m_shouts_player->set_volume(ao_config->effects_volume()); - connect(ao_config, SIGNAL(effects_volume_changed(int)), this, - SLOT(on_config_effects_volume_changed(int))); + connect(ao_config, SIGNAL(effects_volume_changed(int)), this, SLOT(on_config_effects_volume_changed(int))); m_system_player = new AOSfxPlayer(this, ao_app); m_system_player->set_volume(ao_config->system_volume()); - connect(ao_config, SIGNAL(system_volume_changed(int)), this, - SLOT(on_config_system_volume_changed(int))); + connect(ao_config, SIGNAL(system_volume_changed(int)), this, SLOT(on_config_system_volume_changed(int))); m_music_player = new AOMusicPlayer(this, ao_app); m_music_player->set_volume(ao_config->music_volume()); - connect(ao_config, SIGNAL(music_volume_changed(int)), this, - SLOT(on_config_music_volume_changed(int))); + connect(ao_config, SIGNAL(music_volume_changed(int)), this, SLOT(on_config_music_volume_changed(int))); m_blips_player = new AOBlipPlayer(this, ao_app); m_blips_player->set_volume(ao_config->blips_volume()); - connect(ao_config, SIGNAL(blips_volume_changed(int)), this, - SLOT(on_config_blips_volume_changed(int))); + connect(ao_config, SIGNAL(blips_volume_changed(int)), this, SLOT(on_config_blips_volume_changed(int))); ui_background = new AOImageDisplay(this, ao_app); @@ -241,56 +237,40 @@ void Courtroom::connect_widgets() { connect(keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); - connect(ao_app, SIGNAL(reload_theme()), this, - SLOT(on_app_reload_theme_requested())); + connect(ao_app, SIGNAL(reload_theme()), this, SLOT(on_app_reload_theme_requested())); connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); - connect(text_delay_timer, SIGNAL(timeout()), this, - SLOT(start_chat_ticking())); + connect(text_delay_timer, SIGNAL(timeout()), this, SLOT(start_chat_ticking())); connect(sfx_delay_timer, SIGNAL(timeout()), this, SLOT(play_sfx())); connect(chat_tick_timer, SIGNAL(timeout()), this, SLOT(chat_tick())); connect(realization_timer, SIGNAL(timeout()), this, SLOT(realization_done())); - connect(testimony_show_timer, SIGNAL(timeout()), this, - SLOT(hide_testimony())); - connect(testimony_hide_timer, SIGNAL(timeout()), this, - SLOT(show_testimony())); + connect(testimony_show_timer, SIGNAL(timeout()), this, SLOT(hide_testimony())); + connect(testimony_hide_timer, SIGNAL(timeout()), this, SLOT(show_testimony())); - connect(ui_emote_left, SIGNAL(clicked()), this, - SLOT(on_emote_left_clicked())); - connect(ui_emote_right, SIGNAL(clicked()), this, - SLOT(on_emote_right_clicked())); + connect(ui_emote_left, SIGNAL(clicked()), this, SLOT(on_emote_left_clicked())); + connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); - connect(ui_emote_dropdown, SIGNAL(activated(int)), this, - SLOT(on_emote_dropdown_changed(int))); - connect(ui_pos_dropdown, SIGNAL(activated(int)), this, - SLOT(on_pos_dropdown_changed(int))); + connect(ui_emote_dropdown, SIGNAL(activated(int)), this, SLOT(on_emote_dropdown_changed(int))); + connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); connect(ui_mute_list, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(on_mute_list_item_changed(QListWidgetItem *))); - connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, - SLOT(on_chat_return_pressed())); - - connect(ui_ooc_chat_name, SIGNAL(textEdited(QString)), ao_config, - SLOT(set_username(QString))); - connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, - SLOT(setText(QString))); - connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, - SLOT(on_ooc_return_pressed())); - - connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, - SLOT(on_music_list_clicked())); - connect(ui_area_list, SIGNAL(clicked(QModelIndex)), this, - SLOT(on_area_list_clicked())); - connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, - SLOT(on_music_list_double_clicked(QModelIndex))); - connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, - SLOT(on_area_list_double_clicked(QModelIndex))); + connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_chat_return_pressed())); + + connect(ui_ooc_chat_name, SIGNAL(textEdited(QString)), ao_config, SLOT(set_username(QString))); + connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); + connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); + + connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_music_list_clicked())); + connect(ui_area_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_area_list_clicked())); + connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_music_list_double_clicked(QModelIndex))); + connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_area_list_double_clicked(QModelIndex))); // connect events for shout/effect/wtce buttons happen in load_shouts(), // load_effects(), load_wtce() @@ -298,67 +278,47 @@ void Courtroom::connect_widgets() connect(ui_shout_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_effect_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - connect(ui_effect_down, SIGNAL(clicked(bool)), this, - SLOT(on_cycle_clicked())); + connect(ui_effect_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_wtce_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_wtce_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_mute, SIGNAL(clicked()), this, SLOT(on_mute_clicked())); - connect(ui_defense_minus, SIGNAL(clicked()), this, - SLOT(on_defense_minus_clicked())); - connect(ui_defense_plus, SIGNAL(clicked()), this, - SLOT(on_defense_plus_clicked())); - connect(ui_prosecution_minus, SIGNAL(clicked()), this, - SLOT(on_prosecution_minus_clicked())); - connect(ui_prosecution_plus, SIGNAL(clicked()), this, - SLOT(on_prosecution_plus_clicked())); - - connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, - SLOT(on_text_color_changed(int))); - - connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, - SLOT(on_chat_config_changed())); - connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, - SLOT(on_chat_config_changed())); - connect(ao_config, SIGNAL(log_uses_newline_changed(bool)), this, - SLOT(on_chat_config_changed())); - - connect(ui_music_search, SIGNAL(textChanged(QString)), this, - SLOT(on_music_search_edited(QString))); - connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, - SLOT(on_sfx_search_edited(QString))); - - connect(ui_change_character, SIGNAL(clicked()), this, - SLOT(on_change_character_clicked())); + connect(ui_defense_minus, SIGNAL(clicked()), this, SLOT(on_defense_minus_clicked())); + connect(ui_defense_plus, SIGNAL(clicked()), this, SLOT(on_defense_plus_clicked())); + connect(ui_prosecution_minus, SIGNAL(clicked()), this, SLOT(on_prosecution_minus_clicked())); + connect(ui_prosecution_plus, SIGNAL(clicked()), this, SLOT(on_prosecution_plus_clicked())); + + connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, SLOT(on_text_color_changed(int))); + + connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_uses_newline_changed(bool)), this, SLOT(on_chat_config_changed())); + + connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); + connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited(QString))); + + connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); - connect(ui_switch_area_music, SIGNAL(clicked()), this, - SLOT(on_switch_area_music_clicked())); + connect(ui_switch_area_music, SIGNAL(clicked()), this, SLOT(on_switch_area_music_clicked())); - connect(ui_config_panel, SIGNAL(clicked()), this, - SLOT(on_config_panel_clicked())); - connect(ui_note_button, SIGNAL(clicked()), this, - SLOT(on_note_button_clicked())); + connect(ui_config_panel, SIGNAL(clicked()), this, SLOT(on_config_panel_clicked())); + connect(ui_note_button, SIGNAL(clicked()), this, SLOT(on_note_button_clicked())); - connect(ui_vp_notepad, SIGNAL(textChanged()), this, - SLOT(on_note_text_changed())); + connect(ui_vp_notepad, SIGNAL(textChanged()), this, SLOT(on_note_text_changed())); connect(ui_pre, SIGNAL(clicked()), this, SLOT(on_pre_clicked())); connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); - connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, - SLOT(on_sfx_list_clicked(QModelIndex))); + connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_sfx_list_clicked(QModelIndex))); - connect(ui_evidence_button, SIGNAL(clicked()), this, - SLOT(on_evidence_button_clicked())); + connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); - connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, - SLOT(on_add_button_clicked())); - connect(ui_set_notes, SIGNAL(clicked(bool)), this, - SLOT(on_set_notes_clicked())); + connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, SLOT(on_add_button_clicked())); + connect(ui_set_notes, SIGNAL(clicked(bool)), this, SLOT(on_set_notes_clicked())); } void Courtroom::reset_widget_names() @@ -457,8 +417,7 @@ void Courtroom::insert_widget_name(QString p_widget_name, QWidget *p_widget) p_widget->setObjectName(p_widget_name); } -void Courtroom::insert_widget_names(QVector &p_widget_names, - QVector &p_widgets) +void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) { for (int i = 0; i < p_widgets.length(); ++i) insert_widget_name(p_widget_names[i], p_widgets[i]); @@ -577,8 +536,7 @@ void Courtroom::set_widget_layers() void Courtroom::set_widgets() { QString filename = design_ini; - pos_size_type f_courtroom = - ao_app->get_element_dimensions("courtroom", filename); + pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", filename); if (f_courtroom.width < 0 || f_courtroom.height < 0) { @@ -681,8 +639,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_vp_clock, "clock"); ui_vp_clock->hide(); - ui_ic_chat_message->setStyleSheet( - "QLineEdit{background-color: rgba(100, 100, 100, 255);}"); + ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); ui_vp_chatbox->set_image("chatmed.png"); ui_vp_chatbox->hide(); @@ -718,12 +675,10 @@ void Courtroom::set_widgets() set_size_and_pos(ui_pos_dropdown, "pos_dropdown"); set_size_and_pos(ui_defense_bar, "defense_bar"); - ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + - ".png"); + ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + ".png"); set_size_and_pos(ui_prosecution_bar, "prosecution_bar"); - ui_prosecution_bar->set_image( - "prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); + ui_prosecution_bar->set_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); // set_size_and_pos(ui_shouts[0], "hold_it"); // ui_shouts[0]->show(); @@ -752,8 +707,7 @@ void Courtroom::set_widgets() ui_shout_down->hide(); // courtroom_config.ini necessary + check for crash - if (ao_app->read_theme_ini("enable_single_shout", cc_config_ini) == "true" && - ui_shouts.size() > 0) + if (ao_app->read_theme_ini("enable_single_shout", cc_config_ini) == "true" && ui_shouts.size() > 0) { for (auto &shout : ui_shouts) move_widget(shout, "bullet"); @@ -805,8 +759,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_wtce[i], wtce_names[i]); } - if (ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == - "true") // courtroom_config.ini necessary + if (ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true") // courtroom_config.ini necessary { for (auto &wtce : ui_wtce) move_widget(wtce, "wtce"); @@ -872,8 +825,7 @@ void Courtroom::set_widgets() // behavior will occur if the button is hidden due to 'config_panel' not being // found in courtroom_design.ini This is to assist with people who switch to // incompatible and/or smaller themes and have the button disappear - if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || - !ui_config_panel->isVisible()) + if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || !ui_config_panel->isVisible()) { ui_config_panel->setVisible(true); ui_config_panel->move(0, 0); @@ -1013,8 +965,7 @@ void Courtroom::set_widgets() note_scroll_area->setWidget(ui_note_area); ui_note_area->set_image("note_area.png"); ui_note_area->add_button->set_image("add_button.png"); - ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, - QSizePolicy::Fixed); + ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); ui_note_area->setLayout(ui_note_area->m_layout); ui_note_area->show(); note_scroll_area->hide(); @@ -1036,8 +987,7 @@ void Courtroom::set_size_and_pos(QWidget *p_widget, QString p_identifier) { QString filename = design_ini; - pos_size_type design_ini_result = - ao_app->get_element_dimensions(p_identifier, filename); + pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); if (design_ini_result.width < 0 || design_ini_result.height < 0) { @@ -1055,8 +1005,7 @@ void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) { QString filename = design_ini; - pos_size_type design_ini_result = - ao_app->get_element_dimensions(p_identifier, filename); + pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); if (design_ini_result.width < 0 || design_ini_result.height < 0) { @@ -1070,15 +1019,12 @@ void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) } template -int Courtroom::adapt_numbered_items(QVector &item_vector, - QString config_item_number, - QString item_name) +int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_item_number, QString item_name) { // &item_vector must be a vector of size at least 1! // Redraw the new correct number of items. - int new_item_number = - ao_app->read_theme_ini(config_item_number, cc_config_ini).toInt(); + int new_item_number = ao_app->read_theme_ini(config_item_number, cc_config_ini).toInt(); int current_item_number = item_vector.size(); // Note we use the fact that, if config_item_number is not there, // read_theme_ini returns an empty string, which .toInt() would fail to @@ -1132,12 +1078,10 @@ void Courtroom::check_effects() for (int i = 0; i < ui_effects.size(); ++i) { - QString path = ao_app->find_asset_path( - {ao_app->get_character_path(current_char, effect_names.at(i))}, - animated_extensions()); + QString path = + ao_app->find_asset_path({ao_app->get_character_path(current_char, effect_names.at(i))}, animated_extensions()); if (path.isEmpty()) - path = ao_app->find_theme_asset_path(effect_names.at(i), - animated_extensions()); + path = ao_app->find_theme_asset_path(effect_names.at(i), animated_extensions()); effects_enabled[i] = (!path.isEmpty()); } } @@ -1153,12 +1097,10 @@ void Courtroom::check_free_blocks() for (int i = 0; i < ui_free_blocks.size(); ++i) { - QString path = ao_app->find_asset_path( - {ao_app->get_character_path(current_char, free_block_names.at(i))}, - animated_extensions()); - if (path.isEmpty()) - path = ao_app->find_theme_asset_path(free_block_names.at(i), + QString path = ao_app->find_asset_path({ao_app->get_character_path(current_char, free_block_names.at(i))}, animated_extensions()); + if (path.isEmpty()) + path = ao_app->find_theme_asset_path(free_block_names.at(i), animated_extensions()); free_blocks_enabled[i] = (!path.isEmpty()); } } @@ -1174,13 +1116,11 @@ void Courtroom::check_shouts() for (int i = 0; i < ui_shouts.size(); ++i) { - QString path = ao_app->find_asset_path( - {ao_app->get_character_path(current_char, shout_names.at(i))}, - animated_extensions()); + QString path = + ao_app->find_asset_path({ao_app->get_character_path(current_char, shout_names.at(i))}, animated_extensions()); if (path.isEmpty()) - path = ao_app->find_theme_asset_path(shout_names.at(i), - animated_extensions()); + path = ao_app->find_theme_asset_path(shout_names.at(i), animated_extensions()); shouts_enabled[i] = (!path.isEmpty()); } @@ -1197,12 +1137,10 @@ void Courtroom::check_wtce() for (int i = 0; i < ui_wtce.size(); ++i) { - QString path = ao_app->find_asset_path( - {ao_app->get_character_path(current_char, wtce_names.at(i))}, - animated_extensions()); + QString path = + ao_app->find_asset_path({ao_app->get_character_path(current_char, wtce_names.at(i))}, animated_extensions()); if (path.isEmpty()) - path = ao_app->find_theme_asset_path(wtce_names.at(i), - animated_extensions()); + path = ao_app->find_theme_asset_path(wtce_names.at(i), animated_extensions()); wtce_enabled[i] = (!path.isEmpty()); } } @@ -1220,8 +1158,7 @@ void Courtroom::delete_widget(QWidget *p_widget) grand_parent = this; // set new parent - for (QWidget *child : - p_widget->findChildren(QString(), Qt::FindDirectChildrenOnly)) + for (QWidget *child : p_widget->findChildren(QString(), Qt::FindDirectChildrenOnly)) child->setParent(grand_parent); // delete widget @@ -1235,8 +1172,7 @@ void Courtroom::load_effects() delete_widget(widget); // And create new effects - int effect_number = - ao_app->read_theme_ini("effect_number", cc_config_ini).toInt(); + int effect_number = ao_app->read_theme_ini("effect_number", cc_config_ini).toInt(); effects_enabled.resize(effect_number); ui_effects.resize(effect_number); @@ -1250,8 +1186,7 @@ void Courtroom::load_effects() // And connect their actions for (auto &effect : ui_effects) - connect(effect, SIGNAL(clicked(bool)), this, - SLOT(on_effect_button_clicked())); + connect(effect, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked())); // And add names effect_names.clear(); @@ -1272,8 +1207,7 @@ void Courtroom::load_free_blocks() delete_widget(widget); // And create new free block buttons - int free_block_number = - ao_app->read_theme_ini("free_block_number", cc_config_ini).toInt(); + int free_block_number = ao_app->read_theme_ini("free_block_number", cc_config_ini).toInt(); free_blocks_enabled.resize(free_block_number); ui_free_blocks.resize(free_block_number); @@ -1289,8 +1223,7 @@ void Courtroom::load_free_blocks() free_block_names.clear(); for (int i = 1; i <= ui_free_blocks.size(); ++i) { - QString name = - "free_block_" + ao_app->get_spbutton("[FREE BLOCKS]", i).trimmed(); + QString name = "free_block_" + ao_app->get_spbutton("[FREE BLOCKS]", i).trimmed(); if (!name.isEmpty()) { free_block_names.append(name); @@ -1306,8 +1239,7 @@ void Courtroom::load_shouts() delete_widget(widget); // And create new shouts - int shout_number = - ao_app->read_theme_ini("shout_number", cc_config_ini).toInt(); + int shout_number = ao_app->read_theme_ini("shout_number", cc_config_ini).toInt(); shouts_enabled.resize(shout_number); ui_shouts.resize(shout_number); @@ -1345,8 +1277,7 @@ void Courtroom::load_wtce() delete_widget(widget); // And create new wtce buttons - int wtce_number = - ao_app->read_theme_ini("wtce_number", cc_config_ini).toInt(); + int wtce_number = ao_app->read_theme_ini("wtce_number", cc_config_ini).toInt(); wtce_enabled.resize(wtce_number); ui_wtce.resize(wtce_number); @@ -1414,8 +1345,7 @@ void Courtroom::set_judge_wtce() wtce->hide(); // check if we use a single wtce or multiple - const bool is_single_wtce = - ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true"; + const bool is_single_wtce = ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true"; // update visibility for next/previous ui_wtce_up->setVisible(is_judge && is_single_wtce); @@ -1469,8 +1399,7 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier) set_font(widget, p_identifier, ""); } -void Courtroom::set_font(QWidget *widget, QString p_identifier, - QString override_color) +void Courtroom::set_font(QWidget *widget, QString p_identifier, QString override_color) { QString design_file = fonts_ini; QString class_name = widget->metaObject()->className(); @@ -1491,8 +1420,7 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, if (override_color.isEmpty()) { - QString color = - ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); + QString color = ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); if (color.isEmpty()) color = "255, 255, 255"; override_color = "rgba(" + color + ", 255)"; @@ -1501,9 +1429,7 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, int bold = ao_app->get_font_property(p_identifier + "_bold", design_file); QString is_bold = (bold == 1 ? "bold" : ""); - QString style_sheet_string = class_name + - " { background-color: rgba(0, 0, 0, 0);\n" + - "color: " + override_color + + QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: " + override_color + ";\n" "font: " + is_bold + "; }"; @@ -1515,8 +1441,7 @@ void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier) set_qtextedit_font(widget, p_identifier, ""); } -void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier, - QString override_color) +void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier, QString override_color) { set_font(widget, p_identifier, override_color); diff --git a/src/discord_rich_presence.cpp b/src/discord_rich_presence.cpp index 05cf53c39..2c22c03ec 100644 --- a/src/discord_rich_presence.cpp +++ b/src/discord_rich_presence.cpp @@ -109,12 +109,10 @@ void Discord::state_server(std::string name, std::string server_id) void Discord::state_character(std::string name) { - auto name_internal = - QString(name.c_str()).toLower().replace(' ', '_').toStdString(); + auto name_internal = QString(name.c_str()).toLower().replace(' ', '_').toStdString(); auto name_friendly = QString(name.c_str()).replace('_', ' ').toStdString(); const std::string playing_as = "Playing as " + name_friendly; - qDebug() << "Discord RPC: Setting character state (" << playing_as.c_str() - << ")"; + qDebug() << "Discord RPC: Setting character state (" << playing_as.c_str() << ")"; DiscordRichPresence presence; std::memset(&presence, 0, sizeof(presence)); diff --git a/src/emotes.cpp b/src/emotes.cpp index 406692a7f..dcdf31254 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -32,8 +32,7 @@ void Courtroom::reconstruct_emotes() // resize and move set_size_and_pos(ui_emotes, "emotes"); - QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", - "courtroom_design.ini"); + QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", "courtroom_design.ini"); const int button_width = 40; int x_spacing = f_spacing.x(); @@ -43,10 +42,8 @@ void Courtroom::reconstruct_emotes() int y_spacing = f_spacing.y(); int y_mod_count = 0; - emote_columns = - ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; - emote_rows = - ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; + emote_columns = ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; + emote_rows = ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; max_emotes_on_page = emote_columns * emote_rows; @@ -61,8 +58,7 @@ void Courtroom::reconstruct_emotes() f_emote->set_emote_number(n); - connect(f_emote, SIGNAL(emote_clicked(int)), this, - SLOT(on_emote_clicked(int))); + connect(f_emote, SIGNAL(emote_clicked(int)), this, SLOT(on_emote_clicked(int))); ++x_mod_count; @@ -130,8 +126,7 @@ void Courtroom::set_emote_page() { int n_real_emote = n_emote + current_emote_page * max_emotes_on_page; AOEmoteButton *f_emote = ui_emote_list.at(n_emote); - f_emote->set_image(current_char, n_real_emote, - n_real_emote == current_emote); + f_emote->set_image(current_char, n_real_emote, n_real_emote == current_emote); f_emote->show(); } } @@ -157,16 +152,14 @@ void Courtroom::select_emote(int p_id) int max = (max_emotes_on_page - 1) + current_emote_page * max_emotes_on_page; if (current_emote >= min && current_emote <= max) - ui_emote_list.at(current_emote % max_emotes_on_page) - ->set_image(current_char, current_emote, false); + ui_emote_list.at(current_emote % max_emotes_on_page)->set_image(current_char, current_emote, false); int old_emote = current_emote; current_emote = p_id; if (current_emote >= min && current_emote <= max) - ui_emote_list.at(current_emote % max_emotes_on_page) - ->set_image(current_char, current_emote, true); + ui_emote_list.at(current_emote % max_emotes_on_page)->set_image(current_char, current_emote, true); int emote_mod = ao_app->get_emote_mod(current_char, current_emote); diff --git a/src/evidence.cpp b/src/evidence.cpp index 2107fc6d1..7c4ddb3d7 100644 --- a/src/evidence.cpp +++ b/src/evidence.cpp @@ -35,8 +35,7 @@ void Courtroom::construct_evidence() set_size_and_pos(ui_evidence, "evidence_background"); set_size_and_pos(ui_evidence_buttons, "evidence_buttons"); - QPoint f_spacing = ao_app->get_button_spacing("evidence_button_spacing", - "courtroom_design.ini"); + QPoint f_spacing = ao_app->get_button_spacing("evidence_button_spacing", "courtroom_design.ini"); const int button_width = 70; int x_spacing = f_spacing.x(); @@ -46,12 +45,8 @@ void Courtroom::construct_evidence() int y_spacing = f_spacing.y(); int y_mod_count = 0; - evidence_columns = ((ui_evidence_buttons->width() - button_width) / - (x_spacing + button_width)) + - 1; - evidence_rows = ((ui_evidence_buttons->height() - button_height) / - (y_spacing + button_height)) + - 1; + evidence_columns = ((ui_evidence_buttons->width() - button_width) / (x_spacing + button_width)) + 1; + evidence_rows = ((ui_evidence_buttons->height() - button_height) / (y_spacing + button_height)) + 1; max_evidence_on_page = evidence_columns * evidence_rows; @@ -60,19 +55,15 @@ void Courtroom::construct_evidence() int x_pos = (button_width + x_spacing) * x_mod_count; int y_pos = (button_height + y_spacing) * y_mod_count; - AOEvidenceButton *f_evidence = - new AOEvidenceButton(ui_evidence_buttons, ao_app, x_pos, y_pos); + AOEvidenceButton *f_evidence = new AOEvidenceButton(ui_evidence_buttons, ao_app, x_pos, y_pos); ui_evidence_list.append(f_evidence); f_evidence->set_id(n); - connect(f_evidence, SIGNAL(evidence_clicked(int)), this, - SLOT(on_evidence_clicked(int))); - connect(f_evidence, SIGNAL(evidence_double_clicked(int)), this, - SLOT(on_evidence_double_clicked(int))); - connect(f_evidence, SIGNAL(on_hover(int, bool)), this, - SLOT(on_evidence_hover(int, bool))); + connect(f_evidence, SIGNAL(evidence_clicked(int)), this, SLOT(on_evidence_clicked(int))); + connect(f_evidence, SIGNAL(evidence_double_clicked(int)), this, SLOT(on_evidence_double_clicked(int))); + connect(f_evidence, SIGNAL(on_hover(int, bool)), this, SLOT(on_evidence_hover(int, bool))); ++x_mod_count; @@ -83,22 +74,14 @@ void Courtroom::construct_evidence() } } - connect(ui_evidence_name, SIGNAL(returnPressed()), this, - SLOT(on_evidence_name_edited())); - connect(ui_evidence_left, SIGNAL(clicked()), this, - SLOT(on_evidence_left_clicked())); - connect(ui_evidence_right, SIGNAL(clicked()), this, - SLOT(on_evidence_right_clicked())); - connect(ui_evidence_present, SIGNAL(clicked()), this, - SLOT(on_evidence_present_clicked())); - connect(ui_evidence_delete, SIGNAL(clicked()), this, - SLOT(on_evidence_delete_clicked())); - connect(ui_evidence_image_name, SIGNAL(returnPressed()), this, - SLOT(on_evidence_image_name_edited())); - connect(ui_evidence_image_button, SIGNAL(clicked()), this, - SLOT(on_evidence_image_button_clicked())); - connect(ui_evidence_x, SIGNAL(clicked()), this, - SLOT(on_evidence_x_clicked())); + connect(ui_evidence_name, SIGNAL(returnPressed()), this, SLOT(on_evidence_name_edited())); + connect(ui_evidence_left, SIGNAL(clicked()), this, SLOT(on_evidence_left_clicked())); + connect(ui_evidence_right, SIGNAL(clicked()), this, SLOT(on_evidence_right_clicked())); + connect(ui_evidence_present, SIGNAL(clicked()), this, SLOT(on_evidence_present_clicked())); + connect(ui_evidence_delete, SIGNAL(clicked()), this, SLOT(on_evidence_delete_clicked())); + connect(ui_evidence_image_name, SIGNAL(returnPressed()), this, SLOT(on_evidence_image_name_edited())); + connect(ui_evidence_image_button, SIGNAL(clicked()), this, SLOT(on_evidence_image_button_clicked())); + connect(ui_evidence_x, SIGNAL(clicked()), this, SLOT(on_evidence_x_clicked())); ui_evidence->hide(); } @@ -147,21 +130,17 @@ void Courtroom::set_evidence_page() if (current_evidence_page > 0) ui_evidence_left->show(); - for (int n_evidence_button = 0; n_evidence_button < evidence_on_page; - ++n_evidence_button) + for (int n_evidence_button = 0; n_evidence_button < evidence_on_page; ++n_evidence_button) { - int n_real_evidence = - n_evidence_button + current_evidence_page * max_evidence_on_page; - AOEvidenceButton *f_evidence_button = - ui_evidence_list.at(n_evidence_button); + int n_real_evidence = n_evidence_button + current_evidence_page * max_evidence_on_page; + AOEvidenceButton *f_evidence_button = ui_evidence_list.at(n_evidence_button); // ie. the add evidence button if (n_real_evidence == (total_evidence - 1)) f_evidence_button->set_theme_image("addevidence.png"); else if (n_real_evidence < (total_evidence - 1)) { - f_evidence_button->set_image( - local_evidence_list.at(n_real_evidence).image); + f_evidence_button->set_image(local_evidence_list.at(n_real_evidence).image); if (n_real_evidence == current_evidence) f_evidence_button->set_selected(true); @@ -244,8 +223,7 @@ void Courtroom::on_evidence_clicked(int p_id) if (f_real_id == local_evidence_list.size()) { - ao_app->send_server_packet( - new AOPacket("PE###empty.png#%")); + ao_app->send_server_packet(new AOPacket("PE###empty.png#%")); return; } else if (f_real_id > local_evidence_list.size()) @@ -337,8 +315,7 @@ void Courtroom::on_evidence_delete_clicked() ui_evidence_description->setReadOnly(true); ui_evidence_overlay->hide(); - ao_app->send_server_packet( - new AOPacket("DE#" + QString::number(current_evidence) + "#%")); + ao_app->send_server_packet(new AOPacket("DE#" + QString::number(current_evidence) + "#%")); current_evidence = 0; diff --git a/src/hardware_functions.cpp b/src/hardware_functions.cpp index 733d95e85..78a9bb683 100644 --- a/src/hardware_functions.cpp +++ b/src/hardware_functions.cpp @@ -10,8 +10,7 @@ BOOL bIsRetrieved; QString get_hdid() { - bIsRetrieved = GetVolumeInformation(TEXT("C:\\"), NULL, NULL, &dwVolSerial, - NULL, NULL, NULL, NULL); + bIsRetrieved = GetVolumeInformation(TEXT("C:\\"), NULL, NULL, &dwVolSerial, NULL, NULL, NULL, NULL); if (bIsRetrieved) return QString::number(dwVolSerial, 16); diff --git a/src/lobby.cpp b/src/lobby.cpp index 5e3e4183d..2970d9fee 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -50,22 +50,17 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_progress_bar->setStyleSheet("QProgressBar{ color: white; }"); ui_cancel = new AOButton(ui_loading_background, ao_app); - connect(ui_public_servers, SIGNAL(clicked()), this, - SLOT(on_public_servers_clicked())); + connect(ui_public_servers, SIGNAL(clicked()), this, SLOT(on_public_servers_clicked())); connect(ui_favorites, SIGNAL(clicked()), this, SLOT(on_favorites_clicked())); connect(ui_refresh, SIGNAL(pressed()), this, SLOT(on_refresh_pressed())); connect(ui_refresh, SIGNAL(released()), this, SLOT(on_refresh_released())); - connect(ui_add_to_fav, SIGNAL(pressed()), this, - SLOT(on_add_to_fav_pressed())); - connect(ui_add_to_fav, SIGNAL(released()), this, - SLOT(on_add_to_fav_released())); + connect(ui_add_to_fav, SIGNAL(pressed()), this, SLOT(on_add_to_fav_pressed())); + connect(ui_add_to_fav, SIGNAL(released()), this, SLOT(on_add_to_fav_released())); connect(ui_connect, SIGNAL(pressed()), this, SLOT(on_connect_pressed())); connect(ui_connect, SIGNAL(released()), this, SLOT(on_connect_released())); connect(ui_about, SIGNAL(clicked()), this, SLOT(on_about_clicked())); - connect(ui_server_list, SIGNAL(clicked(QModelIndex)), this, - SLOT(on_server_list_clicked(QModelIndex))); - connect(ui_chatmessage, SIGNAL(returnPressed()), this, - SLOT(on_chatfield_return_pressed())); + connect(ui_server_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_server_list_clicked(QModelIndex))); + connect(ui_chatmessage, SIGNAL(returnPressed()), this, SLOT(on_chatfield_return_pressed())); connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); set_widgets(); @@ -84,18 +79,17 @@ void Lobby::set_widgets() // Most common symptom of bad config files, missing assets, or misnamed // theme folder - call_notice( - "It doesn't look like your client is set up correctly. This can be " - "due to the following reasons: \n" - "1. Check you downloaded and extracted the resources correctly from " - "the DRO Discord including the large 'base' folder.\n" - "2. If you did, check that the base folder is in the same folder " - "where you launched Danganronpa Online from: " + - QDir::currentPath() + - "\n" - "3. If it is there, check that your current theme folder exists in " - "base/themes. According to base/config.ini, your current theme is " + - ao_app->get_theme()); + call_notice("It doesn't look like your client is set up correctly. This can be " + "due to the following reasons: \n" + "1. Check you downloaded and extracted the resources correctly from " + "the DRO Discord including the large 'base' folder.\n" + "2. If you did, check that the base folder is in the same folder " + "where you launched Danganronpa Online from: " + + QDir::currentPath() + + "\n" + "3. If it is there, check that your current theme folder exists in " + "base/themes. According to base/config.ini, your current theme is " + + ao_app->get_theme()); this->resize(517, 666); } @@ -144,17 +138,15 @@ void Lobby::set_widgets() set_size_and_pos(ui_chatbox, "chatbox"); ui_chatbox->setReadOnly(true); - ui_chatbox->setStyleSheet( - "QTextBrowser{background-color: rgba(0, 0, 0, 0);}"); + ui_chatbox->setStyleSheet("QTextBrowser{background-color: rgba(0, 0, 0, 0);}"); set_size_and_pos(ui_chatname, "chatname"); ui_chatname->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "selection-background-color: rgba(0, 0, 0, 0);"); set_size_and_pos(ui_chatmessage, "chatmessage"); - ui_chatmessage->setStyleSheet( - "background-color: rgba(0, 0, 0, 0);" - "selection-background-color: rgba(0, 0, 0, 0);"); + ui_chatmessage->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "selection-background-color: rgba(0, 0, 0, 0);"); ui_loading_background->resize(this->width(), this->height()); ui_loading_background->set_image("loadingbackground.png"); @@ -183,8 +175,7 @@ void Lobby::set_size_and_pos(QWidget *p_widget, QString p_identifier) { QString filename = "lobby_design.ini"; - pos_size_type design_ini_result = - ao_app->get_element_dimensions(p_identifier, filename); + pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); if (design_ini_result.width < 0 || design_ini_result.height < 0) { @@ -242,8 +233,7 @@ void Lobby::set_font(QWidget *widget, QString p_identifier) // 2. "font_default" // 3. System font QFontDatabase font_database; - QString font_name = - ao_app->get_font_name("font_" + p_identifier, design_file); + QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); if (!font_database.families().contains(font_name)) font_name = ao_app->get_font_name("font_default", "lobby_fonts.ini"); QFont font(font_name, f_weight); @@ -251,24 +241,21 @@ void Lobby::set_font(QWidget *widget, QString p_identifier) QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); - bool bold = - (bool)ao_app->get_font_property(p_identifier + "_bold", design_file); + bool bold = (bool)ao_app->get_font_property(p_identifier + "_bold", design_file); QString is_bold = ""; if (bold) is_bold = "bold"; - bool center = - (bool)ao_app->get_font_property(p_identifier + "_center", design_file); + bool center = (bool)ao_app->get_font_property(p_identifier + "_center", design_file); QString is_center = ""; if (center) is_center = "qproperty-alignment: AlignCenter;"; QString class_name = widget->metaObject()->className(); - QString style_sheet_string = - class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: rgba(" + - QString::number(f_color.red()) + ", " + QString::number(f_color.green()) + - ", " + QString::number(f_color.blue()) + ", 255);\n" + is_center + "\n" + - "font: " + is_bold + "; }"; + QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: rgba(" + + QString::number(f_color.red()) + ", " + QString::number(f_color.green()) + ", " + + QString::number(f_color.blue()) + ", 255);\n" + is_center + "\n" + "font: " + is_bold + + "; }"; widget->setStyleSheet(style_sheet_string); } @@ -495,8 +482,7 @@ void Lobby::set_choose_a_server() void Lobby::set_player_count(int players_online, int max_players) { - QString f_string = "Online: " + QString::number(players_online) + "/" + - QString::number(max_players); + QString f_string = "Online: " + QString::number(players_online) + "/" + QString::number(max_players); ui_player_count->setText(f_string); ui_player_count->setAlignment(Qt::AlignHCenter); diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp index 15e40a794..308e1c360 100644 --- a/src/networkmanager.cpp +++ b/src/networkmanager.cpp @@ -15,15 +15,11 @@ NetworkManager::NetworkManager(AOApplication *parent) : QObject(parent) ms_reconnect_timer = new QTimer(this); ms_reconnect_timer->setSingleShot(true); - QObject::connect(ms_reconnect_timer, SIGNAL(timeout()), this, - SLOT(retry_ms_connect())); - - QObject::connect(ms_socket, SIGNAL(readyRead()), this, - SLOT(handle_ms_packet())); - QObject::connect(server_socket, SIGNAL(readyRead()), this, - SLOT(handle_server_packet())); - QObject::connect(server_socket, SIGNAL(disconnected()), ao_app, - SLOT(server_disconnected())); + QObject::connect(ms_reconnect_timer, SIGNAL(timeout()), this, SLOT(retry_ms_connect())); + + QObject::connect(ms_socket, SIGNAL(readyRead()), this, SLOT(handle_ms_packet())); + QObject::connect(server_socket, SIGNAL(readyRead()), this, SLOT(handle_server_packet())); + QObject::connect(server_socket, SIGNAL(disconnected()), ao_app, SLOT(server_disconnected())); } NetworkManager::~NetworkManager() @@ -47,8 +43,7 @@ void NetworkManager::connect_to_master_nosrv() QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); - QObject::connect(ms_socket, SIGNAL(connected()), this, - SLOT(on_ms_nosrv_connect_success())); + QObject::connect(ms_socket, SIGNAL(connected()), this, SLOT(on_ms_nosrv_connect_success())); ms_socket->connectToHost(ms_nosrv_hostname, ms_port); } @@ -103,8 +98,7 @@ void NetworkManager::handle_ms_packet() } } - QStringList packet_list = - in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); + QStringList packet_list = in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); for (QString packet : packet_list) { @@ -152,26 +146,22 @@ void NetworkManager::on_srv_lookup() break; } else if (ms_socket->state() != QAbstractSocket::ConnectingState && - ms_socket->state() != QAbstractSocket::HostLookupState && - ms_socket->error() != -1) + ms_socket->state() != QAbstractSocket::HostLookupState && ms_socket->error() != -1) { qDebug() << ms_socket->error(); - qWarning() << "Error connecting to master server:" - << ms_socket->errorString(); + qWarning() << "Error connecting to master server:" << ms_socket->errorString(); ms_socket->abort(); ms_socket->close(); break; } - } while (timer.elapsed() < - timeout_milliseconds); // Very expensive spin-wait loop - it will - // bring CPU to 100%! + } while (timer.elapsed() < timeout_milliseconds); // Very expensive spin-wait loop - it will + // bring CPU to 100%! if (connected) { // Connect a one-shot signal in case the master server disconnects // randomly - QObject::connect( - ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, - SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); + QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, + SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); break; } else @@ -194,8 +184,7 @@ void NetworkManager::on_ms_nosrv_connect_success() { Q_EMIT ms_connect_finished(true, false); - QObject::disconnect(ms_socket, SIGNAL(connected()), this, - SLOT(on_ms_nosrv_connect_success())); + QObject::disconnect(ms_socket, SIGNAL(connected()), this, SLOT(on_ms_nosrv_connect_success())); QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); @@ -203,13 +192,11 @@ void NetworkManager::on_ms_nosrv_connect_success() void NetworkManager::on_ms_socket_error(QAbstractSocket::SocketError error) { - qWarning() << "Master server socket error:" << ms_socket->errorString() << "(" - << error << ")"; + qWarning() << "Master server socket error:" << ms_socket->errorString() << "(" << error << ")"; // Disconnect the one-shot signal - this way, failover connect attempts // don't trigger a full retry - QObject::disconnect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, + QObject::disconnect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); Q_EMIT ms_connect_finished(false, true); @@ -219,8 +206,7 @@ void NetworkManager::on_ms_socket_error(QAbstractSocket::SocketError error) void NetworkManager::retry_ms_connect() { - if (!ms_reconnect_timer->isActive() && - ms_socket->state() != QAbstractSocket::ConnectingState) + if (!ms_reconnect_timer->isActive() && ms_socket->state() != QAbstractSocket::ConnectingState) connect_to_master(); } @@ -246,8 +232,7 @@ void NetworkManager::handle_server_packet() } } - QStringList packet_list = - in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); + QStringList packet_list = in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); for (QString packet : packet_list) { diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 5d9552b22..de5bb3392 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -162,8 +162,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; if (courtroom_constructed) - w_courtroom->append_server_chatmessage(f_contents.at(0), - f_contents.at(1)); + w_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1)); } else if (header == "FL") { @@ -177,8 +176,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (f_contents.size() < 2) goto end; - w_lobby->set_player_count(f_contents.at(0).toInt(), - f_contents.at(1).toInt()); + w_lobby->set_player_count(f_contents.at(0).toInt(), f_contents.at(1).toInt()); } else if (header == "SI") { @@ -238,8 +236,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QCryptographicHash hash(QCryptographicHash::Algorithm::Sha256); hash.addData(server_address.toUtf8()); - discord->state_server(server_name.toStdString(), - hash.result().toBase64().toStdString()); + discord->state_server(server_name.toStdString(), hash.result().toBase64().toStdString()); } else if (header == "CI") { @@ -269,17 +266,14 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ++loaded_chars; - w_lobby->set_loading_text("Loading chars:\n" + - QString::number(loaded_chars) + "/" + + w_lobby->set_loading_text("Loading chars:\n" + QString::number(loaded_chars) + "/" + QString::number(char_list_size)); w_courtroom->append_char(f_char); } - int total_loading_size = - char_list_size + evidence_list_size + music_list_size; - int loading_value = - (loaded_chars / static_cast(total_loading_size)) * 100; + int total_loading_size = char_list_size + evidence_list_size + music_list_size; + int loading_value = (loaded_chars / static_cast(total_loading_size)) * 100; w_lobby->set_loading_value(loading_value); send_server_packet(new AOPacket("RE#%")); @@ -309,17 +303,13 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ++loaded_evidence; - w_lobby->set_loading_text("Loading evidence:\n" + - QString::number(loaded_evidence) + "/" + + w_lobby->set_loading_text("Loading evidence:\n" + QString::number(loaded_evidence) + "/" + QString::number(evidence_list_size)); w_courtroom->append_evidence(f_evi); - int total_loading_size = - char_list_size + evidence_list_size + music_list_size; - int loading_value = ((loaded_chars + loaded_evidence) / - static_cast(total_loading_size)) * - 100; + int total_loading_size = char_list_size + evidence_list_size + music_list_size; + int loading_value = ((loaded_chars + loaded_evidence) / static_cast(total_loading_size)) * 100; w_lobby->set_loading_value(loading_value); QString next_packet_number = QString::number(loaded_evidence); @@ -345,8 +335,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ++loaded_music; - w_lobby->set_loading_text("Loading music:\n" + - QString::number(loaded_music) + "/" + + w_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + QString::number(music_list_size)); if (music_turn) @@ -355,9 +344,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else { - if (f_music.endsWith(".wav") || f_music.endsWith(".mp3") || - f_music.endsWith(".mp4") || f_music.endsWith(".ogg") || - f_music.endsWith(".opus")) + if (f_music.endsWith(".wav") || f_music.endsWith(".mp3") || f_music.endsWith(".mp4") || + f_music.endsWith(".ogg") || f_music.endsWith(".opus")) { music_turn = true; areas--; @@ -372,11 +360,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } qDebug() << f_music; - int total_loading_size = - char_list_size + evidence_list_size + music_list_size; - int loading_value = ((loaded_chars + loaded_evidence + loaded_music) / - static_cast(total_loading_size)) * - 100; + int total_loading_size = char_list_size + evidence_list_size + music_list_size; + int loading_value = + ((loaded_chars + loaded_evidence + loaded_music) / static_cast(total_loading_size)) * 100; w_lobby->set_loading_value(loading_value); } @@ -416,17 +402,14 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ++loaded_chars; - w_lobby->set_loading_text("Loading chars:\n" + - QString::number(loaded_chars) + "/" + + w_lobby->set_loading_text("Loading chars:\n" + QString::number(loaded_chars) + "/" + QString::number(char_list_size)); w_courtroom->append_char(f_char); } - int total_loading_size = - char_list_size + evidence_list_size + music_list_size; - int loading_value = - (loaded_chars / static_cast(total_loading_size)) * 100; + int total_loading_size = char_list_size + evidence_list_size + music_list_size; + int loading_value = (loaded_chars / static_cast(total_loading_size)) * 100; w_lobby->set_loading_value(loading_value); send_server_packet(new AOPacket("RM#%")); @@ -443,8 +426,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) { ++loaded_music; - w_lobby->set_loading_text("Loading music:\n" + - QString::number(loaded_music) + "/" + + w_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + QString::number(music_list_size)); if (musics_time) @@ -453,10 +435,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else { - if (f_contents.at(n_element).endsWith(".wav") || - f_contents.at(n_element).endsWith(".mp3") || - f_contents.at(n_element).endsWith(".mp4") || - f_contents.at(n_element).endsWith(".ogg") || + if (f_contents.at(n_element).endsWith(".wav") || f_contents.at(n_element).endsWith(".mp3") || + f_contents.at(n_element).endsWith(".mp4") || f_contents.at(n_element).endsWith(".ogg") || f_contents.at(n_element).endsWith(".opus")) { musics_time = true; @@ -471,11 +451,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } } - int total_loading_size = - char_list_size + evidence_list_size + music_list_size; - int loading_value = ((loaded_chars + loaded_evidence + loaded_music) / - static_cast(total_loading_size)) * - 100; + int total_loading_size = char_list_size + evidence_list_size + music_list_size; + int loading_value = + ((loaded_chars + loaded_evidence + loaded_music) / static_cast(total_loading_size)) * 100; w_lobby->set_loading_value(loading_value); } @@ -500,10 +478,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else { - if (f_contents.at(n_element).endsWith(".wav") || - f_contents.at(n_element).endsWith(".mp3") || - f_contents.at(n_element).endsWith(".mp4") || - f_contents.at(n_element).endsWith(".ogg") || + if (f_contents.at(n_element).endsWith(".wav") || f_contents.at(n_element).endsWith(".mp3") || + f_contents.at(n_element).endsWith(".mp4") || f_contents.at(n_element).endsWith(".ogg") || f_contents.at(n_element).endsWith(".opus")) { musics_time = true; @@ -579,8 +555,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) 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()); + w_courtroom->set_hp_bar(f_contents.at(0).toInt(), f_contents.at(1).toInt()); } else if (header == "LE") { diff --git a/src/path_functions.cpp b/src/path_functions.cpp index c5b8a9c99..1548d95f9 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -75,10 +75,8 @@ QString AOApplication::get_default_background_path(QString p_file) QString AOApplication::get_evidence_path(QString p_file) { - QString default_path = - get_case_sensitive_path(get_base_path() + "evidence/" + p_file); - QString alt_path = - get_case_sensitive_path(get_base_path() + "items/" + p_file); + QString default_path = get_case_sensitive_path(get_base_path() + "evidence/" + p_file); + QString alt_path = get_case_sensitive_path(get_base_path() + "items/" + p_file); if (QFile(default_path).exists()) return default_path; @@ -88,8 +86,7 @@ QString AOApplication::get_evidence_path(QString p_file) QString Courtroom::get_background_path(QString p_file) { - return ao_app->get_base_path() + "background/" + current_background + "/" + - p_file; + return ao_app->get_base_path() + "background/" + current_background + "/" + p_file; } #ifndef CASE_SENSITIVE_FILESYSTEM @@ -118,8 +115,7 @@ QString AOApplication::get_case_sensitive_path(QString p_file) // Note also the fixed string search here. This is so that, for example, music // files with parentheses don't get interpreted as grouping for a regex // search. - QRegExp file_rx = - QRegExp(file_basename, Qt::CaseInsensitive, QRegExp::FixedString); + QRegExp file_rx = QRegExp(file_basename, Qt::CaseInsensitive, QRegExp::FixedString); QStringList files = QDir(file_parent_dir).entryList(); int result = files.indexOf(file_rx); @@ -132,8 +128,7 @@ QString AOApplication::get_case_sensitive_path(QString p_file) } #endif -QString AOApplication::find_asset_path(QStringList possible_roots, - QStringList possible_exts) +QString AOApplication::find_asset_path(QStringList possible_roots, QStringList possible_exts) { for (QString root : possible_roots) { @@ -150,12 +145,10 @@ QString AOApplication::find_asset_path(QStringList possible_roots, QString AOApplication::find_theme_asset_path(QString p_file, QStringList exts) { QStringList paths{ - get_base_path() + "themes/" + get_theme() + "/gamemodes/" + - get_gamemode() + "/times/" + get_timeofday() + "/" + p_file, - get_base_path() + "themes/" + get_theme() + "/gamemodes/" + - get_gamemode() + "/" + p_file, - get_base_path() + "themes/" + get_theme() + "/times/" + get_timeofday() + - "/" + p_file, + get_base_path() + "themes/" + get_theme() + "/gamemodes/" + get_gamemode() + "/times/" + get_timeofday() + "/" + + p_file, + get_base_path() + "themes/" + get_theme() + "/gamemodes/" + get_gamemode() + "/" + p_file, + get_base_path() + "themes/" + get_theme() + "/times/" + get_timeofday() + "/" + p_file, get_base_path() + "themes/" + get_theme() + "/" + p_file, get_base_path() + "themes/default/" + p_file, }; diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 9fc436a88..bf4c612c7 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -208,8 +208,7 @@ QPoint AOApplication::get_button_spacing(QString p_identifier, QString p_file) return return_value; } -pos_size_type AOApplication::get_element_dimensions(QString p_identifier, - QString p_file) +pos_size_type AOApplication::get_element_dimensions(QString p_identifier, QString p_file) { pos_size_type return_value; return_value.x = 0; @@ -464,11 +463,9 @@ QStringList AOApplication::get_sfx_list() QFile char_sfx_list_ini; base_sfx_list_ini.setFileName(get_base_path() + "configs/sounds.ini"); - char_sfx_list_ini.setFileName( - get_character_path(get_current_char(), "sounds.ini")); + char_sfx_list_ini.setFileName(get_character_path(get_current_char(), "sounds.ini")); - if (!char_sfx_list_ini.open(QIODevice::ReadOnly) && - !base_sfx_list_ini.open(QIODevice::ReadOnly)) + if (!char_sfx_list_ini.open(QIODevice::ReadOnly) && !base_sfx_list_ini.open(QIODevice::ReadOnly)) { return return_value; } @@ -494,8 +491,7 @@ QStringList AOApplication::get_sfx_list() // returns whatever is to the right of "search_line =" within target_tag and // terminator_tag, trimmed returns the empty string if the search line couldnt // be found -QString AOApplication::read_char_ini(QString p_char, QString p_search_line, - QString target_tag, QString terminator_tag) +QString AOApplication::read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag) { QString char_ini_path = get_character_path(p_char, "char.ini"); @@ -528,8 +524,7 @@ QString AOApplication::read_char_ini(QString p_char, QString p_search_line, QStringList line_elements = line.split("="); - if (QString::compare(line_elements.at(0).trimmed(), p_search_line, - Qt::CaseInsensitive) != 0) + if (QString::compare(line_elements.at(0).trimmed(), p_search_line, Qt::CaseInsensitive) != 0) continue; if (line_elements.size() < 2) @@ -633,8 +628,7 @@ int AOApplication::get_emote_number(QString p_char) QString AOApplication::get_emote_comment(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), - "[Emotions]", "[Offsets]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); QStringList result_contents = f_result.split("#"); @@ -649,8 +643,7 @@ QString AOApplication::get_emote_comment(QString p_char, int p_emote) QString AOApplication::get_pre_emote(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), - "[Emotions]", "[Offsets]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); QStringList result_contents = f_result.split("#"); @@ -665,8 +658,7 @@ QString AOApplication::get_pre_emote(QString p_char, int p_emote) QString AOApplication::get_emote(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), - "[Emotions]", "[Offsets]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); QStringList result_contents = f_result.split("#"); @@ -681,15 +673,13 @@ QString AOApplication::get_emote(QString p_char, int p_emote) int AOApplication::get_emote_mod(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), - "[Emotions]", "[Offsets]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); QStringList result_contents = f_result.split("#"); if (result_contents.size() < 4) { - qDebug() << "W: misformatted char.ini: " << p_char << ", " - << QString::number(p_emote); + qDebug() << "W: misformatted char.ini: " << p_char << ", " << QString::number(p_emote); return 0; } else @@ -698,8 +688,7 @@ int AOApplication::get_emote_mod(QString p_char, int p_emote) int AOApplication::get_desk_mod(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), - "[Emotions]", "[Offsets]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); QStringList result_contents = f_result.split("#"); @@ -716,9 +705,7 @@ int AOApplication::get_desk_mod(QString p_char, int p_emote) QStringList AOApplication::get_effect_offset(QString p_char, int p_effect) { - QStringList f_result = - read_char_ini(p_char, QString::number(p_effect), "[Offsets]", "[Overlay]") - .split(","); + QStringList f_result = read_char_ini(p_char, QString::number(p_effect), "[Offsets]", "[Overlay]").split(","); if (f_result.size() < 2) return decltype(f_result){0, 0}; @@ -728,9 +715,7 @@ QStringList AOApplication::get_effect_offset(QString p_char, int p_effect) QStringList AOApplication::get_overlay(QString p_char, int p_effect) { - QStringList f_result = - read_char_ini(p_char, QString::number(p_effect), "[Overlay]", "[SoundN]") - .split("#"); + QStringList f_result = read_char_ini(p_char, QString::number(p_effect), "[Overlay]", "[SoundN]").split("#"); if (f_result.size() < 2) f_result.push_back(""); @@ -740,8 +725,7 @@ QStringList AOApplication::get_overlay(QString p_char, int p_effect) QString AOApplication::get_sfx_name(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), - "[SoundN]", "[SoundT]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[SoundN]", "[SoundT]"); if (f_result == "") return "1"; @@ -751,8 +735,7 @@ QString AOApplication::get_sfx_name(QString p_char, int p_emote) int AOApplication::get_sfx_delay(QString p_char, int p_emote) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), - "[SoundT]", "[TextDelay]"); + QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[SoundT]", "[TextDelay]"); if (f_result == "") return 1; @@ -762,8 +745,7 @@ int AOApplication::get_sfx_delay(QString p_char, int p_emote) int AOApplication::get_text_delay(QString p_char, QString p_emote) { - QString f_result = - read_char_ini(p_char, p_emote, "[TextDelay]", "END_OF_FILE"); + QString f_result = read_char_ini(p_char, p_emote, "[TextDelay]", "END_OF_FILE"); if (f_result == "") return -1; From 7a2f3ef55a0193002c753a9fb6a76df7ef3af35c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 12 Feb 2021 04:39:21 +0100 Subject: [PATCH 198/842] Added OIgnoreSuppression option for DRAudio::Option This allows the stream family with the option enabled to ignore suppression/mute calls. --- include/draudio.h | 1 + include/draudiostreamfamily.h | 2 ++ src/draudioengine.cpp | 3 +++ src/draudiostreamfamily.cpp | 14 +++++++++++++- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/draudio.h b/include/draudio.h index a6b20c48c..3daab7e8a 100644 --- a/include/draudio.h +++ b/include/draudio.h @@ -16,6 +16,7 @@ enum class Family enum Option { OSuppressed = 0x1, + ONoSuppression = 0x2, }; Q_DECLARE_FLAGS(Options, Option) diff --git a/include/draudiostreamfamily.h b/include/draudiostreamfamily.h index 43a661693..832d75b2e 100644 --- a/include/draudiostreamfamily.h +++ b/include/draudiostreamfamily.h @@ -27,6 +27,7 @@ class DRAudioStreamFamily : public QObject DRAudio::Options get_options(); // options getter bool is_suppressed(); + bool is_ignore_suppression(); using iterator = QVector::iterator; iterator begin(); @@ -38,6 +39,7 @@ public slots: void set_options(DRAudio::Options p_options); // options setter void set_suppressed(bool p_enabled); + void set_ignore_suppression(bool p_enabled); signals: void volume_changed(std::int32_t); diff --git a/src/draudioengine.cpp b/src/draudioengine.cpp index 88fe89562..220d73ff9 100644 --- a/src/draudioengine.cpp +++ b/src/draudioengine.cpp @@ -50,6 +50,9 @@ DRAudioEngine::DRAudioEngine(QObject *p_parent) : QObject(p_parent) d->family_map.insert(DRAudio::Family::FEffect, family_ptr(new DRAudioStreamFamily(DRAudio::Family::FEffect))); d->family_map.insert(DRAudio::Family::FMusic, family_ptr(new DRAudioStreamFamily(DRAudio::Family::FMusic))); d->family_map.insert(DRAudio::Family::FBlip, family_ptr(new DRAudioStreamFamily(DRAudio::Family::FBlip))); + + // family-specific options + get_family(DRAudio::Family::FSystem)->set_ignore_suppression(true); } d->parents.append(this); diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp index 3be523458..e8afd7096 100644 --- a/src/draudiostreamfamily.cpp +++ b/src/draudiostreamfamily.cpp @@ -22,6 +22,11 @@ bool DRAudioStreamFamily::is_suppressed() return m_options.testFlag(DRAudio::OSuppressed); } +bool DRAudioStreamFamily::is_ignore_suppression() +{ + return m_options.testFlag(DRAudio::OIgnoreSuppression); +} + DRAudioStreamFamily::iterator DRAudioStreamFamily::begin() { return m_stream_list.begin(); @@ -73,6 +78,13 @@ void DRAudioStreamFamily::set_suppressed(bool p_enabled) set_options(options); } +void DRAudioStreamFamily::set_ignore_suppression(bool p_enabled) +{ + DRAudio::Options options = m_options; + options.setFlag(DRAudio::OIgnoreSuppression, p_enabled); + set_options(options); +} + std::optional DRAudioStreamFamily::create_stream(QString p_file) { stream_ptr stream(new DRAudioStream(m_family)); @@ -113,7 +125,7 @@ int32_t DRAudioStreamFamily::calculate_volume() { float volume = float(m_volume) * 0.01f; - if (is_suppressed() || DRAudioEngine::is_suppressed()) + if (!is_ignore_suppression() && (is_suppressed() || DRAudioEngine::is_suppressed())) { volume = 0; } From b114afefa55885e7177fca7485613aba6a6401a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 12 Feb 2021 13:43:29 +0100 Subject: [PATCH 199/842] Renamed NoSuppression to IgnoreSuppression --- include/draudio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/draudio.h b/include/draudio.h index 3daab7e8a..adb59f9da 100644 --- a/include/draudio.h +++ b/include/draudio.h @@ -16,7 +16,7 @@ enum class Family enum Option { OSuppressed = 0x1, - ONoSuppression = 0x2, + OIgnoreSuppression = 0x2, }; Q_DECLARE_FLAGS(Options, Option) From f88286ca6bfd82ec5cce35753bcd50921ef79954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 13 Feb 2021 04:22:46 +0100 Subject: [PATCH 200/842] Fixed AOSystemPlayer not using the System channel --- src/aosystemplayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aosystemplayer.cpp b/src/aosystemplayer.cpp index b70cb801a..cf6c971bf 100644 --- a/src/aosystemplayer.cpp +++ b/src/aosystemplayer.cpp @@ -13,5 +13,5 @@ AOSystemPlayer::AOSystemPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOO void AOSystemPlayer::play(QString p_name) { const QString file = ao_app->find_asset_path({ao_app->get_sounds_path(p_name)}, audio_extensions()); - DRAudioEngine::get_family(DRAudio::Family::FEffect)->play_stream(file); + DRAudioEngine::get_family(DRAudio::Family::FSystem)->play_stream(file); } From 3e229ada687d5369af5b08091475b4117731584a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 13 Feb 2021 05:21:46 +0100 Subject: [PATCH 201/842] Renamed `Disable Background Audio` to `Mute Focus` * Added clarification to the tooltip of the aforementioned check box. --- res/ui/config_panel.ui | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index cac45a09c..25a77ce9f 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 0 + 3 @@ -116,6 +116,9 @@ + + Qt::Vertical + 20 @@ -270,6 +273,9 @@ + + Qt::Vertical + 20 @@ -416,6 +422,9 @@ + + Qt::Vertical + 20 @@ -489,10 +498,10 @@ - Allow the program to temporarily disable sounds when out of focus. + <html><head/><body><p>If enabled, audio will be muted temporarily when the client is not in focus.</p><p><br/></p><p>This does not apply to system sounds.</p></body></html> - Disable Background Audio + Mute Focus @@ -743,6 +752,9 @@ + + Qt::Vertical + 20 From 05a6f7d72160d1e94c0f352f942084127cf87b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 13 Feb 2021 05:45:57 +0100 Subject: [PATCH 202/842] Renamed disable_background_audio to mute_background_audio (clarity) --- include/aoconfig.h | 8 ++++---- include/aoconfigpanel.h | 2 +- res/ui/config_panel.ui | 6 +++--- src/aoconfig.cpp | 28 ++++++++++++++-------------- src/aoconfigpanel.cpp | 8 ++++---- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 18acc1bbd..263677e75 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -37,7 +37,7 @@ class AOConfig : public QObject // audio int master_volume(); - bool disable_background_audio(); + bool mute_background_audio(); int system_volume(); int effect_volume(); int music_volume(); @@ -76,8 +76,8 @@ public slots: void set_log_music(int p_state); void set_log_is_recording(bool p_enabled); void set_log_is_recording(int p_state); - void set_disable_background_audio(bool p_enabled); - void set_disable_background_audio(int p_state); + void set_mute_background_audio(bool p_enabled); + void set_mute_background_audio(int p_state); void set_master_volume(int p_number); void set_system_volume(int p_number); void set_effect_volume(int p_number); @@ -106,7 +106,7 @@ public slots: void log_music_changed(bool); void log_is_recording_changed(bool); void master_volume_changed(int); - void disable_background_audio_changed(bool); + void mute_background_audio_changed(bool); void system_volume_changed(int); void effect_volume_changed(int); void music_volume_changed(int); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 60fd9b7e0..1ad1ab1be 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -89,7 +89,7 @@ private slots: // audio QSlider *w_master = nullptr; QLabel *w_master_value = nullptr; - QCheckBox *w_disable_background_audio = nullptr; + QCheckBox *w_mute_background_audio = nullptr; QSlider *w_system = nullptr; QLabel *w_system_value = nullptr; QSlider *w_effect = nullptr; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 25a77ce9f..4817883da 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -496,12 +496,12 @@ - + - <html><head/><body><p>If enabled, audio will be muted temporarily when the client is not in focus.</p><p><br/></p><p>This does not apply to system sounds.</p></body></html> + <html><head/><body><p>If enabled, audio will be temporarily muted when the client is not in focus.\n\nThis does not apply to system sounds.</p></body></html> - Mute Focus + Mute Background Audio diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index cfaa1a85e..38b0f5388 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -45,7 +45,7 @@ class AOConfigPrivate : public QObject bool log_is_recording; int master_volume; - bool disable_background_audio; + bool mute_background_audio; int effect_volume; int system_volume; int music_volume; @@ -197,12 +197,12 @@ public slots: audio_engine->set_volume(p_number); invoke_parents("master_volume_changed", Q_ARG(int, p_number)); } - void set_disable_background_audio(bool p_enabled) + void set_mute_background_audio(bool p_enabled) { - if (disable_background_audio == p_enabled) + if (mute_background_audio == p_enabled) return; - disable_background_audio = p_enabled; - invoke_parents("disable_background_audio_changed", Q_ARG(bool, p_enabled)); + mute_background_audio = p_enabled; + invoke_parents("mute_background_audio_changed", Q_ARG(bool, p_enabled)); } void set_system_volume(int p_number) { @@ -273,7 +273,7 @@ public slots: log_music = cfg.value("music_change_log", true).toBool(); log_is_recording = cfg.value("enable_logging", true).toBool(); - disable_background_audio = cfg.value("disable_background_audio").toBool(); + mute_background_audio = cfg.value("mute_background_audio").toBool(); master_volume = cfg.value("default_master", 50).toInt(); system_volume = cfg.value("default_system", 50).toInt(); effect_volume = cfg.value("default_sfx", 50).toInt(); @@ -308,7 +308,7 @@ public slots: cfg.setValue("music_change_log", log_music); cfg.setValue("enable_logging", log_is_recording); cfg.setValue("default_master", master_volume); - cfg.setValue("disable_background_audio", disable_background_audio); + cfg.setValue("mute_background_audio", mute_background_audio); cfg.setValue("default_sfx", effect_volume); cfg.setValue("default_system", system_volume); cfg.setValue("default_music", music_volume); @@ -330,7 +330,7 @@ public slots: private slots: void on_application_state_changed(Qt::ApplicationState p_state) { - audio_engine->set_suppressed(disable_background_audio && p_state != Qt::ApplicationActive); + audio_engine->set_suppressed(mute_background_audio && p_state != Qt::ApplicationActive); } }; @@ -459,9 +459,9 @@ int AOConfig::master_volume() return d->master_volume; } -bool AOConfig::disable_background_audio() +bool AOConfig::mute_background_audio() { - return d->disable_background_audio; + return d->mute_background_audio; } int AOConfig::system_volume() @@ -618,14 +618,14 @@ void AOConfig::set_log_is_recording(int p_state) set_log_is_recording(p_state == Qt::Checked); } -void AOConfig::set_disable_background_audio(bool p_enabled) +void AOConfig::set_mute_background_audio(bool p_enabled) { - d->set_disable_background_audio(p_enabled); + d->set_mute_background_audio(p_enabled); } -void AOConfig::set_disable_background_audio(int p_state) +void AOConfig::set_mute_background_audio(int p_state) { - set_disable_background_audio(p_state == Qt::Checked); + set_mute_background_audio(p_state == Qt::Checked); } void AOConfig::set_master_volume(int p_number) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index c7c1a59cd..ddf7272ad 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -52,7 +52,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // audio w_master = AO_GUI_WIDGET(QSlider, "master"); w_master_value = AO_GUI_WIDGET(QLabel, "master_value"); - w_disable_background_audio = AO_GUI_WIDGET(QCheckBox, "disable_background_audio"); + w_mute_background_audio = AO_GUI_WIDGET(QCheckBox, "mute_background_audio"); w_system = AO_GUI_WIDGET(QSlider, "system"); w_system_value = AO_GUI_WIDGET(QLabel, "system_value"); w_effect = AO_GUI_WIDGET(QSlider, "effect"); @@ -87,7 +87,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, SLOT(setChecked(bool))); connect(m_config, SIGNAL(master_volume_changed(int)), w_master, SLOT(setValue(int))); - connect(m_config, SIGNAL(disable_background_audio_changed(bool)), w_disable_background_audio, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(mute_background_audio_changed(bool)), w_mute_background_audio, SLOT(setChecked(bool))); connect(m_config, SIGNAL(system_volume_changed(int)), w_system, SLOT(setValue(int))); connect(m_config, SIGNAL(effect_volume_changed(int)), w_effect, SLOT(setValue(int))); connect(m_config, SIGNAL(music_volume_changed(int)), w_music, SLOT(setValue(int))); @@ -115,7 +115,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_uses_newline(int))); connect(w_log_music, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_music(int))); connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_is_recording(int))); - connect(w_disable_background_audio, SIGNAL(stateChanged(int)), m_config, SLOT(set_disable_background_audio(int))); + connect(w_mute_background_audio, SIGNAL(stateChanged(int)), m_config, SLOT(set_mute_background_audio(int))); connect(w_master, SIGNAL(valueChanged(int)), m_config, SLOT(set_master_volume(int))); connect(w_master, SIGNAL(valueChanged(int)), this, SLOT(on_master_value_changed(int))); connect(w_system, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_volume(int))); @@ -156,7 +156,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_log_music->setChecked(m_config->log_music_enabled()); w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); w_master->setValue(m_config->master_volume()); - w_disable_background_audio->setChecked(m_config->disable_background_audio()); + w_mute_background_audio->setChecked(m_config->mute_background_audio()); w_system->setValue(m_config->system_volume()); w_effect->setValue(m_config->effect_volume()); w_music->setValue(m_config->music_volume()); From 63bee7ec76efcfa93306c296f15b0c3f6ffa3d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 13 Feb 2021 06:23:47 +0100 Subject: [PATCH 203/842] Fixed line breaks in tooltip being broken. --- res/ui/config_panel.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 4817883da..8dab8e03a 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -498,7 +498,7 @@ - <html><head/><body><p>If enabled, audio will be temporarily muted when the client is not in focus.\n\nThis does not apply to system sounds.</p></body></html> + <html><head/><body><p>If enabled, audio will be temporarily muted when the client is not in focus.<br/><br/>This does not apply to system sounds.</p></body></html> Mute Background Audio From 063e41a2acf7c9d577af21828442bc6eaef9e057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 13 Feb 2021 20:11:44 +0100 Subject: [PATCH 204/842] Destroying the courtroom will now stop all current audio from playing --- include/courtroom.h | 1 + src/courtroom.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/include/courtroom.h b/include/courtroom.h index 2146fd85c..96335b53b 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -54,6 +54,7 @@ class Courtroom : public QMainWindow public: explicit Courtroom(AOApplication *p_ao_app); + ~Courtroom(); void append_char(char_type p_char) { diff --git a/src/courtroom.cpp b/src/courtroom.cpp index b12846c92..72818a2d0 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -34,6 +34,14 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() set_widget_names(); } +Courtroom::~Courtroom() +{ + // shutdown all audio + for (auto &family : DRAudioEngine::get_family_list()) + for (auto &stream : family->get_stream_list()) + stream->stop(); +} + void Courtroom::enter_courtroom(int p_cid) { bool changed_character = (m_cid != p_cid); From 051dfda5a7677aa4fa9bc33591f1e13a22b241d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 13 Feb 2021 20:28:52 +0100 Subject: [PATCH 205/842] Removed AOBassHandle (not used anymore) --- dronline-client.pro | 2 - include/aobasshandle.h | 53 ----------------------- src/aobasshandle.cpp | 96 ------------------------------------------ 3 files changed, 151 deletions(-) delete mode 100644 include/aobasshandle.h delete mode 100644 src/aobasshandle.cpp diff --git a/dronline-client.pro b/dronline-client.pro index dc5717a38..e9f6aec35 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -15,7 +15,6 @@ DEFINES += DRO_ACKMS HEADERS += \ include/aoapplication.h \ - include/aobasshandle.h \ include/aoblipplayer.h \ include/aobutton.h \ include/aocharbutton.h \ @@ -62,7 +61,6 @@ HEADERS += \ SOURCES += \ src/aoapplication.cpp \ - src/aobasshandle.cpp \ src/aoblipplayer.cpp \ src/aobutton.cpp \ src/aocharbutton.cpp \ diff --git a/include/aobasshandle.h b/include/aobasshandle.h deleted file mode 100644 index 16678c4b5..000000000 --- a/include/aobasshandle.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef AOBASSHANDLE_HPP -#define AOBASSHANDLE_HPP - -#include -#include - -#include - -#include "aoexception.h" - -/** - * @brief The AOBassHandle is a small class to play sounds. - * The class may destroy itself once the audio provided is done playing. - */ - -class AOBassHandle : public QObject -{ - Q_OBJECT - -public: - AOBassHandle(QObject *p_parent = nullptr); - AOBassHandle(QString p_file, bool p_suicide, QObject *p_parent = nullptr) noexcept(false); - ~AOBassHandle(); - - QString get_file(); - void set_file(QString p_file, bool p_suicide = false) noexcept(false); - - // static - static void CALLBACK static_sync(HSYNC handle, DWORD channel, DWORD data, void *user); - -public slots: - void play(); - void stop(); - - void set_volume(int p_volume); - -signals: - void stopped(); - void body_discovery(); - -private: - QString m_file; - HSTREAM m_handle = 0; - HSYNC m_sync = 0; - bool m_suicide = false; - - void sync(DWORD data); - -private slots: - void suicide(); -}; - -#endif // AOBASSHANDLE_HPP diff --git a/src/aobasshandle.cpp b/src/aobasshandle.cpp deleted file mode 100644 index 16a4d2379..000000000 --- a/src/aobasshandle.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include "aobasshandle.h" - -AOBassHandle::AOBassHandle(QObject *p_parent) : QObject(p_parent) -{ -} - -AOBassHandle::AOBassHandle(QString p_file, bool p_suicide, QObject *p_parent) noexcept(false) : QObject(p_parent) -{ - set_file(p_file, p_suicide); -} - -AOBassHandle::~AOBassHandle() -{ - if (m_handle && m_sync) - { - BASS_ChannelRemoveSync(m_handle, m_sync); - } - - // nothing will go wrong if the handle isn't initialized, I promise...! - if (m_handle) - { - BASS_StreamFree(m_handle); - } -} - -void AOBassHandle::suicide() -{ - // safety first - disconnect(); - - // suicide note - Q_EMIT body_discovery(); - - delete this; -} - -QString AOBassHandle::get_file() -{ - return m_file; -} - -void AOBassHandle::set_file(QString p_file, bool p_suicide) noexcept(false) -{ - if (m_handle) - return; // already created - - m_file = p_file; - - // create a handle based on the file - m_handle = BASS_StreamCreateFile(FALSE, m_file.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); - if (!m_handle) - throw AOException(QString("%1 could not be initialized to play").arg(p_file)); - - m_sync = BASS_ChannelSetSync(m_handle, BASS_SYNC_END, 0, &AOBassHandle::static_sync, this); - if (!m_sync) - { - // free stream since we can't sync - BASS_StreamFree(m_handle); - - throw AOException(QString("could not set sync for %1").arg(m_file)); - } - - m_suicide = p_suicide; - if (m_suicide) - connect(this, &AOBassHandle::stopped, this, &AOBassHandle::suicide); -} - -void AOBassHandle::set_volume(int p_volume) -{ - BASS_ChannelSetAttribute(m_handle, BASS_ATTRIB_VOL, p_volume / 100.0f); -} - -void AOBassHandle::play() -{ - BASS_ChannelPlay(m_handle, FALSE); -} - -void AOBassHandle::stop() -{ - BASS_ChannelStop(m_handle); -} - -void CALLBACK AOBassHandle::static_sync(HSYNC handle, DWORD channel, DWORD data, void *user) -{ - if (auto self = static_cast(user)) - { - self->sync(data); - } -} - -void AOBassHandle::sync(DWORD data) -{ - Q_UNUSED(data) - - Q_EMIT stopped(); -} From b663ffd14195e95ab9ddaf80a808bf835b4abfe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sun, 14 Feb 2021 02:32:57 +0100 Subject: [PATCH 206/842] Removed deprecated headers declaration --- include/courtroom.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/courtroom.h b/include/courtroom.h index 96335b53b..0d3f73a79 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -1,7 +1,6 @@ #ifndef COURTROOM_H #define COURTROOM_H -#include "aobasshandle.h" #include "aoblipplayer.h" #include "aobutton.h" #include "aocharbutton.h" From 510b8d5c158e54d39b4c35c20c57de045524968b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sun, 14 Feb 2021 04:19:22 +0100 Subject: [PATCH 207/842] Removed AOException --- dronline-client.pro | 2 -- include/aoexception.h | 21 --------------------- src/aoexception.cpp | 10 ---------- 3 files changed, 33 deletions(-) delete mode 100644 include/aoexception.h delete mode 100644 src/aoexception.cpp diff --git a/dronline-client.pro b/dronline-client.pro index e9f6aec35..636d5ce1f 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -25,7 +25,6 @@ HEADERS += \ include/aoevidencebutton.h \ include/aoevidencedescription.h \ include/aoevidencedisplay.h \ - include/aoexception.h \ include/aoguiloader.h \ include/aoimagedisplay.h \ include/aolabel.h \ @@ -71,7 +70,6 @@ SOURCES += \ src/aoevidencebutton.cpp \ src/aoevidencedescription.cpp \ src/aoevidencedisplay.cpp \ - src/aoexception.cpp \ src/aoguiloader.cpp \ src/aoimagedisplay.cpp \ src/aolabel.cpp \ diff --git a/include/aoexception.h b/include/aoexception.h deleted file mode 100644 index 173ccb5d4..000000000 --- a/include/aoexception.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef AOEXCEPTION_HPP -#define AOEXCEPTION_HPP - -#include - -#include - -class AOException : public std::exception -{ -public: - AOException() = default; - AOException(QString p_msg); - virtual ~AOException() noexcept = default; - - virtual const char *what() const noexcept; - -private: - QString m_msg; -}; - -#endif // AOEXCEPTION_HPP diff --git a/src/aoexception.cpp b/src/aoexception.cpp deleted file mode 100644 index 6100aa69e..000000000 --- a/src/aoexception.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "aoexception.h" - -AOException::AOException(QString p_msg) : m_msg(p_msg) -{ -} - -const char *AOException::what() const noexcept -{ - return m_msg.toStdString().c_str(); -} From 3cd778f0cecfc689866ba35ab32ab750415450ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sun, 14 Feb 2021 05:12:27 +0100 Subject: [PATCH 208/842] AOConfig clean up --- include/aoconfig.h | 11 --------- src/aoconfig.cpp | 55 ------------------------------------------- src/aoconfigpanel.cpp | 20 ++++++++-------- 3 files changed, 10 insertions(+), 76 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 263677e75..fbff9995c 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -52,32 +52,22 @@ public slots: // setters public slots: void set_autosave(bool p_enabled); - void set_autosave(int p_state); void set_username(QString p_string); void set_callwords(QString p_string); void set_server_alerts(bool p_enabled); - void set_server_alerts(int p_state); void set_always_pre(bool p_enabled); - void set_always_pre(int p_state); void set_theme(QString p_string); void set_gamemode(QString p_string); void set_manual_gamemode(bool p_enabled); - void set_manual_gamemode(int p_state); void set_timeofday(QString p_string); void set_manual_timeofday(bool p_enabled); - void set_manual_timeofday(int p_state); void set_chat_tick_interval(int p_number); void set_log_max_lines(int p_number); void set_log_is_topdown(bool p_enabled); - void set_log_is_topdown(int p_state); void set_log_uses_newline(bool p_enabled); - void set_log_uses_newline(int p_state); void set_log_music(bool p_enabled); - void set_log_music(int p_state); void set_log_is_recording(bool p_enabled); - void set_log_is_recording(int p_state); void set_mute_background_audio(bool p_enabled); - void set_mute_background_audio(int p_state); void set_master_volume(int p_number); void set_system_volume(int p_number); void set_effect_volume(int p_number); @@ -85,7 +75,6 @@ public slots: void set_blip_volume(int p_number); void set_blip_rate(int p_number); void set_blank_blips(bool p_enabled); - void set_blank_blips(int p_state); // signals signals: diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 38b0f5388..20d8522ab 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -498,11 +498,6 @@ void AOConfig::set_autosave(bool p_enabled) d->set_autosave(p_enabled); } -void AOConfig::set_autosave(int p_state) -{ - set_autosave(p_state == Qt::Checked); -} - void AOConfig::set_username(QString p_string) { d->set_username(p_string); @@ -523,11 +518,6 @@ void AOConfig::set_gamemode(QString p_string) d->set_gamemode(p_string); } -void AOConfig::set_manual_gamemode(int p_state) -{ - set_manual_gamemode(p_state == Qt::Checked); -} - void AOConfig::set_manual_gamemode(bool p_enabled) { d->set_manual_gamemode(p_enabled); @@ -538,31 +528,16 @@ void AOConfig::set_timeofday(QString p_string) d->set_timeofday(p_string); } -void AOConfig::set_manual_timeofday(int p_state) -{ - set_manual_timeofday(p_state == Qt::Checked); -} - void AOConfig::set_manual_timeofday(bool p_enabled) { d->set_manual_timeofday(p_enabled); } -void AOConfig::set_server_alerts(int p_state) -{ - set_server_alerts(p_state == Qt::Checked); -} - void AOConfig::set_server_alerts(bool p_enabled) { d->set_server_alerts(p_enabled); } -void AOConfig::set_always_pre(int p_state) -{ - set_always_pre(p_state == Qt::Checked); -} - void AOConfig::set_always_pre(bool p_enabled) { d->set_always_pre(p_enabled); @@ -583,51 +558,26 @@ void AOConfig::set_log_is_topdown(bool p_enabled) d->set_log_is_topdown(p_enabled); } -void AOConfig::set_log_is_topdown(int p_state) -{ - set_log_is_topdown(p_state == Qt::Checked); -} - void AOConfig::set_log_uses_newline(bool p_enabled) { d->set_log_uses_newline(p_enabled); } -void AOConfig::set_log_uses_newline(int p_state) -{ - set_log_uses_newline(p_state == Qt::Checked); -} - void AOConfig::set_log_music(bool p_enabled) { d->set_log_music(p_enabled); } -void AOConfig::set_log_music(int p_state) -{ - set_log_music(p_state == Qt::Checked); -} - void AOConfig::set_log_is_recording(bool p_enabled) { d->set_log_is_recording(p_enabled); } -void AOConfig::set_log_is_recording(int p_state) -{ - set_log_is_recording(p_state == Qt::Checked); -} - void AOConfig::set_mute_background_audio(bool p_enabled) { d->set_mute_background_audio(p_enabled); } -void AOConfig::set_mute_background_audio(int p_state) -{ - set_mute_background_audio(p_state == Qt::Checked); -} - void AOConfig::set_master_volume(int p_number) { d->set_master_volume(p_number); @@ -663,11 +613,6 @@ void AOConfig::set_blank_blips(bool p_enabled) d->set_blank_blips(p_enabled); } -void AOConfig::set_blank_blips(int p_state) -{ - set_blank_blips(p_state == Qt::Checked); -} - void AOConfig::save_file() { d->save_file(); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index ddf7272ad..9c51429eb 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -98,24 +98,24 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // output connect(w_close, SIGNAL(clicked()), this, SLOT(close())); connect(w_save, SIGNAL(clicked()), m_config, SLOT(save_file())); - connect(w_autosave, SIGNAL(stateChanged(int)), m_config, SLOT(set_autosave(int))); + connect(w_autosave, SIGNAL(toggled(bool)), m_config, SLOT(set_autosave(bool))); connect(w_username, SIGNAL(textEdited(QString)), m_config, SLOT(set_username(QString))); connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); - connect(w_server_alerts, SIGNAL(stateChanged(int)), m_config, SLOT(set_server_alerts(int))); + connect(w_server_alerts, SIGNAL(toggled(bool)), m_config, SLOT(set_server_alerts(bool))); connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); - connect(w_manual_gamemode, SIGNAL(stateChanged(int)), m_config, SLOT(set_manual_gamemode(int))); + connect(w_manual_gamemode, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_gamemode(bool))); connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); - connect(w_manual_timeofday, SIGNAL(stateChanged(int)), m_config, SLOT(set_manual_timeofday(int))); - connect(w_always_pre, SIGNAL(stateChanged(int)), m_config, SLOT(set_always_pre(int))); + connect(w_manual_timeofday, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_timeofday(bool))); + connect(w_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_topdown(bool))); - connect(w_log_uses_newline, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_uses_newline(int))); - connect(w_log_music, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_music(int))); - connect(w_log_is_recording, SIGNAL(stateChanged(int)), m_config, SLOT(set_log_is_recording(int))); - connect(w_mute_background_audio, SIGNAL(stateChanged(int)), m_config, SLOT(set_mute_background_audio(int))); + connect(w_log_uses_newline, SIGNAL(toggled(bool)), m_config, SLOT(set_log_uses_newline(bool))); + connect(w_log_music, SIGNAL(toggled(bool)), m_config, SLOT(set_log_music(bool))); + connect(w_log_is_recording, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_recording(bool))); + connect(w_mute_background_audio, SIGNAL(toggled(bool)), m_config, SLOT(set_mute_background_audio(bool))); connect(w_master, SIGNAL(valueChanged(int)), m_config, SLOT(set_master_volume(int))); connect(w_master, SIGNAL(valueChanged(int)), this, SLOT(on_master_value_changed(int))); connect(w_system, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_volume(int))); @@ -127,7 +127,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_blip, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_volume(int))); connect(w_blip, SIGNAL(valueChanged(int)), this, SLOT(on_blip_value_changed(int))); connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_rate(int))); - connect(w_blank_blips, SIGNAL(stateChanged(int)), m_config, SLOT(set_blank_blips(int))); + connect(w_blank_blips, SIGNAL(toggled(bool)), m_config, SLOT(set_blank_blips(bool))); // set values w_autosave->setChecked(m_config->autosave()); From 5a0617065cbf6bbf233da14b6187347de3ca213a Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 15 Feb 2021 21:07:30 -0500 Subject: [PATCH 209/842] Validate timer_id for timer functions is non-negative --- src/courtroom.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 72818a2d0..4a219933c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2457,7 +2457,7 @@ void Courtroom::on_set_notes_clicked() void Courtroom::resume_timer(int timer_id) { - if (timer_id >= timer_number) + if (timer_id >= timer_number || timer_id < 0) return; ui_timers[timer_id]->resume(); @@ -2465,7 +2465,7 @@ void Courtroom::resume_timer(int timer_id) void Courtroom::set_timer_time(int timer_id, int new_time) { - if (timer_id >= timer_number) + if (timer_id >= timer_number || timer_id < 0) return; ui_timers[timer_id]->set_time(QTime(0, 0).addMSecs(new_time)); @@ -2473,7 +2473,7 @@ void Courtroom::set_timer_time(int timer_id, int new_time) void Courtroom::set_timer_timestep(int timer_id, int timestep_length) { - if (timer_id >= timer_number) + if (timer_id >= timer_number || timer_id < 0) return; ui_timers[timer_id]->set_timestep_length(timestep_length); @@ -2481,7 +2481,7 @@ void Courtroom::set_timer_timestep(int timer_id, int timestep_length) void Courtroom::set_timer_firing(int timer_id, int firing_interval) { - if (timer_id >= timer_number) + if (timer_id >= timer_number || timer_id < 0) return; ui_timers[timer_id]->set_firing_interval(firing_interval); @@ -2489,7 +2489,7 @@ void Courtroom::set_timer_firing(int timer_id, int firing_interval) void Courtroom::pause_timer(int timer_id) { - if (timer_id >= timer_number) + if (timer_id >= timer_number || timer_id < 0) return; ui_timers[timer_id]->pause(); From 2ca8d8c2ba08249000a9ea903f3ad11d6f48b426 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 15 Feb 2021 21:13:35 -0500 Subject: [PATCH 210/842] Rename to more appropriate name --- include/aoapplication.h | 4 ++-- src/courtroom.cpp | 2 +- src/text_file_functions.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index dfc07bdfb..4a2c82a6e 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -355,8 +355,8 @@ class AOApplication : public QApplication // Returns the text between target_tag and terminator_tag in p_file QString get_stylesheet(QString target_tag, QString p_file); - // Returns string list (characters, color) from p_file - QVector get_highlight_color(); + // Returns string list (highlight character to color name) + QVector get_highlight_colors(); // Returns special button on cc_config according to index QString get_spbutton(QString p_tag, int index); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 4a219933c..6b7a2c74a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1469,7 +1469,7 @@ void Courtroom::chat_tick() // render_character should only be false if the character is a highlight // character specifically marked as a character that should not be // rendered. - QVector f_vec = ao_app->get_highlight_color(); + QVector f_vec = ao_app->get_highlight_colors(); if (m_color_stack.isEmpty()) m_color_stack.push(""); diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 967193519..2adc6ecae 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -316,7 +316,7 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) return f_text; // This is the empty string if no appends took place } -QVector AOApplication::get_highlight_color() +QVector AOApplication::get_highlight_colors() { // File lookup order // 1. In the theme folder (gamemode-timeofday/main/default), look for From a808772318c833efb529c7e00fcd045bc260ff51 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 15 Feb 2021 21:52:03 -0500 Subject: [PATCH 211/842] Implement basic get_chatmessage_colors() (no file IO yet) --- include/aoapplication.h | 3 +++ src/courtroom.cpp | 48 ++++++++++--------------------------- src/text_file_functions.cpp | 10 ++++++++ 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 4a2c82a6e..9e97ee95e 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -355,6 +355,9 @@ class AOApplication : public QApplication // Returns the text between target_tag and terminator_tag in p_file QString get_stylesheet(QString target_tag, QString p_file); + // Returns string list (color name to color HEX) + QMap get_chatmessage_colors(); + // Returns string list (highlight character to color name) QVector get_highlight_colors(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6b7a2c74a..5979014c2 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1573,45 +1573,21 @@ void Courtroom::play_sfx() void Courtroom::set_text_color() { + QMap all_colors = ao_app->get_chatmessage_colors(); + Color color = (Color)m_chatmessage[CMTextColor].toInt(); - switch (m_chatmessage[CMTextColor].toInt()) + QString hex_color; + if (all_colors.contains(color)) { - case CGreen: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setRgb(101, 200, 86); - break; - case CRed: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setRgb(186, 21, 24); - break; - case COrange: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setRgb(213, 89, 0); - break; - case CBlue: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setRgb(21, 136, 200); - break; - case CYellow: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setRgb(231, 206, 78); - break; - case CPurple: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setRgb(247, 118, 253); - break; - case CPink: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setRgb(218, 124, 128); - break; - default: - qDebug() << "W: undefined text color: " << m_chatmessage[CMTextColor]; - [[fallthrough]]; - case CWhite: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setRgb(213, 213, 213); - break; + hex_color = all_colors[color]; + } + else + { + hex_color = all_colors[CWhite]; } + + ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); + m_base_string_color.setNamedColor(hex_color); } void Courtroom::set_ip_list(QString p_list) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 2adc6ecae..0da2280fa 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -316,6 +316,16 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) return f_text; // This is the empty string if no appends took place } +QMap AOApplication::get_chatmessage_colors() +{ + QMap default_colors = { + {CGreen, "#65C856"}, {CRed, "#BA1518"}, {COrange, "#D55900"}, {CBlue, "#1588C8"}, + {CYellow, "#E7CE4E"}, {CPurple, "#F776FD"}, {CPink, "#DA7C80"}, {CWhite, "#D5D5D5"}, + }; + + return default_colors; +} + QVector AOApplication::get_highlight_colors() { // File lookup order From 723ba708f04336fcfc32a774bf51c2d78c7edc65 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 15 Feb 2021 22:15:48 -0500 Subject: [PATCH 212/842] Rework so color map includes color name --- include/aoapplication.h | 2 +- src/courtroom.cpp | 6 +++--- src/text_file_functions.cpp | 17 +++++++++++------ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 9e97ee95e..ad37841ef 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -356,7 +356,7 @@ class AOApplication : public QApplication QString get_stylesheet(QString target_tag, QString p_file); // Returns string list (color name to color HEX) - QMap get_chatmessage_colors(); + QMap get_chatmessage_colors(); // Returns string list (highlight character to color name) QVector get_highlight_colors(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 5979014c2..6a623cf23 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1573,17 +1573,17 @@ void Courtroom::play_sfx() void Courtroom::set_text_color() { - QMap all_colors = ao_app->get_chatmessage_colors(); + QMap all_colors = ao_app->get_chatmessage_colors(); Color color = (Color)m_chatmessage[CMTextColor].toInt(); QString hex_color; if (all_colors.contains(color)) { - hex_color = all_colors[color]; + hex_color = all_colors[color][1]; } else { - hex_color = all_colors[CWhite]; + hex_color = all_colors[CWhite][1]; } ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 0da2280fa..3d5eefe9f 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -316,12 +316,17 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) return f_text; // This is the empty string if no appends took place } -QMap AOApplication::get_chatmessage_colors() -{ - QMap default_colors = { - {CGreen, "#65C856"}, {CRed, "#BA1518"}, {COrange, "#D55900"}, {CBlue, "#1588C8"}, - {CYellow, "#E7CE4E"}, {CPurple, "#F776FD"}, {CPink, "#DA7C80"}, {CWhite, "#D5D5D5"}, - }; +QMap AOApplication::get_chatmessage_colors() +{ + QMap default_colors; + default_colors[CWhite] = QStringList{"White", "#D5D5D5"}; + default_colors[CGreen] = QStringList{"Green", "#65C856"}; + default_colors[CRed] = QStringList{"Red", "#BA1518"}; + default_colors[COrange] = QStringList{"Orange", "#D55900"}; + default_colors[CBlue] = QStringList{"Blue", "#1588C8"}; + default_colors[CYellow] = QStringList{"Yellow", "#E7CE4E"}; + default_colors[CPurple] = QStringList{"Purple", "#F776FD"}; + default_colors[CPink] = QStringList{"Pink", "#DA7C80"}; return default_colors; } From 6a60aab901d6f599146c8b5911849ec2c5fafc30 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 15 Feb 2021 23:03:45 -0500 Subject: [PATCH 213/842] Implement file IO --- src/text_file_functions.cpp | 62 ++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 3d5eefe9f..3064cd1fc 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -328,7 +328,67 @@ QMap AOApplication::get_chatmessage_colors() default_colors[CPurple] = QStringList{"Purple", "#F776FD"}; default_colors[CPink] = QStringList{"Pink", "#DA7C80"}; - return default_colors; + // File lookup order + // 1. In the theme folder (gamemode-timeofday/main/default), look for + // "courtroom_config.ini". + + QString path = find_theme_asset_path("courtroom_config.ini"); + if (path.isEmpty()) + return default_colors; + + QFile design_ini(path); + if (!design_ini.open(QIODevice::ReadOnly)) + return default_colors; + + // Make copy for new colors + QMap read_colors; + for (QMap::iterator item = default_colors.begin(); item != default_colors.end(); ++item) + { + Color color = item.key(); + QStringList color_parameters = item.value(); + read_colors[color] = color_parameters; + } + + QTextStream in(&design_ini); + bool tag_found = false; + while (!in.atEnd()) + { + QString line = in.readLine(); + + if (line.startsWith("[IC_COLORS]", Qt::CaseInsensitive)) + { + tag_found = true; + continue; + } + + if (tag_found) + { + if ((line.startsWith("[") && line.endsWith("]"))) + break; + // Syntax + // Name = Color Code (hex or named color) + QString color_name = line.split("=")[0].trimmed(); + QString color_code = line.split("=")[1].trimmed(); + + // We will try and override colors in our currently stored list of colors + // We match by the 0th color parameter + for (QMap::iterator item = default_colors.begin(); item != default_colors.end(); ++item) + { + Color default_color = item.key(); + QStringList default_color_parameters = item.value(); + QString default_color_name = default_color_parameters[0]; + + if (color_name == default_color_name) + { + read_colors[default_color] = QStringList{color_name, color_code}; + break; + } + } + } + } + + design_ini.close(); + return read_colors; } QVector AOApplication::get_highlight_colors() From 0cb449137023a618a16ab0e508b58e94fe923f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 20 Feb 2021 13:58:05 +0100 Subject: [PATCH 214/842] Removed unused type, renamed some pointer types * Removed unused struct: `pos_type` * Renamed some pointer-types to make more sense --- dronline-client.pro | 1 + include/aoblipplayer.h | 2 +- include/aomusicplayer.h | 2 +- include/datatypes.h | 12 +----------- include/draudioengine.h | 6 ++---- include/draudiostream.h | 4 +++- include/draudiostreamfamily.h | 12 ++++++------ src/aoblipplayer.cpp | 10 +++++----- src/datatypes.cpp | 7 +++++++ src/draudioengine.cpp | 18 +++++++++++------- src/draudiostream.cpp | 2 +- src/draudiostreamfamily.cpp | 16 ++++++++-------- 12 files changed, 47 insertions(+), 45 deletions(-) create mode 100644 src/datatypes.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 636d5ce1f..b6cec662d 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -92,6 +92,7 @@ SOURCES += \ src/charselect.cpp \ src/courtroom.cpp \ src/courtroom_widgets.cpp \ + src/datatypes.cpp \ src/debug_functions.cpp \ src/discord_rich_presence.cpp \ src/draudio.cpp \ diff --git a/include/aoblipplayer.h b/include/aoblipplayer.h index 8dbc01254..8addb50b1 100644 --- a/include/aoblipplayer.h +++ b/include/aoblipplayer.h @@ -29,7 +29,7 @@ public slots: void blip_tick(); private: - DRAudioEngine::family_ptr m_family; + DRAudioStreamFamily::ptr m_family; std::optional m_blip; std::optional m_file; }; diff --git a/include/aomusicplayer.h b/include/aomusicplayer.h index bf548b192..0b081dda3 100644 --- a/include/aomusicplayer.h +++ b/include/aomusicplayer.h @@ -15,6 +15,6 @@ public slots: void stop(); private: - DRAudioEngine::family_ptr m_family; + DRAudioStreamFamily::ptr m_family; QString m_song; }; diff --git a/include/datatypes.h b/include/datatypes.h index d47e1f649..9e6eedabf 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -13,11 +13,7 @@ struct record_type bool music = false; record_type() = default; - record_type(QString p_name, QString p_line, QString p_color, bool p_is_system, bool p_is_music) - : name(p_name), line(p_line), system(p_is_system), music(p_is_music) - { - Q_UNUSED(p_color); - } + record_type(QString p_name, QString p_line, QString p_color, bool p_is_system, bool p_is_music); }; typedef std::shared_ptr record_type_ptr; @@ -82,12 +78,6 @@ struct area_type QString background; }; -struct pos_type -{ - int x; - int y; -}; - struct pos_size_type { int x = 0; diff --git a/include/draudioengine.h b/include/draudioengine.h index 47f2bf7d8..c6a9432d2 100644 --- a/include/draudioengine.h +++ b/include/draudioengine.h @@ -7,13 +7,11 @@ class DRAudioEngine : public QObject Q_OBJECT public: - using family_ptr = QSharedPointer; - DRAudioEngine(QObject *p_parent = nullptr); ~DRAudioEngine(); - static family_ptr get_family(DRAudio::Family p_family); - static QList get_family_list(); + static DRAudioStreamFamily::ptr get_family(DRAudio::Family p_family); + static QList get_family_list(); static std::int32_t get_volume(); static DRAudio::Options get_options(); // option getter diff --git a/include/draudiostream.h b/include/draudiostream.h index 54305f523..990a46f41 100644 --- a/include/draudiostream.h +++ b/include/draudiostream.h @@ -18,6 +18,8 @@ class DRAudioStream : public QObject Q_OBJECT public: + using ptr = QSharedPointer; + ~DRAudioStream(); DRAudio::Family get_family(); @@ -31,7 +33,7 @@ public slots: void stop(); std::optional set_file(QString m_file); - void set_volume(std::int32_t p_volume); + void set_volume(float p_volume); signals: void file_changed(QString p_file); diff --git a/include/draudiostreamfamily.h b/include/draudiostreamfamily.h index 832d75b2e..ec8865587 100644 --- a/include/draudiostreamfamily.h +++ b/include/draudiostreamfamily.h @@ -16,11 +16,11 @@ class DRAudioStreamFamily : public QObject Q_OBJECT public: - using stream_ptr = QSharedPointer; + using ptr = QSharedPointer; - std::optional create_stream(QString p_file); - std::optional play_stream(QString p_file); - QVector get_stream_list(); + std::optional create_stream(QString p_file); + std::optional play_stream(QString p_file); + QVector get_stream_list(); std::int32_t get_volume(); std::int32_t get_capacity(); @@ -29,7 +29,7 @@ class DRAudioStreamFamily : public QObject bool is_suppressed(); bool is_ignore_suppression(); - using iterator = QVector::iterator; + using iterator = QVector::iterator; iterator begin(); iterator end(); @@ -51,7 +51,7 @@ public slots: DRAudioStreamFamily(DRAudio::Family p_family); - std::int32_t calculate_volume(); + float calculate_volume(); void adjust_capacity(); void adjust_options(); diff --git a/src/aoblipplayer.cpp b/src/aoblipplayer.cpp index 8285043a0..2837b9290 100644 --- a/src/aoblipplayer.cpp +++ b/src/aoblipplayer.cpp @@ -8,16 +8,16 @@ AOBlipPlayer::AOBlipPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObjec void AOBlipPlayer::set_blips(QString p_blip) { - if (m_blip.has_value() && m_blip.value() == p_blip) + if (m_blipName.has_value() && m_blipName.value() == p_blip) return; - m_blip = p_blip; - m_file = ao_app->get_sounds_path(m_blip.value()); + m_blipName = p_blip; + m_blipFile = ao_app->get_sounds_path(m_blipName.value()); } void AOBlipPlayer::blip_tick() { - if (!m_file.has_value()) + if (!m_blipFile.has_value()) return; - m_family->play_stream(m_file.value()); + m_family->play_stream(m_blipFile.value()); } diff --git a/src/datatypes.cpp b/src/datatypes.cpp new file mode 100644 index 000000000..bfdf2d259 --- /dev/null +++ b/src/datatypes.cpp @@ -0,0 +1,7 @@ +#include "datatypes.h" + +record_type::record_type(QString p_name, QString p_line, QString p_color, bool p_is_system, bool p_is_music) + : name(p_name), line(p_line), system(p_is_system), music(p_is_music) +{ + Q_UNUSED(p_color); +} diff --git a/src/draudioengine.cpp b/src/draudioengine.cpp index 220d73ff9..4cb57aa73 100644 --- a/src/draudioengine.cpp +++ b/src/draudioengine.cpp @@ -27,7 +27,7 @@ class DRAudioEnginePrivate : public QObject QObjectList parents; std::int32_t volume = 0; DRAudio::Options options; - QMap family_map; + QMap family_map; }; namespace @@ -46,10 +46,14 @@ DRAudioEngine::DRAudioEngine(QObject *p_parent) : QObject(p_parent) d = new DRAudioEnginePrivate(qApp); // there's plenty of way of optimizing this - d->family_map.insert(DRAudio::Family::FSystem, family_ptr(new DRAudioStreamFamily(DRAudio::Family::FSystem))); - d->family_map.insert(DRAudio::Family::FEffect, family_ptr(new DRAudioStreamFamily(DRAudio::Family::FEffect))); - d->family_map.insert(DRAudio::Family::FMusic, family_ptr(new DRAudioStreamFamily(DRAudio::Family::FMusic))); - d->family_map.insert(DRAudio::Family::FBlip, family_ptr(new DRAudioStreamFamily(DRAudio::Family::FBlip))); + d->family_map.insert(DRAudio::Family::FSystem, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FSystem))); + d->family_map.insert(DRAudio::Family::FEffect, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FEffect))); + d->family_map.insert(DRAudio::Family::FMusic, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FMusic))); + d->family_map.insert(DRAudio::Family::FBlip, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FBlip))); // family-specific options get_family(DRAudio::Family::FSystem)->set_ignore_suppression(true); @@ -65,12 +69,12 @@ DRAudioEngine::~DRAudioEngine() BASS_Free(); } -DRAudioEngine::family_ptr DRAudioEngine::get_family(DRAudio::Family p_family) +DRAudioStreamFamily::ptr DRAudioEngine::get_family(DRAudio::Family p_family) { return d->family_map.value(p_family); } -QList DRAudioEngine::get_family_list() +QList DRAudioEngine::get_family_list() { return d->family_map.values(); } diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index 7cbea28a9..b5e2cee68 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -94,7 +94,7 @@ std::optional DRAudioStream::set_file(QString p_file) return std::nullopt; } -void DRAudioStream::set_volume(int32_t p_volume) +void DRAudioStream::set_volume(float p_volume) { if (!m_hstream) return; diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp index e8afd7096..0611288c9 100644 --- a/src/draudiostreamfamily.cpp +++ b/src/draudiostreamfamily.cpp @@ -85,9 +85,9 @@ void DRAudioStreamFamily::set_ignore_suppression(bool p_enabled) set_options(options); } -std::optional DRAudioStreamFamily::create_stream(QString p_file) +std::optional DRAudioStreamFamily::create_stream(QString p_file) { - stream_ptr stream(new DRAudioStream(m_family)); + DRAudioStream::ptr stream(new DRAudioStream(m_family)); if (auto err = stream->set_file(p_file); err) { @@ -104,9 +104,9 @@ std::optional DRAudioStreamFamily::create_strea return stream; } -std::optional DRAudioStreamFamily::play_stream(QString p_file) +std::optional DRAudioStreamFamily::play_stream(QString p_file) { - std::optional r_stream = create_stream(p_file); + std::optional r_stream = create_stream(p_file); if (r_stream.has_value()) { auto stream = r_stream.value(); @@ -116,12 +116,12 @@ std::optional DRAudioStreamFamily::play_stream( return r_stream; } -QVector DRAudioStreamFamily::get_stream_list() +QVector DRAudioStreamFamily::get_stream_list() { return m_stream_list; } -int32_t DRAudioStreamFamily::calculate_volume() +float DRAudioStreamFamily::calculate_volume() { float volume = float(m_volume) * 0.01f; @@ -132,10 +132,10 @@ int32_t DRAudioStreamFamily::calculate_volume() else { // master volume adjustment - volume = volume * (float(DRAudioEngine::get_volume()) * 0.01f); + volume *= (float(DRAudioEngine::get_volume()) * 0.01f); } - return volume * 100.f; + return volume; } void DRAudioStreamFamily::adjust_capacity() From b5b399078f80387ff5df8743860f1b22a7e07b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 20 Feb 2021 13:59:07 +0100 Subject: [PATCH 215/842] Follow-up, previous commit didn't include some missing changes. --- include/aoblipplayer.h | 4 ++-- src/discord_rich_presence.cpp | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/aoblipplayer.h b/include/aoblipplayer.h index 8addb50b1..197afa5ab 100644 --- a/include/aoblipplayer.h +++ b/include/aoblipplayer.h @@ -30,6 +30,6 @@ public slots: private: DRAudioStreamFamily::ptr m_family; - std::optional m_blip; - std::optional m_file; + std::optional m_blipName; + std::optional m_blipFile; }; diff --git a/src/discord_rich_presence.cpp b/src/discord_rich_presence.cpp index 2c22c03ec..074f5978c 100644 --- a/src/discord_rich_presence.cpp +++ b/src/discord_rich_presence.cpp @@ -37,9 +37,11 @@ void Discord::start(const char *APPLICATION_ID) qInfo() << "Discord RPC ready for" << user->username; }; handlers.disconnected = [](int errorCode, const char *message) { + Q_UNUSED(errorCode) qInfo() << "Discord RPC disconnected! " << message; }; handlers.errored = [](int errorCode, const char *message) { + Q_UNUSED(errorCode) qWarning() << "Discord RPC errored out! " << message; }; qInfo() << "Initializing Discord RPC"; From be248a8d1292da9d3fc2041b68cbb301f7831787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Sat, 20 Feb 2021 21:43:08 +0100 Subject: [PATCH 216/842] Fixed audio volume being too low. --- src/draudiostreamfamily.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp index 0611288c9..629cd189f 100644 --- a/src/draudiostreamfamily.cpp +++ b/src/draudiostreamfamily.cpp @@ -135,7 +135,7 @@ float DRAudioStreamFamily::calculate_volume() volume *= (float(DRAudioEngine::get_volume()) * 0.01f); } - return volume; + return volume * 100.0f; } void DRAudioStreamFamily::adjust_capacity() From 80e5c1fd3a1a621cf65b3efd1c626d3b62542eb9 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 20 Feb 2021 21:22:14 -0500 Subject: [PATCH 217/842] Make text color shades be read from courtroom_text_colors.ini --- src/text_file_functions.cpp | 70 +++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 3064cd1fc..0767bc478 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -330,64 +331,57 @@ QMap AOApplication::get_chatmessage_colors() // File lookup order // 1. In the theme folder (gamemode-timeofday/main/default), look for - // "courtroom_config.ini". + // "courtroom_text_colors.ini". - QString path = find_theme_asset_path("courtroom_config.ini"); + QString path = find_theme_asset_path("courtroom_text_colors.ini"); if (path.isEmpty()) return default_colors; - QFile design_ini(path); - if (!design_ini.open(QIODevice::ReadOnly)) - return default_colors; + QSettings text_colors(path, QSettings::IniFormat); // Make copy for new colors QMap read_colors; - for (QMap::iterator item = default_colors.begin(); item != default_colors.end(); ++item) + foreach (Color color, default_colors.keys()) { - Color color = item.key(); - QStringList color_parameters = item.value(); + QStringList color_parameters = default_colors[color]; read_colors[color] = color_parameters; } - QTextStream in(&design_ini); - bool tag_found = false; - while (!in.atEnd()) + foreach (QString color_name, text_colors.childGroups()) { - QString line = in.readLine(); + // Capitalize because inis may not necessarily capitalize, but DRO does + color_name = color_name[0].toUpper() + color_name.mid(1).toLower(); - if (line.startsWith("[IC_COLORS]", Qt::CaseInsensitive)) - { - tag_found = true; - continue; - } + // Ignore colors that are not default colors + // Check if color is valid + Color matched_default_color; + bool valid = false; - if (tag_found) + foreach (Color default_color, default_colors.keys()) { - if ((line.startsWith("[") && line.endsWith("]"))) - break; - // Syntax - // Name = Color Code (hex or named color) - QString color_name = line.split("=")[0].trimmed(); - QString color_code = line.split("=")[1].trimmed(); - - // We will try and override colors in our currently stored list of colors - // We match by the 0th color parameter - for (QMap::iterator item = default_colors.begin(); item != default_colors.end(); ++item) + QStringList default_color_parameters = default_colors[default_color]; + if (default_color_parameters[0] == color_name) { - Color default_color = item.key(); - QStringList default_color_parameters = item.value(); - QString default_color_name = default_color_parameters[0]; - - if (color_name == default_color_name) - { - read_colors[default_color] = QStringList{color_name, color_code}; - break; - } + matched_default_color = default_color; + valid = true; + break; } } + + if (!valid) + continue; + + // At this point we know the color is valid + text_colors.beginGroup(color_name); + QString color_code = text_colors.value("code").toString(); + if (color_code.isEmpty()) + continue; + + // We will override colors in our currently stored list of colors + read_colors[matched_default_color] = QStringList{color_name, color_code}; + text_colors.endGroup(); } - design_ini.close(); return read_colors; } From 0da9ae6ca474d403b2605e22b5fe2c0d9e1660ee Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 20 Feb 2021 21:32:19 -0500 Subject: [PATCH 218/842] Make client fall back to default color if given color cannot be parsed --- src/courtroom.cpp | 9 +++++---- src/text_file_functions.cpp | 5 +++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6a623cf23..c70444992 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1576,18 +1576,19 @@ void Courtroom::set_text_color() QMap all_colors = ao_app->get_chatmessage_colors(); Color color = (Color)m_chatmessage[CMTextColor].toInt(); - QString hex_color; + QString color_code; if (all_colors.contains(color)) { - hex_color = all_colors[color][1]; + color_code = all_colors[color][1]; } else { - hex_color = all_colors[CWhite][1]; + // Fallback color in case some other client sent a message with an out of bounds color + color_code = all_colors[CWhite][1]; } ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setNamedColor(hex_color); + m_base_string_color.setNamedColor(color_code); } void Courtroom::set_ip_list(QString p_list) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 0767bc478..599f4df62 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -376,6 +376,11 @@ QMap AOApplication::get_chatmessage_colors() QString color_code = text_colors.value("code").toString(); if (color_code.isEmpty()) continue; + // Check if the color is valid. If not, set it to white + if (!QColor::isValidColor(color_code)) + { + color_code = default_colors[CWhite][1]; + } // We will override colors in our currently stored list of colors read_colors[matched_default_color] = QStringList{color_name, color_code}; From ffddcfacbb7f08790bec4e7e52a7adc221b8522c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Wed, 24 Feb 2021 12:59:50 +0100 Subject: [PATCH 219/842] Alternative implementation of colors As explained in #117, this is a closer implementation of what the review was talking about. It is fully functional. --- include/aoapplication.h | 2 +- include/datatypes.h | 24 ++++++++++- src/courtroom.cpp | 21 +++------ src/datatypes.cpp | 17 ++++++++ src/draudioengine.cpp | 5 +-- src/text_file_functions.cpp | 86 +++++++++++++++---------------------- 6 files changed, 82 insertions(+), 73 deletions(-) create mode 100644 src/datatypes.cpp diff --git a/include/aoapplication.h b/include/aoapplication.h index ad37841ef..d5caa2410 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -356,7 +356,7 @@ class AOApplication : public QApplication QString get_stylesheet(QString target_tag, QString p_file); // Returns string list (color name to color HEX) - QMap get_chatmessage_colors(); + QMap get_chatmessage_colors(); // Returns string list (highlight character to color name) QVector get_highlight_colors(); diff --git a/include/datatypes.h b/include/datatypes.h index d47e1f649..751c3b45f 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -1,7 +1,9 @@ #ifndef DATATYPES_H #define DATATYPES_H +#include #include + #include struct record_type @@ -116,9 +118,11 @@ enum ChatMessage : std::int32_t CMShowName, }; +namespace dr +{ enum Color : std::int32_t { - CWhite, + CDefault, CGreen, CRed, COrange, @@ -127,6 +131,24 @@ enum Color : std::int32_t CPurple, CPink, CRainbow, + + // aliases + CWhite = CDefault, }; +struct ColorInfo +{ +public: + ColorInfo() = default; + ColorInfo(QString p_showname, QString p_code) : name(p_showname.toLower()), showname(p_showname), code(p_code) + {} + + QString name; + QString showname; + QString code; +}; + +QMap get_default_color_map(); +} // namespace dr + #endif // DATATYPES_H diff --git a/src/courtroom.cpp b/src/courtroom.cpp index c70444992..dad168cfd 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -980,7 +980,7 @@ void Courtroom::handle_chatmessage_3() int f_anim_state = 0; // BLUE is from an enum in datatypes.h - bool text_is_blue = m_chatmessage[CMTextColor].toInt() == CBlue; + bool text_is_blue = m_chatmessage[CMTextColor].toInt() == dr::CBlue; if (!text_is_blue && text_state == 1) // talking @@ -1431,7 +1431,7 @@ void Courtroom::chat_tick() if (f_character == " ") ui_vp_message->insertPlainText(" "); - else if (m_chatmessage[CMTextColor].toInt() == CRainbow) + else if (m_chatmessage[CMTextColor].toInt() == dr::CRainbow) { QString html_color; @@ -1573,20 +1573,9 @@ void Courtroom::play_sfx() void Courtroom::set_text_color() { - QMap all_colors = ao_app->get_chatmessage_colors(); - Color color = (Color)m_chatmessage[CMTextColor].toInt(); - - QString color_code; - if (all_colors.contains(color)) - { - color_code = all_colors[color][1]; - } - else - { - // Fallback color in case some other client sent a message with an out of bounds color - color_code = all_colors[CWhite][1]; - } - + const QMap color_map = ao_app->get_chatmessage_colors(); + const dr::Color color = dr::Color(m_chatmessage[CMTextColor].toInt()); + const QString color_code = color_map[color_map.contains(color) ? color : dr::CDefault].code; ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setNamedColor(color_code); } diff --git a/src/datatypes.cpp b/src/datatypes.cpp new file mode 100644 index 000000000..31bd1a05b --- /dev/null +++ b/src/datatypes.cpp @@ -0,0 +1,17 @@ +#include "datatypes.h" + +QMap dr::get_default_color_map() +{ + QMap default_color_map; + + default_color_map[dr::CWhite] = ColorInfo("White", "#d5d5d5"); + default_color_map[dr::CGreen] = ColorInfo("Green", "#65c856"); + default_color_map[dr::CRed] = ColorInfo("Red", "#ba1518"); + default_color_map[dr::COrange] = ColorInfo("Orange", "#d55900"); + default_color_map[dr::CBlue] = ColorInfo("Blue", "#1588c8"); + default_color_map[dr::CYellow] = ColorInfo("Yellow", "#e7ce4e"); + default_color_map[dr::CPurple] = ColorInfo("Purple", "#f776fd"); + default_color_map[dr::CPink] = ColorInfo("Pink", "#da7c80"); + + return default_color_map; +} diff --git a/src/draudioengine.cpp b/src/draudioengine.cpp index 220d73ff9..1980d4e6f 100644 --- a/src/draudioengine.cpp +++ b/src/draudioengine.cpp @@ -10,6 +10,8 @@ class DRAudioEnginePrivate : public QObject Q_OBJECT public: + using ptr = std::unique_ptr; + DRAudioEnginePrivate(QObject *parent = nullptr) : QObject(parent) {} @@ -21,9 +23,6 @@ class DRAudioEnginePrivate : public QObject } } -private: - friend class DRAudioEngine; - QObjectList parents; std::int32_t volume = 0; DRAudio::Options options; diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 599f4df62..394a1fe56 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -317,77 +317,59 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) return f_text; // This is the empty string if no appends took place } -QMap AOApplication::get_chatmessage_colors() -{ - QMap default_colors; - default_colors[CWhite] = QStringList{"White", "#D5D5D5"}; - default_colors[CGreen] = QStringList{"Green", "#65C856"}; - default_colors[CRed] = QStringList{"Red", "#BA1518"}; - default_colors[COrange] = QStringList{"Orange", "#D55900"}; - default_colors[CBlue] = QStringList{"Blue", "#1588C8"}; - default_colors[CYellow] = QStringList{"Yellow", "#E7CE4E"}; - default_colors[CPurple] = QStringList{"Purple", "#F776FD"}; - default_colors[CPink] = QStringList{"Pink", "#DA7C80"}; +QMap AOApplication::get_chatmessage_colors() +{ + QMap color_map = dr::get_default_color_map(); // File lookup order // 1. In the theme folder (gamemode-timeofday/main/default), look for // "courtroom_text_colors.ini". - QString path = find_theme_asset_path("courtroom_text_colors.ini"); + const QString file_name = "courtroom_text_colors.ini"; + QString path = find_theme_asset_path(file_name); if (path.isEmpty()) - return default_colors; - - QSettings text_colors(path, QSettings::IniFormat); - - // Make copy for new colors - QMap read_colors; - foreach (Color color, default_colors.keys()) { - QStringList color_parameters = default_colors[color]; - read_colors[color] = color_parameters; + qInfo().noquote() << QString("[color] theme %1 is missing file: %2, using default colors instead") + .arg(get_theme()) + .arg(file_name); + return color_map; } + qInfo().noquote() << QString("[color] loading colors for theme %1").arg(get_theme()); + + QSettings color_settings(path, QSettings::IniFormat); - foreach (QString color_name, text_colors.childGroups()) + QMap color_replacement_map; + for (QString &i_group : color_settings.childGroups()) { - // Capitalize because inis may not necessarily capitalize, but DRO does - color_name = color_name[0].toUpper() + color_name.mid(1).toLower(); + const QString lower_name = i_group.toLower(); - // Ignore colors that are not default colors - // Check if color is valid - Color matched_default_color; - bool valid = false; + color_settings.beginGroup(i_group); + if (!color_settings.contains("code")) + continue; + const QString code = color_settings.value("code").toString().toLower(); - foreach (Color default_color, default_colors.keys()) + if (!QColor::isValidColor(code)) { - QStringList default_color_parameters = default_colors[default_color]; - if (default_color_parameters[0] == color_name) - { - matched_default_color = default_color; - valid = true; - break; - } + qWarning().noquote() << QString("[color] for color %1: color code is invalid: %2").arg(lower_name).arg(code); + continue; } - if (!valid) - continue; + if (color_replacement_map.contains(lower_name)) + qWarning().noquote() << QString("[color] color %1 is already defined, replacing anyway").arg(lower_name); + color_replacement_map[lower_name].code = code; + } - // At this point we know the color is valid - text_colors.beginGroup(color_name); - QString color_code = text_colors.value("code").toString(); - if (color_code.isEmpty()) + // replace the data in the map we will return + for (dr::Color &i_color : color_map.keys()) + { + dr::ColorInfo &color_info = color_map[i_color]; + const QString lower_name = color_info.name.toLower(); + if (!color_replacement_map.contains(lower_name)) continue; - // Check if the color is valid. If not, set it to white - if (!QColor::isValidColor(color_code)) - { - color_code = default_colors[CWhite][1]; - } - - // We will override colors in our currently stored list of colors - read_colors[matched_default_color] = QStringList{color_name, color_code}; - text_colors.endGroup(); + color_info.code = color_replacement_map[lower_name].code; } - return read_colors; + return color_map; } QVector AOApplication::get_highlight_colors() From 3df5af6ef5a18889b0067768f42c2437bee9c656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Thu, 25 Feb 2021 04:00:09 +0100 Subject: [PATCH 220/842] Fixaed datatypes.cpp not being linked to build --- dronline-client.pro | 1 + 1 file changed, 1 insertion(+) diff --git a/dronline-client.pro b/dronline-client.pro index 636d5ce1f..b6cec662d 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -92,6 +92,7 @@ SOURCES += \ src/charselect.cpp \ src/courtroom.cpp \ src/courtroom_widgets.cpp \ + src/datatypes.cpp \ src/debug_functions.cpp \ src/discord_rich_presence.cpp \ src/draudio.cpp \ From 1e4a6a7d546727f43c6b371d640c21e7cbefc629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Thu, 25 Feb 2021 04:52:57 +0100 Subject: [PATCH 221/842] Forgot to end the ini group, oops! --- src/text_file_functions.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 394a1fe56..95426d24d 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -356,7 +356,8 @@ QMap AOApplication::get_chatmessage_colors() if (color_replacement_map.contains(lower_name)) qWarning().noquote() << QString("[color] color %1 is already defined, replacing anyway").arg(lower_name); - color_replacement_map[lower_name].code = code; + color_replacement_map[lower_name].code = code.toLower(); + color_settings.endGroup(); } // replace the data in the map we will return From 47ad5b45a4a35f7c64b375a81e878a8da2c15ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Thu, 25 Feb 2021 05:14:01 +0100 Subject: [PATCH 222/842] Resolving undefined method --- include/datatypes.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/datatypes.h b/include/datatypes.h index fa28c5288..49e22b5ec 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -15,7 +15,11 @@ struct record_type bool music = false; record_type() = default; - record_type(QString p_name, QString p_line, QString p_color, bool p_is_system, bool p_is_music); + record_type(QString p_name, QString p_line, QString p_color, bool p_is_system, bool p_is_music) + : name(p_name), line(p_line), system(p_is_system), music(p_is_music) + { + Q_UNUSED(p_color); + } }; typedef std::shared_ptr record_type_ptr; From 3daf8a951cfad848a3a86c7bdce42f3021f7e2be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 26 Feb 2021 12:23:43 +0100 Subject: [PATCH 223/842] Added audio-device selection DRAudioEngine has been updated to automatically pick a device and can be provided a favorite device. If a favorite device has been designed, DRAudioEngine will attempt to transfert all currently playing sounds to the favorite device of choice. Otherwise, it will attempt to pick the designated audio device chosen by the OS. Finally, If there is no designated audio device, it will pick the first one available. Finally, if there are no devices available the client will simply prevent audio from playing and will report the issue in the console. DRAudioEngine will always attempt to have an audio device active. As long as one is enabled within the OS, it will attempt its best to use it. Failing this, sounds will simply be disabled and won't be able to play until an appropriate device is available. **Favorite Device** * To select a favorite device simply switch to the wanted device in the dropdown menu in AOConfigPanel **NOTE: The dropdown will always reflect the currently used device. If clicked on the dropdown, the favorite device will be highlighted in green to indicate it's the favorite device. If clicking outside of the dropdown, it will simply close the dropdown list of choices and will continue showing the current device.** --- dronline-client.pro | 5 + include/aoconfig.h | 7 + include/aoconfigpanel.h | 17 +- include/datatypes.h | 4 +- include/draudio.h | 9 +- include/draudiodevice.h | 47 +++ include/draudioengine.h | 28 +- include/draudioengine_p.h | 60 ++++ include/draudioerror.h | 2 +- include/draudiostream.h | 43 ++- include/draudiostreamfamily.h | 42 +-- res/ui/config_panel.ui | 45 +-- src/aoconfig.cpp | 569 ++++++++++++++++------------------ src/aoconfigpanel.cpp | 82 ++++- src/draudio.cpp | 2 +- src/draudiodevice.cpp | 64 ++++ src/draudioengine.cpp | 169 ++++++---- src/draudioengine_p.cpp | 153 +++++++++ src/draudioerror.cpp | 4 +- src/draudiostream.cpp | 99 +++++- src/draudiostreamfamily.cpp | 48 ++- src/main.cpp | 6 - 22 files changed, 1021 insertions(+), 484 deletions(-) create mode 100644 include/draudiodevice.h create mode 100644 include/draudioengine_p.h create mode 100644 src/draudiodevice.cpp create mode 100644 src/draudioengine_p.cpp diff --git a/dronline-client.pro b/dronline-client.pro index b6cec662d..30646694f 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -12,6 +12,7 @@ INCLUDEPATH += $$PWD/include $$PWD/3rd $$PWD/3rd/include DEPENDPATH += $$PWD/include DEFINES += DRO_ACKMS +CONFIG(debug, debug|release):DEFINES += DR_DEV HEADERS += \ include/aoapplication.h \ @@ -48,7 +49,9 @@ HEADERS += \ include/debug_functions.h \ include/discord_rich_presence.h \ include/draudio.h \ + include/draudiodevice.h \ include/draudioengine.h \ + include/draudioengine_p.h \ include/draudioerror.h \ include/draudiostream.h \ include/draudiostreamfamily.h \ @@ -96,7 +99,9 @@ SOURCES += \ src/debug_functions.cpp \ src/discord_rich_presence.cpp \ src/draudio.cpp \ + src/draudiodevice.cpp \ src/draudioengine.cpp \ + src/draudioengine_p.cpp \ src/draudioerror.cpp \ src/draudiostream.cpp \ src/draudiostreamfamily.cpp \ diff --git a/include/aoconfig.h b/include/aoconfig.h index fbff9995c..adbe864e0 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -36,6 +36,7 @@ class AOConfig : public QObject bool log_is_recording_enabled(); // audio + std::optional favorite_device_driver(); int master_volume(); bool mute_background_audio(); int system_volume(); @@ -68,6 +69,9 @@ public slots: void set_log_music(bool p_enabled); void set_log_is_recording(bool p_enabled); void set_mute_background_audio(bool p_enabled); + + // audio + void set_favorite_device_driver(QString p_device_driver); void set_master_volume(int p_number); void set_system_volume(int p_number); void set_effect_volume(int p_number); @@ -94,6 +98,9 @@ public slots: void log_uses_newline_changed(bool); void log_music_changed(bool); void log_is_recording_changed(bool); + + // audio + void favorite_device_changed(QString); void master_volume_changed(int); void mute_background_audio_changed(bool); void system_volume_changed(int); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 1ad1ab1be..bd55c7f35 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -1,7 +1,10 @@ #ifndef AOCONFIGPANEL_H #define AOCONFIGPANEL_H -// qt +#include "aoconfig.h" +#include "aoguiloader.h" +#include "draudioengine.h" + #include #include #include @@ -13,10 +16,6 @@ #include #include -// src -#include "aoconfig.h" -#include "aoguiloader.h" - class AOApplication; class AOConfigPanel : public QWidget @@ -39,12 +38,17 @@ public slots: void refresh_theme_list(); void refresh_gamemode_list(); void refresh_timeofday_list(); + void update_audio_device_list(); private slots: void on_reload_theme_clicked(); void on_gamemode_index_changed(QString p_text); void on_timeofday_index_changed(QString p_text); void on_log_is_topdown_changed(bool p_enabled); + void on_device_current_index_changed(int p_index); + void on_audio_device_changed(DRAudioDevice p_device); + void on_favorite_audio_device_changed(DRAudioDevice p_device); + void on_audio_device_list_changed(QList p_device_list); void on_master_value_changed(int p_num); void on_system_value_changed(int p_num); void on_effect_value_changed(int p_num); @@ -57,6 +61,7 @@ private slots: // driver AOConfig *m_config = nullptr; + DRAudioEngine *m_engine = nullptr; // behaviour QPushButton *w_save = nullptr; @@ -87,6 +92,8 @@ private slots: QCheckBox *w_log_is_recording = nullptr; // audio + QComboBox *w_device = nullptr; + QCheckBox *w_favorite_device = nullptr; QSlider *w_master = nullptr; QLabel *w_master_value = nullptr; QCheckBox *w_mute_background_audio = nullptr; diff --git a/include/datatypes.h b/include/datatypes.h index 49e22b5ec..01218da97 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -92,7 +92,7 @@ struct pos_size_type int height = 0; }; -enum ChatMessage : std::int32_t +enum ChatMessage : int32_t { CMDeskModifier = 0, CMPreAnim, @@ -114,7 +114,7 @@ enum ChatMessage : std::int32_t namespace dr { -enum Color : std::int32_t +enum Color : int32_t { CDefault, CGreen, diff --git a/include/draudio.h b/include/draudio.h index adb59f9da..7d70371bb 100644 --- a/include/draudio.h +++ b/include/draudio.h @@ -17,10 +17,17 @@ enum Option { OSuppressed = 0x1, OIgnoreSuppression = 0x2, + + // engine + + /** + * If enabled, the engine will suppress all audio when the application is inactive. + */ + OEngineMuteBackgroundAudio = 0x4, }; Q_DECLARE_FLAGS(Options, Option) -QString get_bass_error(int p_error_code); +QString get_bass_error(const int32_t p_error_code); QString get_last_bass_error(); } // namespace DRAudio Q_DECLARE_METATYPE(DRAudio::Options) diff --git a/include/draudiodevice.h b/include/draudiodevice.h new file mode 100644 index 000000000..a4285f708 --- /dev/null +++ b/include/draudiodevice.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include +#include +#include + +class DRAudioEngine; +class DRAudioEnginePrivate; + +class DRAudioDevice +{ +public: + enum State : DWORD + { + SEnabled = BASS_DEVICE_ENABLED, + SDefault = BASS_DEVICE_DEFAULT, + SInit = BASS_DEVICE_INIT, + }; + Q_DECLARE_FLAGS(States, State) + + DRAudioDevice(); + DRAudioDevice(DWORD p_device, BASS_DEVICEINFO p_device_info); + + std::optional get_id() const; + QString get_name() const; + QString get_driver() const; + States get_states() const; + // condition check + bool is_state(State p_state) const; + bool is_enabled() const; + bool is_default() const; + bool is_init() const; + +private: + friend class DRAudioEngine; + friend class DRAudioEnginePrivate; + + std::optional m_id; + QString m_name; + QString m_driver; + States m_states; + + bool merge(DRAudioDevice &p_device); +}; +Q_DECLARE_METATYPE(DRAudioDevice) diff --git a/include/draudioengine.h b/include/draudioengine.h index c6a9432d2..f1c5b83cd 100644 --- a/include/draudioengine.h +++ b/include/draudioengine.h @@ -10,24 +10,38 @@ class DRAudioEngine : public QObject DRAudioEngine(QObject *p_parent = nullptr); ~DRAudioEngine(); + static std::optional get_device(); + static std::optional get_favorite_device(); + static QList get_device_list(); static DRAudioStreamFamily::ptr get_family(DRAudio::Family p_family); static QList get_family_list(); - static std::int32_t get_volume(); + static int32_t get_volume(); static DRAudio::Options get_options(); - // option getter + // option get + static bool is_option(DRAudio::Option p_option); static bool is_suppressed(); + static bool is_mute_background_audio(); public slots: - void set_volume(std::int32_t p_volume); + void set_device(DRAudioDevice p_device); + void set_favorite_device(DRAudioDevice p_device); + void set_favorite_device_by_driver(QString p_device_driver); + void set_volume(int32_t p_volume); void set_options(DRAudio::Options p_options); - // option setter + // option set + void set_option(DRAudio::Option p_option, bool p_enabled); void set_suppressed(bool p_enabled); + void set_mute_background_audio(bool p_enabled); signals: - void volume_changed(std::int32_t); + void device_changed(DRAudioDevice); + void favorite_device_changed(DRAudioDevice); + void device_list_changed(QList); + void volume_changed(int32_t); void options_changed(DRAudio::Options); private: - void adjust_options(); - void adjust_volume(); + friend class DRAudioStream; + + static void check_stream_error(); }; diff --git a/include/draudioengine_p.h b/include/draudioengine_p.h new file mode 100644 index 000000000..466ba20d1 --- /dev/null +++ b/include/draudioengine_p.h @@ -0,0 +1,60 @@ +#pragma once + +#include "draudio.h" +#include "draudiostreamfamily.h" + +#include +#include +#include +#include +#include + +#include + +void print_device_list(const QList &device_list); + +class DRAudioEngine; +class DRAudioEngineData; + +class DRAudioEnginePrivate : public QObject +{ + Q_OBJECT + +public: + using ptr = QPointer; + + DRAudioEnginePrivate(); + ~DRAudioEnginePrivate(); + + void invoke_signal(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)); + +private: + friend class DRAudioEngine; + friend class DRAudioEngineData; + + void update_device(); + void update_device_list(); + void update_options(); + void update_volume(); + + void check_stream_error(); + + QObjectList children; + QPointer engine; + + QPointer event_timer; + bool check_device = false; + + std::optional device; + std::optional previous_device; + std::optional favorite_device; + + int32_t volume = 0; + DRAudio::Options options; + + QMap device_map; + QMap family_map; + +private slots: + void on_event_tick(); +}; diff --git a/include/draudioerror.h b/include/draudioerror.h index 96b227d3c..72be6ff27 100644 --- a/include/draudioerror.h +++ b/include/draudioerror.h @@ -8,7 +8,7 @@ class DRAudioError DRAudioError(); DRAudioError(QString p_error); - QString get_error(); + QString what(); private: QString m_error; diff --git a/include/draudiostream.h b/include/draudiostream.h index 990a46f41..f302e388f 100644 --- a/include/draudiostream.h +++ b/include/draudiostream.h @@ -1,57 +1,80 @@ #pragma once #include "draudio.h" +#include "draudiodevice.h" #include "draudioerror.h" #include #include +#include #include #include -#include +class DRAudioEngine; +class DRAudioEnginePrivate; class DRAudioStreamFamily; +class DRAudioStreamSync +{ +public: + HSYNC sync; + DWORD type; +}; + class DRAudioStream : public QObject { Q_OBJECT + DRAudioStream(DRAudio::Family p_family); + public: using ptr = QSharedPointer; ~DRAudioStream(); - DRAudio::Family get_family(); - std::optional get_file(); + DRAudio::Family get_family() const; + std::optional get_file() const; // state - bool is_playing(); + bool is_playing() const; public slots: - void play(); - void stop(); - std::optional set_file(QString m_file); void set_volume(float p_volume); + void play(); + void stop(); + signals: void file_changed(QString p_file); void finished(); public: - static void CALLBACK sync_on_end(HSYNC hsync, DWORD ch, DWORD data, void *userdata); + static void CALLBACK on_sync_callback(HSYNC hsync, DWORD ch, DWORD data, void *userdata); private: + friend class DRAudioEngine; + friend class DRAudioEnginePrivate; friend class DRAudioStreamFamily; - DRAudioStream(DRAudio::Family p_family); + void cache_position(); + void update_device(); // static method DRAudio::Family m_family; std::optional m_file; + float m_volume; // bass std::optional m_hstream; - std::stack m_hsync_stack; + QStack m_hsync_stack; + std::optional m_position; + +private slots: + void on_device_error(); + +signals: + void device_error(QPrivateSignal); }; diff --git a/include/draudiostreamfamily.h b/include/draudiostreamfamily.h index ec8865587..db209d733 100644 --- a/include/draudiostreamfamily.h +++ b/include/draudiostreamfamily.h @@ -10,6 +10,7 @@ #include class DRAudioEngine; +class DRAudioEngineData; class DRAudioStreamFamily : public QObject { @@ -20,49 +21,50 @@ class DRAudioStreamFamily : public QObject std::optional create_stream(QString p_file); std::optional play_stream(QString p_file); - QVector get_stream_list(); - std::int32_t get_volume(); - std::int32_t get_capacity(); - DRAudio::Options get_options(); - // options getter - bool is_suppressed(); - bool is_ignore_suppression(); + // get + QVector get_stream_list() const; + int32_t get_volume() const; + int32_t get_capacity() const; + DRAudio::Options get_options() const; - using iterator = QVector::iterator; - iterator begin(); - iterator end(); + // options get + bool is_suppressed() const; + bool is_ignore_suppression() const; public slots: - void set_volume(std::int32_t p_volume); - void set_capacity(std::int32_t p_capacity); + void set_volume(int32_t p_volume); + void set_capacity(int32_t p_capacity); void set_options(DRAudio::Options p_options); // options setter void set_suppressed(bool p_enabled); void set_ignore_suppression(bool p_enabled); signals: - void volume_changed(std::int32_t); - void capacity_changed(std::int32_t); + void volume_changed(int32_t); + void capacity_changed(int32_t); void options_changed(DRAudio::Options); private: friend class DRAudioEngine; + friend class DRAudioEngineData; + friend class DRAudioEnginePrivate; DRAudioStreamFamily(DRAudio::Family p_family); float calculate_volume(); - void adjust_capacity(); - void adjust_options(); - void adjust_volume(); + void update_device(); + void update_capacity(); + void update_options(); + void update_volume(); DRAudio::Family m_family; - std::int32_t m_volume = 0; - std::int32_t m_capacity = 0; + int32_t m_volume = 0; + int32_t m_capacity = 0; DRAudio::Options m_options; QVector> m_stream_list; private slots: - void on_stream_stopped(); + void on_stream_finished(); }; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 8dab8e03a..2d778f5d5 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -448,6 +448,9 @@ + + + @@ -495,7 +498,7 @@ - + <html><head/><body><p>If enabled, audio will be temporarily muted when the client is not in focus.<br/><br/>This does not apply to system sounds.</p></body></html> @@ -505,14 +508,21 @@ - + + + + Qt::Horizontal + + + + System: - + @@ -560,14 +570,14 @@ - + Music: - + @@ -615,14 +625,14 @@ - + Effects: - + @@ -664,14 +674,14 @@ - + Blips: - + @@ -713,14 +723,14 @@ - + Blip rate: - + 1 @@ -733,14 +743,14 @@ - + Blank blips: - + @@ -750,7 +760,7 @@ - + Qt::Vertical @@ -763,13 +773,6 @@ - - - - Qt::Horizontal - - - diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 20d8522ab..333d0c821 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -20,11 +20,28 @@ class AOConfigPrivate : public QObject { Q_OBJECT - friend AOConfig; +public: + AOConfigPrivate(QObject *p_parent = nullptr); + ~AOConfigPrivate(); + + // setters +public slots: + void read_file(); + void save_file(); + +private: + void invoke_signal(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)); + void update_favorite_device(); + +private slots: + void on_application_state_changed(Qt::ApplicationState p_state); + +private: + friend class AOConfig; QSettings cfg; // hate me more - QVector parents; + QVector children; // data bool autosave; @@ -44,6 +61,8 @@ class AOConfigPrivate : public QObject bool log_music; bool log_is_recording; + // audio + std::optional favorite_device_driver; int master_volume; bool mute_background_audio; int effect_volume; @@ -55,284 +74,128 @@ class AOConfigPrivate : public QObject // audio sync DRAudioEngine *audio_engine = nullptr; +}; -public: - AOConfigPrivate(QObject *p_parent = nullptr) - : QObject(p_parent), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat), - audio_engine(new DRAudioEngine(this)) - { - Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); - connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, - SLOT(on_application_state_changed(Qt::ApplicationState))); +AOConfigPrivate::AOConfigPrivate(QObject *p_parent) + : QObject(p_parent), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat), + audio_engine(new DRAudioEngine(this)) +{ + Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); + connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, + SLOT(on_application_state_changed(Qt::ApplicationState))); - read_file(); - } - ~AOConfigPrivate() - { - if (autosave) - { - save_file(); - } - } + read_file(); +} - // setters -public slots: - void set_autosave(bool p_enabled) - { - if (autosave == p_enabled) - return; - autosave = p_enabled; - invoke_parents("autosave_changed", Q_ARG(bool, p_enabled)); - } - void set_username(QString p_string) - { - if (username == p_string) - return; - username = p_string; - invoke_parents("username_changed", Q_ARG(QString, p_string)); - } - void set_callwords(QString p_string) - { - if (callwords == p_string) - return; - callwords = p_string; - invoke_parents("callwords_changed", Q_ARG(QString, p_string)); - } - void set_server_alerts(bool p_enabled) - { - if (server_alerts == p_enabled) - return; - server_alerts = p_enabled; - invoke_parents("server_alerts_changed", Q_ARG(bool, p_enabled)); - } - void set_theme(QString p_string) - { - if (theme == p_string) - return; - theme = p_string; - invoke_parents("theme_changed", Q_ARG(QString, p_string)); - } - void set_gamemode(QString p_string) - { - if (gamemode == p_string) - return; - gamemode = p_string; - invoke_parents("gamemode_changed", Q_ARG(QString, p_string)); - } - void set_manual_gamemode(bool p_enabled) - { - if (manual_gamemode == p_enabled) - return; - manual_gamemode = p_enabled; - invoke_parents("manual_gamemode_changed", Q_ARG(bool, p_enabled)); - } - void set_timeofday(QString p_string) - { - if (timeofday == p_string) - return; - timeofday = p_string; - invoke_parents("timeofday_changed", Q_ARG(QString, p_string)); - } - void set_manual_timeofday(bool p_enabled) - { - if (manual_timeofday == p_enabled) - return; - manual_timeofday = p_enabled; - invoke_parents("manual_timeofday_changed", Q_ARG(bool, p_enabled)); - } - void set_always_pre(bool p_enabled) - { - if (always_pre == p_enabled) - return; - always_pre = p_enabled; - invoke_parents("always_pre_changed", Q_ARG(bool, p_enabled)); - } - void set_chat_tick_interval(int p_number) - { - if (chat_tick_interval == p_number) - return; - chat_tick_interval = p_number; - invoke_parents("chat_tick_interval_changed", Q_ARG(int, p_number)); - } - void set_log_max_lines(int p_number) - { - if (log_max_lines == p_number) - return; - log_max_lines = p_number; - invoke_parents("log_max_lines_changed", Q_ARG(int, p_number)); - } - void set_log_is_topdown(bool p_enabled) - { - if (log_is_topdown == p_enabled) - return; - log_is_topdown = p_enabled; - invoke_parents("log_is_topdown_changed", Q_ARG(bool, p_enabled)); - } - void set_log_uses_newline(bool p_enabled) - { - if (log_uses_newline == p_enabled) - return; - log_uses_newline = p_enabled; - invoke_parents("log_uses_newline_changed", Q_ARG(bool, p_enabled)); - } - void set_log_music(bool p_enabled) - { - if (log_music == p_enabled) - return; - log_music = p_enabled; - invoke_parents("log_music_changed", Q_ARG(bool, p_enabled)); - } - void set_log_is_recording(bool p_enabled) - { - if (log_is_recording == p_enabled) - return; - log_is_recording = p_enabled; - invoke_parents("log_is_recording_changed", Q_ARG(bool, p_enabled)); - } - void set_master_volume(int p_number) - { - if (master_volume == p_number) - return; - master_volume = p_number; - audio_engine->set_volume(p_number); - invoke_parents("master_volume_changed", Q_ARG(int, p_number)); - } - void set_mute_background_audio(bool p_enabled) - { - if (mute_background_audio == p_enabled) - return; - mute_background_audio = p_enabled; - invoke_parents("mute_background_audio_changed", Q_ARG(bool, p_enabled)); - } - void set_system_volume(int p_number) - { - if (system_volume == p_number) - return; - system_volume = p_number; - audio_engine->get_family(DRAudio::Family::FSystem)->set_volume(p_number); - invoke_parents("system_volume_changed", Q_ARG(int, p_number)); - } - void set_effects_volume(int p_number) - { - if (effect_volume == p_number) - return; - effect_volume = p_number; - audio_engine->get_family(DRAudio::Family::FEffect)->set_volume(p_number); - invoke_parents("effect_volume_changed", Q_ARG(int, p_number)); - } - void set_music_volume(int p_number) - { - if (music_volume == p_number) - return; - music_volume = p_number; - audio_engine->get_family(DRAudio::Family::FMusic)->set_volume(p_number); - invoke_parents("music_volume_changed", Q_ARG(int, p_number)); - } - void set_blips_volume(int p_number) - { - if (blip_volume == p_number) - return; - blip_volume = p_number; - audio_engine->get_family(DRAudio::Family::FBlip)->set_volume(p_number); - invoke_parents("blip_volume_changed", Q_ARG(int, p_number)); - } - void set_blip_rate(int p_number) - { - if (blip_rate == p_number) - return; - blip_rate = p_number; - invoke_parents("blip_rate_changed", Q_ARG(int, p_number)); - } - void set_blank_blips(bool p_enabled) - { - if (blank_blips == p_enabled) - return; - blank_blips = p_enabled; - invoke_parents("blank_blips_changed", Q_ARG(bool, p_enabled)); - } - void read_file() - { - autosave = cfg.value("autosave", true).toBool(); - username = cfg.value("username").toString(); - callwords = cfg.value("callwords").toString(); - server_alerts = cfg.value("server_alerts", true).toBool(); - - theme = cfg.value("theme").toString(); - if (theme.trimmed().isEmpty()) - theme = "default"; - - gamemode = cfg.value("gamemode").toString(); - manual_gamemode = cfg.value("manual_gamemode", false).toBool(); - timeofday = cfg.value("timeofday", "").toString(); - manual_timeofday = cfg.value("manual_timeofday", false).toBool(); - always_pre = cfg.value("always_pre", true).toBool(); - chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); - log_max_lines = cfg.value("chatlog_limit", 200).toInt(); - log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); - log_uses_newline = cfg.value("chatlog_newline", false).toBool(); - log_music = cfg.value("music_change_log", true).toBool(); - log_is_recording = cfg.value("enable_logging", true).toBool(); - - mute_background_audio = cfg.value("mute_background_audio").toBool(); - master_volume = cfg.value("default_master", 50).toInt(); - system_volume = cfg.value("default_system", 50).toInt(); - effect_volume = cfg.value("default_sfx", 50).toInt(); - music_volume = cfg.value("default_music", 50).toInt(); - blip_volume = cfg.value("default_blip", 50).toInt(); - blip_rate = cfg.value("blip_rate", 1000000000).toInt(); - blank_blips = cfg.value("blank_blips").toBool(); - - // audio update - audio_engine->set_volume(master_volume); - audio_engine->get_family(DRAudio::Family::FSystem)->set_volume(system_volume); - audio_engine->get_family(DRAudio::Family::FEffect)->set_volume(effect_volume); - audio_engine->get_family(DRAudio::Family::FMusic)->set_volume(music_volume); - audio_engine->get_family(DRAudio::Family::FBlip)->set_volume(blip_volume); - } - void save_file() +AOConfigPrivate::~AOConfigPrivate() +{ + if (autosave) { - cfg.setValue("autosave", autosave); - cfg.setValue("username", username); - cfg.setValue("callwords", callwords); - cfg.setValue("server_alerts", server_alerts); - cfg.setValue("theme", theme); - cfg.setValue("gamemode", gamemode); - cfg.setValue("manual_gamemode", manual_gamemode); - cfg.setValue("timeofday", timeofday); - cfg.setValue("manual_timeofday", manual_timeofday); - cfg.setValue("always_pre", always_pre); - cfg.setValue("chat_tick_interval", chat_tick_interval); - cfg.setValue("chatlog_limit", log_max_lines); - cfg.setValue("chatlog_scrolldown", log_is_topdown); - cfg.setValue("chatlog_newline", log_uses_newline); - cfg.setValue("music_change_log", log_music); - cfg.setValue("enable_logging", log_is_recording); - cfg.setValue("default_master", master_volume); - cfg.setValue("mute_background_audio", mute_background_audio); - cfg.setValue("default_sfx", effect_volume); - cfg.setValue("default_system", system_volume); - cfg.setValue("default_music", music_volume); - cfg.setValue("default_blip", blip_volume); - cfg.setValue("blip_rate", blip_rate); - cfg.setValue("blank_blips", blank_blips); - cfg.sync(); + save_file(); } +} -private: - void invoke_parents(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)) +void AOConfigPrivate::read_file() +{ + autosave = cfg.value("autosave", true).toBool(); + username = cfg.value("username").toString(); + callwords = cfg.value("callwords").toString(); + server_alerts = cfg.value("server_alerts", true).toBool(); + + theme = cfg.value("theme").toString(); + if (theme.trimmed().isEmpty()) + theme = "default"; + + gamemode = cfg.value("gamemode").toString(); + manual_gamemode = cfg.value("manual_gamemode", false).toBool(); + timeofday = cfg.value("timeofday", "").toString(); + manual_timeofday = cfg.value("manual_timeofday", false).toBool(); + always_pre = cfg.value("always_pre", true).toBool(); + chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); + log_max_lines = cfg.value("chatlog_limit", 200).toInt(); + log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); + log_uses_newline = cfg.value("chatlog_newline", false).toBool(); + log_music = cfg.value("music_change_log", true).toBool(); + log_is_recording = cfg.value("enable_logging", true).toBool(); + + if (cfg.contains("favorite_device_driver")) + favorite_device_driver = cfg.value("favorite_device_driver").toString(); + + mute_background_audio = cfg.value("mute_background_audio").toBool(); + master_volume = cfg.value("default_master", 50).toInt(); + system_volume = cfg.value("default_system", 50).toInt(); + effect_volume = cfg.value("default_sfx", 50).toInt(); + music_volume = cfg.value("default_music", 50).toInt(); + blip_volume = cfg.value("default_blip", 50).toInt(); + blip_rate = cfg.value("blip_rate", 1000000000).toInt(); + blank_blips = cfg.value("blank_blips").toBool(); + + // audio update + audio_engine->set_volume(master_volume); + audio_engine->get_family(DRAudio::Family::FSystem)->set_volume(system_volume); + audio_engine->get_family(DRAudio::Family::FEffect)->set_volume(effect_volume); + audio_engine->get_family(DRAudio::Family::FMusic)->set_volume(music_volume); + audio_engine->get_family(DRAudio::Family::FBlip)->set_volume(blip_volume); + + // audio device + update_favorite_device(); +} + +void AOConfigPrivate::save_file() +{ + cfg.setValue("autosave", autosave); + cfg.setValue("username", username); + cfg.setValue("callwords", callwords); + cfg.setValue("server_alerts", server_alerts); + cfg.setValue("theme", theme); + cfg.setValue("gamemode", gamemode); + cfg.setValue("manual_gamemode", manual_gamemode); + cfg.setValue("timeofday", timeofday); + cfg.setValue("manual_timeofday", manual_timeofday); + cfg.setValue("always_pre", always_pre); + cfg.setValue("chat_tick_interval", chat_tick_interval); + cfg.setValue("chatlog_limit", log_max_lines); + cfg.setValue("chatlog_scrolldown", log_is_topdown); + cfg.setValue("chatlog_newline", log_uses_newline); + cfg.setValue("music_change_log", log_music); + cfg.setValue("enable_logging", log_is_recording); + + // audio + if (favorite_device_driver.has_value()) + cfg.setValue("favorite_device_driver", favorite_device_driver.value()); + + cfg.setValue("mute_background_audio", mute_background_audio); + cfg.setValue("default_master", master_volume); + cfg.setValue("default_sfx", effect_volume); + cfg.setValue("default_system", system_volume); + cfg.setValue("default_music", music_volume); + cfg.setValue("default_blip", blip_volume); + cfg.setValue("blip_rate", blip_rate); + cfg.setValue("blank_blips", blank_blips); + cfg.sync(); +} + +void AOConfigPrivate::invoke_signal(QString p_method_name, QGenericArgument p_arg1) +{ + for (QObject *i_child : children) { - for (QObject *i_parent : parents) - { - QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), p_arg1); - } + QMetaObject::invokeMethod(i_child, p_method_name.toStdString().c_str(), p_arg1); } +} -private slots: - void on_application_state_changed(Qt::ApplicationState p_state) - { - audio_engine->set_suppressed(mute_background_audio && p_state != Qt::ApplicationActive); - } -}; +void AOConfigPrivate::update_favorite_device() +{ + if (!favorite_device_driver.has_value()) + return; + audio_engine->set_favorite_device_by_driver(favorite_device_driver.value()); +} + +void AOConfigPrivate::on_application_state_changed(Qt::ApplicationState p_state) +{ + audio_engine->set_suppressed(mute_background_audio && p_state != Qt::ApplicationActive); +} + +// AOConfig //////////////////////////////////////////////////////////////////// /*! * private classes are cool @@ -352,13 +215,13 @@ AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) } // ao2 is the pinnacle of thread security - d->parents.append(this); + d->children.append(this); } AOConfig::~AOConfig() { // totally safe! - d->parents.removeAll(this); + d->children.removeAll(this); } QString AOConfig::get_string(QString p_name, QString p_default) @@ -454,6 +317,12 @@ bool AOConfig::log_is_recording_enabled() { return d->log_is_recording; } + +std::optional AOConfig::favorite_device_driver() +{ + return d->favorite_device_driver; +} + int AOConfig::master_volume() { return d->master_volume; @@ -493,129 +362,215 @@ bool AOConfig::blank_blips_enabled() return d->blank_blips; } +void AOConfig::save_file() +{ + d->save_file(); +} + void AOConfig::set_autosave(bool p_enabled) { - d->set_autosave(p_enabled); + if (d->autosave == p_enabled) + return; + d->autosave = p_enabled; + d->invoke_signal("autosave_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_username(QString p_string) { - d->set_username(p_string); + if (d->username == p_string) + return; + d->username = p_string; + d->invoke_signal("username_changed", Q_ARG(QString, p_string)); } void AOConfig::set_callwords(QString p_string) { - d->set_callwords(p_string); + if (d->callwords == p_string) + return; + d->callwords = p_string; + d->invoke_signal("callwords_changed", Q_ARG(QString, p_string)); +} + +void AOConfig::set_server_alerts(bool p_enabled) +{ + if (d->server_alerts == p_enabled) + return; + d->server_alerts = p_enabled; + d->invoke_signal("server_alerts_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_theme(QString p_string) { - d->set_theme(p_string); + if (d->theme == p_string) + return; + d->theme = p_string; + d->invoke_signal("theme_changed", Q_ARG(QString, p_string)); } void AOConfig::set_gamemode(QString p_string) { - d->set_gamemode(p_string); + if (d->gamemode == p_string) + return; + d->gamemode = p_string; + d->invoke_signal("gamemode_changed", Q_ARG(QString, p_string)); } void AOConfig::set_manual_gamemode(bool p_enabled) { - d->set_manual_gamemode(p_enabled); + if (d->manual_gamemode == p_enabled) + return; + d->manual_gamemode = p_enabled; + d->invoke_signal("manual_gamemode_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_timeofday(QString p_string) { - d->set_timeofday(p_string); + if (d->timeofday == p_string) + return; + d->timeofday = p_string; + d->invoke_signal("timeofday_changed", Q_ARG(QString, p_string)); } void AOConfig::set_manual_timeofday(bool p_enabled) { - d->set_manual_timeofday(p_enabled); -} - -void AOConfig::set_server_alerts(bool p_enabled) -{ - d->set_server_alerts(p_enabled); + if (d->manual_timeofday == p_enabled) + return; + d->manual_timeofday = p_enabled; + d->invoke_signal("manual_timeofday_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_always_pre(bool p_enabled) { - d->set_always_pre(p_enabled); + if (d->always_pre == p_enabled) + return; + d->always_pre = p_enabled; + d->invoke_signal("always_pre_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_chat_tick_interval(int p_number) { - d->set_chat_tick_interval(p_number); + if (d->chat_tick_interval == p_number) + return; + d->chat_tick_interval = p_number; + d->invoke_signal("chat_tick_interval_changed", Q_ARG(int, p_number)); } void AOConfig::set_log_max_lines(int p_number) { - d->set_log_max_lines(p_number); + if (d->log_max_lines == p_number) + return; + d->log_max_lines = p_number; + d->invoke_signal("log_max_lines_changed", Q_ARG(int, p_number)); } void AOConfig::set_log_is_topdown(bool p_enabled) { - d->set_log_is_topdown(p_enabled); + if (d->log_is_topdown == p_enabled) + return; + d->log_is_topdown = p_enabled; + d->invoke_signal("log_is_topdown_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_log_uses_newline(bool p_enabled) { - d->set_log_uses_newline(p_enabled); + if (d->log_uses_newline == p_enabled) + return; + d->log_uses_newline = p_enabled; + d->invoke_signal("log_uses_newline_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_log_music(bool p_enabled) { - d->set_log_music(p_enabled); + if (d->log_music == p_enabled) + return; + d->log_music = p_enabled; + d->invoke_signal("log_music_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_log_is_recording(bool p_enabled) { - d->set_log_is_recording(p_enabled); + if (d->log_is_recording == p_enabled) + return; + d->log_is_recording = p_enabled; + d->invoke_signal("log_is_recording_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_master_volume(int p_number) +{ + if (d->master_volume == p_number) + return; + d->master_volume = p_number; + d->audio_engine->set_volume(p_number); + d->invoke_signal("master_volume_changed", Q_ARG(int, p_number)); } void AOConfig::set_mute_background_audio(bool p_enabled) { - d->set_mute_background_audio(p_enabled); + if (d->mute_background_audio == p_enabled) + return; + d->mute_background_audio = p_enabled; + d->invoke_signal("mute_background_audio_changed", Q_ARG(bool, p_enabled)); } -void AOConfig::set_master_volume(int p_number) +void AOConfig::set_favorite_device_driver(QString p_device_driver) { - d->set_master_volume(p_number); + if (d->favorite_device_driver.has_value() && d->favorite_device_driver.value() == p_device_driver) + return; + d->favorite_device_driver = p_device_driver; + d->update_favorite_device(); + d->invoke_signal("favorite_device_changed", Q_ARG(QString, p_device_driver)); } void AOConfig::set_system_volume(int p_number) { - d->set_system_volume(p_number); + if (d->system_volume == p_number) + return; + d->system_volume = p_number; + d->audio_engine->get_family(DRAudio::Family::FSystem)->set_volume(p_number); + d->invoke_signal("system_volume_changed", Q_ARG(int, p_number)); } void AOConfig::set_effect_volume(int p_number) { - d->set_effects_volume(p_number); + if (d->effect_volume == p_number) + return; + d->effect_volume = p_number; + d->audio_engine->get_family(DRAudio::Family::FEffect)->set_volume(p_number); + d->invoke_signal("effect_volume_changed", Q_ARG(int, p_number)); } void AOConfig::set_music_volume(int p_number) { - d->set_music_volume(p_number); + if (d->music_volume == p_number) + return; + d->music_volume = p_number; + d->audio_engine->get_family(DRAudio::Family::FMusic)->set_volume(p_number); + d->invoke_signal("music_volume_changed", Q_ARG(int, p_number)); } void AOConfig::set_blip_volume(int p_number) { - d->set_blips_volume(p_number); + if (d->blip_volume == p_number) + return; + d->blip_volume = p_number; + d->audio_engine->get_family(DRAudio::Family::FBlip)->set_volume(p_number); + d->invoke_signal("blip_volume_changed", Q_ARG(int, p_number)); } void AOConfig::set_blip_rate(int p_number) { - d->set_blip_rate(p_number); + if (d->blip_rate == p_number) + return; + d->blip_rate = p_number; + d->invoke_signal("blip_rate_changed", Q_ARG(int, p_number)); } void AOConfig::set_blank_blips(bool p_enabled) { - d->set_blank_blips(p_enabled); -} - -void AOConfig::save_file() -{ - d->save_file(); + if (d->blank_blips == p_enabled) + return; + d->blank_blips = p_enabled; + d->invoke_signal("blank_blips_changed", Q_ARG(bool, p_enabled)); } // moc diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 9c51429eb..6c238f596 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -8,7 +8,7 @@ #include "aoapplication.h" // ruined AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) - : QWidget(p_parent), m_config(new AOConfig(this)) + : QWidget(p_parent), m_config(new AOConfig(this)), m_engine(new DRAudioEngine(this)) { ao_app = p_ao_app; @@ -50,6 +50,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); // audio + w_device = AO_GUI_WIDGET(QComboBox, "device"); + w_favorite_device = AO_GUI_WIDGET(QCheckBox, "favorite_device"); w_master = AO_GUI_WIDGET(QSlider, "master"); w_master_value = AO_GUI_WIDGET(QLabel, "master_value"); w_mute_background_audio = AO_GUI_WIDGET(QCheckBox, "mute_background_audio"); @@ -95,6 +97,12 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, SLOT(setValue(int))); connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, SLOT(setChecked(bool))); + connect(m_engine, SIGNAL(device_changed(DRAudioDevice)), this, SLOT(on_audio_device_changed(DRAudioDevice))); + connect(m_engine, SIGNAL(device_list_changed(QList)), this, + SLOT(on_audio_device_list_changed(QList))); + connect(m_engine, SIGNAL(favorite_device_changed(DRAudioDevice)), this, + SLOT(on_favorite_audio_device_changed(DRAudioDevice))); + // output connect(w_close, SIGNAL(clicked()), this, SLOT(close())); connect(w_save, SIGNAL(clicked()), m_config, SLOT(save_file())); @@ -116,6 +124,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_log_music, SIGNAL(toggled(bool)), m_config, SLOT(set_log_music(bool))); connect(w_log_is_recording, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_recording(bool))); connect(w_mute_background_audio, SIGNAL(toggled(bool)), m_config, SLOT(set_mute_background_audio(bool))); + connect(w_device, SIGNAL(currentIndexChanged(int)), this, SLOT(on_device_current_index_changed(int))); connect(w_master, SIGNAL(valueChanged(int)), m_config, SLOT(set_master_volume(int))); connect(w_master, SIGNAL(valueChanged(int)), this, SLOT(on_master_value_changed(int))); connect(w_system, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_volume(int))); @@ -155,6 +164,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); w_log_music->setChecked(m_config->log_music_enabled()); w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); + + // audio + update_audio_device_list(); w_master->setValue(m_config->master_volume()); w_mute_background_audio->setChecked(m_config->mute_background_audio()); w_system->setValue(m_config->system_volume()); @@ -270,6 +282,40 @@ void AOConfigPanel::refresh_timeofday_list() w_timeofday->blockSignals(false); } +void AOConfigPanel::update_audio_device_list() +{ + const auto current_device = m_engine->get_device(); + const auto favorite_device = m_engine->get_favorite_device(); + + QSignalBlocker device_blocker(w_device); + w_device->clear(); + + std::optional current_device_index; + { + for (DRAudioDevice &i_device : m_engine->get_device_list()) + { + if (!i_device.is_enabled()) + continue; + w_device->addItem(i_device.get_name(), i_device.get_driver()); + int item_index = w_device->count() - 1; + + if (current_device.has_value() && current_device.value().get_driver() == i_device.get_driver()) + current_device_index = item_index; + + if (favorite_device.has_value() && favorite_device.value().get_driver() == i_device.get_driver()) + w_device->setItemData(item_index, QColor(Qt::green), Qt::BackgroundRole); + } + + const bool has_items(w_device->count()); + if (!has_items) + w_device->addItem(tr("")); + w_device->setEnabled(has_items); + } + + if (current_device_index.has_value()) + w_device->setCurrentIndex(current_device_index.value()); +} + void AOConfigPanel::on_reload_theme_clicked() { qDebug() << "reload theme clicked"; @@ -294,6 +340,40 @@ void AOConfigPanel::on_log_is_topdown_changed(bool p_enabled) w_log_orientation_bottom_up->setChecked(!p_enabled); } +void AOConfigPanel::on_device_current_index_changed(int p_index) +{ + if (p_index == -1 || p_index >= w_device->count()) + return; + + const QString target_device_driver = w_device->itemData(p_index).toString(); + for (DRAudioDevice &i_device : m_engine->get_device_list()) + if (target_device_driver == i_device.get_driver()) + { + m_engine->set_device(i_device); + m_engine->set_favorite_device(i_device); + m_config->set_favorite_device_driver(i_device.get_driver()); + break; + } +} + +void AOConfigPanel::on_audio_device_changed(DRAudioDevice p_device) +{ + Q_UNUSED(p_device) + update_audio_device_list(); +} + +void AOConfigPanel::on_favorite_audio_device_changed(DRAudioDevice p_device) +{ + Q_UNUSED(p_device) + update_audio_device_list(); +} + +void AOConfigPanel::on_audio_device_list_changed(QList p_device_list) +{ + Q_UNUSED(p_device_list); + update_audio_device_list(); +} + void AOConfigPanel::on_master_value_changed(int p_num) { w_master_value->setText(QString::number(p_num) + "%"); diff --git a/src/draudio.cpp b/src/draudio.cpp index 299134dbb..a2f5cc677 100644 --- a/src/draudio.cpp +++ b/src/draudio.cpp @@ -2,7 +2,7 @@ #include -QString DRAudio::get_bass_error(int32_t p_error_code) +QString DRAudio::get_bass_error(const int32_t p_error_code) { switch (p_error_code) { diff --git a/src/draudiodevice.cpp b/src/draudiodevice.cpp new file mode 100644 index 000000000..d25b1e9b1 --- /dev/null +++ b/src/draudiodevice.cpp @@ -0,0 +1,64 @@ +#include "draudiodevice.h" + +DRAudioDevice::DRAudioDevice() : m_name("") +{} + +DRAudioDevice::DRAudioDevice(DWORD p_device, BASS_DEVICEINFO p_device_info) + : m_id(p_device), m_name(p_device_info.name), m_driver(p_device_info.driver), m_states(State(p_device_info.flags)) +{} + +std::optional DRAudioDevice::get_id() const +{ + return m_id; +} + +QString DRAudioDevice::get_name() const +{ + return m_name; +} + +QString DRAudioDevice::get_driver() const +{ + return m_driver; +} + +DRAudioDevice::States DRAudioDevice::get_states() const +{ + return m_states; +} + +bool DRAudioDevice::is_state(DRAudioDevice::State p_state) const +{ + return m_states.testFlag(p_state); +} + +bool DRAudioDevice::is_enabled() const +{ + return is_state(SEnabled); +} + +bool DRAudioDevice::is_default() const +{ + return is_state(SDefault); +} + +bool DRAudioDevice::is_init() const +{ + return is_state(SInit); +} + +bool DRAudioDevice::merge(DRAudioDevice &p_device) +{ + bool is_changed = false; + auto check_and_set = [&](auto &a, auto &b) { + if (a == b) + return; + is_changed = true; + a = b; + }; + + check_and_set(m_name, p_device.m_name); + check_and_set(m_driver, p_device.m_driver); + check_and_set(m_states, p_device.m_states); + return is_changed; +} diff --git a/src/draudioengine.cpp b/src/draudioengine.cpp index 62d1c82ab..92116311f 100644 --- a/src/draudioengine.cpp +++ b/src/draudioengine.cpp @@ -1,71 +1,81 @@ #include "draudioengine.h" +#include "draudioengine_p.h" + #include #include -#include -#include +#include -class DRAudioEnginePrivate : public QObject -{ - Q_OBJECT +static const int EVENT_TIMER_INTERVAL_DEFAULT = 100; +static class DRAudioEngineData +{ public: - using ptr = std::unique_ptr; + using ptr = DRAudioEnginePrivate::ptr; - DRAudioEnginePrivate(QObject *parent = nullptr) : QObject(parent) - {} - - void invoke_signal(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)) + ptr &operator->() { - for (QObject *i_parent : parents) + if (d == nullptr) { - QMetaObject::invokeMethod(i_parent, p_method_name.toStdString().c_str(), p_arg1); + d = ptr(new DRAudioEnginePrivate); + + // create engine shortcut + d->engine = new DRAudioEngine; + + // create families + d->family_map.insert(DRAudio::Family::FSystem, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FSystem))); + d->family_map.insert(DRAudio::Family::FEffect, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FEffect))); + d->family_map.insert(DRAudio::Family::FMusic, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FMusic))); + d->family_map.insert(DRAudio::Family::FBlip, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FBlip))); + + // set family-specific options + d->family_map.value(DRAudio::Family::FSystem)->set_ignore_suppression(true); + + // create and init event loop + d->event_timer->setInterval(EVENT_TIMER_INTERVAL_DEFAULT); + d->event_timer->setSingleShot(false); + + QObject::connect(d->event_timer, SIGNAL(timeout()), d, SLOT(on_event_tick())); + + d->event_timer->start(); + + d->on_event_tick(); } + + return d; } - QObjectList parents; - std::int32_t volume = 0; - DRAudio::Options options; - QMap family_map; -}; +private: + ptr d; +} d; -namespace +DRAudioEngine::DRAudioEngine(QObject *p_parent) : QObject(p_parent) { -static QPointer d; + d->children.append(this); } -DRAudioEngine::DRAudioEngine(QObject *p_parent) : QObject(p_parent) +DRAudioEngine::~DRAudioEngine() { - if (BOOL r = BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, 0, NULL); r == FALSE) - qWarning() << DRAudioError(QString("failed to init: %1").arg(DRAudio::get_last_bass_error())).get_error(); - - if (d == nullptr) - { - Q_ASSERT_X(qApp, "initialization", "QApplication is required"); - d = new DRAudioEnginePrivate(qApp); - - // there's plenty of way of optimizing this - d->family_map.insert(DRAudio::Family::FSystem, - DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FSystem))); - d->family_map.insert(DRAudio::Family::FEffect, - DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FEffect))); - d->family_map.insert(DRAudio::Family::FMusic, - DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FMusic))); - d->family_map.insert(DRAudio::Family::FBlip, - DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FBlip))); - - // family-specific options - get_family(DRAudio::Family::FSystem)->set_ignore_suppression(true); - } + d->children.removeAll(this); +} - d->parents.append(this); +std::optional DRAudioEngine::get_device() +{ + return d->device; } -DRAudioEngine::~DRAudioEngine() +std::optional DRAudioEngine::get_favorite_device() { - d->parents.removeAll(this); + return d->favorite_device; +} - BASS_Free(); +QList DRAudioEngine::get_device_list() +{ + return d->device_map.values(); } DRAudioStreamFamily::ptr DRAudioEngine::get_family(DRAudio::Family p_family) @@ -88,50 +98,85 @@ DRAudio::Options DRAudioEngine::get_options() return d->options; } +bool DRAudioEngine::is_option(DRAudio::Option p_option) +{ + return d->options.testFlag(p_option); +} + bool DRAudioEngine::is_suppressed() { return d->options.testFlag(DRAudio::OSuppressed); } +bool DRAudioEngine::is_mute_background_audio() +{ + return is_option(DRAudio::OEngineMuteBackgroundAudio); +} +void DRAudioEngine::set_device(DRAudioDevice p_device) +{ + if (!d->device_map.contains(p_device.get_driver())) + return; + if (d->device.has_value() && d->device->get_driver() == p_device.get_driver()) + return; + d->previous_device = d->device; + d->device = d->device_map.value(p_device.get_driver()); + d->update_device(); + Q_EMIT d->invoke_signal("device_changed", Q_ARG(DRAudioDevice, d->device.value())); +} + +void DRAudioEngine::set_favorite_device(DRAudioDevice p_device) +{ + if (d->favorite_device.has_value() && d->favorite_device.value().get_driver() == p_device.get_driver()) + return; + d->favorite_device = p_device; + Q_EMIT d->invoke_signal("favorite_device_changed", Q_ARG(DRAudioDevice, d->favorite_device.value())); +} + +void DRAudioEngine::set_favorite_device_by_driver(QString p_device_driver) +{ + DRAudioDevice favorite_device; + favorite_device.m_driver = p_device_driver; + d->check_device = true; + set_favorite_device(favorite_device); +} + void DRAudioEngine::set_volume(int32_t p_volume) { p_volume = std::clamp(p_volume, 0, 100); - if (d->volume == p_volume) return; - d->volume = p_volume; - adjust_volume(); - Q_EMIT d->invoke_signal("volume_changed", Q_ARG(std::int32_t, d->volume)); + d->update_volume(); + Q_EMIT d->invoke_signal("volume_changed", Q_ARG(int32_t, d->volume)); } void DRAudioEngine::set_options(DRAudio::Options p_options) { if (d->options == p_options) return; - d->options = p_options; - adjust_options(); + d->update_options(); Q_EMIT d->invoke_signal("options_changed", Q_ARG(DRAudio::Options, d->options)); } -void DRAudioEngine::set_suppressed(bool p_enabled) +void DRAudioEngine::set_option(DRAudio::Option p_option, bool p_enabled) { - DRAudio::Options options = d->options; - options.setFlag(DRAudio::OSuppressed, p_enabled); - set_options(options); + DRAudio::Options new_options = d->options; + new_options.setFlag(p_option, p_enabled); + set_options(new_options); } -void DRAudioEngine::adjust_options() +void DRAudioEngine::set_suppressed(bool p_enabled) { - adjust_volume(); + set_option(DRAudio::OSuppressed, p_enabled); } -void DRAudioEngine::adjust_volume() +void DRAudioEngine::set_mute_background_audio(bool p_enabled) { - for (auto &i_group : d->family_map.values()) - i_group->adjust_volume(); + set_option(DRAudio::OEngineMuteBackgroundAudio, p_enabled); } -// needed by moc -#include "draudioengine.moc" +void DRAudioEngine::check_stream_error() +{ + d->check_stream_error(); +} diff --git a/src/draudioengine_p.cpp b/src/draudioengine_p.cpp new file mode 100644 index 000000000..54c0b7172 --- /dev/null +++ b/src/draudioengine_p.cpp @@ -0,0 +1,153 @@ +#include "draudioengine_p.h" + +/*! + I could've avoided making this necessary but I didn't + want to make the code extra complex. + + So here, instead have this very convuloted workaround + instead. +*/ +#include "draudioengine.h" + +#include +#include + +static const int EVENT_TIMER_INTERVAL_DEFAULT = 100; + +DRAudioEnginePrivate::DRAudioEnginePrivate() : QObject(nullptr), event_timer(new QTimer) +{} + +DRAudioEnginePrivate::~DRAudioEnginePrivate() +{ + event_timer->stop(); + + BASS_Free(); +} + +void DRAudioEnginePrivate::invoke_signal(QString p_method_name, QGenericArgument p_arg1) +{ + for (QObject *i_child : children) + { + QMetaObject::invokeMethod(i_child, p_method_name.toStdString().c_str(), p_arg1); + } +} + +void DRAudioEnginePrivate::update_device() +{ + // cache the stream's position before we make the switch + for (DRAudioStreamFamily::ptr &i_family : family_map.values()) + for (DRAudioStream::ptr &i_stream : i_family->get_stream_list()) + i_stream->cache_position(); + + if (previous_device.has_value()) + { + qInfo().noquote() << QString("Shutting down current device: %1").arg(previous_device->get_name()); + if (BASS_Free() == FALSE) + qCritical() << DRAudioError(QString("Failed to free: %1").arg(DRAudio::get_last_bass_error())).what(); + previous_device.reset(); + } + + if (!device.has_value()) + qCritical() << DRAudioError("Failed to initialize, device not set!").what(); + + qInfo().noquote() << QString("Initializing device: %1").arg(device->get_name()); + if (BASS_Init(device->get_id().value_or(0), 48000, BASS_DEVICE_LATENCY, 0, NULL) == FALSE) + qCritical() << DRAudioError(QString("Failed to initialize: %1").arg(DRAudio::get_last_bass_error())).what(); + + qInfo() << "Switching to initiliazed device"; + if (BASS_SetDevice(device->get_id().value_or(0)) == FALSE) + qCritical() << DRAudioError(QString("Failed to set device: %1").arg(DRAudio::get_last_bass_error())).what(); + + for (DRAudioStreamFamily::ptr &i_family : family_map.values()) + i_family->update_device(); +} + +void DRAudioEnginePrivate::update_device_list() +{ + QVector updated_device_list; + + { + BASS_DEVICEINFO device_info; + for (int device = 1; BASS_GetDeviceInfo(device, &device_info); ++device) + updated_device_list.append(DRAudioDevice(device, device_info)); + } + + bool is_changed = false; + for (DRAudioDevice &device : updated_device_list) + { + if (device_map.contains(device.get_driver())) + { + if (device_map[device.get_driver()].merge(device)) + is_changed = true; + continue; + } + + device_map.insert(device.get_driver(), device); + is_changed = true; + } + + if (!is_changed) + return; + check_device = true; + Q_EMIT invoke_signal("device_list_changed", Q_ARG(QList, device_map.values())); +} + +void DRAudioEnginePrivate::update_options() +{ + update_volume(); +} + +void DRAudioEnginePrivate::update_volume() +{ + for (auto &i_group : family_map.values()) + i_group->update_volume(); +} + +void DRAudioEnginePrivate::check_stream_error() +{ + event_timer->start(EVENT_TIMER_INTERVAL_DEFAULT); + check_device = true; +} + +void DRAudioEnginePrivate::on_event_tick() +{ + update_device_list(); + + if (device.has_value() && !check_device) + return; + check_device = false; + + std::optional target_device; + for (DRAudioDevice &i_device : device_map.values()) + { + if (!i_device.is_enabled()) + continue; + + if (favorite_device && favorite_device.value().get_driver() == i_device.get_driver()) + { + // if our favorite device doesn't have an id then + // it was most likely set through the driver + // in which case we need to update it + if (!favorite_device->get_id().has_value()) + { + favorite_device.reset(); + engine->set_favorite_device(i_device); + } + + target_device = i_device; + break; + } + + if (!target_device.has_value() || i_device.is_default()) + target_device = i_device; + } + + if (!target_device.has_value()) + { +#ifdef QT_DEBUG + qWarning().noquote() << DRAudioError(QString("NO DEVICE AVAILABLE!")).what(); +#endif + return; + } + engine->set_device(target_device.value()); +} diff --git a/src/draudioerror.cpp b/src/draudioerror.cpp index 56007f91d..b6ed24f6f 100644 --- a/src/draudioerror.cpp +++ b/src/draudioerror.cpp @@ -3,10 +3,10 @@ DRAudioError::DRAudioError() {} -DRAudioError::DRAudioError(QString p_error) : m_error(QString("[bass]%1").arg(p_error)) +DRAudioError::DRAudioError(QString p_error) : m_error(QString("[bass] %1").arg(p_error)) {} -QString DRAudioError::get_error() +QString DRAudioError::what() { return m_error; } diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index b5e2cee68..aeaf1f554 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -7,7 +7,9 @@ #include DRAudioStream::DRAudioStream(DRAudio::Family p_family) : m_family(p_family) -{} +{ + connect(this, &DRAudioStream::device_error, this, &DRAudioStream::on_device_error, Qt::QueuedConnection); +} DRAudioStream::~DRAudioStream() { @@ -17,7 +19,7 @@ DRAudioStream::~DRAudioStream() while (!m_hsync_stack.empty()) { - BASS_ChannelRemoveSync(hstream, m_hsync_stack.top()); + BASS_ChannelRemoveSync(hstream, m_hsync_stack.top().sync); m_hsync_stack.pop(); } @@ -25,17 +27,17 @@ DRAudioStream::~DRAudioStream() } } -DRAudio::Family DRAudioStream::get_family() +DRAudio::Family DRAudioStream::get_family() const { return m_family; } -std::optional DRAudioStream::get_file() +std::optional DRAudioStream::get_file() const { return m_file; } -bool DRAudioStream::is_playing() +bool DRAudioStream::is_playing() const { if (!m_hstream) return false; @@ -51,7 +53,7 @@ void DRAudioStream::play() { qWarning() << DRAudioError( QString("failed to play file %1: %2").arg(m_file.value()).arg(DRAudio::get_last_bass_error())) - .get_error(); + .what(); Q_EMIT finished(); } } @@ -85,10 +87,17 @@ std::optional DRAudioStream::set_file(QString p_file) m_hstream = stream_handle; // bass events - HSYNC sync_handle = BASS_ChannelSetSync(stream_handle, BASS_SYNC_END, 0, &DRAudioStream::sync_on_end, this); - if (sync_handle == 0) - return DRAudioError( - QString("failed to create sync for file %1: %2").arg(p_file).arg(DRAudio::get_last_bass_error())); + for (const DWORD type : {BASS_SYNC_END, BASS_SYNC_DEV_FAIL}) + { + HSYNC sync_handle = + BASS_ChannelSetSync(stream_handle, type | BASS_SYNC_MIXTIME, 0, &DRAudioStream::on_sync_callback, this); + if (sync_handle == 0) + return DRAudioError(QString("failed to create sync %1 for file %2: %3") + .arg(type) + .arg(p_file) + .arg(DRAudio::get_last_bass_error())); + m_hsync_stack.push(DRAudioStreamSync{sync_handle, type}); + } Q_EMIT file_changed(p_file); return std::nullopt; @@ -98,10 +107,13 @@ void DRAudioStream::set_volume(float p_volume) { if (!m_hstream) return; + m_volume = p_volume; BASS_ChannelSetAttribute(m_hstream.value(), BASS_ATTRIB_VOL, float(p_volume) * 0.01f); } -void DRAudioStream::sync_on_end(HSYNC hsync, DWORD ch, DWORD data, void *userdata) +#include + +void DRAudioStream::on_sync_callback(HSYNC hsync, DWORD ch, DWORD data, void *userdata) { Q_UNUSED(hsync) Q_UNUSED(ch) @@ -112,5 +124,68 @@ void DRAudioStream::sync_on_end(HSYNC hsync, DWORD ch, DWORD data, void *userdat * the pointer we provided get deleted mid-way through the program lifetime */ DRAudioStream *self = static_cast(userdata); - Q_EMIT self->finished(); + + for (auto &v : self->m_hsync_stack) + { + if (v.sync != hsync) + continue; + + const QString filePath = self->get_file().value(); + + switch (v.type) + { + case BASS_SYNC_END: + qDebug().noquote() << QString("[%1] %2 has been triggered!").arg(filePath).arg("BASS_SYNC_END"); + Q_EMIT self->finished(); + break; + + case BASS_SYNC_DEV_FAIL: + qDebug().noquote() << QString("[%1] %2 has been triggered!").arg(filePath).arg("BASS_SYNC_DEV_FAIL"); + Q_EMIT self->device_error(QPrivateSignal()); + break; + } + } +} + +void DRAudioStream::cache_position() +{ + if (!m_hstream.has_value()) + return; + if (m_position.has_value()) + return; + m_position = BASS_ChannelGetPosition(m_hstream.value(), BASS_POS_BYTE); + BASS_ChannelStop(m_hstream.value()); +} + +void DRAudioStream::on_device_error() +{ + cache_position(); + DRAudioEngine::check_stream_error(); +} + +void DRAudioStream::update_device() +{ + if (is_playing()) + return; + const QString file = m_file.value(); + + m_file.reset(); + m_hstream.reset(); + m_hsync_stack.clear(); + + blockSignals(true); + set_file(file); + set_volume(m_volume); + blockSignals(false); + + if (m_position.has_value()) + { + if (BASS_ChannelSetPosition(m_hstream.value(), m_position.value(), BASS_POS_BYTE) == FALSE) + qWarning() << DRAudioError( + QString("failed to set position for %1: %2").arg(file).arg(DRAudio::get_last_bass_error())) + .what(); + m_position.reset(); + } + + play(); } diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp index 629cd189f..1be22c990 100644 --- a/src/draudiostreamfamily.cpp +++ b/src/draudiostreamfamily.cpp @@ -7,36 +7,26 @@ DRAudioStreamFamily::DRAudioStreamFamily(DRAudio::Family p_family) : m_family(p_family) {} -int32_t DRAudioStreamFamily::get_capacity() +int32_t DRAudioStreamFamily::get_capacity() const { return m_capacity; } -DRAudio::Options DRAudioStreamFamily::get_options() +DRAudio::Options DRAudioStreamFamily::get_options() const { return m_options; } -bool DRAudioStreamFamily::is_suppressed() +bool DRAudioStreamFamily::is_suppressed() const { return m_options.testFlag(DRAudio::OSuppressed); } -bool DRAudioStreamFamily::is_ignore_suppression() +bool DRAudioStreamFamily::is_ignore_suppression() const { return m_options.testFlag(DRAudio::OIgnoreSuppression); } -DRAudioStreamFamily::iterator DRAudioStreamFamily::begin() -{ - return m_stream_list.begin(); -} - -DRAudioStreamFamily::iterator DRAudioStreamFamily::end() -{ - return m_stream_list.end(); -} - void DRAudioStreamFamily::set_capacity(int32_t p_capacity) { p_capacity = std::max(p_capacity, 0); @@ -45,7 +35,7 @@ void DRAudioStreamFamily::set_capacity(int32_t p_capacity) return; m_capacity = p_capacity; - adjust_capacity(); + update_capacity(); Q_EMIT capacity_changed(m_capacity); } @@ -55,7 +45,7 @@ void DRAudioStreamFamily::set_options(DRAudio::Options p_options) return; m_options = p_options; - adjust_options(); + update_options(); Q_EMIT options_changed(m_options); } @@ -67,7 +57,7 @@ void DRAudioStreamFamily::set_volume(int32_t p_volume) return; m_volume = p_volume; - adjust_volume(); + update_volume(); Q_EMIT volume_changed(m_volume); } @@ -91,15 +81,15 @@ std::optional DRAudioStreamFamily::create_stream(QString p_f if (auto err = stream->set_file(p_file); err) { - qWarning() << err->get_error(); + qWarning() << err->what(); return std::nullopt; } m_stream_list.append(stream); - adjust_capacity(); + update_capacity(); stream->set_volume(calculate_volume()); - connect(stream.get(), SIGNAL(finished()), this, SLOT(on_stream_stopped())); + connect(stream.get(), SIGNAL(finished()), this, SLOT(on_stream_finished())); return stream; } @@ -116,7 +106,7 @@ std::optional DRAudioStreamFamily::play_stream(QString p_fil return r_stream; } -QVector DRAudioStreamFamily::get_stream_list() +QVector DRAudioStreamFamily::get_stream_list() const { return m_stream_list; } @@ -138,7 +128,13 @@ float DRAudioStreamFamily::calculate_volume() return volume * 100.0f; } -void DRAudioStreamFamily::adjust_capacity() +void DRAudioStreamFamily::update_device() +{ + for (DRAudioStream::ptr i_stream : m_stream_list) + i_stream->update_device(); +} + +void DRAudioStreamFamily::update_capacity() { if (m_capacity == 0) return; @@ -146,20 +142,20 @@ void DRAudioStreamFamily::adjust_capacity() m_stream_list.removeFirst(); } -void DRAudioStreamFamily::adjust_options() +void DRAudioStreamFamily::update_options() { // suppressed - adjust_volume(); + update_volume(); } -void DRAudioStreamFamily::adjust_volume() +void DRAudioStreamFamily::update_volume() { const float volume = calculate_volume(); for (auto &stream : m_stream_list) stream->set_volume(volume); } -void DRAudioStreamFamily::on_stream_stopped() +void DRAudioStreamFamily::on_stream_finished() { DRAudioStream *invoker = dynamic_cast(sender()); if (invoker == nullptr) diff --git a/src/main.cpp b/src/main.cpp index b7831bfd3..bd426bae1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,12 +5,6 @@ #include "lobby.h" #include "networkmanager.h" -#include -#include -#include -#include -#include - int main(int argc, char *argv[]) { #if QT_VERSION > QT_VERSION_CHECK(5, 6, 0) From e931f90294a377357065ec0243d55a3430210fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 26 Feb 2021 13:33:00 +0100 Subject: [PATCH 224/842] Extra tab-code so that it always switch to the first upon creation --- res/ui/config_panel.ui | 2 +- src/aoconfigpanel.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 2d778f5d5..dcdc3f853 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 3 + 0 diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 6c238f596..07d849c84 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -19,7 +19,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) loader.load_from_file(":res/ui/config_panel.ui", this); // tab - setFocusProxy(AO_GUI_WIDGET(QTabWidget, "tab_widget")); + QTabWidget *tab_widget = AO_GUI_WIDGET(QTabWidget, "tab_widget"); + setFocusProxy(tab_widget); + tab_widget->setCurrentIndex(0); // behaviour w_save = AO_GUI_WIDGET(QPushButton, "save"); From d3297f792391dee462150cd5e9a232045bcce034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 26 Feb 2021 13:41:13 +0100 Subject: [PATCH 225/842] Extra info for missing file --- src/draudiostream.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index aeaf1f554..7a920e028 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -73,7 +73,7 @@ std::optional DRAudioStream::set_file(QString p_file) const QFileInfo file(p_file); if (!file.exists()) - return DRAudioError("file does not exist"); + return DRAudioError(QString("file does not exist: %1").arg(p_file.isEmpty() ? "" : p_file)); if (m_file == p_file) return std::nullopt; @@ -135,12 +135,10 @@ void DRAudioStream::on_sync_callback(HSYNC hsync, DWORD ch, DWORD data, void *us switch (v.type) { case BASS_SYNC_END: - qDebug().noquote() << QString("[%1] %2 has been triggered!").arg(filePath).arg("BASS_SYNC_END"); Q_EMIT self->finished(); break; case BASS_SYNC_DEV_FAIL: - qDebug().noquote() << QString("[%1] %2 has been triggered!").arg(filePath).arg("BASS_SYNC_DEV_FAIL"); Q_EMIT self->device_error(QPrivateSignal()); break; } From b3890774751463dad4c3148d64f6317754f23c6c Mon Sep 17 00:00:00 2001 From: TrickyLeifa <26681464+TrickyLeifa@users.noreply.github.com> Date: Fri, 26 Feb 2021 05:13:39 -0800 Subject: [PATCH 226/842] Extra error checks to secure against crashes --- src/draudioengine_p.cpp | 9 +++++++++ src/draudiostream.cpp | 14 ++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/draudioengine_p.cpp b/src/draudioengine_p.cpp index 54c0b7172..bd8c4101e 100644 --- a/src/draudioengine_p.cpp +++ b/src/draudioengine_p.cpp @@ -48,15 +48,24 @@ void DRAudioEnginePrivate::update_device() } if (!device.has_value()) + { qCritical() << DRAudioError("Failed to initialize, device not set!").what(); + return; + } qInfo().noquote() << QString("Initializing device: %1").arg(device->get_name()); if (BASS_Init(device->get_id().value_or(0), 48000, BASS_DEVICE_LATENCY, 0, NULL) == FALSE) + { qCritical() << DRAudioError(QString("Failed to initialize: %1").arg(DRAudio::get_last_bass_error())).what(); + return; + } qInfo() << "Switching to initiliazed device"; if (BASS_SetDevice(device->get_id().value_or(0)) == FALSE) + { qCritical() << DRAudioError(QString("Failed to set device: %1").arg(DRAudio::get_last_bass_error())).what(); + return; + } for (DRAudioStreamFamily::ptr &i_family : family_map.values()) i_family->update_device(); diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index aeaf1f554..16091517d 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -39,14 +39,14 @@ std::optional DRAudioStream::get_file() const bool DRAudioStream::is_playing() const { - if (!m_hstream) + if (!m_hstream.has_value()) return false; return BASS_ChannelIsActive(m_hstream.value()) == BASS_ACTIVE_PLAYING; } void DRAudioStream::play() { - if (!m_hstream) + if (!m_hstream.has_value()) return; const BOOL result = BASS_ChannelPlay(m_hstream.value(), FALSE); if (result == FALSE) @@ -60,7 +60,7 @@ void DRAudioStream::play() void DRAudioStream::stop() { - if (!m_hstream) + if (!m_hstream.has_value()) return; BASS_ChannelStop(m_hstream.value()); Q_EMIT finished(); @@ -105,7 +105,7 @@ std::optional DRAudioStream::set_file(QString p_file) void DRAudioStream::set_volume(float p_volume) { - if (!m_hstream) + if (!m_hstream.has_value()) return; m_volume = p_volume; BASS_ChannelSetAttribute(m_hstream.value(), BASS_ATTRIB_VOL, float(p_volume) * 0.01f); @@ -165,6 +165,12 @@ void DRAudioStream::on_device_error() void DRAudioStream::update_device() { + if (!m_hstream.has_value()) + { + Q_EMIT finished(); + return; + } + if (is_playing()) return; const QString file = m_file.value(); From 1af9c8a786e762c58ff40d588a5bbda84956cddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 26 Feb 2021 14:43:27 +0100 Subject: [PATCH 227/842] Removed unimplemented function declaration --- include/draudioengine_p.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/draudioengine_p.h b/include/draudioengine_p.h index 466ba20d1..12a974f67 100644 --- a/include/draudioengine_p.h +++ b/include/draudioengine_p.h @@ -11,8 +11,6 @@ #include -void print_device_list(const QList &device_list); - class DRAudioEngine; class DRAudioEngineData; From 011e843571de166af38f4a0da60a0c2043b17e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= Date: Fri, 26 Feb 2021 18:45:49 +0100 Subject: [PATCH 228/842] Fixed pathing issues, compiler warnings, ... * Fixed pathing issues with AOEmoteButton * Fixed various compiling warnings being given and added backward-compatibility for some of them * Changed dr namespace to uppercase --- include/aoapplication.h | 2 +- include/datatypes.h | 12 ++++++++-- src/aoapplication.cpp | 46 +++++++++++++++++++++++++++++-------- src/aoemotebutton.cpp | 4 ++-- src/aonotearea.cpp | 2 +- src/courtroom.cpp | 14 ++++++----- src/datatypes.cpp | 18 +++++++-------- src/hardware_functions.cpp | 2 +- src/networkmanager.cpp | 7 +++--- src/text_file_functions.cpp | 10 ++++---- 10 files changed, 76 insertions(+), 41 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index d5caa2410..32025bdf6 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -356,7 +356,7 @@ class AOApplication : public QApplication QString get_stylesheet(QString target_tag, QString p_file); // Returns string list (color name to color HEX) - QMap get_chatmessage_colors(); + QMap get_chatmessage_colors(); // Returns string list (highlight character to color name) QVector get_highlight_colors(); diff --git a/include/datatypes.h b/include/datatypes.h index 01218da97..196e46df7 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -112,8 +112,16 @@ enum ChatMessage : int32_t CMShowName, }; -namespace dr +namespace DR { +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) +using SplitBehavior = QString::SplitBehavior; +#else +using SplitBehavior = Qt::SplitBehaviorFlags; +#endif +const SplitBehavior KeepEmptyParts = SplitBehavior::KeepEmptyParts; +const SplitBehavior SkipEmptyParts = SplitBehavior::SkipEmptyParts; + enum Color : int32_t { CDefault, @@ -143,6 +151,6 @@ struct ColorInfo }; QMap get_default_color_map(); -} // namespace dr +} // namespace DR #endif // DATATYPES_H diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index e09031f15..e02e997bf 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -9,9 +9,14 @@ #include "aoconfigpanel.h" #include -#include #include +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) +#include +#else +#include +#endif + AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) { discord = new AttorneyOnline::Discord(); @@ -49,9 +54,16 @@ void AOApplication::construct_lobby() w_lobby = new Lobby(this); lobby_constructed = true; - QRect screenGeometry = QApplication::desktop()->screenGeometry(); - int x = (screenGeometry.width() - w_lobby->width()) / 2; - int y = (screenGeometry.height() - w_lobby->height()) / 2; +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + QRect screen_geometry = QApplication::desktop()->screenGeometry(); +#else + QScreen *screen = QApplication::screenAt(w_lobby->pos()); + if (screen == nullptr) + return; + QRect screen_geometry = screen->geometry(); +#endif + int x = (screen_geometry.width() - w_lobby->width()) / 2; + int y = (screen_geometry.height() - w_lobby->height()) / 2; w_lobby->move(x, y); discord->state_lobby(); @@ -84,9 +96,16 @@ void AOApplication::construct_courtroom() connect(w_courtroom, SIGNAL(destroyed()), this, SLOT(on_courtroom_destroyed())); courtroom_constructed = true; - QRect screenGeometry = QApplication::desktop()->screenGeometry(); - int x = (screenGeometry.width() - w_courtroom->width()) / 2; - int y = (screenGeometry.height() - w_courtroom->height()) / 2; +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + QRect screen_geometry = QApplication::desktop()->screenGeometry(); +#else + QScreen *screen = QApplication::screenAt(w_courtroom->pos()); + if (screen == nullptr) + return; + QRect screen_geometry = screen->geometry(); +#endif + int x = (screen_geometry.width() - w_courtroom->width()) / 2; + int y = (screen_geometry.height() - w_courtroom->height()) / 2; w_courtroom->move(x, y); } @@ -162,9 +181,16 @@ void AOApplication::toggle_config_panel() config_panel->setVisible(!config_panel->isVisible()); if (config_panel->isVisible()) { - QRect screenGeometry = QApplication::desktop()->screenGeometry(); - int x = (screenGeometry.width() - config_panel->width()) / 2; - int y = (screenGeometry.height() - config_panel->height()) / 2; +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + QRect screen_geometry = QApplication::desktop()->screenGeometry(); +#else + QScreen *screen = QApplication::screenAt(config_panel->pos()); + if (screen == nullptr) + return; + QRect screen_geometry = screen->geometry(); +#endif + int x = (screen_geometry.width() - config_panel->width()) / 2; + int y = (screen_geometry.height() - config_panel->height()) / 2; config_panel->setFocus(); config_panel->raise(); config_panel->move(x, y); diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index 1d3c435ea..af0912a05 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -55,7 +55,7 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) if (file_exists(selected_texture_path)) { - w_selected->setStyleSheet(QString("border-image: url(%1)").arg(selected_texture_path)); + w_selected->setStyleSheet(QString("border-image: url(\"%1\")").arg(selected_texture_path)); } else { @@ -70,7 +70,7 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) const bool texture_exist = file_exists(texture_path); setText(texture_exist ? QString() : ao_app->get_emote_comment(p_chr, p_emote_number)); setStyleSheet(texture_exist - ? QString("%1 { border-image: url(%2); }").arg(metaObject()->className()).arg(texture_path) + ? QString("%1 { border-image: url(\"%2\"); }").arg(metaObject()->className()).arg(texture_path) : QString()); } diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp index bc93a8ad9..d5557a02c 100644 --- a/src/aonotearea.cpp +++ b/src/aonotearea.cpp @@ -79,7 +79,7 @@ void Courtroom::set_note_files() QString f_filestring = f_notepicker->real_file; QString f_filename = f_notepicker->m_line->text(); - t += QString::number(i) + " = " + f_filestring + " = " + f_filename + "\n\n"; + t += QString("%1 = %2 = %3\n\n").arg(i).arg(f_filestring).arg(f_filename).toUtf8(); } config_file.close(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index dad168cfd..13abd2ab3 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -324,7 +324,7 @@ void Courtroom::handle_music_anim() if (ao_app->read_theme_ini("enable_const_music_speed", cc_config_ini) == "true") dist = res_b.width; else - dist = fm.width(ui_vp_music_name->toPlainText()); + dist = fm.horizontalAdvance(ui_vp_music_name->toPlainText()); int time = static_cast(1000000 * dist / speed); music_anim->setLoopCount(-1); music_anim->setDuration(time); @@ -980,7 +980,7 @@ void Courtroom::handle_chatmessage_3() int f_anim_state = 0; // BLUE is from an enum in datatypes.h - bool text_is_blue = m_chatmessage[CMTextColor].toInt() == dr::CBlue; + bool text_is_blue = m_chatmessage[CMTextColor].toInt() == DR::CBlue; if (!text_is_blue && text_state == 1) // talking @@ -1032,12 +1032,14 @@ void Courtroom::handle_chatmessage_3() break; default: qDebug() << "W: invalid anim_state: " << f_anim_state; + [[fallthrough]]; case 3: if (m_msg_is_first_person == false) { ui_vp_player_char->play_idle(f_char, f_emote); } anim_state = 3; + break; } int effect = m_chatmessage[CMEffectState].toInt(); @@ -1431,7 +1433,7 @@ void Courtroom::chat_tick() if (f_character == " ") ui_vp_message->insertPlainText(" "); - else if (m_chatmessage[CMTextColor].toInt() == dr::CRainbow) + else if (m_chatmessage[CMTextColor].toInt() == DR::CRainbow) { QString html_color; @@ -1573,9 +1575,9 @@ void Courtroom::play_sfx() void Courtroom::set_text_color() { - const QMap color_map = ao_app->get_chatmessage_colors(); - const dr::Color color = dr::Color(m_chatmessage[CMTextColor].toInt()); - const QString color_code = color_map[color_map.contains(color) ? color : dr::CDefault].code; + const QMap color_map = ao_app->get_chatmessage_colors(); + const DR::Color color = DR::Color(m_chatmessage[CMTextColor].toInt()); + const QString color_code = color_map[color_map.contains(color) ? color : DR::CDefault].code; ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); m_base_string_color.setNamedColor(color_code); } diff --git a/src/datatypes.cpp b/src/datatypes.cpp index 43f89a6a6..e9e48a65d 100644 --- a/src/datatypes.cpp +++ b/src/datatypes.cpp @@ -1,17 +1,17 @@ #include "datatypes.h" -QMap dr::get_default_color_map() +QMap DR::get_default_color_map() { QMap default_color_map; - default_color_map[dr::CWhite] = ColorInfo("White", "#d5d5d5"); - default_color_map[dr::CGreen] = ColorInfo("Green", "#65c856"); - default_color_map[dr::CRed] = ColorInfo("Red", "#ba1518"); - default_color_map[dr::COrange] = ColorInfo("Orange", "#d55900"); - default_color_map[dr::CBlue] = ColorInfo("Blue", "#1588c8"); - default_color_map[dr::CYellow] = ColorInfo("Yellow", "#e7ce4e"); - default_color_map[dr::CPurple] = ColorInfo("Purple", "#f776fd"); - default_color_map[dr::CPink] = ColorInfo("Pink", "#da7c80"); + default_color_map[DR::CWhite] = ColorInfo("White", "#d5d5d5"); + default_color_map[DR::CGreen] = ColorInfo("Green", "#65c856"); + default_color_map[DR::CRed] = ColorInfo("Red", "#ba1518"); + default_color_map[DR::COrange] = ColorInfo("Orange", "#d55900"); + default_color_map[DR::CBlue] = ColorInfo("Blue", "#1588c8"); + default_color_map[DR::CYellow] = ColorInfo("Yellow", "#e7ce4e"); + default_color_map[DR::CPurple] = ColorInfo("Purple", "#f776fd"); + default_color_map[DR::CPink] = ColorInfo("Pink", "#da7c80"); return default_color_map; } diff --git a/src/hardware_functions.cpp b/src/hardware_functions.cpp index 78a9bb683..3af0b92a6 100644 --- a/src/hardware_functions.cpp +++ b/src/hardware_functions.cpp @@ -10,7 +10,7 @@ BOOL bIsRetrieved; QString get_hdid() { - bIsRetrieved = GetVolumeInformation(TEXT("C:\\"), NULL, NULL, &dwVolSerial, NULL, NULL, NULL, NULL); + bIsRetrieved = GetVolumeInformation(TEXT("C:\\"), NULL, 0, &dwVolSerial, NULL, NULL, NULL, 0); if (bIsRetrieved) return QString::number(dwVolSerial, 16); diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp index 308e1c360..f3439f990 100644 --- a/src/networkmanager.cpp +++ b/src/networkmanager.cpp @@ -23,8 +23,7 @@ NetworkManager::NetworkManager(AOApplication *parent) : QObject(parent) } NetworkManager::~NetworkManager() -{ -} +{} void NetworkManager::connect_to_master() { @@ -98,7 +97,7 @@ void NetworkManager::handle_ms_packet() } } - QStringList packet_list = in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); + QStringList packet_list = in_data.split("%", DR::SkipEmptyParts); for (QString packet : packet_list) { @@ -232,7 +231,7 @@ void NetworkManager::handle_server_packet() } } - QStringList packet_list = in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); + QStringList packet_list = in_data.split("%", DR::SkipEmptyParts); for (QString packet : packet_list) { diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 95426d24d..58fb2bb89 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -317,9 +317,9 @@ QString AOApplication::get_stylesheet(QString target_tag, QString p_file) return f_text; // This is the empty string if no appends took place } -QMap AOApplication::get_chatmessage_colors() +QMap AOApplication::get_chatmessage_colors() { - QMap color_map = dr::get_default_color_map(); + QMap color_map = DR::get_default_color_map(); // File lookup order // 1. In the theme folder (gamemode-timeofday/main/default), look for @@ -338,7 +338,7 @@ QMap AOApplication::get_chatmessage_colors() QSettings color_settings(path, QSettings::IniFormat); - QMap color_replacement_map; + QMap color_replacement_map; for (QString &i_group : color_settings.childGroups()) { const QString lower_name = i_group.toLower(); @@ -361,9 +361,9 @@ QMap AOApplication::get_chatmessage_colors() } // replace the data in the map we will return - for (dr::Color &i_color : color_map.keys()) + for (DR::Color &i_color : color_map.keys()) { - dr::ColorInfo &color_info = color_map[i_color]; + DR::ColorInfo &color_info = color_map[i_color]; const QString lower_name = color_info.name.toLower(); if (!color_replacement_map.contains(lower_name)) continue; From 4100d4db7a0b9704194e3b2b3e064645c8be3b63 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 5 Mar 2021 11:05:13 +0100 Subject: [PATCH 229/842] Resolves #123 * Added [Ignore Suppression] option to all audio channels. * The system audio channel permanently has this rule in effect. --- include/aoconfig.h | 69 +-- include/aoconfigpanel.h | 5 +- include/draudioengine.h | 4 +- res/ui/config_panel.ui | 951 ++++++++++++++++++++++++++-------------- src/aoconfig.cpp | 146 ++++-- src/aoconfigpanel.cpp | 22 +- src/draudioengine.cpp | 9 +- 7 files changed, 794 insertions(+), 412 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index adbe864e0..f0994c048 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -13,38 +13,41 @@ class AOConfig : public QObject ~AOConfig(); // generic getters - QString get_string(QString p_name, QString p_default = nullptr); - bool get_bool(QString p_name, bool p_default = false); - int get_number(QString p_name, int p_default = 0); + QString get_string(QString p_name, QString p_default = nullptr) const; + bool get_bool(QString p_name, bool p_default = false) const; + int get_number(QString p_name, int p_default = 0) const; // getters - bool autosave(); - QString username(); - QString callwords(); - bool server_alerts_enabled(); - QString theme(); - QString gamemode(); - bool manual_gamemode_enabled(); - QString timeofday(); - bool manual_timeofday_enabled(); - bool always_pre_enabled(); - int chat_tick_interval(); - int log_max_lines(); - bool log_is_topdown_enabled(); - bool log_uses_newline_enabled(); - bool log_music_enabled(); - bool log_is_recording_enabled(); + bool autosave() const; + QString username() const; + QString callwords() const; + bool server_alerts_enabled() const; + QString theme() const; + QString gamemode() const; + bool manual_gamemode_enabled() const; + QString timeofday() const; + bool manual_timeofday_enabled() const; + bool always_pre_enabled() const; + int chat_tick_interval() const; + int log_max_lines() const; + bool log_is_topdown_enabled() const; + bool log_uses_newline_enabled() const; + bool log_music_enabled() const; + bool log_is_recording_enabled() const; // audio - std::optional favorite_device_driver(); - int master_volume(); - bool mute_background_audio(); - int system_volume(); - int effect_volume(); - int music_volume(); - int blip_volume(); - int blip_rate(); - bool blank_blips_enabled(); + std::optional favorite_device_driver() const; + int master_volume() const; + bool suppress_background_audio() const; + int system_volume() const; + int effect_volume() const; + bool effect_ignore_suppression() const; + int music_volume() const; + bool music_ignore_suppression() const; + int blip_volume() const; + bool blip_ignore_suppression() const; + int blip_rate() const; + bool blank_blips_enabled() const; // io public slots: @@ -68,15 +71,18 @@ public slots: void set_log_uses_newline(bool p_enabled); void set_log_music(bool p_enabled); void set_log_is_recording(bool p_enabled); - void set_mute_background_audio(bool p_enabled); + void set_suppress_background_audio(bool p_enabled); // audio void set_favorite_device_driver(QString p_device_driver); void set_master_volume(int p_number); void set_system_volume(int p_number); void set_effect_volume(int p_number); + void set_effect_ignore_suppression(bool p_enabled); void set_music_volume(int p_number); + void set_music_ignore_suppression(bool p_enabled); void set_blip_volume(int p_number); + void set_blip_ignore_suppression(bool p_enabled); void set_blip_rate(int p_number); void set_blank_blips(bool p_enabled); @@ -102,11 +108,14 @@ public slots: // audio void favorite_device_changed(QString); void master_volume_changed(int); - void mute_background_audio_changed(bool); + void suppress_background_audio_changed(bool); void system_volume_changed(int); void effect_volume_changed(int); + void effect_ignore_suppression_changed(bool); void music_volume_changed(int); + void music_ignore_suppression_changed(bool); void blip_volume_changed(int); + void blip_ignore_suppression_changed(bool); void blip_rate_changed(int); void blank_blips_changed(bool); }; diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index bd55c7f35..f9d37803e 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -96,14 +96,17 @@ private slots: QCheckBox *w_favorite_device = nullptr; QSlider *w_master = nullptr; QLabel *w_master_value = nullptr; - QCheckBox *w_mute_background_audio = nullptr; + QCheckBox *w_suppress_background_audio = nullptr; QSlider *w_system = nullptr; QLabel *w_system_value = nullptr; QSlider *w_effect = nullptr; + QCheckBox *w_effect_ignore_suppression = nullptr; QLabel *w_effect_value = nullptr; QSlider *w_music = nullptr; + QCheckBox *w_music_ignore_suppression = nullptr; QLabel *w_music_value = nullptr; QSlider *w_blip = nullptr; + QCheckBox *w_blip_ignore_suppression = nullptr; QLabel *w_blip_value = nullptr; QSpinBox *w_blip_rate = nullptr; QCheckBox *w_blank_blips = nullptr; diff --git a/include/draudioengine.h b/include/draudioengine.h index f1c5b83cd..1c9990009 100644 --- a/include/draudioengine.h +++ b/include/draudioengine.h @@ -20,7 +20,7 @@ class DRAudioEngine : public QObject // option get static bool is_option(DRAudio::Option p_option); static bool is_suppressed(); - static bool is_mute_background_audio(); + static bool is_suppress_background_audio(); public slots: void set_device(DRAudioDevice p_device); @@ -31,7 +31,7 @@ public slots: // option set void set_option(DRAudio::Option p_option, bool p_enabled); void set_suppressed(bool p_enabled); - void set_mute_background_audio(bool p_enabled); + void set_suppress_background_audio(bool p_enabled); signals: void device_changed(DRAudioDevice); diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index dcdc3f853..7e4663003 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -7,7 +7,7 @@ 0 0 487 - 361 + 300 @@ -29,7 +29,7 @@ - 0 + 3 @@ -439,340 +439,641 @@ Audio - - - - - Master: - - - - - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - <html><head/><body><p>If enabled, audio will be temporarily muted when the client is not in focus.<br/><br/>This does not apply to system sounds.</p></body></html> + + + + + QScrollArea#scrollArea { background-color: transparent }; - - Mute Background Audio + + QFrame::NoFrame - - - - - - Qt::Horizontal + + QFrame::Raised - - - - - - System: + + 0 - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - Music: - - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - Effects: + + true - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - Blips: + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - + + + + 0 + -80 + 433 + 637 + + + + + 0 + 0 + + + + QWidget#scrollAreaWidgetContents { background-color: transparent }; + + + + 0 - - 0% + + 0 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 0 - - - - - - - - Blip rate: - + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + Master + + + + + + + Qt::Horizontal + + + + + + + + + + + Device: + + + + + + + + + + Volume: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Options + + + + + + + 0 + 0 + + + + <html><head/><body><p>If enabled, audio will be temporarily suppressed when the client is not in focus.<br/><br/>This option is never applied to the System audio channel.</p></body></html> + + + Suppress Background Audio + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + System + + + + + + + Qt::Horizontal + + + + + + + + + + + Volume: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Options + + + + + + false + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + Ignore Suppression + + + true + + + true + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + Music + + + + + + + Qt::Horizontal + + + + + + + + + + + Volume: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Options + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + Ignore Suppression + + + + + + + + + + + + + 0 + 0 + + + + Effects + + + + + + + Qt::Horizontal + + + + + + + + + + + Volume + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Options + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + Ignore Suppression + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + Blips + + + + + + + Qt::Horizontal + + + + + + + + + + + Volume: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Options + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + Ignore Suppression + + + + + + + + + Blip Rate: + + + + + + + + + 1 + + + 1000000000 + + + 1000000000 + + + + + + + Blanks + + + + + + + + + + + + - - - - 1 - - - 1000000000 - - - 1000000000 - - - - - - - Blank blips: - - - - - - - - 0 - 0 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 333d0c821..93730cb88 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -4,13 +4,13 @@ // qt #include +#include #include #include #include +#include #include -#include - /*! We have to suffer through a lot of boilerplate code but hey, when has ao2 ever cared? @@ -21,7 +21,7 @@ class AOConfigPrivate : public QObject Q_OBJECT public: - AOConfigPrivate(QObject *p_parent = nullptr); + AOConfigPrivate(); ~AOConfigPrivate(); // setters @@ -64,11 +64,14 @@ private slots: // audio std::optional favorite_device_driver; int master_volume; - bool mute_background_audio; - int effect_volume; + bool suppress_background_audio; int system_volume; + int effect_volume; + bool effect_ignore_suppression; int music_volume; + bool music_ignore_suppression; int blip_volume; + bool blip_ignore_suppression; int blip_rate; bool blank_blips; @@ -76,8 +79,8 @@ private slots: DRAudioEngine *audio_engine = nullptr; }; -AOConfigPrivate::AOConfigPrivate(QObject *p_parent) - : QObject(p_parent), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat), +AOConfigPrivate::AOConfigPrivate() + : QObject(nullptr), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat), audio_engine(new DRAudioEngine(this)) { Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); @@ -121,12 +124,15 @@ void AOConfigPrivate::read_file() if (cfg.contains("favorite_device_driver")) favorite_device_driver = cfg.value("favorite_device_driver").toString(); - mute_background_audio = cfg.value("mute_background_audio").toBool(); + suppress_background_audio = cfg.value("suppress_background_audio").toBool(); master_volume = cfg.value("default_master", 50).toInt(); system_volume = cfg.value("default_system", 50).toInt(); effect_volume = cfg.value("default_sfx", 50).toInt(); + effect_ignore_suppression = cfg.value("effect_ignore_suppression", false).toBool(); music_volume = cfg.value("default_music", 50).toInt(); + music_ignore_suppression = cfg.value("music_ignore_suppression", false).toBool(); blip_volume = cfg.value("default_blip", 50).toInt(); + blip_ignore_suppression = cfg.value("blip_ignore_suppression", false).toBool(); blip_rate = cfg.value("blip_rate", 1000000000).toInt(); blank_blips = cfg.value("blank_blips").toBool(); @@ -134,8 +140,11 @@ void AOConfigPrivate::read_file() audio_engine->set_volume(master_volume); audio_engine->get_family(DRAudio::Family::FSystem)->set_volume(system_volume); audio_engine->get_family(DRAudio::Family::FEffect)->set_volume(effect_volume); + audio_engine->get_family(DRAudio::Family::FEffect)->set_ignore_suppression(effect_ignore_suppression); audio_engine->get_family(DRAudio::Family::FMusic)->set_volume(music_volume); + audio_engine->get_family(DRAudio::Family::FMusic)->set_ignore_suppression(effect_ignore_suppression); audio_engine->get_family(DRAudio::Family::FBlip)->set_volume(blip_volume); + audio_engine->get_family(DRAudio::Family::FBlip)->set_ignore_suppression(effect_ignore_suppression); // audio device update_favorite_device(); @@ -164,12 +173,15 @@ void AOConfigPrivate::save_file() if (favorite_device_driver.has_value()) cfg.setValue("favorite_device_driver", favorite_device_driver.value()); - cfg.setValue("mute_background_audio", mute_background_audio); + cfg.setValue("suppress_background_audio", suppress_background_audio); cfg.setValue("default_master", master_volume); - cfg.setValue("default_sfx", effect_volume); cfg.setValue("default_system", system_volume); + cfg.setValue("default_sfx", effect_volume); + cfg.setValue("effect_ignore_suppression", effect_ignore_suppression); cfg.setValue("default_music", music_volume); + cfg.setValue("music_ignore_suppression", music_ignore_suppression); cfg.setValue("default_blip", blip_volume); + cfg.setValue("blip_ignore_suppression", blip_ignore_suppression); cfg.setValue("blip_rate", blip_rate); cfg.setValue("blank_blips", blank_blips); cfg.sync(); @@ -192,7 +204,7 @@ void AOConfigPrivate::update_favorite_device() void AOConfigPrivate::on_application_state_changed(Qt::ApplicationState p_state) { - audio_engine->set_suppressed(mute_background_audio && p_state != Qt::ApplicationActive); + audio_engine->set_suppressed(suppress_background_audio && p_state != Qt::ApplicationActive); } // AOConfig //////////////////////////////////////////////////////////////////// @@ -202,7 +214,7 @@ void AOConfigPrivate::on_application_state_changed(Qt::ApplicationState p_state) */ namespace { -static QPointer d; +static QSharedPointer d; } AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) @@ -211,7 +223,7 @@ AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) if (d == nullptr) { Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); - d = new AOConfigPrivate(qApp); + d = QSharedPointer(new AOConfigPrivate); } // ao2 is the pinnacle of thread security @@ -224,140 +236,155 @@ AOConfig::~AOConfig() d->children.removeAll(this); } -QString AOConfig::get_string(QString p_name, QString p_default) +QString AOConfig::get_string(QString p_name, QString p_default) const { return d->cfg.value(p_name, p_default).toString(); } -bool AOConfig::get_bool(QString p_name, bool p_default) +bool AOConfig::get_bool(QString p_name, bool p_default) const { return d->cfg.value(p_name, p_default).toBool(); } -int AOConfig::get_number(QString p_name, int p_default) +int AOConfig::get_number(QString p_name, int p_default) const { return d->cfg.value(p_name, p_default).toInt(); } -bool AOConfig::autosave() +bool AOConfig::autosave() const { return d->autosave; } -QString AOConfig::username() +QString AOConfig::username() const { return d->username; } -QString AOConfig::callwords() +QString AOConfig::callwords() const { return d->callwords; } -QString AOConfig::theme() +QString AOConfig::theme() const { return d->theme; } -QString AOConfig::gamemode() +QString AOConfig::gamemode() const { return d->gamemode; } -bool AOConfig::manual_gamemode_enabled() +bool AOConfig::manual_gamemode_enabled() const { return d->manual_gamemode; } -QString AOConfig::timeofday() +QString AOConfig::timeofday() const { return d->timeofday; } -bool AOConfig::manual_timeofday_enabled() +bool AOConfig::manual_timeofday_enabled() const { return d->manual_timeofday; } -bool AOConfig::server_alerts_enabled() +bool AOConfig::server_alerts_enabled() const { return d->server_alerts; } -bool AOConfig::always_pre_enabled() +bool AOConfig::always_pre_enabled() const { return d->always_pre; } -int AOConfig::chat_tick_interval() +int AOConfig::chat_tick_interval() const { return d->chat_tick_interval; } -int AOConfig::log_max_lines() +int AOConfig::log_max_lines() const { return d->log_max_lines; } -bool AOConfig::log_is_topdown_enabled() +bool AOConfig::log_is_topdown_enabled() const { return d->log_is_topdown; } -bool AOConfig::log_uses_newline_enabled() +bool AOConfig::log_uses_newline_enabled() const { return d->log_uses_newline; } -bool AOConfig::log_music_enabled() +bool AOConfig::log_music_enabled() const { return d->log_music; } -bool AOConfig::log_is_recording_enabled() +bool AOConfig::log_is_recording_enabled() const { return d->log_is_recording; } -std::optional AOConfig::favorite_device_driver() +std::optional AOConfig::favorite_device_driver() const { return d->favorite_device_driver; } -int AOConfig::master_volume() +int AOConfig::master_volume() const { return d->master_volume; } -bool AOConfig::mute_background_audio() +bool AOConfig::suppress_background_audio() const { - return d->mute_background_audio; + return d->suppress_background_audio; } -int AOConfig::system_volume() +int AOConfig::system_volume() const { return d->system_volume; } -int AOConfig::effect_volume() +int AOConfig::effect_volume() const { return d->effect_volume; } -int AOConfig::music_volume() +bool AOConfig::effect_ignore_suppression() const +{ + return d->effect_ignore_suppression; +} + +int AOConfig::music_volume() const { return d->music_volume; } -int AOConfig::blip_volume() +bool AOConfig::music_ignore_suppression() const +{ + return d->music_ignore_suppression; +} + +int AOConfig::blip_volume() const { return d->blip_volume; } -int AOConfig::blip_rate() +bool AOConfig::blip_ignore_suppression() const +{ + return d->blip_ignore_suppression; +} + +int AOConfig::blip_rate() const { return d->blip_rate; } -bool AOConfig::blank_blips_enabled() +bool AOConfig::blank_blips_enabled() const { return d->blank_blips; } @@ -504,12 +531,12 @@ void AOConfig::set_master_volume(int p_number) d->invoke_signal("master_volume_changed", Q_ARG(int, p_number)); } -void AOConfig::set_mute_background_audio(bool p_enabled) +void AOConfig::set_suppress_background_audio(bool p_enabled) { - if (d->mute_background_audio == p_enabled) + if (d->suppress_background_audio == p_enabled) return; - d->mute_background_audio = p_enabled; - d->invoke_signal("mute_background_audio_changed", Q_ARG(bool, p_enabled)); + d->suppress_background_audio = p_enabled; + d->invoke_signal("suppress_background_audio_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_favorite_device_driver(QString p_device_driver) @@ -539,6 +566,15 @@ void AOConfig::set_effect_volume(int p_number) d->invoke_signal("effect_volume_changed", Q_ARG(int, p_number)); } +void AOConfig::set_effect_ignore_suppression(bool p_enabled) +{ + if (d->music_ignore_suppression == p_enabled) + return; + d->music_ignore_suppression = p_enabled; + d->audio_engine->get_family(DRAudio::Family::FMusic)->set_ignore_suppression(p_enabled); + d->invoke_signal("music_ignore_suppression_changed", Q_ARG(bool, p_enabled)); +} + void AOConfig::set_music_volume(int p_number) { if (d->music_volume == p_number) @@ -548,6 +584,15 @@ void AOConfig::set_music_volume(int p_number) d->invoke_signal("music_volume_changed", Q_ARG(int, p_number)); } +void AOConfig::set_music_ignore_suppression(bool p_enabled) +{ + if (d->music_ignore_suppression == p_enabled) + return; + d->music_ignore_suppression = p_enabled; + d->audio_engine->get_family(DRAudio::Family::FMusic)->set_ignore_suppression(p_enabled); + d->invoke_signal("music_ignore_suppression_changed", Q_ARG(bool, p_enabled)); +} + void AOConfig::set_blip_volume(int p_number) { if (d->blip_volume == p_number) @@ -557,6 +602,15 @@ void AOConfig::set_blip_volume(int p_number) d->invoke_signal("blip_volume_changed", Q_ARG(int, p_number)); } +void AOConfig::set_blip_ignore_suppression(bool p_enabled) +{ + if (d->blip_ignore_suppression == p_enabled) + return; + d->blip_ignore_suppression = p_enabled; + d->audio_engine->get_family(DRAudio::Family::FBlip)->set_ignore_suppression(p_enabled); + d->invoke_signal("blip_ignore_suppression_changed", Q_ARG(bool, p_enabled)); +} + void AOConfig::set_blip_rate(int p_number) { if (d->blip_rate == p_number) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 07d849c84..60bc0fbef 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -56,14 +56,17 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_favorite_device = AO_GUI_WIDGET(QCheckBox, "favorite_device"); w_master = AO_GUI_WIDGET(QSlider, "master"); w_master_value = AO_GUI_WIDGET(QLabel, "master_value"); - w_mute_background_audio = AO_GUI_WIDGET(QCheckBox, "mute_background_audio"); + w_suppress_background_audio = AO_GUI_WIDGET(QCheckBox, "suppress_background_audio"); w_system = AO_GUI_WIDGET(QSlider, "system"); w_system_value = AO_GUI_WIDGET(QLabel, "system_value"); w_effect = AO_GUI_WIDGET(QSlider, "effect"); + w_effect_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "effect_ignore_suppression"); w_effect_value = AO_GUI_WIDGET(QLabel, "effect_value"); w_music = AO_GUI_WIDGET(QSlider, "music"); + w_music_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "music_ignore_suppression"); w_music_value = AO_GUI_WIDGET(QLabel, "music_value"); w_blip = AO_GUI_WIDGET(QSlider, "blip"); + w_blip_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "blip_ignore_suppression"); w_blip_value = AO_GUI_WIDGET(QLabel, "blip_value"); w_blip_rate = AO_GUI_WIDGET(QSpinBox, "blip_rate"); w_blank_blips = AO_GUI_WIDGET(QCheckBox, "blank_blips"); @@ -91,11 +94,16 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, SLOT(setChecked(bool))); connect(m_config, SIGNAL(master_volume_changed(int)), w_master, SLOT(setValue(int))); - connect(m_config, SIGNAL(mute_background_audio_changed(bool)), w_mute_background_audio, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(suppress_background_audio_changed(bool)), w_suppress_background_audio, + SLOT(setChecked(bool))); connect(m_config, SIGNAL(system_volume_changed(int)), w_system, SLOT(setValue(int))); connect(m_config, SIGNAL(effect_volume_changed(int)), w_effect, SLOT(setValue(int))); + connect(m_config, SIGNAL(effect_ignore_suppression_changed(bool)), w_effect_ignore_suppression, + SLOT(setChecked(bool))); connect(m_config, SIGNAL(music_volume_changed(int)), w_music, SLOT(setValue(int))); + connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), w_music_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(blip_volume_changed(int)), w_blip, SLOT(setValue(int))); + connect(m_config, SIGNAL(blip_ignore_suppression_changed(bool)), w_blip_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, SLOT(setValue(int))); connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, SLOT(setChecked(bool))); @@ -125,7 +133,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_log_uses_newline, SIGNAL(toggled(bool)), m_config, SLOT(set_log_uses_newline(bool))); connect(w_log_music, SIGNAL(toggled(bool)), m_config, SLOT(set_log_music(bool))); connect(w_log_is_recording, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_recording(bool))); - connect(w_mute_background_audio, SIGNAL(toggled(bool)), m_config, SLOT(set_mute_background_audio(bool))); + connect(w_suppress_background_audio, SIGNAL(toggled(bool)), m_config, SLOT(set_suppress_background_audio(bool))); connect(w_device, SIGNAL(currentIndexChanged(int)), this, SLOT(on_device_current_index_changed(int))); connect(w_master, SIGNAL(valueChanged(int)), m_config, SLOT(set_master_volume(int))); connect(w_master, SIGNAL(valueChanged(int)), this, SLOT(on_master_value_changed(int))); @@ -133,10 +141,13 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_system, SIGNAL(valueChanged(int)), this, SLOT(on_system_value_changed(int))); connect(w_effect, SIGNAL(valueChanged(int)), m_config, SLOT(set_effect_volume(int))); connect(w_effect, SIGNAL(valueChanged(int)), this, SLOT(on_effect_value_changed(int))); + connect(w_effect_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_effect_ignore_suppression(bool))); connect(w_music, SIGNAL(valueChanged(int)), m_config, SLOT(set_music_volume(int))); connect(w_music, SIGNAL(valueChanged(int)), this, SLOT(on_music_value_changed(int))); + connect(w_music_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_music_ignore_suppression(bool))); connect(w_blip, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_volume(int))); connect(w_blip, SIGNAL(valueChanged(int)), this, SLOT(on_blip_value_changed(int))); + connect(w_blip_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_blip_ignore_suppression(bool))); connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_rate(int))); connect(w_blank_blips, SIGNAL(toggled(bool)), m_config, SLOT(set_blank_blips(bool))); @@ -170,11 +181,14 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // audio update_audio_device_list(); w_master->setValue(m_config->master_volume()); - w_mute_background_audio->setChecked(m_config->mute_background_audio()); + w_suppress_background_audio->setChecked(m_config->suppress_background_audio()); w_system->setValue(m_config->system_volume()); w_effect->setValue(m_config->effect_volume()); + w_effect_ignore_suppression->setChecked(m_config->effect_ignore_suppression()); w_music->setValue(m_config->music_volume()); + w_music_ignore_suppression->setChecked(m_config->music_ignore_suppression()); w_blip->setValue(m_config->blip_volume()); + w_blip_ignore_suppression->setChecked(m_config->blip_ignore_suppression()); w_blip_rate->setValue(m_config->blip_rate()); w_blank_blips->setChecked(m_config->blank_blips_enabled()); diff --git a/src/draudioengine.cpp b/src/draudioengine.cpp index 92116311f..d721fd0f7 100644 --- a/src/draudioengine.cpp +++ b/src/draudioengine.cpp @@ -108,10 +108,11 @@ bool DRAudioEngine::is_suppressed() return d->options.testFlag(DRAudio::OSuppressed); } -bool DRAudioEngine::is_mute_background_audio() +bool DRAudioEngine::is_suppress_background_audio() { - return is_option(DRAudio::OEngineMuteBackgroundAudio); + return is_option(DRAudio::OEngineSuppressBackgroundAudio); } + void DRAudioEngine::set_device(DRAudioDevice p_device) { if (!d->device_map.contains(p_device.get_driver())) @@ -171,9 +172,9 @@ void DRAudioEngine::set_suppressed(bool p_enabled) set_option(DRAudio::OSuppressed, p_enabled); } -void DRAudioEngine::set_mute_background_audio(bool p_enabled) +void DRAudioEngine::set_suppress_background_audio(bool p_enabled) { - set_option(DRAudio::OEngineMuteBackgroundAudio, p_enabled); + set_option(DRAudio::OEngineSuppressBackgroundAudio, p_enabled); } void DRAudioEngine::check_stream_error() From a5f5dad383bfcaf824fa645dba556031ad95ab3e Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 5 Mar 2021 12:40:22 +0100 Subject: [PATCH 230/842] Header didn't save --- include/draudio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/draudio.h b/include/draudio.h index 7d70371bb..ec19fc931 100644 --- a/include/draudio.h +++ b/include/draudio.h @@ -23,7 +23,7 @@ enum Option /** * If enabled, the engine will suppress all audio when the application is inactive. */ - OEngineMuteBackgroundAudio = 0x4, + OEngineSuppressBackgroundAudio = 0x4, }; Q_DECLARE_FLAGS(Options, Option) From 74432422e7c783195806bbd36215037cf3b63a7e Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 5 Mar 2021 16:25:58 +0100 Subject: [PATCH 231/842] Reworked the options for Suppress Background Audio --- include/aoconfigpanel.h | 3 +- res/ui/config_panel.ui | 952 +++++++++++++++------------------------- src/aoconfigpanel.cpp | 2 +- 3 files changed, 358 insertions(+), 599 deletions(-) diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index f9d37803e..203aa5bd3 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -96,7 +97,7 @@ private slots: QCheckBox *w_favorite_device = nullptr; QSlider *w_master = nullptr; QLabel *w_master_value = nullptr; - QCheckBox *w_suppress_background_audio = nullptr; + QGroupBox *w_suppress_background_audio = nullptr; QSlider *w_system = nullptr; QLabel *w_system_value = nullptr; QSlider *w_effect = nullptr; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 7e4663003..4312b736e 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -7,7 +7,7 @@ 0 0 487 - 300 + 387 @@ -439,640 +439,398 @@ Audio - + - - - QScrollArea#scrollArea { background-color: transparent }; - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - true - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - 0 - -80 - 433 - 637 - - - - - 0 - 0 - - - - QWidget#scrollAreaWidgetContents { background-color: transparent }; - - - - 0 - - - 0 + + + + + Device: - - 0 + + + + + + + + + Master: + + + + - - - - - - 0 - 0 - - - - - 0 - 20 - - - - Master - - - - - - - Qt::Horizontal - - - - - - - - - - - Device: - - - - - - - - - - Volume: - - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - Options + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal - - - - - - 0 - 0 - - - - <html><head/><body><p>If enabled, audio will be temporarily suppressed when the client is not in focus.<br/><br/>This option is never applied to the System audio channel.</p></body></html> - - - Suppress Background Audio - - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - System - - - - - - - Qt::Horizontal - - - - - - - - - - - Volume: - - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + System: + + + + + - - - Options + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal - - - - - false - - - <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> - - - Ignore Suppression - - - true - - - true - - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - Music - - - - - - - Qt::Horizontal - - - - + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Music: + + + + + - - - - - Volume: - - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + - - - Options + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> - - - Ignore Suppression - - - - + + + + + + Effects: + + + + + - - - - - - 0 - 0 - - - - Effects - - - - - - - Qt::Horizontal - - - - + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + - - - - - Volume - - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Blips + + + + + - - - Options + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal - - - - - <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> - - - Ignore Suppression - - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - Blips - - - - - - - Qt::Horizontal - - - - + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Suppress Background Audio + + + true + + + false + + + + + + false + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + System + + + true + + + true + + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + Music + + + <html><head/><body><p>If enabled, audio will be temporarily suppressed when the client is not in focus.<br/><br/>This option is never applied to the System audio channel.</p></body></html> + + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + Effects + + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + Blips + + + + + + + + + + + + Blip Rate: + + + + + - - - - - Volume: - - - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - + + + 1 + + + 1000000000 + + + 1000000000 + + - - - Options + + + Blanks - - - - - <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> - - - Ignore Suppression - - - - - - - - - Blip Rate: - - - - - - - - - 1 - - - 1000000000 - - - 1000000000 - - - - - - - Blanks - - - - - - - - - - + + + + + + + Qt::Vertical + + + + 20 + 40 + + + diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 60bc0fbef..06c6c06ad 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -56,7 +56,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_favorite_device = AO_GUI_WIDGET(QCheckBox, "favorite_device"); w_master = AO_GUI_WIDGET(QSlider, "master"); w_master_value = AO_GUI_WIDGET(QLabel, "master_value"); - w_suppress_background_audio = AO_GUI_WIDGET(QCheckBox, "suppress_background_audio"); + w_suppress_background_audio = AO_GUI_WIDGET(QGroupBox, "suppress_background_audio"); w_system = AO_GUI_WIDGET(QSlider, "system"); w_system_value = AO_GUI_WIDGET(QLabel, "system_value"); w_effect = AO_GUI_WIDGET(QSlider, "effect"); From cf4feb220ee6cbf8bc3d96e755092275344d3e51 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 5 Mar 2021 16:41:08 +0100 Subject: [PATCH 232/842] Small fix to tooltips --- res/ui/config_panel.ui | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 4312b736e..5c08498f5 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -719,6 +719,9 @@ + + <html><head/><body><p>If enabled, audio will be temporarily suppressed when the client is not in focus.<br/><br/>This option is never applied to the System audio channel.</p></body></html> + Suppress Background Audio @@ -751,14 +754,11 @@ - <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<span style=" color:#0000ff;">Suppress Background Audio</span>] is enabled and activated.</p></body></html> Music - - <html><head/><body><p>If enabled, audio will be temporarily suppressed when the client is not in focus.<br/><br/>This option is never applied to the System audio channel.</p></body></html> - From bed28a5d08d9e1b9df57912bfe3e11d19a4542a1 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 5 Mar 2021 16:49:18 +0100 Subject: [PATCH 233/842] Typo fix --- res/ui/config_panel.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 5c08498f5..9eecbbc1c 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -669,7 +669,7 @@ - Blips + Blips: From 2f5be7c050e5c0722506e37b3d15ea4ebce69bb5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 5 Mar 2021 19:04:47 +0100 Subject: [PATCH 234/842] Resolves #125 and #126 * Added option to use timestamps for game chat, filter out music switches and filter empty messages * Added baseline timestamp for chat --- include/aoconfig.h | 18 ++-- include/aoconfigpanel.h | 6 +- include/courtroom.h | 8 +- include/datatypes.h | 62 ++++++++++--- res/ui/config_panel.ui | 176 +++++++++++++++++++++++------------- src/aoapplication.cpp | 4 +- src/aoconfig.cpp | 77 +++++++++++----- src/aoconfigpanel.cpp | 33 +++++-- src/aotextarea.cpp | 6 +- src/courtroom.cpp | 123 ++++++++++++------------- src/courtroom_widgets.cpp | 5 +- src/text_file_functions.cpp | 2 +- 12 files changed, 328 insertions(+), 192 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index f0994c048..6a975c4e2 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -30,9 +30,11 @@ class AOConfig : public QObject bool always_pre_enabled() const; int chat_tick_interval() const; int log_max_lines() const; + bool log_display_timestamp_enabled() const; + bool log_display_empty_messages_enabled() const; bool log_is_topdown_enabled() const; - bool log_uses_newline_enabled() const; - bool log_music_enabled() const; + bool log_format_use_newline_enabled() const; + bool log_display_music_switch_enabled() const; bool log_is_recording_enabled() const; // audio @@ -67,9 +69,11 @@ public slots: void set_manual_timeofday(bool p_enabled); void set_chat_tick_interval(int p_number); void set_log_max_lines(int p_number); + void set_log_display_timestamp(bool p_enabled); + void set_log_display_empty_messages(bool p_enabled); void set_log_is_topdown(bool p_enabled); - void set_log_uses_newline(bool p_enabled); - void set_log_music(bool p_enabled); + void set_log_format_use_newline(bool p_enabled); + void set_log_display_music_switch(bool p_enabled); void set_log_is_recording(bool p_enabled); void set_suppress_background_audio(bool p_enabled); @@ -100,9 +104,11 @@ public slots: void always_pre_changed(bool); void chat_tick_interval_changed(int); void log_max_lines_changed(int); + void log_display_timestamp_changed(bool); + void log_display_empty_messages_changed(bool); void log_is_topdown_changed(bool); - void log_uses_newline_changed(bool); - void log_music_changed(bool); + void log_format_use_newline_changed(bool); + void log_display_music_switch_changed(bool); void log_is_recording_changed(bool); // audio diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 203aa5bd3..846ec4862 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -86,10 +86,12 @@ private slots: // IC Chatlog QSpinBox *w_log_max_lines = nullptr; - QCheckBox *w_log_uses_newline = nullptr; + QCheckBox *w_log_display_timestamp = nullptr; + QCheckBox *w_log_format_use_newline = nullptr; + QCheckBox *w_log_display_empty_messages = nullptr; + QCheckBox *w_log_display_music_switch = nullptr; QRadioButton *w_log_orientation_top_down = nullptr; QRadioButton *w_log_orientation_bottom_up = nullptr; - QCheckBox *w_log_music = nullptr; QCheckBox *w_log_is_recording = nullptr; // audio diff --git a/include/courtroom.h b/include/courtroom.h index 0d3f73a79..92c254c65 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -437,10 +438,6 @@ class Courtroom : public QMainWindow int emote_rows = 2; int max_emotes_on_page = 10; - int m_chatlog_limit = 200; - bool m_chatlog_newline = false; - bool m_chatlog_scrolldown = false; - // inmchatlog_changed; QVector local_evidence_list; @@ -498,7 +495,8 @@ class Courtroom : public QMainWindow QVector ui_timers; QTextEdit *ui_ic_chatlog = nullptr; - record_type_array m_ic_records; + QList m_ic_record_list; + QQueue m_ic_record_queue; AOTextArea *ui_server_chatlog = nullptr; diff --git a/include/datatypes.h b/include/datatypes.h index 196e46df7..22b77ec8e 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -1,30 +1,66 @@ #ifndef DATATYPES_H #define DATATYPES_H +#include #include +#include #include #include -struct record_type +namespace DR { - QString name; - QString line; - QString color; - bool system = false; - bool music = false; +class ChatRecord +{ +public: + using list = QVector; + + ChatRecord(QString p_name, QString p_message) : name(p_name), message(p_message) + {} - record_type() = default; - record_type(QString p_name, QString p_line, QString p_color, bool p_is_system, bool p_is_music) - : name(p_name), line(p_line), system(p_is_system), music(p_is_music) + QDateTime get_timestamp() const { - Q_UNUSED(p_color); + return timestamp; + } + QString get_name() const + { + return name; + } + QString get_message() const + { + return message; + } + bool is_system() const + { + return system; + } + bool is_music() const + { + return music; } -}; -typedef std::shared_ptr record_type_ptr; + // set + void set_system(bool p_enabled) + { + if (system == p_enabled) + return; + system = p_enabled; + } + void set_music(bool p_enabled) + { + if (music == p_enabled) + return; + music = p_enabled; + } -typedef QVector record_type_array; +private: + QDateTime timestamp = QDateTime::currentDateTime(); + QString name; + QString message; + bool system = false; + bool music = false; +}; +} // namespace DR struct server_type { diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 9eecbbc1c..f36f1902c 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 3 + 2 @@ -317,61 +317,105 @@ Log - - - - - Max length: - - - - - - - lines - - - 10000 - - - 200 - - - - - + + + - - - - 0 - 0 - - - - Extra newlines are - + - Newline + Max length: - - - - 0 - 0 - + + + lines - - Music switch + + 20 + + + 20000 + + + 200 - + + + + Options + + + + + + + 0 + 0 + + + + Extra newlines are + + + Display Timestamp + + + + + + + + 0 + 0 + + + + Extra newlines are + + + Format use newline + + + + + + + + 0 + 0 + + + + Extra newlines are + + + Display empty messages + + + + + + + + 0 + 0 + + + + Display music switch + + + + + + + @@ -400,27 +444,31 @@ - - - - Save to disk: - - - - - - - - 0 - 0 - - - - - - + + + + + + Save to disk: + + + + + + + + 0 + 0 + + + + + + + + - + Qt::Vertical diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index e02e997bf..e3d3fc5fd 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -238,7 +238,7 @@ int AOApplication::get_chat_tick_interval() bool AOApplication::get_chatlog_newline() { - return config->log_uses_newline_enabled(); + return config->log_format_use_newline_enabled(); } bool AOApplication::get_enable_logging_enabled() @@ -248,7 +248,7 @@ bool AOApplication::get_enable_logging_enabled() bool AOApplication::get_music_change_log_enabled() { - return config->log_music_enabled(); + return config->log_display_music_switch_enabled(); } void AOApplication::add_favorite_server(int p_server) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 93730cb88..b0bc83ae3 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -56,9 +56,11 @@ private slots: bool always_pre; int chat_tick_interval; int log_max_lines; + bool log_display_timestamp; + bool log_display_empty_messages; bool log_is_topdown; - bool log_uses_newline; - bool log_music; + bool log_format_use_newline; + bool log_display_music_switch; bool log_is_recording; // audio @@ -117,8 +119,10 @@ void AOConfigPrivate::read_file() chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); log_max_lines = cfg.value("chatlog_limit", 200).toInt(); log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); - log_uses_newline = cfg.value("chatlog_newline", false).toBool(); - log_music = cfg.value("music_change_log", true).toBool(); + log_display_timestamp = cfg.value("chatlog_display_timestamp", false).toBool(); + log_display_empty_messages = cfg.value("log_display_empty_messages", false).toBool(); + log_format_use_newline = cfg.value("chatlog_newline", false).toBool(); + log_display_music_switch = cfg.value("music_change_log", true).toBool(); log_is_recording = cfg.value("enable_logging", true).toBool(); if (cfg.contains("favorite_device_driver")) @@ -164,9 +168,11 @@ void AOConfigPrivate::save_file() cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("chatlog_limit", log_max_lines); + cfg.setValue("chatlog_display_timestamp", log_display_timestamp); + cfg.setValue("chatlog_newline", log_format_use_newline); + cfg.setValue("chatlog_display_empty_messages", log_display_empty_messages); + cfg.setValue("music_change_log", log_display_music_switch); cfg.setValue("chatlog_scrolldown", log_is_topdown); - cfg.setValue("chatlog_newline", log_uses_newline); - cfg.setValue("music_change_log", log_music); cfg.setValue("enable_logging", log_is_recording); // audio @@ -305,24 +311,35 @@ int AOConfig::chat_tick_interval() const { return d->chat_tick_interval; } + int AOConfig::log_max_lines() const { return d->log_max_lines; } +bool AOConfig::log_display_timestamp_enabled() const +{ + return d->log_display_timestamp; +} + +bool AOConfig::log_display_empty_messages_enabled() const +{ + return d->log_display_empty_messages; +} + bool AOConfig::log_is_topdown_enabled() const { return d->log_is_topdown; } -bool AOConfig::log_uses_newline_enabled() const +bool AOConfig::log_format_use_newline_enabled() const { - return d->log_uses_newline; + return d->log_format_use_newline; } -bool AOConfig::log_music_enabled() const +bool AOConfig::log_display_music_switch_enabled() const { - return d->log_music; + return d->log_display_music_switch; } bool AOConfig::log_is_recording_enabled() const @@ -490,28 +507,44 @@ void AOConfig::set_log_max_lines(int p_number) d->invoke_signal("log_max_lines_changed", Q_ARG(int, p_number)); } -void AOConfig::set_log_is_topdown(bool p_enabled) +void AOConfig::set_log_display_timestamp(bool p_enabled) { - if (d->log_is_topdown == p_enabled) + if (d->log_display_timestamp == p_enabled) return; - d->log_is_topdown = p_enabled; - d->invoke_signal("log_is_topdown_changed", Q_ARG(bool, p_enabled)); + d->log_display_timestamp = p_enabled; + d->invoke_signal("log_display_timestamp_changed", Q_ARG(bool, p_enabled)); } -void AOConfig::set_log_uses_newline(bool p_enabled) +void AOConfig::set_log_display_empty_messages(bool p_enabled) { - if (d->log_uses_newline == p_enabled) + if (d->log_display_empty_messages == p_enabled) return; - d->log_uses_newline = p_enabled; - d->invoke_signal("log_uses_newline_changed", Q_ARG(bool, p_enabled)); + d->log_display_empty_messages = p_enabled; + d->invoke_signal("log_display_empty_messages_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_log_format_use_newline(bool p_enabled) +{ + if (d->log_format_use_newline == p_enabled) + return; + d->log_format_use_newline = p_enabled; + d->invoke_signal("log_format_use_newline_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_log_is_topdown(bool p_enabled) +{ + if (d->log_is_topdown == p_enabled) + return; + d->log_is_topdown = p_enabled; + d->invoke_signal("log_is_topdown_changed", Q_ARG(bool, p_enabled)); } -void AOConfig::set_log_music(bool p_enabled) +void AOConfig::set_log_display_music_switch(bool p_enabled) { - if (d->log_music == p_enabled) + if (d->log_display_music_switch == p_enabled) return; - d->log_music = p_enabled; - d->invoke_signal("log_music_changed", Q_ARG(bool, p_enabled)); + d->log_display_music_switch = p_enabled; + d->invoke_signal("log_display_music_switch_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_log_is_recording(bool p_enabled) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 06c6c06ad..fd6e1755b 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -45,10 +45,12 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // IC Chatlog w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); - w_log_uses_newline = AO_GUI_WIDGET(QCheckBox, "log_newline"); + w_log_display_timestamp = AO_GUI_WIDGET(QCheckBox, "log_display_timestamp"); + w_log_format_use_newline = AO_GUI_WIDGET(QCheckBox, "log_format_use_newline"); + w_log_display_empty_messages = AO_GUI_WIDGET(QCheckBox, "log_display_empty_messages"); + w_log_display_music_switch = AO_GUI_WIDGET(QCheckBox, "log_display_music_switch"); w_log_orientation_top_down = AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); w_log_orientation_bottom_up = AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); - w_log_music = AO_GUI_WIDGET(QCheckBox, "log_music"); w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); // audio @@ -88,11 +90,18 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, SLOT(setChecked(bool))); connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); + + // log connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); + connect(m_config, SIGNAL(log_display_timestamp_changed(bool)), w_log_display_timestamp, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_format_use_newline_changed(bool)), w_log_format_use_newline, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_empty_messages_changed(bool)), w_log_display_empty_messages, + SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_music_switch_changed(bool)), w_log_display_music_switch, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); - connect(m_config, SIGNAL(log_uses_newline_changed(bool)), w_log_uses_newline, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_music_changed(bool)), w_log_music, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, SLOT(setChecked(bool))); + + // audio connect(m_config, SIGNAL(master_volume_changed(int)), w_master, SLOT(setValue(int))); connect(m_config, SIGNAL(suppress_background_audio_changed(bool)), w_suppress_background_audio, SLOT(setChecked(bool))); @@ -128,10 +137,14 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_manual_timeofday, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_timeofday(bool))); connect(w_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); + + // out, log connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); + connect(w_log_display_timestamp, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_timestamp(bool))); + connect(w_log_format_use_newline, SIGNAL(toggled(bool)), m_config, SLOT(set_log_format_use_newline(bool))); + connect(w_log_display_empty_messages, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_empty_messages(bool))); + connect(w_log_display_music_switch, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_music_switch(bool))); connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_topdown(bool))); - connect(w_log_uses_newline, SIGNAL(toggled(bool)), m_config, SLOT(set_log_uses_newline(bool))); - connect(w_log_music, SIGNAL(toggled(bool)), m_config, SLOT(set_log_music(bool))); connect(w_log_is_recording, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_recording(bool))); connect(w_suppress_background_audio, SIGNAL(toggled(bool)), m_config, SLOT(set_suppress_background_audio(bool))); connect(w_device, SIGNAL(currentIndexChanged(int)), this, SLOT(on_device_current_index_changed(int))); @@ -163,6 +176,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_manual_timeofday->setChecked(m_config->manual_timeofday_enabled()); w_always_pre->setChecked(m_config->always_pre_enabled()); w_chat_tick_interval->setValue(m_config->chat_tick_interval()); + + // log w_log_max_lines->setValue(m_config->log_max_lines()); if (m_config->log_is_topdown_enabled()) @@ -174,8 +189,10 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_log_orientation_bottom_up->setChecked(true); } - w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); - w_log_music->setChecked(m_config->log_music_enabled()); + w_log_display_timestamp->setChecked(m_config->log_display_timestamp_enabled()); + w_log_format_use_newline->setChecked(m_config->log_format_use_newline_enabled()); + w_log_display_empty_messages->setChecked(m_config->log_display_empty_messages_enabled()); + w_log_display_music_switch->setChecked(m_config->log_display_music_switch_enabled()); w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); // audio diff --git a/src/aotextarea.cpp b/src/aotextarea.cpp index c1821fd36..95aaad9d2 100644 --- a/src/aotextarea.cpp +++ b/src/aotextarea.cpp @@ -1,13 +1,13 @@ #include "aotextarea.h" +#include #include #include #include #include AOTextArea::AOTextArea(QWidget *p_parent) : QTextBrowser(p_parent) -{ -} +{} void AOTextArea::append_chatmessage(QString p_name, QString p_message) { @@ -18,6 +18,7 @@ void AOTextArea::append_chatmessage(QString p_name, QString p_message) this->moveCursor(QTextCursor::End); this->append(""); + this->insertHtml(QString("[%1] ").arg(QDateTime::currentDateTime().toString("hh:mm"))); this->insertHtml("" + p_name.toHtmlEscaped() + ": "); // cheap workarounds ahoy @@ -39,6 +40,7 @@ void AOTextArea::append_error(QString p_message) this->moveCursor(QTextCursor::End); this->append(""); + this->insertHtml(QString("[%1] ").arg(QDateTime::currentDateTime().toString("hh:mm"))); p_message += " "; QString result = p_message.replace("\n", "
"); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 13abd2ab3..6ad5e1270 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1111,57 +1111,28 @@ void Courtroom::handle_chatmessage_3() void Courtroom::on_chat_config_changed() { - // forward declaration for a possible update of the chatlog - bool chatlog_changed = false; - - int chatlog_limit = ao_config->log_max_lines(); - // default chatlog_limit? - chatlog_limit = chatlog_limit <= 0 ? 200 : chatlog_limit; // TODO declare the default somewhere so - // it's not a magic number - if (chatlog_limit < m_chatlog_limit) // only update if we need to chop away records - chatlog_changed = true; - m_chatlog_limit = chatlog_limit; - - bool chatlog_scrolldown = ao_config->log_is_topdown_enabled(); - if (m_chatlog_scrolldown != chatlog_scrolldown) - chatlog_changed = true; - m_chatlog_scrolldown = chatlog_scrolldown; - - bool chatlog_newline = ao_config->log_uses_newline_enabled(); - if (m_chatlog_newline != chatlog_newline) - chatlog_changed = true; - m_chatlog_newline = chatlog_newline; - - // refresh the log if needed - if (chatlog_changed) - update_ic_log(chatlog_changed); + update_ic_log(true); } void Courtroom::update_ic_log(bool p_reset_log) { // resize if needed - int len = m_ic_records.length(); - if (len > m_chatlog_limit) - m_ic_records = m_ic_records.mid(len - m_chatlog_limit); + const int record_count = m_ic_record_list.length() + m_ic_record_queue.length(); + if (record_count > ao_config->log_max_lines()) + m_ic_record_list = m_ic_record_list.mid(record_count - ao_config->log_max_lines()); - /* - * first, we figure out whatever we append the last message or if we reset - * the entire log - * */ - record_type_array records_to_add; - // populate if (p_reset_log) { - // we need the entire recordings - records_to_add = m_ic_records; + // we need all recordings + QQueue new_queue; + while (!m_ic_record_list.isEmpty()) + new_queue.append(m_ic_record_list.takeFirst()); + new_queue.append(m_ic_record_queue); + m_ic_record_queue = std::move(new_queue); // clear log ui_ic_chatlog->clear(); } - else - { - records_to_add.append(m_ic_records.last()); - } // prepare the formats we need // default color @@ -1196,48 +1167,65 @@ void Courtroom::update_ic_log(bool p_reset_log) // need vscroll bar for cache QScrollBar *vscrollbar = ui_ic_chatlog->verticalScrollBar(); + // format values + const bool chatlog_scrolldown = ao_config->log_is_topdown_enabled(); + const bool chatlog_newline = ao_config->log_format_use_newline_enabled(); + // cache previous values const QTextCursor prev_cursor = ui_ic_chatlog->textCursor(); const int scroll_pos = vscrollbar->value(); const bool is_scrolled = - m_chatlog_scrolldown ? scroll_pos == vscrollbar->maximum() : scroll_pos == vscrollbar->minimum(); + chatlog_scrolldown ? scroll_pos == vscrollbar->maximum() : scroll_pos == vscrollbar->minimum(); // recover cursor QTextCursor cursor = ui_ic_chatlog->textCursor(); // figure out if we need to move up or down - const QTextCursor::MoveOperation move_type = m_chatlog_scrolldown ? QTextCursor::End : QTextCursor::Start; + const QTextCursor::MoveOperation move_type = chatlog_scrolldown ? QTextCursor::End : QTextCursor::Start; - for (record_type_ptr record : records_to_add) + while (!m_ic_record_queue.isEmpty()) { + DR::ChatRecord record = m_ic_record_queue.takeFirst(); + m_ic_record_list.append(record); + + if (record.get_message().trimmed().isEmpty() && !ao_config->log_display_empty_messages_enabled()) + continue; + + if (record.is_music() && !ao_config->log_display_music_switch_enabled()) + continue; + // move cursor cursor.movePosition(move_type); - const QString record_end = (QString(QChar::LineFeed) + (m_chatlog_newline ? QString(QChar::LineFeed) : "")); - if (record->system) + const QString record_end = (QString(QChar::LineFeed) + (chatlog_newline ? QString(QChar::LineFeed) : "")); + + if (ao_config->log_display_timestamp_enabled()) + cursor.insertText(QString("[%1] ").arg(record.get_timestamp().toString("hh:mm")), name_format); + + if (record.is_system()) { - cursor.insertText(record->line + record_end, system_format); + cursor.insertText(record.get_message() + record_end, system_format); } else { QString separator; - if (m_chatlog_newline) + if (chatlog_newline) separator = QString(QChar::LineFeed); - else if (!record->music) + else if (!record.is_music()) separator = ": "; else separator = " "; - cursor.insertText(record->name + separator, name_format); - cursor.insertText(record->line + record_end, line_format); + cursor.insertText(record.get_name() + separator, name_format); + cursor.insertText(record.get_message() + record_end, line_format); } } // figure out the number of blocks we need overall // this is always going to amount to at least the current length of records - int block_count = m_ic_records.length() + 1; // there's always one extra block + int block_count = m_ic_record_list.length() + 1; // there's always one extra block // to do that, we need to go through the records - for (record_type_ptr record : m_ic_records) - if (!record->system) - if (m_chatlog_newline) + for (DR::ChatRecord &record : m_ic_record_list) + if (!record.is_system()) + if (chatlog_newline) block_count += 2; // if newline is actived, it always inserts two extra // newlines; therefor two paragraphs @@ -1245,9 +1233,9 @@ void Courtroom::update_ic_log(bool p_reset_log) int blocks_to_delete = ui_ic_chatlog->document()->blockCount() - block_count; // the orientation at which we need to delete from - const QTextCursor::MoveOperation start_location = m_chatlog_scrolldown ? QTextCursor::Start : QTextCursor::End; + const QTextCursor::MoveOperation start_location = chatlog_scrolldown ? QTextCursor::Start : QTextCursor::End; const QTextCursor::MoveOperation block_orientation = - m_chatlog_scrolldown ? QTextCursor::NextBlock : QTextCursor::PreviousBlock; + chatlog_scrolldown ? QTextCursor::NextBlock : QTextCursor::PreviousBlock; /* Blocks appear like this * textQChar(0x2029) @@ -1279,7 +1267,7 @@ void Courtroom::update_ic_log(bool p_reset_log) * it/figure out the amount of blocks if we have a scroll up log, so we add it * again if we removed any break characters at all * */ - if (!m_chatlog_scrolldown && blocks_to_delete > 0) + if (!chatlog_scrolldown && blocks_to_delete > 0) cursor.insertBlock(); /* @@ -1298,16 +1286,22 @@ void Courtroom::update_ic_log(bool p_reset_log) else { ui_ic_chatlog->moveCursor(move_type); - vscrollbar->setValue(m_chatlog_scrolldown ? vscrollbar->maximum() : vscrollbar->minimum()); + vscrollbar->setValue(chatlog_scrolldown ? vscrollbar->maximum() : vscrollbar->minimum()); } } void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music) { - // record new entry - m_ic_records.append(std::make_shared(p_name, p_line, "", p_system, p_music)); + if (p_name.trimmed().isEmpty()) + p_name = "Anonymous"; + + if (p_line.trimmed().isEmpty()) + p_line = p_line.trimmed(); - // update + DR::ChatRecord new_record(p_name, p_line); + new_record.set_music(p_music); + new_record.set_system(p_system); + m_ic_record_queue.append(new_record); update_ic_log(false); } @@ -1673,12 +1667,9 @@ void Courtroom::handle_song(QStringList p_contents) if (!mute_map.value(n_char)) { - if (ao_app->get_music_change_log_enabled()) - { - append_ic_text(str_char, "has played a song: " + f_song, false, true); - if (ao_config->log_is_recording_enabled()) - save_textlog(str_char + " has played a song: " + f_song); - } + append_ic_text(str_char, "has played a song: " + f_song, false, true); + if (ao_config->log_is_recording_enabled()) + save_textlog(str_char + " has played a song: " + f_song); m_music_player->play(f_song); } } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index bb2728ec4..0b5af7b70 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -284,8 +284,11 @@ void Courtroom::connect_widgets() connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, SLOT(on_text_color_changed(int))); connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_display_timestamp_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_format_use_newline_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_display_empty_messages_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_display_music_switch_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); - connect(ao_config, SIGNAL(log_uses_newline_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited(QString))); diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 58fb2bb89..4d2454c52 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -33,7 +33,7 @@ int AOApplication::read_blip_rate() bool AOApplication::read_chatlog_newline() { - return config->log_uses_newline_enabled(); + return config->log_format_use_newline_enabled(); } int AOApplication::get_default_music() From 9fa555003db5b3c249fda92156ad79720aec5e83 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 5 Mar 2021 19:34:49 +0100 Subject: [PATCH 235/842] Fixed name duplication --- res/ui/config_panel.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index f36f1902c..8cca6a46e 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 2 + 0 @@ -487,7 +487,7 @@ Audio - + From 952fc473d51edfc08223be2398ae4cd76e79d652 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 8 Mar 2021 11:54:52 +0100 Subject: [PATCH 236/842] Path sanitizing, short audio file fix, ... * Added ultra basic path-sanitizer to prevent folder jumping. * Fixed short audio files not playing properly. * Tweaked the `Disconnected from server` notification to immediately stop all audio upon appearance --- include/aoapplication.h | 1 + include/courtroom.h | 5 +++-- include/draudiostreamfamily.h | 5 +++-- src/aoapplication.cpp | 12 ++++++++++++ src/audio_functions.cpp | 11 +++++++++-- src/courtroom.cpp | 13 ++++++------- src/draudiostream.cpp | 13 ++++--------- src/draudiostreamfamily.cpp | 9 ++++++--- src/path_functions.cpp | 9 +++++---- 9 files changed, 49 insertions(+), 29 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 32025bdf6..1d9cd0d5d 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -114,6 +114,7 @@ class AOApplication : public QApplication QString get_background_path(QString p_file); QString get_default_background_path(QString p_file); QString get_evidence_path(QString p_file); + QString sanitize_path(QString p_file); /** * @brief Returns the first case-sensitive file that is the combination of one diff --git a/include/courtroom.h b/include/courtroom.h index 0d3f73a79..341a182b0 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -850,10 +850,11 @@ private slots: */ public: - bool is_audio_muted(); + bool is_audio_suppressed() const; public slots: - void set_audio_mute_enabled(bool p_enabled); + void suppress_audio(bool p_enabled); + void stop_all_audio(); private: bool m_audio_mute = false; diff --git a/include/draudiostreamfamily.h b/include/draudiostreamfamily.h index db209d733..f88cc8202 100644 --- a/include/draudiostreamfamily.h +++ b/include/draudiostreamfamily.h @@ -18,12 +18,13 @@ class DRAudioStreamFamily : public QObject public: using ptr = QSharedPointer; + using stream_list = QVector; std::optional create_stream(QString p_file); std::optional play_stream(QString p_file); // get - QVector get_stream_list() const; + stream_list get_stream_list() const; int32_t get_volume() const; int32_t get_capacity() const; DRAudio::Options get_options() const; @@ -63,7 +64,7 @@ public slots: int32_t m_volume = 0; int32_t m_capacity = 0; DRAudio::Options m_options; - QVector> m_stream_list; + stream_list m_stream_list; private slots: void on_stream_finished(); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index e02e997bf..a90b026c1 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -9,7 +9,9 @@ #include "aoconfigpanel.h" #include +#include #include +#include #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) #include @@ -176,6 +178,15 @@ QString AOApplication::get_current_char() return ""; } +QString AOApplication::sanitize_path(QString p_file) +{ + QStringList list = p_file.split(QRegularExpression("[\\/]")); + while (!list.isEmpty()) + if (list.takeFirst().contains(QRegularExpression("\\.{2,}"))) + return nullptr; + return p_file; +} + void AOApplication::toggle_config_panel() { config_panel->setVisible(!config_panel->isVisible()); @@ -269,6 +280,7 @@ void AOApplication::server_disconnected() { if (!courtroom_constructed) return; + w_courtroom->stop_all_audio(); call_notice("Disconnected from server."); construct_lobby(); destruct_courtroom(); diff --git a/src/audio_functions.cpp b/src/audio_functions.cpp index 84bd8ce3f..34bf0fadb 100644 --- a/src/audio_functions.cpp +++ b/src/audio_functions.cpp @@ -1,11 +1,11 @@ #include "courtroom.h" -bool Courtroom::is_audio_muted() +bool Courtroom::is_audio_suppressed() const { return m_audio_mute; } -void Courtroom::set_audio_mute_enabled(bool p_enabled) +void Courtroom::suppress_audio(bool p_enabled) { if (m_audio_mute == p_enabled) return; @@ -15,3 +15,10 @@ void Courtroom::set_audio_mute_enabled(bool p_enabled) for (auto &family : DRAudioEngine::get_family_list()) family->set_suppressed(m_audio_mute); } + +void Courtroom::stop_all_audio() +{ + for (auto &family : DRAudioEngine::get_family_list()) + for (auto &stream : family->get_stream_list()) + stream->stop(); +} diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 13abd2ab3..99c0eac87 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -21,6 +21,8 @@ #include #include +#include "networkmanager.h" + Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; @@ -36,10 +38,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() Courtroom::~Courtroom() { - // shutdown all audio - for (auto &family : DRAudioEngine::get_family_list()) - for (auto &stream : family->get_stream_list()) - stream->stop(); + stop_all_audio(); } void Courtroom::enter_courtroom(int p_cid) @@ -144,7 +143,7 @@ void Courtroom::enter_courtroom(int p_cid) ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors // unmute audio - set_audio_mute_enabled(false); + suppress_audio(false); testimony_in_progress = false; @@ -166,7 +165,7 @@ void Courtroom::done_received() { m_cid = -1; - set_audio_mute_enabled(true); + suppress_audio(true); set_char_select_page(); @@ -2204,7 +2203,7 @@ void Courtroom::on_wtce_clicked() void Courtroom::on_change_character_clicked() { - set_audio_mute_enabled(true); + suppress_audio(true); set_char_select(); diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index 4369748c8..1683f01fc 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -89,13 +89,11 @@ std::optional DRAudioStream::set_file(QString p_file) // bass events for (const DWORD type : {BASS_SYNC_END, BASS_SYNC_DEV_FAIL}) { - HSYNC sync_handle = - BASS_ChannelSetSync(stream_handle, type | BASS_SYNC_MIXTIME, 0, &DRAudioStream::on_sync_callback, this); + const DWORD final_type = type == BASS_SYNC_DEV_FAIL ? type | BASS_SYNC_MIXTIME : type; + HSYNC sync_handle = BASS_ChannelSetSync(stream_handle, final_type, 0, &DRAudioStream::on_sync_callback, this); if (sync_handle == 0) - return DRAudioError(QString("failed to create sync %1 for file %2: %3") - .arg(type) - .arg(p_file) - .arg(DRAudio::get_last_bass_error())); + return DRAudioError( + QString("failed to create sync %1 for file %2: %3").arg(type).arg(p_file, DRAudio::get_last_bass_error())); m_hsync_stack.push(DRAudioStreamSync{sync_handle, type}); } @@ -124,14 +122,11 @@ void DRAudioStream::on_sync_callback(HSYNC hsync, DWORD ch, DWORD data, void *us * the pointer we provided get deleted mid-way through the program lifetime */ DRAudioStream *self = static_cast(userdata); - for (auto &v : self->m_hsync_stack) { if (v.sync != hsync) continue; - const QString filePath = self->get_file().value(); - switch (v.type) { case BASS_SYNC_END: diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp index 1be22c990..acd1d35f4 100644 --- a/src/draudiostreamfamily.cpp +++ b/src/draudiostreamfamily.cpp @@ -87,7 +87,6 @@ std::optional DRAudioStreamFamily::create_stream(QString p_f m_stream_list.append(stream); update_capacity(); - stream->set_volume(calculate_volume()); connect(stream.get(), SIGNAL(finished()), this, SLOT(on_stream_finished())); @@ -106,7 +105,7 @@ std::optional DRAudioStreamFamily::play_stream(QString p_fil return r_stream; } -QVector DRAudioStreamFamily::get_stream_list() const +DRAudioStreamFamily::stream_list DRAudioStreamFamily::get_stream_list() const { return m_stream_list; } @@ -161,11 +160,15 @@ void DRAudioStreamFamily::on_stream_finished() if (invoker == nullptr) return; - decltype(m_stream_list) new_stream_list; + stream_list new_stream_list; for (auto &i_stream : m_stream_list) { if (i_stream.get() == invoker) + { + if (auto file = i_stream->get_file(); file) + qDebug() << "removing" << file.value(); continue; + } new_stream_list.append(std::move(i_stream)); } m_stream_list = std::move(new_stream_list); diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 1548d95f9..2c73ccb4a 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -130,16 +130,17 @@ QString AOApplication::get_case_sensitive_path(QString p_file) QString AOApplication::find_asset_path(QStringList possible_roots, QStringList possible_exts) { - for (QString root : possible_roots) + for (QString &root : possible_roots) { - for (QString ext : possible_exts) + for (QString &ext : possible_exts) { - QString full_path = get_case_sensitive_path(root + ext); + QString full_path = sanitize_path(get_case_sensitive_path(root + ext)); if (file_exists(full_path)) return full_path; } } - return ""; + + return nullptr; } QString AOApplication::find_theme_asset_path(QString p_file, QStringList exts) From 21735152014d535a1f1d1e4e51ec52f9a42d1eea Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 8 Mar 2021 21:39:35 -0500 Subject: [PATCH 237/842] Add config panel option for discord rich status style --- include/aoconfig.h | 6 +++ include/aoconfigpanel.h | 4 ++ include/datatypes.h | 10 ++++- res/ui/config_panel.ui | 50 +++++++++++++++++++++---- src/aoconfig.cpp | 83 +++++++++++++++++++++++++++++++++++++---- src/aoconfigpanel.cpp | 41 ++++++++++++++++++++ 6 files changed, 178 insertions(+), 16 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index f0994c048..77f50b6bd 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -1,6 +1,7 @@ #ifndef AOCONFIG_H #define AOCONFIG_H +#include "datatypes.h" // qt #include @@ -22,6 +23,7 @@ class AOConfig : public QObject QString username() const; QString callwords() const; bool server_alerts_enabled() const; + DR::DiscordRichPresence discord_rich_presence() const; QString theme() const; QString gamemode() const; bool manual_gamemode_enabled() const; @@ -59,6 +61,9 @@ public slots: void set_username(QString p_string); void set_callwords(QString p_string); void set_server_alerts(bool p_enabled); + void set_discord_rich_presence_complete(bool p_enabled); + void set_discord_rich_presence_minimal(bool p_enabled); + void set_discord_rich_presence_disabled(bool p_enabled); void set_always_pre(bool p_enabled); void set_theme(QString p_string); void set_gamemode(QString p_string); @@ -92,6 +97,7 @@ public slots: void username_changed(QString); void callwords_changed(QString); void server_alerts_changed(bool); + void discord_rich_presence_changed(DR::DiscordRichPresence); void theme_changed(QString); void gamemode_changed(QString); void manual_gamemode_changed(bool); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 203aa5bd3..ad631953d 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -42,6 +42,7 @@ public slots: void update_audio_device_list(); private slots: + void on_discord_rich_presence_changed(DR::DiscordRichPresence drp_status); void on_reload_theme_clicked(); void on_gamemode_index_changed(QString p_text); void on_timeofday_index_changed(QString p_text); @@ -73,6 +74,9 @@ private slots: QLineEdit *w_username = nullptr; QLineEdit *w_callwords = nullptr; QCheckBox *w_server_alerts = nullptr; + QRadioButton *w_discord_rich_presence_complete = nullptr; + QRadioButton *w_discord_rich_presence_minimal = nullptr; + QRadioButton *w_discord_rich_presence_disabled = nullptr; // game QComboBox *w_theme = nullptr; diff --git a/include/datatypes.h b/include/datatypes.h index 196e46df7..4d5d7ef2b 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -138,12 +138,20 @@ enum Color : int32_t CWhite = CDefault, }; +enum DiscordRichPresence : int32_t +{ + DRPComplete, + DRPMinimal, + DRPDisabled, +}; + struct ColorInfo { public: ColorInfo() = default; ColorInfo(QString p_showname, QString p_code) : name(p_showname.toLower()), showname(p_showname), code(p_code) - {} + { + } QString name; QString showname; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 9eecbbc1c..58f797c17 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 3 + 0 @@ -101,20 +101,43 @@
- - + + - + 0 0 - - + + Discord Rich Status + + + + + Disabled + + + + + + + Minimal + + + + + + + Complete + + + + - + Qt::Vertical @@ -127,6 +150,19 @@ + + + + + 0 + 0 + + + + + + + diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 93730cb88..b4c489ad1 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -1,5 +1,5 @@ #include "aoconfig.h" - +#include "datatypes.h" #include "draudioengine.h" // qt @@ -47,12 +47,13 @@ private slots: bool autosave; QString username; QString callwords; + bool server_alerts; + DR::DiscordRichPresence discord_rich_presence; QString theme; QString gamemode; bool manual_gamemode; QString timeofday; bool manual_timeofday; - bool server_alerts; bool always_pre; int chat_tick_interval; int log_max_lines; @@ -104,7 +105,24 @@ void AOConfigPrivate::read_file() username = cfg.value("username").toString(); callwords = cfg.value("callwords").toString(); server_alerts = cfg.value("server_alerts", true).toBool(); - + QString raw_discord_rich_presence = cfg.value("discord_rich_presence", "complete").toString(); + if (raw_discord_rich_presence == "complete") + { + discord_rich_presence = DR::DRPComplete; + } + else if (raw_discord_rich_presence == "minimal") + { + discord_rich_presence = DR::DRPMinimal; + } + else if (raw_discord_rich_presence == "disabled") + { + discord_rich_presence = DR::DRPDisabled; + } + else + { + qWarning() << "Unknown Discord Rich Presence status, defaulting to 'complete'"; + discord_rich_presence = DR::DRPComplete; + } theme = cfg.value("theme").toString(); if (theme.trimmed().isEmpty()) theme = "default"; @@ -156,6 +174,20 @@ void AOConfigPrivate::save_file() cfg.setValue("username", username); cfg.setValue("callwords", callwords); cfg.setValue("server_alerts", server_alerts); + QString raw_discord_rich_presence = ""; + switch (discord_rich_presence) + { + case DR::DRPComplete: + raw_discord_rich_presence = "complete"; + break; + case DR::DRPMinimal: + raw_discord_rich_presence = "minimal"; + break; + case DR::DRPDisabled: + raw_discord_rich_presence = "disabled"; + break; + } + cfg.setValue("discord_rich_presence", raw_discord_rich_presence); cfg.setValue("theme", theme); cfg.setValue("gamemode", gamemode); cfg.setValue("manual_gamemode", manual_gamemode); @@ -266,6 +298,16 @@ QString AOConfig::callwords() const return d->callwords; } +bool AOConfig::server_alerts_enabled() const +{ + return d->server_alerts; +} + +DR::DiscordRichPresence AOConfig::discord_rich_presence() const +{ + return d->discord_rich_presence; +} + QString AOConfig::theme() const { return d->theme; @@ -291,11 +333,6 @@ bool AOConfig::manual_timeofday_enabled() const return d->manual_timeofday; } -bool AOConfig::server_alerts_enabled() const -{ - return d->server_alerts; -} - bool AOConfig::always_pre_enabled() const { return d->always_pre; @@ -426,6 +463,36 @@ void AOConfig::set_server_alerts(bool p_enabled) d->invoke_signal("server_alerts_changed", Q_ARG(bool, p_enabled)); } +void AOConfig::set_discord_rich_presence_complete(bool p_enabled) +{ + if (!p_enabled) + return; + if (d->discord_rich_presence == DR::DRPComplete) + return; + d->discord_rich_presence = DR::DRPComplete; + d->invoke_signal("discord_rich_presence_changed", Q_ARG(DR::DiscordRichPresence, DR::DRPComplete)); +} + +void AOConfig::set_discord_rich_presence_minimal(bool p_enabled) +{ + if (!p_enabled) + return; + if (d->discord_rich_presence == DR::DRPMinimal) + return; + d->discord_rich_presence = DR::DRPMinimal; + d->invoke_signal("discord_rich_presence_changed", Q_ARG(DR::DiscordRichPresence, DR::DRPMinimal)); +} + +void AOConfig::set_discord_rich_presence_disabled(bool p_enabled) +{ + if (!p_enabled) + return; + if (d->discord_rich_presence == DR::DRPDisabled) + return; + d->discord_rich_presence = DR::DRPDisabled; + d->invoke_signal("discord_rich_presence_changed", Q_ARG(DR::DiscordRichPresence, DR::DRPDisabled)); +} + void AOConfig::set_theme(QString p_string) { if (d->theme == p_string) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 06c6c06ad..7beb5a688 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -1,4 +1,5 @@ #include "aoconfigpanel.h" +#include "datatypes.h" // qt #include @@ -32,6 +33,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_username = AO_GUI_WIDGET(QLineEdit, "username"); w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); + w_discord_rich_presence_complete = AO_GUI_WIDGET(QRadioButton, "discord_rich_presence_complete"); + w_discord_rich_presence_minimal = AO_GUI_WIDGET(QRadioButton, "discord_rich_presence_minimal"); + w_discord_rich_presence_disabled = AO_GUI_WIDGET(QRadioButton, "discord_rich_presence_disabled"); // game w_theme = AO_GUI_WIDGET(QComboBox, "theme"); @@ -81,6 +85,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(discord_rich_presence_changed(DR::DiscordRichPresence)), this, + SLOT(on_discord_rich_presence_changed(DR::DiscordRichPresence))); connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, SLOT(setChecked(bool))); @@ -120,6 +126,12 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_username, SIGNAL(textEdited(QString)), m_config, SLOT(set_username(QString))); connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); connect(w_server_alerts, SIGNAL(toggled(bool)), m_config, SLOT(set_server_alerts(bool))); + connect(w_discord_rich_presence_complete, SIGNAL(toggled(bool)), m_config, + SLOT(set_discord_rich_presence_complete(bool))); + connect(w_discord_rich_presence_minimal, SIGNAL(toggled(bool)), m_config, + SLOT(set_discord_rich_presence_minimal(bool))); + connect(w_discord_rich_presence_disabled, SIGNAL(toggled(bool)), m_config, + SLOT(set_discord_rich_presence_disabled(bool))); connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); @@ -174,6 +186,19 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_log_orientation_bottom_up->setChecked(true); } + switch (m_config->discord_rich_presence()) + { + case DR::DRPComplete: + w_discord_rich_presence_complete->setChecked(true); + break; + case DR::DRPMinimal: + w_discord_rich_presence_minimal->setChecked(true); + break; + case DR::DRPDisabled: + w_discord_rich_presence_disabled->setChecked(true); + break; + } + w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); w_log_music->setChecked(m_config->log_music_enabled()); w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); @@ -214,6 +239,22 @@ void AOConfigPanel::showEvent(QShowEvent *event) } } +void AOConfigPanel::on_discord_rich_presence_changed(DR::DiscordRichPresence drp_status) +{ + switch (drp_status) + { + case DR::DRPComplete: + m_config->set_discord_rich_presence_complete(true); + break; + case DR::DRPMinimal: + m_config->set_discord_rich_presence_minimal(true); + break; + case DR::DRPDisabled: + m_config->set_discord_rich_presence_disabled(true); + break; + } +} + void AOConfigPanel::refresh_theme_list() { const QString p_prev_text = w_theme->currentText(); From 5f56a6bce2e3074bdef8e648be5749efba38c4bb Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 8 Mar 2021 22:29:52 -0500 Subject: [PATCH 238/842] Improve struct name+Add preliminary style functions to Discord file --- include/aoconfig.h | 10 +++---- include/aoconfigpanel.h | 8 +++--- include/datatypes.h | 8 +++--- include/discord_rich_presence.h | 6 +++++ res/ui/config_panel.ui | 10 +++---- src/aoconfig.cpp | 42 ++++++++++++++--------------- src/aoconfigpanel.cpp | 48 ++++++++++++++++----------------- src/discord_rich_presence.cpp | 25 ++++++++++++----- 8 files changed, 88 insertions(+), 69 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 77f50b6bd..1abdbef2c 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -23,7 +23,7 @@ class AOConfig : public QObject QString username() const; QString callwords() const; bool server_alerts_enabled() const; - DR::DiscordRichPresence discord_rich_presence() const; + DR::DiscordRichPresenceStyle discord_rich_presence() const; QString theme() const; QString gamemode() const; bool manual_gamemode_enabled() const; @@ -61,9 +61,9 @@ public slots: void set_username(QString p_string); void set_callwords(QString p_string); void set_server_alerts(bool p_enabled); - void set_discord_rich_presence_complete(bool p_enabled); - void set_discord_rich_presence_minimal(bool p_enabled); - void set_discord_rich_presence_disabled(bool p_enabled); + void set_discord_rich_presence_style_complete(bool p_enabled); + void set_discord_rich_presence_style_minimal(bool p_enabled); + void set_discord_rich_presence_style_disabled(bool p_enabled); void set_always_pre(bool p_enabled); void set_theme(QString p_string); void set_gamemode(QString p_string); @@ -97,7 +97,7 @@ public slots: void username_changed(QString); void callwords_changed(QString); void server_alerts_changed(bool); - void discord_rich_presence_changed(DR::DiscordRichPresence); + void discord_rich_presence_style_changed(DR::DiscordRichPresenceStyle); void theme_changed(QString); void gamemode_changed(QString); void manual_gamemode_changed(bool); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index ad631953d..42681af58 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -42,7 +42,7 @@ public slots: void update_audio_device_list(); private slots: - void on_discord_rich_presence_changed(DR::DiscordRichPresence drp_status); + void on_discord_rich_presence_style_changed(DR::DiscordRichPresenceStyle drp_status); void on_reload_theme_clicked(); void on_gamemode_index_changed(QString p_text); void on_timeofday_index_changed(QString p_text); @@ -74,9 +74,9 @@ private slots: QLineEdit *w_username = nullptr; QLineEdit *w_callwords = nullptr; QCheckBox *w_server_alerts = nullptr; - QRadioButton *w_discord_rich_presence_complete = nullptr; - QRadioButton *w_discord_rich_presence_minimal = nullptr; - QRadioButton *w_discord_rich_presence_disabled = nullptr; + QRadioButton *w_discord_rich_presence_style_complete = nullptr; + QRadioButton *w_discord_rich_presence_style_minimal = nullptr; + QRadioButton *w_discord_rich_presence_style_disabled = nullptr; // game QComboBox *w_theme = nullptr; diff --git a/include/datatypes.h b/include/datatypes.h index 4d5d7ef2b..ef8621011 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -138,11 +138,11 @@ enum Color : int32_t CWhite = CDefault, }; -enum DiscordRichPresence : int32_t +enum DiscordRichPresenceStyle : int32_t { - DRPComplete, - DRPMinimal, - DRPDisabled, + DRPSComplete, + DRPSMinimal, + DRPSDisabled, }; struct ColorInfo diff --git a/include/discord_rich_presence.h b/include/discord_rich_presence.h index 078717509..c3ed17cc5 100644 --- a/include/discord_rich_presence.h +++ b/include/discord_rich_presence.h @@ -5,6 +5,8 @@ // std #include +#include "datatypes.h" + namespace AttorneyOnline { @@ -18,6 +20,9 @@ class Discord int m_index = 0; std::string server_name, server_id; int64_t timestamp; + DR::DiscordRichPresenceStyle style; + DiscordRichPresence current_presence; + void refresh_presence(DiscordRichPresence * = nullptr); public: Discord(); @@ -30,6 +35,7 @@ class Discord void start(const char *APPLICATION_ID); void restart(const char *APPLICATION_ID); void toggle(int p_index); + void set_style(DR::DiscordRichPresenceStyle new_style); }; } // namespace AttorneyOnline diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 58f797c17..b2d8f8e6e 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -114,23 +114,23 @@ - + - Disabled + Complete - + Minimal - + - Complete + Disabled diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index b4c489ad1..d113b768e 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -48,7 +48,7 @@ private slots: QString username; QString callwords; bool server_alerts; - DR::DiscordRichPresence discord_rich_presence; + DR::DiscordRichPresenceStyle discord_rich_presence; QString theme; QString gamemode; bool manual_gamemode; @@ -108,20 +108,20 @@ void AOConfigPrivate::read_file() QString raw_discord_rich_presence = cfg.value("discord_rich_presence", "complete").toString(); if (raw_discord_rich_presence == "complete") { - discord_rich_presence = DR::DRPComplete; + discord_rich_presence = DR::DRPSComplete; } else if (raw_discord_rich_presence == "minimal") { - discord_rich_presence = DR::DRPMinimal; + discord_rich_presence = DR::DRPSMinimal; } else if (raw_discord_rich_presence == "disabled") { - discord_rich_presence = DR::DRPDisabled; + discord_rich_presence = DR::DRPSDisabled; } else { qWarning() << "Unknown Discord Rich Presence status, defaulting to 'complete'"; - discord_rich_presence = DR::DRPComplete; + discord_rich_presence = DR::DRPSComplete; } theme = cfg.value("theme").toString(); if (theme.trimmed().isEmpty()) @@ -177,13 +177,13 @@ void AOConfigPrivate::save_file() QString raw_discord_rich_presence = ""; switch (discord_rich_presence) { - case DR::DRPComplete: + case DR::DRPSComplete: raw_discord_rich_presence = "complete"; break; - case DR::DRPMinimal: + case DR::DRPSMinimal: raw_discord_rich_presence = "minimal"; break; - case DR::DRPDisabled: + case DR::DRPSDisabled: raw_discord_rich_presence = "disabled"; break; } @@ -303,7 +303,7 @@ bool AOConfig::server_alerts_enabled() const return d->server_alerts; } -DR::DiscordRichPresence AOConfig::discord_rich_presence() const +DR::DiscordRichPresenceStyle AOConfig::discord_rich_presence() const { return d->discord_rich_presence; } @@ -463,34 +463,34 @@ void AOConfig::set_server_alerts(bool p_enabled) d->invoke_signal("server_alerts_changed", Q_ARG(bool, p_enabled)); } -void AOConfig::set_discord_rich_presence_complete(bool p_enabled) +void AOConfig::set_discord_rich_presence_style_complete(bool p_enabled) { if (!p_enabled) return; - if (d->discord_rich_presence == DR::DRPComplete) + if (d->discord_rich_presence == DR::DRPSComplete) return; - d->discord_rich_presence = DR::DRPComplete; - d->invoke_signal("discord_rich_presence_changed", Q_ARG(DR::DiscordRichPresence, DR::DRPComplete)); + d->discord_rich_presence = DR::DRPSComplete; + d->invoke_signal("discord_rich_presence_style_changed", Q_ARG(DR::DiscordRichPresenceStyle, DR::DRPSComplete)); } -void AOConfig::set_discord_rich_presence_minimal(bool p_enabled) +void AOConfig::set_discord_rich_presence_style_minimal(bool p_enabled) { if (!p_enabled) return; - if (d->discord_rich_presence == DR::DRPMinimal) + if (d->discord_rich_presence == DR::DRPSMinimal) return; - d->discord_rich_presence = DR::DRPMinimal; - d->invoke_signal("discord_rich_presence_changed", Q_ARG(DR::DiscordRichPresence, DR::DRPMinimal)); + d->discord_rich_presence = DR::DRPSMinimal; + d->invoke_signal("discord_rich_presence_style_changed", Q_ARG(DR::DiscordRichPresenceStyle, DR::DRPSMinimal)); } -void AOConfig::set_discord_rich_presence_disabled(bool p_enabled) +void AOConfig::set_discord_rich_presence_style_disabled(bool p_enabled) { if (!p_enabled) return; - if (d->discord_rich_presence == DR::DRPDisabled) + if (d->discord_rich_presence == DR::DRPSDisabled) return; - d->discord_rich_presence = DR::DRPDisabled; - d->invoke_signal("discord_rich_presence_changed", Q_ARG(DR::DiscordRichPresence, DR::DRPDisabled)); + d->discord_rich_presence = DR::DRPSDisabled; + d->invoke_signal("discord_rich_presence_style_changed", Q_ARG(DR::DiscordRichPresenceStyle, DR::DRPSDisabled)); } void AOConfig::set_theme(QString p_string) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 7beb5a688..32cd2bfd0 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -33,9 +33,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_username = AO_GUI_WIDGET(QLineEdit, "username"); w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); - w_discord_rich_presence_complete = AO_GUI_WIDGET(QRadioButton, "discord_rich_presence_complete"); - w_discord_rich_presence_minimal = AO_GUI_WIDGET(QRadioButton, "discord_rich_presence_minimal"); - w_discord_rich_presence_disabled = AO_GUI_WIDGET(QRadioButton, "discord_rich_presence_disabled"); + w_discord_rich_presence_style_complete = AO_GUI_WIDGET(QRadioButton, "discord_rich_presence_style_complete"); + w_discord_rich_presence_style_minimal = AO_GUI_WIDGET(QRadioButton, "discord_rich_presence_style_minimal"); + w_discord_rich_presence_style_disabled = AO_GUI_WIDGET(QRadioButton, "discord_rich_presence_style_disabled"); // game w_theme = AO_GUI_WIDGET(QComboBox, "theme"); @@ -85,8 +85,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(discord_rich_presence_changed(DR::DiscordRichPresence)), this, - SLOT(on_discord_rich_presence_changed(DR::DiscordRichPresence))); + connect(m_config, SIGNAL(discord_rich_presence_style_changed(DR::DiscordRichPresenceStyle)), this, + SLOT(on_discord_rich_presence_style_changed(DR::DiscordRichPresenceStyle))); connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, SLOT(setChecked(bool))); @@ -126,12 +126,12 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_username, SIGNAL(textEdited(QString)), m_config, SLOT(set_username(QString))); connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); connect(w_server_alerts, SIGNAL(toggled(bool)), m_config, SLOT(set_server_alerts(bool))); - connect(w_discord_rich_presence_complete, SIGNAL(toggled(bool)), m_config, - SLOT(set_discord_rich_presence_complete(bool))); - connect(w_discord_rich_presence_minimal, SIGNAL(toggled(bool)), m_config, - SLOT(set_discord_rich_presence_minimal(bool))); - connect(w_discord_rich_presence_disabled, SIGNAL(toggled(bool)), m_config, - SLOT(set_discord_rich_presence_disabled(bool))); + connect(w_discord_rich_presence_style_complete, SIGNAL(toggled(bool)), m_config, + SLOT(set_discord_rich_presence_style_complete(bool))); + connect(w_discord_rich_presence_style_minimal, SIGNAL(toggled(bool)), m_config, + SLOT(set_discord_rich_presence_style_minimal(bool))); + connect(w_discord_rich_presence_style_disabled, SIGNAL(toggled(bool)), m_config, + SLOT(set_discord_rich_presence_style_disabled(bool))); connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); @@ -188,14 +188,14 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) switch (m_config->discord_rich_presence()) { - case DR::DRPComplete: - w_discord_rich_presence_complete->setChecked(true); + case DR::DRPSComplete: + w_discord_rich_presence_style_complete->setChecked(true); break; - case DR::DRPMinimal: - w_discord_rich_presence_minimal->setChecked(true); + case DR::DRPSMinimal: + w_discord_rich_presence_style_minimal->setChecked(true); break; - case DR::DRPDisabled: - w_discord_rich_presence_disabled->setChecked(true); + case DR::DRPSDisabled: + w_discord_rich_presence_style_disabled->setChecked(true); break; } @@ -239,18 +239,18 @@ void AOConfigPanel::showEvent(QShowEvent *event) } } -void AOConfigPanel::on_discord_rich_presence_changed(DR::DiscordRichPresence drp_status) +void AOConfigPanel::on_discord_rich_presence_style_changed(DR::DiscordRichPresenceStyle drp_status) { switch (drp_status) { - case DR::DRPComplete: - m_config->set_discord_rich_presence_complete(true); + case DR::DRPSComplete: + m_config->set_discord_rich_presence_style_complete(true); break; - case DR::DRPMinimal: - m_config->set_discord_rich_presence_minimal(true); + case DR::DRPSMinimal: + m_config->set_discord_rich_presence_style_minimal(true); break; - case DR::DRPDisabled: - m_config->set_discord_rich_presence_disabled(true); + case DR::DRPSDisabled: + m_config->set_discord_rich_presence_style_disabled(true); break; } } diff --git a/src/discord_rich_presence.cpp b/src/discord_rich_presence.cpp index 074f5978c..78fdf9b61 100644 --- a/src/discord_rich_presence.cpp +++ b/src/discord_rich_presence.cpp @@ -1,4 +1,5 @@ #include "discord_rich_presence.h" +#include "datatypes.h" #include #include @@ -73,6 +74,19 @@ void Discord::toggle(int p_index) qDebug() << p_index << "is not a valid APPLICATION_ID Index"; } +void Discord::set_style(DR::DiscordRichPresenceStyle new_style) +{ + style = new_style; + refresh_presence(&this->current_presence); +} + +void Discord::refresh_presence(DiscordRichPresence *new_presence) +{ + // TODO: Change presence according to style + current_presence = *new_presence; + Discord_UpdatePresence(new_presence); +} + void Discord::state_lobby() { DiscordRichPresence presence; @@ -83,7 +97,7 @@ void Discord::state_lobby() presence.state = "In Lobby"; presence.details = "Idle"; - Discord_UpdatePresence(&presence); + refresh_presence(&presence); } void Discord::state_server(std::string name, std::string server_id) @@ -106,7 +120,7 @@ void Discord::state_server(std::string name, std::string server_id) this->server_id = server_id; this->server_name = name; this->timestamp = timestamp; - Discord_UpdatePresence(&presence); + refresh_presence(&presence); } void Discord::state_character(std::string name) @@ -125,15 +139,14 @@ void Discord::state_character(std::string name) presence.startTimestamp = this->timestamp; presence.state = playing_as.c_str(); - // presence.smallImageKey = "danganronpa_online"; presence.smallImageKey = "danganronpa_online"; presence.smallImageText = "Danganronpa Online"; - Discord_UpdatePresence(&presence); + refresh_presence(&presence); } void Discord::state_spectate() { - qDebug() << "Discord RPC: Setting specator state"; + qDebug() << "Discord RPC: Setting spectator state"; DiscordRichPresence presence; std::memset(&presence, 0, sizeof(presence)); @@ -145,7 +158,7 @@ void Discord::state_spectate() presence.startTimestamp = this->timestamp; presence.state = "Spectating"; - Discord_UpdatePresence(&presence); + refresh_presence(&presence); } } // namespace AttorneyOnline From 19a4c556539968ccda44613fba431934274f08d7 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 9 Mar 2021 11:03:15 -0500 Subject: [PATCH 239/842] Implement Complete/Minimal/Disabled Discord statuses --- icon-2x.png | Bin 0 -> 180868 bytes include/discord_rich_presence.h | 11 +-- res/ui/config_panel.ui | 2 +- src/aoapplication.cpp | 6 +- src/aoconfigpanel.cpp | 1 + src/courtroom.cpp | 3 +- src/discord_rich_presence.cpp | 154 +++++++++++++++++++------------- 7 files changed, 104 insertions(+), 73 deletions(-) create mode 100644 icon-2x.png diff --git a/icon-2x.png b/icon-2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9c46fb462e1e7c3c726ba648a9fc94394d930892 GIT binary patch literal 180868 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 Jfs2mV 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

PrX7Hyph+NGZ-M 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 H0e)Xk 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 literal 0 HcmV?d00001 diff --git a/include/discord_rich_presence.h b/include/discord_rich_presence.h index c3ed17cc5..cbb77997d 100644 --- a/include/discord_rich_presence.h +++ b/include/discord_rich_presence.h @@ -14,15 +14,16 @@ class Discord { private: const char *APPLICATION_ID[2] = { - "538080629535801347", - "538080629535801347", + "818850172330442793", + "818850172330442793", }; // insert second one here blah blah int m_index = 0; - std::string server_name, server_id; + std::string server_name, server_id, state, details, matchSecret; int64_t timestamp; DR::DiscordRichPresenceStyle style; - DiscordRichPresence current_presence; - void refresh_presence(DiscordRichPresence * = nullptr); + DiscordRichPresence *complete_presence = new DiscordRichPresence(); + DiscordRichPresence *minimal_presence = new DiscordRichPresence(); + void refresh_presence(); public: Discord(); diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index b2d8f8e6e..9c533d48b 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -110,7 +110,7 @@ - Discord Rich Status + Discord Rich Status Style diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index e02e997bf..6df8cb918 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -33,6 +33,8 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(config_panel, SIGNAL(reload_theme()), this, SLOT(on_config_reload_theme_requested())); connect(this, SIGNAL(reload_theme()), config_panel, SLOT(on_config_reload_theme_requested())); config_panel->hide(); + + discord->set_style(config->discord_rich_presence()); } AOApplication::~AOApplication() @@ -66,9 +68,9 @@ void AOApplication::construct_lobby() int y = (screen_geometry.height() - w_lobby->height()) / 2; w_lobby->move(x, y); - discord->state_lobby(); - w_lobby->show(); + + discord->state_lobby(); } void AOApplication::destruct_lobby() diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 32cd2bfd0..c3f48ebf6 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -253,6 +253,7 @@ void AOConfigPanel::on_discord_rich_presence_style_changed(DR::DiscordRichPresen m_config->set_discord_rich_presence_style_disabled(true); break; } + ao_app->discord->set_style(drp_status); } void AOConfigPanel::refresh_theme_list() diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 13abd2ab3..1788b70b3 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -60,6 +60,7 @@ void Courtroom::enter_courtroom(int p_cid) { f_char = ao_app->get_char_name(char_list.at(m_cid).name); QString r_char = f_char; + /* // regex for removing non letter (except _) characters QRegularExpression re(QString::fromUtf8("[-`~!@#$%^&*()—+=|:;<>«»,.?/{}\'\"\\[\\]]")); r_char.remove(re); @@ -71,7 +72,7 @@ void Courtroom::enter_courtroom(int p_cid) else { ao_app->discord->toggle(0); - } + }*/ ao_app->discord->state_character(r_char.toStdString()); } diff --git a/src/discord_rich_presence.cpp b/src/discord_rich_presence.cpp index 78fdf9b61..4fa2e6bc0 100644 --- a/src/discord_rich_presence.cpp +++ b/src/discord_rich_presence.cpp @@ -11,20 +11,14 @@ namespace AttorneyOnline Discord::Discord() { - // DiscordEventHandlers handlers; - // std::memset(&handlers, 0, sizeof(handlers)); - // handlers = {}; - // handlers.ready = [] { - // qInfo() << "Discord RPC ready"; - // }; - // handlers.disconnected = [](int errorCode, const char* message) { - // qInfo() << "Discord RPC disconnected! " << message; - // }; - // handlers.errored = [](int errorCode, const char* message) { - // qWarning() << "Discord RPC errored out! " << message; - // }; - // qInfo() << "Initializing Discord RPC"; - // Discord_Initialize(APPLICATION_ID1, &handlers, 1, nullptr); + // Set up the minimal presence + minimal_presence->largeImageKey = "danganronpa_online"; + minimal_presence->largeImageText = "Sore Wa Chigau Yo!"; + minimal_presence->instance = 1; + minimal_presence->state = ""; + minimal_presence->details = ""; + minimal_presence->matchSecret = ""; + minimal_presence->startTimestamp = 0; start(APPLICATION_ID[0]); } @@ -62,6 +56,7 @@ void Discord::restart(const char *APPLICATION_ID) void Discord::toggle(int p_index) { + // What is the point of this? if (p_index >= 0 && p_index < 2) { if (p_index != m_index) @@ -77,88 +72,119 @@ void Discord::toggle(int p_index) void Discord::set_style(DR::DiscordRichPresenceStyle new_style) { style = new_style; - refresh_presence(&this->current_presence); + refresh_presence(); } -void Discord::refresh_presence(DiscordRichPresence *new_presence) +void Discord::refresh_presence() { - // TODO: Change presence according to style - current_presence = *new_presence; - Discord_UpdatePresence(new_presence); + switch (style) + { + case DR::DRPSComplete: + // Don't modify presence from its current status + Discord_UpdatePresence(complete_presence); + break; + case DR::DRPSMinimal: + // Use instead minimal presence + Discord_UpdatePresence(minimal_presence); + break; + case DR::DRPSDisabled: + // Wipe out + Discord_ClearPresence(); + break; + } + // Note that, no matter what, complete_presence always holds a + // fully updated presence, so it is safe to use it any moment } void Discord::state_lobby() { - DiscordRichPresence presence; - std::memset(&presence, 0, sizeof(presence)); - presence.largeImageKey = "danganronpa_online"; - presence.largeImageText = "Sore Wa Chigau Yo!"; - presence.instance = 1; - - presence.state = "In Lobby"; - presence.details = "Idle"; - refresh_presence(&presence); + this->server_id = QString("").toStdString().c_str(); + this->server_name = QString("").toStdString().c_str(); + + this->details = "Idle"; + this->state = "In Lobby"; + this->timestamp = 0; + this->matchSecret = QString("").toStdString().c_str(); + + complete_presence = new DiscordRichPresence(); + // std::memset(&complete_presence, 0, sizeof(complete_presence)); + complete_presence->largeImageKey = "danganronpa_online"; + complete_presence->largeImageText = "Sore Wa Chigau Yo!"; + complete_presence->instance = 1; + + complete_presence->state = this->state.c_str(); + complete_presence->details = this->details.c_str(); + + refresh_presence(); } void Discord::state_server(std::string name, std::string server_id) { qDebug() << "Discord RPC: Setting server state"; - DiscordRichPresence presence; - std::memset(&presence, 0, sizeof(presence)); - presence.largeImageKey = "danganronpa_online"; - presence.largeImageText = "Sore Wa Chigau Yo!"; - presence.instance = 1; - auto timestamp = static_cast(std::time(nullptr)); - presence.state = "In a Server"; - presence.details = name.c_str(); - presence.matchSecret = server_id.c_str(); - presence.startTimestamp = this->timestamp; - this->server_id = server_id; this->server_name = name; + + this->state = "In a Server"; + this->details = ""; + this->matchSecret = this->server_id; this->timestamp = timestamp; - refresh_presence(&presence); + + complete_presence = new DiscordRichPresence(); + complete_presence->largeImageKey = "danganronpa_online"; + complete_presence->largeImageText = "Sore Wa Chigau Yo!"; + complete_presence->instance = 1; + + complete_presence->state = this->state.c_str(); + complete_presence->details = this->details.c_str(); + complete_presence->matchSecret = this->matchSecret.c_str(); + complete_presence->startTimestamp = this->timestamp; + + refresh_presence(); } void Discord::state_character(std::string name) { - auto name_internal = QString(name.c_str()).toLower().replace(' ', '_').toStdString(); + // auto name_internal = QString(name.c_str()).toLower().replace(' ', '_').toStdString(); auto name_friendly = QString(name.c_str()).replace('_', ' ').toStdString(); const std::string playing_as = "Playing as " + name_friendly; qDebug() << "Discord RPC: Setting character state (" << playing_as.c_str() << ")"; - DiscordRichPresence presence; - std::memset(&presence, 0, sizeof(presence)); - presence.largeImageKey = name_internal.c_str(); - presence.instance = 1; - presence.details = this->server_name.c_str(); - presence.matchSecret = this->server_id.c_str(); - presence.startTimestamp = this->timestamp; - - presence.state = playing_as.c_str(); - presence.smallImageKey = "danganronpa_online"; - presence.smallImageText = "Danganronpa Online"; - refresh_presence(&presence); + this->state = playing_as.c_str(); + this->details = this->server_name; + + complete_presence->largeImageKey = "danganronpa_online"; + complete_presence->largeImageText = "Sore Wa Chigau Yo!"; + complete_presence->instance = 1; + + complete_presence->state = this->state.c_str(); + complete_presence->details = this->details.c_str(); + complete_presence->matchSecret = this->matchSecret.c_str(); + complete_presence->startTimestamp = this->timestamp; + + refresh_presence(); } void Discord::state_spectate() { qDebug() << "Discord RPC: Setting spectator state"; - DiscordRichPresence presence; - std::memset(&presence, 0, sizeof(presence)); - presence.largeImageKey = "danganronpa_online"; - presence.largeImageText = "Sore Wa Chigau Yo!"; - presence.instance = 1; - presence.details = this->server_name.c_str(); - presence.matchSecret = this->server_id.c_str(); - presence.startTimestamp = this->timestamp; - - presence.state = "Spectating"; - refresh_presence(&presence); + this->state = "Spectating"; + this->details = this->server_name; + + complete_presence = new DiscordRichPresence(); + complete_presence->largeImageKey = "danganronpa_online"; + complete_presence->largeImageText = "Sore Wa Chigau Yo!"; + complete_presence->instance = 1; + + complete_presence->state = this->state.c_str(); + complete_presence->details = this->details.c_str(); + complete_presence->matchSecret = this->matchSecret.c_str(); + complete_presence->startTimestamp = this->timestamp; + + refresh_presence(); } } // namespace AttorneyOnline From 8d6a255f466cff73e00c4d17830b2ecccb61aac6 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 9 Mar 2021 11:11:19 -0500 Subject: [PATCH 240/842] Remove now deprecated function+Adapt some messages --- include/discord_rich_presence.h | 6 +---- src/discord_rich_presence.cpp | 43 ++++++++++++--------------------- 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/include/discord_rich_presence.h b/include/discord_rich_presence.h index cbb77997d..5c0634e64 100644 --- a/include/discord_rich_presence.h +++ b/include/discord_rich_presence.h @@ -13,10 +13,7 @@ namespace AttorneyOnline class Discord { private: - const char *APPLICATION_ID[2] = { - "818850172330442793", - "818850172330442793", - }; // insert second one here blah blah + const char *APPLICATION_ID = "818850172330442793"; // insert second one here blah blah int m_index = 0; std::string server_name, server_id, state, details, matchSecret; int64_t timestamp; @@ -35,7 +32,6 @@ class Discord void state_spectate(); void start(const char *APPLICATION_ID); void restart(const char *APPLICATION_ID); - void toggle(int p_index); void set_style(DR::DiscordRichPresenceStyle new_style); }; diff --git a/src/discord_rich_presence.cpp b/src/discord_rich_presence.cpp index 4fa2e6bc0..86bb0887e 100644 --- a/src/discord_rich_presence.cpp +++ b/src/discord_rich_presence.cpp @@ -20,7 +20,7 @@ Discord::Discord() minimal_presence->matchSecret = ""; minimal_presence->startTimestamp = 0; - start(APPLICATION_ID[0]); + start(APPLICATION_ID); } void Discord::start(const char *APPLICATION_ID) @@ -54,21 +54,6 @@ void Discord::restart(const char *APPLICATION_ID) start(APPLICATION_ID); } -void Discord::toggle(int p_index) -{ - // What is the point of this? - if (p_index >= 0 && p_index < 2) - { - if (p_index != m_index) - { - restart(APPLICATION_ID[p_index]); - m_index = p_index; - } - } - else - qDebug() << p_index << "is not a valid APPLICATION_ID Index"; -} - void Discord::set_style(DR::DiscordRichPresenceStyle new_style) { style = new_style; @@ -101,19 +86,19 @@ void Discord::state_lobby() this->server_id = QString("").toStdString().c_str(); this->server_name = QString("").toStdString().c_str(); - this->details = "Idle"; - this->state = "In Lobby"; + this->details = "In Lobby"; + this->state = "Idle"; this->timestamp = 0; this->matchSecret = QString("").toStdString().c_str(); complete_presence = new DiscordRichPresence(); - // std::memset(&complete_presence, 0, sizeof(complete_presence)); + complete_presence->largeImageKey = "danganronpa_online"; complete_presence->largeImageText = "Sore Wa Chigau Yo!"; complete_presence->instance = 1; - complete_presence->state = this->state.c_str(); complete_presence->details = this->details.c_str(); + complete_presence->state = this->state.c_str(); refresh_presence(); } @@ -127,18 +112,19 @@ void Discord::state_server(std::string name, std::string server_id) this->server_id = server_id; this->server_name = name; - this->state = "In a Server"; - this->details = ""; + this->details = this->server_name; + this->state = "Connecting..."; this->matchSecret = this->server_id; this->timestamp = timestamp; complete_presence = new DiscordRichPresence(); + complete_presence->largeImageKey = "danganronpa_online"; complete_presence->largeImageText = "Sore Wa Chigau Yo!"; complete_presence->instance = 1; - complete_presence->state = this->state.c_str(); complete_presence->details = this->details.c_str(); + complete_presence->state = this->state.c_str(); complete_presence->matchSecret = this->matchSecret.c_str(); complete_presence->startTimestamp = this->timestamp; @@ -152,15 +138,17 @@ void Discord::state_character(std::string name) const std::string playing_as = "Playing as " + name_friendly; qDebug() << "Discord RPC: Setting character state (" << playing_as.c_str() << ")"; - this->state = playing_as.c_str(); this->details = this->server_name; + this->state = playing_as.c_str(); + + complete_presence = new DiscordRichPresence(); complete_presence->largeImageKey = "danganronpa_online"; complete_presence->largeImageText = "Sore Wa Chigau Yo!"; complete_presence->instance = 1; - complete_presence->state = this->state.c_str(); complete_presence->details = this->details.c_str(); + complete_presence->state = this->state.c_str(); complete_presence->matchSecret = this->matchSecret.c_str(); complete_presence->startTimestamp = this->timestamp; @@ -171,16 +159,17 @@ void Discord::state_spectate() { qDebug() << "Discord RPC: Setting spectator state"; - this->state = "Spectating"; this->details = this->server_name; + this->state = "Spectating"; complete_presence = new DiscordRichPresence(); + complete_presence->largeImageKey = "danganronpa_online"; complete_presence->largeImageText = "Sore Wa Chigau Yo!"; complete_presence->instance = 1; - complete_presence->state = this->state.c_str(); complete_presence->details = this->details.c_str(); + complete_presence->state = this->state.c_str(); complete_presence->matchSecret = this->matchSecret.c_str(); complete_presence->startTimestamp = this->timestamp; From f065647b0ca56b6eb9b832d900d22e190e386be6 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 9 Mar 2021 11:27:27 -0500 Subject: [PATCH 241/842] Make `Playing as` show char.ini showname --- src/courtroom.cpp | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 1788b70b3..0026d05f2 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -59,22 +59,11 @@ void Courtroom::enter_courtroom(int p_cid) else { f_char = ao_app->get_char_name(char_list.at(m_cid).name); - QString r_char = f_char; - /* - // regex for removing non letter (except _) characters - QRegularExpression re(QString::fromUtf8("[-`~!@#$%^&*()—+=|:;<>«»,.?/{}\'\"\\[\\]]")); - r_char.remove(re); + QString showname_char = ao_app->get_showname(f_char); + if (showname_char.isEmpty()) + showname_char = f_char; - if (!rpc_char_list.contains(f_char.toLower())) - { - ao_app->discord->toggle(1); - } - else - { - ao_app->discord->toggle(0); - }*/ - - ao_app->discord->state_character(r_char.toStdString()); + ao_app->discord->state_character(showname_char.toStdString()); } current_char = f_char; From 0b27701cef7415dd2d13daca04afccbade710d02 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 9 Mar 2021 11:53:06 -0500 Subject: [PATCH 242/842] Add tooltips and cleanup --- res/ui/config_panel.ui | 22 +++++++++++++++++++++- src/discord_rich_presence.cpp | 4 ---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 9c533d48b..05c111488 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 0 + 3 @@ -109,12 +109,26 @@ 0 + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sets the preferred Discord Rich Status style.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" color:#ff0000;">[WARNING] </span>Changes may take up to 15 seconds to be reflected in your Discord profile. Moreover, Discord has a rate limit of one update per 15 seconds. The more you fiddle with the options too fast, the longer it may take for your style to be updated.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; color:#8a602c;"><br /></p></body></html> + Discord Rich Status Style + + <html><head/><body><p>* Shows you are playing the game.</p><p>* Shows the server you are connected to.</p><p>* Shows the name of the character you are playing as.</p></body></html> + Complete @@ -122,6 +136,9 @@ + + <html><head/><body><p>* Shows you are playing the game.</p><p>* Does not show the server you are connected to.</p><p>* Does not show the name of the character you are playing as.</p></body></html> + Minimal @@ -129,6 +146,9 @@ + + <html><head/><body><p>* Does not show you are playing the game.</p><p>* Does not show the server you are connected to.</p><p>* Does not show the name of the character you are playing as.</p></body></html> + Disabled diff --git a/src/discord_rich_presence.cpp b/src/discord_rich_presence.cpp index 86bb0887e..5f4eb8caa 100644 --- a/src/discord_rich_presence.cpp +++ b/src/discord_rich_presence.cpp @@ -65,15 +65,12 @@ void Discord::refresh_presence() switch (style) { case DR::DRPSComplete: - // Don't modify presence from its current status Discord_UpdatePresence(complete_presence); break; case DR::DRPSMinimal: - // Use instead minimal presence Discord_UpdatePresence(minimal_presence); break; case DR::DRPSDisabled: - // Wipe out Discord_ClearPresence(); break; } @@ -133,7 +130,6 @@ void Discord::state_server(std::string name, std::string server_id) void Discord::state_character(std::string name) { - // auto name_internal = QString(name.c_str()).toLower().replace(' ', '_').toStdString(); auto name_friendly = QString(name.c_str()).replace('_', ' ').toStdString(); const std::string playing_as = "Playing as " + name_friendly; qDebug() << "Discord RPC: Setting character state (" << playing_as.c_str() << ")"; From 5cb88d1b0d7ff820144163b60ddd15e859573783 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 9 Mar 2021 11:57:53 -0500 Subject: [PATCH 243/842] Remove more leftover comments --- include/discord_rich_presence.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/discord_rich_presence.h b/include/discord_rich_presence.h index 5c0634e64..916af626f 100644 --- a/include/discord_rich_presence.h +++ b/include/discord_rich_presence.h @@ -13,7 +13,7 @@ namespace AttorneyOnline class Discord { private: - const char *APPLICATION_ID = "818850172330442793"; // insert second one here blah blah + const char *APPLICATION_ID = "818850172330442793"; int m_index = 0; std::string server_name, server_id, state, details, matchSecret; int64_t timestamp; From abd7ccdc3f735b1b78081e8495a0efeb703f7bed Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 9 Mar 2021 15:15:33 -0500 Subject: [PATCH 244/842] Add halign --- src/courtroom_widgets.cpp | 40 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index bb2728ec4..ab5c0a83e 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1420,10 +1420,48 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, QString override int bold = ao_app->get_font_property(p_identifier + "_bold", design_file); QString is_bold = (bold == 1 ? "bold" : ""); + QString alignment = " "; + int halign = ao_app->get_font_property(p_identifier + "_halign", design_file); + switch (halign) + { + case 0: + alignment += "AlignLeft"; + break; + case 1: + alignment += "AlignHCenter | "; + break; + case 2: + alignment += "AlignRight | "; + break; + default: + qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; + alignment += "AlignLeft | "; + } + /* + int valign = ao_app->get_font_property(p_identifier + "_halign", design_file); + switch (valign) + { + case 0: + alignment += "AlignTop"; + break; + case 1: + alignment += "AlignVCenter"; + break; + case 2: + alignment += "AlignBottom"; + break; + default: + qWarning() << "Unknown vertical alignment for " + p_identifier + ". Assuming Top."; + alignment += "AlignTop"; + } + */ QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: " + override_color + ";\n" "font: " + - is_bold + "; }"; + is_bold + + ";\n" + "qproperty-alignment: " + + alignment + "; }"; widget->setStyleSheet(style_sheet_string); } From b2db86a3c03704fd03ae6bc5a22ebbcaf1a90c1f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 9 Mar 2021 15:44:17 -0500 Subject: [PATCH 245/842] Move QTextEdit uses to DRTextEdit --- dronline-client.pro | 2 + include/aonotepad.h | 5 +-- include/aotimer.h | 9 +++-- include/courtroom.h | 18 ++++----- include/drtextedit.h | 15 ++++++++ include/lobby.h | 9 +++-- src/aonotepad.cpp | 2 +- src/aotimer.cpp | 2 +- src/courtroom.cpp | 2 +- src/courtroom_widgets.cpp | 77 +++++++++++++++++++-------------------- src/drtextedit.cpp | 15 ++++++++ src/lobby.cpp | 16 ++++---- 12 files changed, 101 insertions(+), 71 deletions(-) create mode 100644 include/drtextedit.h create mode 100644 src/drtextedit.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 30646694f..e93369022 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -55,6 +55,7 @@ HEADERS += \ include/draudioerror.h \ include/draudiostream.h \ include/draudiostreamfamily.h \ + include/drtextedit.h \ include/file_functions.h \ include/hardware_functions.h \ include/lobby.h \ @@ -105,6 +106,7 @@ SOURCES += \ src/draudioerror.cpp \ src/draudiostream.cpp \ src/draudiostreamfamily.cpp \ + src/drtextedit.cpp \ src/emotes.cpp \ src/evidence.cpp \ src/file_functions.cpp \ diff --git a/include/aonotepad.h b/include/aonotepad.h index b9a74ee42..6684dcbe1 100644 --- a/include/aonotepad.h +++ b/include/aonotepad.h @@ -1,11 +1,10 @@ #ifndef AONOTEPAD_H #define AONOTEPAD_H -#include - #include "aoapplication.h" +#include "drtextedit.h" -class AONotepad : public QTextEdit +class AONotepad : public DRTextEdit { Q_OBJECT diff --git a/include/aotimer.h b/include/aotimer.h index d053bfc4e..c3a6b76e2 100644 --- a/include/aotimer.h +++ b/include/aotimer.h @@ -2,12 +2,13 @@ #define AOTIMER_H #include -#include #include #include #include -#include -#include + +#include "aobutton.h" +#include "aolabel.h" +#include "drtextedit.h" class ManualTimer { @@ -38,7 +39,7 @@ class ManualTimer } }; -class AOTimer : public QTextEdit +class AOTimer : public DRTextEdit { Q_OBJECT diff --git a/include/courtroom.h b/include/courtroom.h index 0d3f73a79..9cbd93c0a 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -98,10 +98,10 @@ class Courtroom : public QMainWindow // same as above, but use override color as color if it is not an empty // string, otherwise use normal logic for color of set_font void set_font(QWidget *widget, QString p_identifier, QString override_color); - // sets font properties for QTextEdit (same as above but also text outline) - void set_qtextedit_font(QTextEdit *widget, QString p_identifier); - // same as second set_font but for qtextedit - void set_qtextedit_font(QTextEdit *widget, QString p_identifier, QString override_color); + // sets font properties for DRTextEdit (same as above but also text outline) + void set_drtextedit_font(DRTextEdit *widget, QString p_identifier); + // same as second set_font but for drtextedit + void set_drtextedit_font(DRTextEdit *widget, QString p_identifier, QString override_color); // helper function that calls above function on the relevant widgets void set_fonts(); @@ -474,11 +474,11 @@ class Courtroom : public QMainWindow QVector rpc_char_list; AOImageDisplay *ui_vp_notepad_image = nullptr; - QTextEdit *ui_vp_notepad = nullptr; + DRTextEdit *ui_vp_notepad = nullptr; AOImageDisplay *ui_vp_chatbox = nullptr; - QTextEdit *ui_vp_showname = nullptr; - QTextEdit *ui_vp_message = nullptr; + DRTextEdit *ui_vp_showname = nullptr; + DRTextEdit *ui_vp_message = nullptr; AOImageDisplay *ui_vp_testimony = nullptr; AOMovie *ui_vp_effect = nullptr; AOMovie *ui_vp_wtce = nullptr; @@ -489,7 +489,7 @@ class Courtroom : public QMainWindow AOImageDisplay *ui_vp_showname_image = nullptr; - QTextEdit *ui_vp_music_name = nullptr; + DRTextEdit *ui_vp_music_name = nullptr; QPropertyAnimation *music_anim = nullptr; QWidget *ui_vp_music_area = nullptr; @@ -497,7 +497,7 @@ class Courtroom : public QMainWindow AOMovie *ui_vp_clock = nullptr; QVector ui_timers; - QTextEdit *ui_ic_chatlog = nullptr; + DRTextEdit *ui_ic_chatlog = nullptr; record_type_array m_ic_records; AOTextArea *ui_server_chatlog = nullptr; diff --git a/include/drtextedit.h b/include/drtextedit.h new file mode 100644 index 000000000..580fa9ba0 --- /dev/null +++ b/include/drtextedit.h @@ -0,0 +1,15 @@ +#ifndef DRTEXTEDIT_H +#define DRTEXTEDIT_H + +#include +#include + +class DRTextEdit : public QTextEdit +{ +public: + DRTextEdit(QWidget *p_parent); + + void setOutline(bool outline); +}; + +#endif // DRTEXTEDIT_H diff --git a/include/lobby.h b/include/lobby.h index f32a3af6a..82498efcc 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -5,6 +5,7 @@ #include "aoimagedisplay.h" #include "aopacket.h" #include "aotextarea.h" +#include "drtextedit.h" #include #include @@ -35,7 +36,7 @@ class Lobby : public QMainWindow void set_stylesheets(); void set_fonts(); void set_font(QWidget *widget, QString p_identifier); - void set_qtextedit_font(QTextEdit *widget, QString p_identifier); + void set_drtextedit_font(DRTextEdit *widget, QString p_identifier); void show_loading_overlay() { ui_loading_background->show(); @@ -63,12 +64,12 @@ class Lobby : public QMainWindow AOButton *ui_add_to_fav = nullptr; AOButton *ui_connect = nullptr; - QTextEdit *ui_version = nullptr; + DRTextEdit *ui_version = nullptr; AOButton *ui_about = nullptr; QListWidget *ui_server_list = nullptr; - QTextEdit *ui_player_count = nullptr; + DRTextEdit *ui_player_count = nullptr; AOTextArea *ui_description = nullptr; AOTextArea *ui_chatbox = nullptr; @@ -77,7 +78,7 @@ class Lobby : public QMainWindow QLineEdit *ui_chatmessage = nullptr; AOImageDisplay *ui_loading_background = nullptr; - QTextEdit *ui_loading_text = nullptr; + DRTextEdit *ui_loading_text = nullptr; QProgressBar *ui_progress_bar = nullptr; AOButton *ui_cancel = nullptr; diff --git a/src/aonotepad.cpp b/src/aonotepad.cpp index 4723bee0f..d21fcf69d 100644 --- a/src/aonotepad.cpp +++ b/src/aonotepad.cpp @@ -1,5 +1,5 @@ #include "aonotepad.h" -AONotepad::AONotepad(QWidget *p_parent, AOApplication *p_ao_app) : QTextEdit(p_parent), ao_app(p_ao_app) +AONotepad::AONotepad(QWidget *p_parent, AOApplication *p_ao_app) : DRTextEdit(p_parent), ao_app(p_ao_app) { } diff --git a/src/aotimer.cpp b/src/aotimer.cpp index 7bed24191..968de2129 100644 --- a/src/aotimer.cpp +++ b/src/aotimer.cpp @@ -1,7 +1,7 @@ #include "aotimer.h" #include -AOTimer::AOTimer(QWidget *p_parent) : QTextEdit(p_parent) +AOTimer::AOTimer(QWidget *p_parent) : DRTextEdit(p_parent) { // Adapted from: // https://stackoverflow.com/questions/36679708/how-to-make-a-chronometer-in-qt-c diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 13abd2ab3..c2c5b12f6 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -898,7 +898,7 @@ void Courtroom::handle_chatmessage_2() // handles IC // Check if char.ini has color property, which overrides the theme's default // showname color QString f_color = ao_app->read_char_ini(real_name, "color", "[Options]", "[Time]"); - set_qtextedit_font(ui_vp_showname, "showname", f_color); + set_drtextedit_font(ui_vp_showname, "showname", f_color); ui_vp_showname->setText(f_showname); ui_vp_showname->setAlignment(Qt::AlignVCenter); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index ab5c0a83e..9b465e2d5 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -62,7 +62,7 @@ void Courtroom::create_widgets() ui_vp_music_display_a = new AOImageDisplay(this, ao_app); ui_vp_music_display_b = new AOImageDisplay(this, ao_app); ui_vp_music_area = new QWidget(ui_vp_music_display_a); - ui_vp_music_name = new QTextEdit(ui_vp_music_area); + ui_vp_music_name = new DRTextEdit(ui_vp_music_area); ui_vp_music_name->setText("DANGANRONPA ONLINE"); ui_vp_music_name->setFrameStyle(QFrame::NoFrame); ui_vp_music_name->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -76,12 +76,12 @@ void Courtroom::create_widgets() ui_vp_evidence_display = new AOEvidenceDisplay(this, ao_app); ui_vp_chatbox = new AOImageDisplay(this, ao_app); - ui_vp_showname = new QTextEdit(ui_vp_chatbox); + ui_vp_showname = new DRTextEdit(ui_vp_chatbox); ui_vp_showname->setFrameStyle(QFrame::NoFrame); ui_vp_showname->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_vp_showname->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_vp_showname->setReadOnly(true); - ui_vp_message = new QTextEdit(ui_vp_chatbox); + ui_vp_message = new DRTextEdit(ui_vp_chatbox); ui_vp_message->setFrameStyle(QFrame::NoFrame); ui_vp_message->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_vp_message->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -94,7 +94,7 @@ void Courtroom::create_widgets() ui_vp_wtce = new AOMovie(this, ao_app); ui_vp_objection = new AOMovie(this, ao_app); - ui_ic_chatlog = new QTextEdit(this); + ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); ui_server_chatlog = new AOTextArea(this); @@ -211,7 +211,7 @@ void Courtroom::create_widgets() ui_evidence_button = new AOButton(this, ao_app); ui_vp_notepad_image = new AOImageDisplay(this, ao_app); - ui_vp_notepad = new QTextEdit(this); + ui_vp_notepad = new DRTextEdit(this); ui_vp_notepad->setFrameStyle(QFrame::NoFrame); ui_timers.resize(1); @@ -1437,24 +1437,7 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, QString override qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; alignment += "AlignLeft | "; } - /* - int valign = ao_app->get_font_property(p_identifier + "_halign", design_file); - switch (valign) - { - case 0: - alignment += "AlignTop"; - break; - case 1: - alignment += "AlignVCenter"; - break; - case 2: - alignment += "AlignBottom"; - break; - default: - qWarning() << "Unknown vertical alignment for " + p_identifier + ". Assuming Top."; - alignment += "AlignTop"; - } - */ + QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: " + override_color + ";\n" "font: " + @@ -1465,39 +1448,53 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, QString override widget->setStyleSheet(style_sheet_string); } -void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier) +void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) { - set_qtextedit_font(widget, p_identifier, ""); + set_drtextedit_font(widget, p_identifier, ""); } -void Courtroom::set_qtextedit_font(QTextEdit *widget, QString p_identifier, QString override_color) +void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier, QString override_color) { set_font(widget, p_identifier, override_color); QString design_file = fonts_ini; - QTextCharFormat widget_format = widget->currentCharFormat(); - if (ao_app->get_font_property(p_identifier + "_outline", design_file) == 1) - widget_format.setTextOutline(QPen(Qt::black, 1)); - else - widget_format.setTextOutline(Qt::NoPen); - widget->setCurrentCharFormat(widget_format); + bool outline = (ao_app->get_font_property(p_identifier + "_outline", design_file) == 1); + widget->setOutline(outline); + + /* + int valign = ao_app->get_font_property(p_identifier + "_halign", design_file); + switch (valign) + { + case 0: + alignment += "AlignTop"; + break; + case 1: + alignment += "AlignVCenter"; + break; + case 2: + alignment += "AlignBottom"; + break; + default: + qWarning() << "Unknown vertical alignment for " + p_identifier + ". Assuming Top."; + alignment += "AlignTop"; + }*/ } void Courtroom::set_fonts() { - set_qtextedit_font(ui_vp_showname, "showname"); - set_qtextedit_font(ui_vp_message, "message"); - set_qtextedit_font(ui_ic_chatlog, "ic_chatlog"); - set_font(ui_server_chatlog, - "server_chatlog"); // Chatlog does not support qtextedit because html + set_drtextedit_font(ui_vp_showname, "showname"); + set_drtextedit_font(ui_vp_message, "message"); + set_drtextedit_font(ui_ic_chatlog, "ic_chatlog"); + // Chatlog does not support drtextedit because html + set_font(ui_server_chatlog, "server_chatlog"); set_font(ui_music_list, "music_list"); set_font(ui_area_list, "area_list"); set_font(ui_sfx_list, "sfx_list"); - set_qtextedit_font(ui_vp_music_name, "music_name"); - set_qtextedit_font(ui_vp_notepad, "notepad"); + set_drtextedit_font(ui_vp_music_name, "music_name"); + set_drtextedit_font(ui_vp_notepad, "notepad"); for (int i = 0; i < timer_number; i++) { - set_qtextedit_font(ui_timers[i], "timer_" + QString::number(i)); + set_drtextedit_font(ui_timers[i], "timer_" + QString::number(i)); } } diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp new file mode 100644 index 000000000..923946b9c --- /dev/null +++ b/src/drtextedit.cpp @@ -0,0 +1,15 @@ +#include "drtextedit.h" + +DRTextEdit::DRTextEdit(QWidget *parent) : QTextEdit(parent) +{ +} + +void DRTextEdit::setOutline(bool outline) +{ + QTextCharFormat widget_format = currentCharFormat(); + if (outline) + widget_format.setTextOutline(QPen(Qt::black, 1)); + else + widget_format.setTextOutline(Qt::NoPen); + setCurrentCharFormat(widget_format); +} diff --git a/src/lobby.cpp b/src/lobby.cpp index 2970d9fee..89ed4fede 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -3,6 +3,7 @@ #include "aoapplication.h" #include "aosfxplayer.h" #include "debug_functions.h" +#include "drtextedit.h" #include "networkmanager.h" #include @@ -10,7 +11,6 @@ #include #include #include -#include Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() { @@ -24,14 +24,14 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_refresh = new AOButton(this, ao_app); ui_add_to_fav = new AOButton(this, ao_app); ui_connect = new AOButton(this, ao_app); - ui_version = new QTextEdit(this); + ui_version = new DRTextEdit(this); ui_version->setFrameStyle(QFrame::NoFrame); ui_version->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_version->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_version->setReadOnly(true); ui_about = new AOButton(this, ao_app); ui_server_list = new QListWidget(this); - ui_player_count = new QTextEdit(this); + ui_player_count = new DRTextEdit(this); ui_player_count->setFrameStyle(QFrame::NoFrame); ui_player_count->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_player_count->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -43,7 +43,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_chatname->setPlaceholderText("Name"); ui_chatmessage = new QLineEdit(this); ui_loading_background = new AOImageDisplay(this, ao_app); - ui_loading_text = new QTextEdit(ui_loading_background); + ui_loading_text = new DRTextEdit(ui_loading_background); ui_progress_bar = new QProgressBar(ui_loading_background); ui_progress_bar->setMinimum(0); ui_progress_bar->setMaximum(100); @@ -191,12 +191,12 @@ void Lobby::set_size_and_pos(QWidget *p_widget, QString p_identifier) void Lobby::set_fonts() { - set_qtextedit_font(ui_player_count, "player_count"); - set_qtextedit_font(ui_description, "description"); + set_drtextedit_font(ui_player_count, "player_count"); + set_font(ui_description, "description"); set_font(ui_chatbox, "chatbox"); set_font(ui_chatname, "chatname"); set_font(ui_chatmessage, "chatmessage"); - set_qtextedit_font(ui_loading_text, "loading_text"); + set_drtextedit_font(ui_loading_text, "loading_text"); set_font(ui_server_list, "server_list"); } @@ -260,7 +260,7 @@ void Lobby::set_font(QWidget *widget, QString p_identifier) widget->setStyleSheet(style_sheet_string); } -void Lobby::set_qtextedit_font(QTextEdit *widget, QString p_identifier) +void Lobby::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) { set_font(widget, p_identifier); From 9c9c3f9f792eb7dff01a382f36fa27524a8a109d Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 9 Mar 2021 16:59:14 -0500 Subject: [PATCH 246/842] Make onTextChanged() detect when a new visual line is added --- include/drtextedit.h | 10 ++++++++++ src/drtextedit.cpp | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/include/drtextedit.h b/include/drtextedit.h index 580fa9ba0..5709dfba1 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -6,10 +6,20 @@ class DRTextEdit : public QTextEdit { + Q_OBJECT + public: DRTextEdit(QWidget *p_parent); void setOutline(bool outline); + int heightSpan(); + +private: + Qt::Alignment verticalAlignment = Qt::AlignTop; + int previous_height = 0; + +private slots: + void onTextChanged(); }; #endif // DRTEXTEDIT_H diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index 923946b9c..256f61d87 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -1,7 +1,13 @@ #include "drtextedit.h" +#include "debug_functions.h" + +#include +#include DRTextEdit::DRTextEdit(QWidget *parent) : QTextEdit(parent) { + connect(this, SIGNAL(textChanged()), this, SLOT(onTextChanged())); + // setViewportMargins(30, 30, 30, 30); // margins are in pixels } void DRTextEdit::setOutline(bool outline) @@ -13,3 +19,17 @@ void DRTextEdit::setOutline(bool outline) widget_format.setTextOutline(Qt::NoPen); setCurrentCharFormat(widget_format); } + +int DRTextEdit::heightSpan() +{ + return document()->size().height(); +} + +void DRTextEdit::onTextChanged() +{ + int new_height = heightSpan(); + if (new_height == previous_height) + return; + previous_height = new_height; + qDebug() << this << previous_height; +} From 5e4b7507b0c65a6faf14cf79801710c4a4e56594 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 9 Mar 2021 19:25:33 -0500 Subject: [PATCH 247/842] Implement vertical alignment. I may be the first person on the internet to ever achieve this with a QTextEdit Or at least that is what my Google Fu said after countless searches. Perhaps someone did it in a closed source thing, but hey, have it for free here --- include/drtextedit.h | 8 ++++-- src/courtroom_widgets.cpp | 25 +++++++++--------- src/drtextedit.cpp | 54 +++++++++++++++++++++++++++++++++++---- 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/include/drtextedit.h b/include/drtextedit.h index 5709dfba1..ac5013879 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -12,10 +12,14 @@ class DRTextEdit : public QTextEdit DRTextEdit(QWidget *p_parent); void setOutline(bool outline); - int heightSpan(); + void setVerticalAlignment(Qt::Alignment); + + bool outline(); + Qt::Alignment verticalAlignment(); private: - Qt::Alignment verticalAlignment = Qt::AlignTop; + bool _outline = false; + Qt::Alignment _verticalAlignment = Qt::AlignTop; int previous_height = 0; private slots: diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 9b465e2d5..486850b0c 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1456,28 +1456,29 @@ void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier, QString override_color) { set_font(widget, p_identifier, override_color); - - QString design_file = fonts_ini; - bool outline = (ao_app->get_font_property(p_identifier + "_outline", design_file) == 1); + // Do outlines + bool outline = (ao_app->get_font_property(p_identifier + "_outline", fonts_ini) == 1); widget->setOutline(outline); - /* - int valign = ao_app->get_font_property(p_identifier + "_halign", design_file); - switch (valign) + // Do vertical alignments + int raw_valign = ao_app->get_font_property(p_identifier + "_valign", fonts_ini); + Qt::Alignment valignment; + switch (raw_valign) { case 0: - alignment += "AlignTop"; + valignment = Qt::AlignTop; break; case 1: - alignment += "AlignVCenter"; + valignment = Qt::AlignVCenter; break; case 2: - alignment += "AlignBottom"; + valignment = Qt::AlignBottom; break; default: - qWarning() << "Unknown vertical alignment for " + p_identifier + ". Assuming Top."; - alignment += "AlignTop"; - }*/ + qWarning() << "Unknown vertical alignment for" << p_identifier << ":" << raw_valign << "Assuming Top."; + valignment = Qt::AlignTop; + } + widget->setVerticalAlignment(valignment); } void Courtroom::set_fonts() diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index 256f61d87..1688960a5 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -7,7 +7,6 @@ DRTextEdit::DRTextEdit(QWidget *parent) : QTextEdit(parent) { connect(this, SIGNAL(textChanged()), this, SLOT(onTextChanged())); - // setViewportMargins(30, 30, 30, 30); // margins are in pixels } void DRTextEdit::setOutline(bool outline) @@ -18,18 +17,63 @@ void DRTextEdit::setOutline(bool outline) else widget_format.setTextOutline(Qt::NoPen); setCurrentCharFormat(widget_format); + + this->_outline = outline; +} + +bool DRTextEdit::outline() +{ + return this->_outline; +} + +void DRTextEdit::setVerticalAlignment(Qt::Alignment verticalAlignment) +{ + // If an invalid vertical alignment is passed, convert to Top. + if (verticalAlignment != Qt::AlignTop && verticalAlignment != Qt::AlignVCenter && + verticalAlignment != Qt::AlignBottom) + verticalAlignment = Qt::AlignTop; + this->_verticalAlignment = verticalAlignment; } -int DRTextEdit::heightSpan() +Qt::Alignment DRTextEdit::verticalAlignment() { - return document()->size().height(); + return this->_verticalAlignment; } void DRTextEdit::onTextChanged() { - int new_height = heightSpan(); + // Do computations to align text vertically according to preference + // We do these computations every time the text changes, so we better be efficient! + + // This stores the total height of the totality of the text saved. + int new_height = document()->size().height(); + // If the height did not change, exit early, as we don't need to do much formatting if (new_height == previous_height) return; previous_height = new_height; - qDebug() << this << previous_height; + + // If the height exceeds the height of the widget, aligning vertically does not make much sense + // so we exit early. + if (new_height > height()) + return; + + // The way we will simulate vertical alignment is by adjusting the top margin to simulate + // center alignment, or bottom alignment. + int topMargin = 0; + switch (_verticalAlignment) + { + case Qt::AlignTop: + return; // Don't need to do much here + case Qt::AlignVCenter: + topMargin = (height() - new_height) / 2; + break; + case Qt::AlignBottom: + topMargin = (height() - new_height); + break; + default: + qDebug() << "Unrecognized vertical alignment " << this->_verticalAlignment << ". Assuming Top."; + break; + } + + setViewportMargins(0, topMargin, 0, 0); } From bd400f777783afb17f3e17fce86b81db5939f6be Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 9 Mar 2021 21:13:02 -0500 Subject: [PATCH 248/842] Properly implement horizontal alignment --- include/drtextedit.h | 4 ++++ src/courtroom_widgets.cpp | 41 ++++++++++++++++++--------------------- src/drtextedit.cpp | 36 +++++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/include/drtextedit.h b/include/drtextedit.h index ac5013879..8f62f6182 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -13,13 +13,17 @@ class DRTextEdit : public QTextEdit void setOutline(bool outline); void setVerticalAlignment(Qt::Alignment); + void setHorizontalAlignment(Qt::Alignment); bool outline(); Qt::Alignment verticalAlignment(); + Qt::Alignment horizontalAlignment(); private: bool _outline = false; Qt::Alignment _verticalAlignment = Qt::AlignTop; + Qt::Alignment _horizontalAlignment = Qt::AlignLeft; + int previous_length = 0; int previous_height = 0; private slots: diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 486850b0c..178a85978 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1420,31 +1420,10 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, QString override int bold = ao_app->get_font_property(p_identifier + "_bold", design_file); QString is_bold = (bold == 1 ? "bold" : ""); - QString alignment = " "; - int halign = ao_app->get_font_property(p_identifier + "_halign", design_file); - switch (halign) - { - case 0: - alignment += "AlignLeft"; - break; - case 1: - alignment += "AlignHCenter | "; - break; - case 2: - alignment += "AlignRight | "; - break; - default: - qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; - alignment += "AlignLeft | "; - } - QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: " + override_color + ";\n" "font: " + - is_bold + - ";\n" - "qproperty-alignment: " + - alignment + "; }"; + is_bold + "; }"; widget->setStyleSheet(style_sheet_string); } @@ -1460,6 +1439,24 @@ void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier, QS bool outline = (ao_app->get_font_property(p_identifier + "_outline", fonts_ini) == 1); widget->setOutline(outline); + // Do horizontal alignments + int raw_halign = ao_app->get_font_property(p_identifier + "_halign", fonts_ini); + switch (raw_halign) + { + case 0: + widget->setHorizontalAlignment(Qt::AlignLeft); + break; + case 1: + widget->setHorizontalAlignment(Qt::AlignHCenter); + break; + case 2: + widget->setHorizontalAlignment(Qt::AlignRight); + break; + default: + qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; + widget->setHorizontalAlignment(Qt::AlignLeft); + } + // Do vertical alignments int raw_valign = ao_app->get_font_property(p_identifier + "_valign", fonts_ini); Qt::Alignment valignment; diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index 1688960a5..c8b39612d 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -33,6 +33,12 @@ void DRTextEdit::setVerticalAlignment(Qt::Alignment verticalAlignment) verticalAlignment != Qt::AlignBottom) verticalAlignment = Qt::AlignTop; this->_verticalAlignment = verticalAlignment; + + // Refresh. To get around the fact both height and length did not change, we set previous_height + // and previous_length to an invalid -1. This will be fixed in the onTextChanged() call + previous_length = -1; + previous_height = -1; + onTextChanged(); } Qt::Alignment DRTextEdit::verticalAlignment() @@ -40,8 +46,36 @@ Qt::Alignment DRTextEdit::verticalAlignment() return this->_verticalAlignment; } +void DRTextEdit::setHorizontalAlignment(Qt::Alignment horizontalAlignment) +{ + // If an invalid horizontal alignment is passed, convert to Left. + if (horizontalAlignment != Qt::AlignLeft && horizontalAlignment != Qt::AlignHCenter && + horizontalAlignment != Qt::AlignRight) + horizontalAlignment = Qt::AlignLeft; + this->_horizontalAlignment = horizontalAlignment; + + // Refresh. To get around the fact length did not change, we set previous_length + // to an invalid -1. This will be fixed in the onTextChanged() call + previous_length = -1; + onTextChanged(); +} + +Qt::Alignment DRTextEdit::horizontalAlignment() +{ + return this->_horizontalAlignment; +} + void DRTextEdit::onTextChanged() { + // We do not care about cases where the length of text has not changed. + // This also prevents recursive calls of this slot. + if (document()->toPlainText().length() == previous_length) + return; + previous_length = document()->toPlainText().length(); + + // Do computations to align text horizontally + setAlignment(_horizontalAlignment); + // Do computations to align text vertically according to preference // We do these computations every time the text changes, so we better be efficient! @@ -63,7 +97,7 @@ void DRTextEdit::onTextChanged() switch (_verticalAlignment) { case Qt::AlignTop: - return; // Don't need to do much here + break; // Don't need to do much here case Qt::AlignVCenter: topMargin = (height() - new_height) / 2; break; From c41b91f80a442d5fd574b17bb251cc0985911985 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 9 Mar 2021 22:12:49 -0500 Subject: [PATCH 249/842] Fix setText resetting alignments --- include/drtextedit.h | 4 +++- src/courtroom.cpp | 1 - src/drtextedit.cpp | 21 +++++++++++---------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/include/drtextedit.h b/include/drtextedit.h index 8f62f6182..40a49c52b 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -23,9 +23,11 @@ class DRTextEdit : public QTextEdit bool _outline = false; Qt::Alignment _verticalAlignment = Qt::AlignTop; Qt::Alignment _horizontalAlignment = Qt::AlignLeft; - int previous_length = 0; int previous_height = 0; + bool processing_change = false; + void _onTextChanged(); + private slots: void onTextChanged(); }; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index c2c5b12f6..dce26c417 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -901,7 +901,6 @@ void Courtroom::handle_chatmessage_2() // handles IC set_drtextedit_font(ui_vp_showname, "showname", f_color); ui_vp_showname->setText(f_showname); - ui_vp_showname->setAlignment(Qt::AlignVCenter); ui_vp_message->clear(); ui_vp_chatbox->hide(); diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index c8b39612d..f9c27d9ee 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -35,8 +35,7 @@ void DRTextEdit::setVerticalAlignment(Qt::Alignment verticalAlignment) this->_verticalAlignment = verticalAlignment; // Refresh. To get around the fact both height and length did not change, we set previous_height - // and previous_length to an invalid -1. This will be fixed in the onTextChanged() call - previous_length = -1; + // to an invalid -1. This will be fixed in the onTextChanged() call previous_height = -1; onTextChanged(); } @@ -53,10 +52,7 @@ void DRTextEdit::setHorizontalAlignment(Qt::Alignment horizontalAlignment) horizontalAlignment != Qt::AlignRight) horizontalAlignment = Qt::AlignLeft; this->_horizontalAlignment = horizontalAlignment; - - // Refresh. To get around the fact length did not change, we set previous_length - // to an invalid -1. This will be fixed in the onTextChanged() call - previous_length = -1; + // Refresh. onTextChanged(); } @@ -67,12 +63,17 @@ Qt::Alignment DRTextEdit::horizontalAlignment() void DRTextEdit::onTextChanged() { - // We do not care about cases where the length of text has not changed. - // This also prevents recursive calls of this slot. - if (document()->toPlainText().length() == previous_length) + // Avoid recursion + if (processing_change) return; - previous_length = document()->toPlainText().length(); + processing_change = true; + _onTextChanged(); + processing_change = false; +} + +void DRTextEdit::_onTextChanged() +{ // Do computations to align text horizontally setAlignment(_horizontalAlignment); From 6e7a1984fa27d46090a6b73ba04e9c2150c6883b Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 9 Mar 2021 22:26:19 -0500 Subject: [PATCH 250/842] Add improved alignment options for lobby assets (not that many but eh) --- src/lobby.cpp | 57 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 89ed4fede..85377b81c 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -246,16 +246,10 @@ void Lobby::set_font(QWidget *widget, QString p_identifier) if (bold) is_bold = "bold"; - bool center = (bool)ao_app->get_font_property(p_identifier + "_center", design_file); - QString is_center = ""; - if (center) - is_center = "qproperty-alignment: AlignCenter;"; - QString class_name = widget->metaObject()->className(); QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: rgba(" + QString::number(f_color.red()) + ", " + QString::number(f_color.green()) + ", " + - QString::number(f_color.blue()) + ", 255);\n" + is_center + "\n" + "font: " + is_bold + - "; }"; + QString::number(f_color.blue()) + ", 255);\n" + "font: " + is_bold + "; }"; widget->setStyleSheet(style_sheet_string); } @@ -264,13 +258,48 @@ void Lobby::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) { set_font(widget, p_identifier); - QString design_file = "lobby_fonts.ini"; - QTextCharFormat widget_format = widget->currentCharFormat(); - if (ao_app->get_font_property(p_identifier + "_outline", design_file) == 1) - widget_format.setTextOutline(QPen(Qt::black, 1)); - else - widget_format.setTextOutline(Qt::NoPen); - widget->setCurrentCharFormat(widget_format); + QString fonts_ini = "lobby_fonts.ini"; + // Do outlines + bool outline = (ao_app->get_font_property(p_identifier + "_outline", fonts_ini) == 1); + widget->setOutline(outline); + + // Do horizontal alignments + int raw_halign = ao_app->get_font_property(p_identifier + "_halign", fonts_ini); + switch (raw_halign) + { + case 0: + widget->setHorizontalAlignment(Qt::AlignLeft); + break; + case 1: + widget->setHorizontalAlignment(Qt::AlignHCenter); + break; + case 2: + widget->setHorizontalAlignment(Qt::AlignRight); + break; + default: + qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; + widget->setHorizontalAlignment(Qt::AlignLeft); + } + + // Do vertical alignments + int raw_valign = ao_app->get_font_property(p_identifier + "_valign", fonts_ini); + Qt::Alignment valignment; + switch (raw_valign) + { + case 0: + valignment = Qt::AlignTop; + break; + case 1: + valignment = Qt::AlignVCenter; + break; + case 2: + valignment = Qt::AlignBottom; + break; + default: + qWarning() << "Unknown vertical alignment for" << p_identifier << ":" << raw_valign << "Assuming Top."; + valignment = Qt::AlignTop; + } + widget->setVerticalAlignment(valignment); } void Lobby::set_loading_text(QString p_text) From ebcbdd379980f174c6d115fbc99a8300946bd413 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 10 Mar 2021 16:33:10 +0100 Subject: [PATCH 251/842] Introduced DRDiscord * Added new Discord integration implementation --- dronline-client.pro | 2 + include/aoapplication.h | 3 +- include/aoconfig.h | 11 +- include/aoconfigpanel.h | 7 +- include/drdiscord.h | 86 +++++++++++++ res/ui/config_panel.ui | 118 +++++------------- src/aoapplication.cpp | 13 +- src/aoconfig.cpp | 82 ++++--------- src/aoconfigpanel.cpp | 50 ++------ src/courtroom.cpp | 5 +- src/drdiscord.cpp | 238 ++++++++++++++++++++++++++++++++++++ src/packet_distribution.cpp | 5 +- 12 files changed, 411 insertions(+), 209 deletions(-) create mode 100644 include/drdiscord.h create mode 100644 src/drdiscord.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 30646694f..ba108f6fd 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -55,6 +55,7 @@ HEADERS += \ include/draudioerror.h \ include/draudiostream.h \ include/draudiostreamfamily.h \ + include/drdiscord.h \ include/file_functions.h \ include/hardware_functions.h \ include/lobby.h \ @@ -105,6 +106,7 @@ SOURCES += \ src/draudioerror.cpp \ src/draudiostream.cpp \ src/draudiostreamfamily.cpp \ + src/drdiscord.cpp \ src/emotes.cpp \ src/evidence.cpp \ src/file_functions.cpp \ diff --git a/include/aoapplication.h b/include/aoapplication.h index 32025bdf6..79b994f4f 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -4,6 +4,7 @@ #include "aopacket.h" #include "datatypes.h" #include "discord_rich_presence.h" +#include "drdiscord.h" #include #include @@ -26,7 +27,7 @@ class AOApplication : public QApplication NetworkManager *net_manager = nullptr; Lobby *w_lobby = nullptr; Courtroom *w_courtroom = nullptr; - AttorneyOnline::Discord *discord = nullptr; + DRDiscord *discord = nullptr; AOConfig *config = nullptr; AOConfigPanel *config_panel = nullptr; diff --git a/include/aoconfig.h b/include/aoconfig.h index 1abdbef2c..4c5d8f199 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -23,7 +23,8 @@ class AOConfig : public QObject QString username() const; QString callwords() const; bool server_alerts_enabled() const; - DR::DiscordRichPresenceStyle discord_rich_presence() const; + bool discord_presence() const; + bool discord_hide_character() const; QString theme() const; QString gamemode() const; bool manual_gamemode_enabled() const; @@ -61,9 +62,8 @@ public slots: void set_username(QString p_string); void set_callwords(QString p_string); void set_server_alerts(bool p_enabled); - void set_discord_rich_presence_style_complete(bool p_enabled); - void set_discord_rich_presence_style_minimal(bool p_enabled); - void set_discord_rich_presence_style_disabled(bool p_enabled); + void set_discord_presence(const bool p_enabled); + void set_discord_hide_character(const bool p_enabled); void set_always_pre(bool p_enabled); void set_theme(QString p_string); void set_gamemode(QString p_string); @@ -97,7 +97,8 @@ public slots: void username_changed(QString); void callwords_changed(QString); void server_alerts_changed(bool); - void discord_rich_presence_style_changed(DR::DiscordRichPresenceStyle); + void discord_presence_changed(bool); + void discord_hide_character_changed(bool); void theme_changed(QString); void gamemode_changed(QString); void manual_gamemode_changed(bool); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 42681af58..a58c4e6bf 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -42,7 +42,6 @@ public slots: void update_audio_device_list(); private slots: - void on_discord_rich_presence_style_changed(DR::DiscordRichPresenceStyle drp_status); void on_reload_theme_clicked(); void on_gamemode_index_changed(QString p_text); void on_timeofday_index_changed(QString p_text); @@ -74,9 +73,9 @@ private slots: QLineEdit *w_username = nullptr; QLineEdit *w_callwords = nullptr; QCheckBox *w_server_alerts = nullptr; - QRadioButton *w_discord_rich_presence_style_complete = nullptr; - QRadioButton *w_discord_rich_presence_style_minimal = nullptr; - QRadioButton *w_discord_rich_presence_style_disabled = nullptr; + + QGroupBox *w_discord_presence = nullptr; + QCheckBox *w_discord_hide_character = nullptr; // game QComboBox *w_theme = nullptr; diff --git a/include/drdiscord.h b/include/drdiscord.h new file mode 100644 index 000000000..dc74fd4c4 --- /dev/null +++ b/include/drdiscord.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +class DRDiscord : public QObject +{ + Q_OBJECT + +public: + enum Option : uint32_t + { + OPresence = 0x1, + OHideCharacter = 0x2, + }; + Q_DECLARE_FLAGS(Options, Option) + + enum class State + { + Idle, + Connected, + }; + Q_ENUM(State) + + DRDiscord(QObject *f_parent = nullptr); + ~DRDiscord(); + + Options get_options() const; + bool option_enabled(const Option p_option) const; + bool presence_enabled() const; + bool hide_character_enabled() const; + State get_state() const; + bool is_state(const State p_state) const; + bool is_idle() const; + bool is_connected() const; + +public slots: + void set_options(const Options &f_options); + void set_option(const Option &f_option, const bool p_enabled); + void set_presence(const bool p_enabled); + void set_hide_character(const bool p_enabled); + void set_state(const State f_state); + void set_server_name(const QString &f_server_name); + void clear_server_name(); + void set_character_name(const QString &f_character_name); + void clear_character_name(); + + void start(const bool p_restart = false); + void stop(); + +signals: + void options_changed(Options); + void state_changed(State); + void server_name_changed(QString); + void server_name_cleared(); + void character_name_changed(QString); + void character_name_cleared(); + void started(); + void stopped(); + +private: + Options m_options; + State m_state = State::Idle; + QPointer m_waiter; + DiscordEventHandlers m_handler; + DiscordRichPresence m_presence; + bool m_started = false; + std::optional m_server_name; + std::optional m_character_name; + QByteArray m_buf_details, m_buf_state; + int64_t m_timestamp; + +private slots: + void on_update_queued(); + + /* +signals: + void presence_changed(QPrivateSignal); + */ +}; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 05c111488..56ca0524f 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 3 + 0 @@ -63,46 +63,15 @@ - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - + Qt::Horizontal - - - - Server alerts: - - - - - + + 0 @@ -110,66 +79,28 @@ - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sets the preferred Discord Rich Status style.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" color:#ff0000;">[WARNING] </span>Changes may take up to 15 seconds to be reflected in your Discord profile. Moreover, Discord has a rate limit of one update per 15 seconds. The more you fiddle with the options too fast, the longer it may take for your style to be updated.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; color:#8a602c;"><br /></p></body></html> + <html><head/><body><p>Enable Discord presence.</p><p><span style=" color:#ffff00;">[NOTE] </span>Changes may take up to 15 seconds to be reflected in your Discord profile.</p></body></html> - Discord Rich Status Style + Discord Presence + + + true + + + false - - - <html><head/><body><p>* Shows you are playing the game.</p><p>* Shows the server you are connected to.</p><p>* Shows the name of the character you are playing as.</p></body></html> - + - Complete - - - - - - - <html><head/><body><p>* Shows you are playing the game.</p><p>* Does not show the server you are connected to.</p><p>* Does not show the name of the character you are playing as.</p></body></html> - - - Minimal - - - - - - - <html><head/><body><p>* Does not show you are playing the game.</p><p>* Does not show the server you are connected to.</p><p>* Does not show the name of the character you are playing as.</p></body></html> - - - Disabled + Hide Character - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -183,6 +114,13 @@ p, li { white-space: pre-wrap; } + + + + Server alerts: + + + @@ -340,13 +278,6 @@ p, li { white-space: pre-wrap; } - - - - Qt::Horizontal - - - @@ -367,6 +298,13 @@ p, li { white-space: pre-wrap; } + + + + Qt::Horizontal + + + diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 6df8cb918..da4874a66 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -19,8 +19,6 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) { - discord = new AttorneyOnline::Discord(); - net_manager = new NetworkManager(this); connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), SLOT(ms_connect_finished(bool, bool))); @@ -34,15 +32,17 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(this, SIGNAL(reload_theme()), config_panel, SLOT(on_config_reload_theme_requested())); config_panel->hide(); - discord->set_style(config->discord_rich_presence()); + discord = new DRDiscord(this); + discord->set_presence(config->discord_presence()); + discord->set_hide_character(config->discord_hide_character()); + connect(config, SIGNAL(discord_presence_changed(bool)), discord, SLOT(set_presence(bool))); + connect(config, SIGNAL(discord_hide_character_changed(bool)), discord, SLOT(set_hide_character(bool))); } AOApplication::~AOApplication() { destruct_lobby(); destruct_courtroom(); - delete discord; - delete config_panel; } void AOApplication::construct_lobby() @@ -70,7 +70,8 @@ void AOApplication::construct_lobby() w_lobby->show(); - discord->state_lobby(); + discord->set_state(DRDiscord::State::Idle); + discord->clear_server_name(); } void AOApplication::destruct_lobby() diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index d113b768e..2f5562b04 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -48,7 +48,8 @@ private slots: QString username; QString callwords; bool server_alerts; - DR::DiscordRichPresenceStyle discord_rich_presence; + bool discord_presence = false; + bool discord_hide_character = false; QString theme; QString gamemode; bool manual_gamemode; @@ -105,24 +106,10 @@ void AOConfigPrivate::read_file() username = cfg.value("username").toString(); callwords = cfg.value("callwords").toString(); server_alerts = cfg.value("server_alerts", true).toBool(); - QString raw_discord_rich_presence = cfg.value("discord_rich_presence", "complete").toString(); - if (raw_discord_rich_presence == "complete") - { - discord_rich_presence = DR::DRPSComplete; - } - else if (raw_discord_rich_presence == "minimal") - { - discord_rich_presence = DR::DRPSMinimal; - } - else if (raw_discord_rich_presence == "disabled") - { - discord_rich_presence = DR::DRPSDisabled; - } - else - { - qWarning() << "Unknown Discord Rich Presence status, defaulting to 'complete'"; - discord_rich_presence = DR::DRPSComplete; - } + + discord_presence = cfg.value("discord_presence", false).toBool(); + discord_hide_character = cfg.value("discord_hide_character", false).toBool(); + theme = cfg.value("theme").toString(); if (theme.trimmed().isEmpty()) theme = "default"; @@ -174,20 +161,10 @@ void AOConfigPrivate::save_file() cfg.setValue("username", username); cfg.setValue("callwords", callwords); cfg.setValue("server_alerts", server_alerts); - QString raw_discord_rich_presence = ""; - switch (discord_rich_presence) - { - case DR::DRPSComplete: - raw_discord_rich_presence = "complete"; - break; - case DR::DRPSMinimal: - raw_discord_rich_presence = "minimal"; - break; - case DR::DRPSDisabled: - raw_discord_rich_presence = "disabled"; - break; - } - cfg.setValue("discord_rich_presence", raw_discord_rich_presence); + + cfg.setValue("discord_presence", discord_presence); + cfg.setValue("discord_hide_character", discord_hide_character); + cfg.setValue("theme", theme); cfg.setValue("gamemode", gamemode); cfg.setValue("manual_gamemode", manual_gamemode); @@ -303,9 +280,14 @@ bool AOConfig::server_alerts_enabled() const return d->server_alerts; } -DR::DiscordRichPresenceStyle AOConfig::discord_rich_presence() const +bool AOConfig::discord_presence() const +{ + return d->discord_presence; +} + +bool AOConfig::discord_hide_character() const { - return d->discord_rich_presence; + return d->discord_hide_character; } QString AOConfig::theme() const @@ -463,34 +445,20 @@ void AOConfig::set_server_alerts(bool p_enabled) d->invoke_signal("server_alerts_changed", Q_ARG(bool, p_enabled)); } -void AOConfig::set_discord_rich_presence_style_complete(bool p_enabled) -{ - if (!p_enabled) - return; - if (d->discord_rich_presence == DR::DRPSComplete) - return; - d->discord_rich_presence = DR::DRPSComplete; - d->invoke_signal("discord_rich_presence_style_changed", Q_ARG(DR::DiscordRichPresenceStyle, DR::DRPSComplete)); -} - -void AOConfig::set_discord_rich_presence_style_minimal(bool p_enabled) +void AOConfig::set_discord_presence(const bool p_enabled) { - if (!p_enabled) - return; - if (d->discord_rich_presence == DR::DRPSMinimal) + if (d->discord_presence == p_enabled) return; - d->discord_rich_presence = DR::DRPSMinimal; - d->invoke_signal("discord_rich_presence_style_changed", Q_ARG(DR::DiscordRichPresenceStyle, DR::DRPSMinimal)); + d->discord_presence = p_enabled; + Q_EMIT d->invoke_signal("discord_presence_changed", Q_ARG(bool, d->discord_presence)); } -void AOConfig::set_discord_rich_presence_style_disabled(bool p_enabled) +void AOConfig::set_discord_hide_character(const bool p_enabled) { - if (!p_enabled) - return; - if (d->discord_rich_presence == DR::DRPSDisabled) + if (d->discord_hide_character == p_enabled) return; - d->discord_rich_presence = DR::DRPSDisabled; - d->invoke_signal("discord_rich_presence_style_changed", Q_ARG(DR::DiscordRichPresenceStyle, DR::DRPSDisabled)); + d->discord_hide_character = p_enabled; + Q_EMIT d->invoke_signal("discord_hide_character_changed", Q_ARG(bool, d->discord_hide_character)); } void AOConfig::set_theme(QString p_string) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index c3f48ebf6..532552839 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -33,9 +33,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_username = AO_GUI_WIDGET(QLineEdit, "username"); w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); - w_discord_rich_presence_style_complete = AO_GUI_WIDGET(QRadioButton, "discord_rich_presence_style_complete"); - w_discord_rich_presence_style_minimal = AO_GUI_WIDGET(QRadioButton, "discord_rich_presence_style_minimal"); - w_discord_rich_presence_style_disabled = AO_GUI_WIDGET(QRadioButton, "discord_rich_presence_style_disabled"); + w_discord_presence = AO_GUI_WIDGET(QGroupBox, "discord_presence"); + w_discord_hide_character = AO_GUI_WIDGET(QCheckBox, "discord_hide_character"); // game w_theme = AO_GUI_WIDGET(QComboBox, "theme"); @@ -85,8 +84,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(discord_rich_presence_style_changed(DR::DiscordRichPresenceStyle)), this, - SLOT(on_discord_rich_presence_style_changed(DR::DiscordRichPresenceStyle))); + connect(m_config, SIGNAL(discord_presence_changed(bool)), w_discord_presence, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(discord_hide_character_changed(bool)), w_discord_hide_character, SLOT(setChecked(bool))); connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, SLOT(setChecked(bool))); @@ -126,12 +125,10 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_username, SIGNAL(textEdited(QString)), m_config, SLOT(set_username(QString))); connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); connect(w_server_alerts, SIGNAL(toggled(bool)), m_config, SLOT(set_server_alerts(bool))); - connect(w_discord_rich_presence_style_complete, SIGNAL(toggled(bool)), m_config, - SLOT(set_discord_rich_presence_style_complete(bool))); - connect(w_discord_rich_presence_style_minimal, SIGNAL(toggled(bool)), m_config, - SLOT(set_discord_rich_presence_style_minimal(bool))); - connect(w_discord_rich_presence_style_disabled, SIGNAL(toggled(bool)), m_config, - SLOT(set_discord_rich_presence_style_disabled(bool))); + + connect(w_discord_presence, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_presence(bool))); + connect(w_discord_hide_character, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_character(const bool))); + connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); @@ -186,18 +183,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_log_orientation_bottom_up->setChecked(true); } - switch (m_config->discord_rich_presence()) - { - case DR::DRPSComplete: - w_discord_rich_presence_style_complete->setChecked(true); - break; - case DR::DRPSMinimal: - w_discord_rich_presence_style_minimal->setChecked(true); - break; - case DR::DRPSDisabled: - w_discord_rich_presence_style_disabled->setChecked(true); - break; - } + w_discord_presence->setChecked(m_config->discord_presence()); + w_discord_hide_character->setChecked(m_config->discord_hide_character()); w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); w_log_music->setChecked(m_config->log_music_enabled()); @@ -239,23 +226,6 @@ void AOConfigPanel::showEvent(QShowEvent *event) } } -void AOConfigPanel::on_discord_rich_presence_style_changed(DR::DiscordRichPresenceStyle drp_status) -{ - switch (drp_status) - { - case DR::DRPSComplete: - m_config->set_discord_rich_presence_style_complete(true); - break; - case DR::DRPSMinimal: - m_config->set_discord_rich_presence_style_minimal(true); - break; - case DR::DRPSDisabled: - m_config->set_discord_rich_presence_style_disabled(true); - break; - } - ao_app->discord->set_style(drp_status); -} - void AOConfigPanel::refresh_theme_list() { const QString p_prev_text = w_theme->currentText(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 0026d05f2..cabd9e2e7 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -53,7 +53,7 @@ void Courtroom::enter_courtroom(int p_cid) if (m_cid == -1) { - ao_app->discord->state_spectate(); + ao_app->discord->clear_character_name(); f_char = ""; } else @@ -62,8 +62,7 @@ void Courtroom::enter_courtroom(int p_cid) QString showname_char = ao_app->get_showname(f_char); if (showname_char.isEmpty()) showname_char = f_char; - - ao_app->discord->state_character(showname_char.toStdString()); + ao_app->discord->set_character_name(showname_char); } current_char = f_char; diff --git a/src/drdiscord.cpp b/src/drdiscord.cpp new file mode 100644 index 000000000..f5658403e --- /dev/null +++ b/src/drdiscord.cpp @@ -0,0 +1,238 @@ +#include "drdiscord.h" + +#include +#include + +#include + +Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, DISCORD_APPLICATION_ID, ("818850172330442793")); +Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, DISCORD_LARGE_IMAGE_KEY, ("danganronpa_online")); +Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, DISCORD_LARGE_IMAGE_TEXT, ("Sore Wa Chigau Yo!")); + +QByteArray resize_buf(const QString &f_str_message, const int f_max_size) +{ + QByteArray l_message = f_str_message.toUtf8(); + + // reduce by one to take '\0' in account + const int l_target_max_size = f_max_size - 1; + + if (l_message.size() > l_target_max_size) + { + const QByteArray l_omission_text("..."); + l_message = l_message.mid(0, l_target_max_size - l_omission_text.size()) + l_omission_text; + } + + return l_message; +} + +DRDiscord::DRDiscord(QObject *f_parent) : QObject(f_parent) +{ + m_waiter = new QTimer(this); + m_waiter->setInterval(std::chrono::seconds(1)); + m_waiter->setSingleShot(true); + connect(m_waiter, SIGNAL(timeout()), this, SLOT(on_update_queued())); + connect(this, SIGNAL(options_changed(Options)), m_waiter, SLOT(start())); + connect(this, SIGNAL(state_changed(State)), m_waiter, SLOT(start())); + connect(this, SIGNAL(server_name_changed(QString)), m_waiter, SLOT(start())); + connect(this, SIGNAL(server_name_cleared()), m_waiter, SLOT(start())); + connect(this, SIGNAL(character_name_changed(QString)), m_waiter, SLOT(start())); + connect(this, SIGNAL(character_name_cleared()), m_waiter, SLOT(start())); + connect(this, SIGNAL(started()), m_waiter, SLOT(start())); + connect(this, SIGNAL(stopped()), m_waiter, SLOT(start())); + + start(); +} + +DRDiscord::~DRDiscord() +{ + this->disconnect(m_waiter); + + stop(); +} + +void DRDiscord::start(const bool p_restart) +{ + if (p_restart) + stop(); + if (m_started) + return; + m_started = true; + m_timestamp = QDateTime::currentDateTime().toSecsSinceEpoch(); + qInfo().noquote() << QString("[Discord-RPC] %1").arg(p_restart ? "Restarting" : "Starting"); + // reset & set + std::memset(&m_handler, 0, sizeof(m_handler)); + m_handler.ready = [](const DiscordUser *user) { + qInfo().noquote() << QString("[Discord-RPC] Ready, as username: %1").arg(user->username); + }; + m_handler.disconnected = [](int errorCode, const char *message) { + qInfo().noquote() << QString("[Discord-RPC] Disconnected, code %1: %2").arg(errorCode).arg(message); + }; + m_handler.errored = [](int errorCode, const char *message) { + qWarning().noquote() << QString("[Discord-RPC] Error %1: %2").arg(errorCode).arg(message); + }; + Discord_Initialize(DISCORD_APPLICATION_ID->constData(), &m_handler, 1, nullptr); + Q_EMIT started(); +} + +void DRDiscord::stop() +{ + if (!m_started) + return; + m_started = false; + m_timestamp = 0; + qInfo().noquote() << "[Discord-RPC] Shutting down"; + Discord_Shutdown(); + Q_EMIT stopped(); +} + +DRDiscord::Options DRDiscord::get_options() const +{ + return m_options; +} + +bool DRDiscord::option_enabled(const DRDiscord::Option p_option) const +{ + return m_options.testFlag(p_option); +} + +bool DRDiscord::presence_enabled() const +{ + return option_enabled(OPresence); +} + +bool DRDiscord::hide_character_enabled() const +{ + return option_enabled(OHideCharacter); +} + +DRDiscord::State DRDiscord::get_state() const +{ + return m_state; +} + +bool DRDiscord::is_state(const DRDiscord::State p_state) const +{ + return m_state == p_state; +} + +bool DRDiscord::is_idle() const +{ + return is_state(State::Idle); +} + +bool DRDiscord::is_connected() const +{ + return is_state(State::Connected); +} + +void DRDiscord::set_options(const Options &f_options) +{ + if (m_options == f_options) + return; + m_options = f_options; + Q_EMIT options_changed(m_options); +} + +void DRDiscord::set_option(const DRDiscord::Option &f_option, const bool p_enabled) +{ + if (option_enabled(f_option) == p_enabled) + return; + Options l_options = m_options; + l_options.setFlag(f_option, p_enabled); + set_options(l_options); +} + +void DRDiscord::set_presence(const bool p_enabled) +{ + set_option(OPresence, p_enabled); +} + +void DRDiscord::set_hide_character(const bool p_enabled) +{ + set_option(OHideCharacter, p_enabled); +} + +void DRDiscord::set_state(const DRDiscord::State f_state) +{ + if (m_state == f_state) + return; + m_state = f_state; + Q_EMIT state_changed(m_state); +} + +void DRDiscord::set_server_name(const QString &f_server_name) +{ + Q_ASSERT_X(!f_server_name.trimmed().isEmpty(), "DRDiscord", "a server name is required"); + if (m_server_name.has_value() && m_server_name.value() == f_server_name) + return; + m_server_name = f_server_name; + Q_EMIT server_name_changed(m_server_name.value()); +} + +void DRDiscord::clear_server_name() +{ + m_server_name.reset(); + Q_EMIT server_name_cleared(); +} + +void DRDiscord::set_character_name(const QString &f_character_name) +{ + Q_ASSERT_X(!f_character_name.trimmed().isEmpty(), "DRDiscord", "a server name is required"); + if (m_character_name.has_value() && m_character_name.value() == f_character_name) + return; + m_character_name = f_character_name; + Q_EMIT character_name_changed(m_character_name.value()); +} + +void DRDiscord::clear_character_name() +{ + m_character_name.reset(); + Q_EMIT character_name_cleared(); +} + +void DRDiscord::on_update_queued() +{ + if (!presence_enabled()) + { + Discord_ClearPresence(); + return; + } + + switch (m_state) + { + default: + case State::Idle: + m_buf_details = "In Lobby"; + m_buf_state.clear(); + break; + + case State::Connected: + m_buf_details = QString("In: %1") + .arg(m_server_name.has_value() ? m_server_name.value().toUtf8() : QString("")) + .toUtf8(); + + if (hide_character_enabled()) + { + m_buf_state.clear(); + } + else + { + m_buf_state = m_character_name.has_value() ? QString("As: %1").arg(m_character_name.value()).toUtf8() + : QByteArray("Spectating"); + } + break; + } + + // respect max size for each fields; see discord_rpc.h struct + m_buf_details = resize_buf(QString::fromUtf8(m_buf_details), 128); + m_buf_state = resize_buf(QString::fromUtf8(m_buf_state), 128); + + // reset & set presence + std::memset(&m_presence, 0, sizeof(m_presence)); + m_presence.largeImageKey = DISCORD_LARGE_IMAGE_KEY->constData(); + m_presence.largeImageText = DISCORD_LARGE_IMAGE_TEXT->constData(); + m_presence.state = m_buf_state.constData(); + m_presence.details = m_buf_details.constData(); + m_presence.startTimestamp = m_timestamp; + Discord_UpdatePresence(&m_presence); +} diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index de5bb3392..4d413ec97 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -234,9 +234,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) f_packet = new AOPacket("RC#%"); send_server_packet(f_packet); - QCryptographicHash hash(QCryptographicHash::Algorithm::Sha256); - hash.addData(server_address.toUtf8()); - discord->state_server(server_name.toStdString(), hash.result().toBase64().toStdString()); + discord->set_state(DRDiscord::State::Connected); + discord->set_server_name(server_name); } else if (header == "CI") { From 011c7f65d863e2c4609b46cef509885a8a796d05 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 10 Mar 2021 17:17:14 +0100 Subject: [PATCH 252/842] Added Hide Server option * See above. --- include/aoconfig.h | 3 +++ include/aoconfigpanel.h | 1 + include/drdiscord.h | 5 ++++- res/ui/config_panel.ui | 7 +++++++ src/aoapplication.cpp | 2 ++ src/aoconfig.cpp | 16 ++++++++++++++++ src/aoconfigpanel.cpp | 4 ++++ src/drdiscord.cpp | 32 ++++++++++++++++++++++++-------- 8 files changed, 61 insertions(+), 9 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 4c5d8f199..bf7b3f3cc 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -24,6 +24,7 @@ class AOConfig : public QObject QString callwords() const; bool server_alerts_enabled() const; bool discord_presence() const; + bool discord_hide_server() const; bool discord_hide_character() const; QString theme() const; QString gamemode() const; @@ -63,6 +64,7 @@ public slots: void set_callwords(QString p_string); void set_server_alerts(bool p_enabled); void set_discord_presence(const bool p_enabled); + void set_discord_hide_server(const bool p_enabled); void set_discord_hide_character(const bool p_enabled); void set_always_pre(bool p_enabled); void set_theme(QString p_string); @@ -98,6 +100,7 @@ public slots: void callwords_changed(QString); void server_alerts_changed(bool); void discord_presence_changed(bool); + void discord_hide_server_changed(bool); void discord_hide_character_changed(bool); void theme_changed(QString); void gamemode_changed(QString); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index a58c4e6bf..37f788ba6 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -75,6 +75,7 @@ private slots: QCheckBox *w_server_alerts = nullptr; QGroupBox *w_discord_presence = nullptr; + QCheckBox *w_discord_hide_server = nullptr; QCheckBox *w_discord_hide_character = nullptr; // game diff --git a/include/drdiscord.h b/include/drdiscord.h index dc74fd4c4..795a5ea68 100644 --- a/include/drdiscord.h +++ b/include/drdiscord.h @@ -17,7 +17,8 @@ class DRDiscord : public QObject enum Option : uint32_t { OPresence = 0x1, - OHideCharacter = 0x2, + OHideServer = 0x2, + OHideCharacter = 0x4, }; Q_DECLARE_FLAGS(Options, Option) @@ -34,6 +35,7 @@ class DRDiscord : public QObject Options get_options() const; bool option_enabled(const Option p_option) const; bool presence_enabled() const; + bool hide_server_enabled() const; bool hide_character_enabled() const; State get_state() const; bool is_state(const State p_state) const; @@ -44,6 +46,7 @@ public slots: void set_options(const Options &f_options); void set_option(const Option &f_option, const bool p_enabled); void set_presence(const bool p_enabled); + void set_hide_server(const bool p_enabled); void set_hide_character(const bool p_enabled); void set_state(const State f_state); void set_server_name(const QString &f_server_name); diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 56ca0524f..a262da382 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -91,6 +91,13 @@ false + + + + Hide Server + + + diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index da4874a66..f1a8734b1 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -34,8 +34,10 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) discord = new DRDiscord(this); discord->set_presence(config->discord_presence()); + discord->set_hide_server(config->discord_hide_server()); discord->set_hide_character(config->discord_hide_character()); connect(config, SIGNAL(discord_presence_changed(bool)), discord, SLOT(set_presence(bool))); + connect(config, SIGNAL(discord_hide_server_changed(bool)), discord, SLOT(set_hide_server(bool))); connect(config, SIGNAL(discord_hide_character_changed(bool)), discord, SLOT(set_hide_character(bool))); } diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 2f5562b04..d12a78b89 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -49,6 +49,7 @@ private slots: QString callwords; bool server_alerts; bool discord_presence = false; + bool discord_hide_server = false; bool discord_hide_character = false; QString theme; QString gamemode; @@ -108,6 +109,7 @@ void AOConfigPrivate::read_file() server_alerts = cfg.value("server_alerts", true).toBool(); discord_presence = cfg.value("discord_presence", false).toBool(); + discord_hide_server = cfg.value("discord_hide_server", false).toBool(); discord_hide_character = cfg.value("discord_hide_character", false).toBool(); theme = cfg.value("theme").toString(); @@ -163,6 +165,7 @@ void AOConfigPrivate::save_file() cfg.setValue("server_alerts", server_alerts); cfg.setValue("discord_presence", discord_presence); + cfg.setValue("discord_hide_server", discord_hide_server); cfg.setValue("discord_hide_character", discord_hide_character); cfg.setValue("theme", theme); @@ -285,6 +288,11 @@ bool AOConfig::discord_presence() const return d->discord_presence; } +bool AOConfig::discord_hide_server() const +{ + return d->discord_hide_server; +} + bool AOConfig::discord_hide_character() const { return d->discord_hide_character; @@ -453,6 +461,14 @@ void AOConfig::set_discord_presence(const bool p_enabled) Q_EMIT d->invoke_signal("discord_presence_changed", Q_ARG(bool, d->discord_presence)); } +void AOConfig::set_discord_hide_server(const bool p_enabled) +{ + if (d->discord_hide_server == p_enabled) + return; + d->discord_hide_server = p_enabled; + Q_EMIT d->invoke_signal("discord_hide_server_changed", Q_ARG(bool, d->discord_hide_server)); +} + void AOConfig::set_discord_hide_character(const bool p_enabled) { if (d->discord_hide_character == p_enabled) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 532552839..cea6e5b0d 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -34,6 +34,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); w_discord_presence = AO_GUI_WIDGET(QGroupBox, "discord_presence"); + w_discord_hide_server = AO_GUI_WIDGET(QCheckBox, "discord_hide_server"); w_discord_hide_character = AO_GUI_WIDGET(QCheckBox, "discord_hide_character"); // game @@ -85,6 +86,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); connect(m_config, SIGNAL(discord_presence_changed(bool)), w_discord_presence, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(discord_hide_server_changed(bool)), w_discord_hide_server, SLOT(setChecked(bool))); connect(m_config, SIGNAL(discord_hide_character_changed(bool)), w_discord_hide_character, SLOT(setChecked(bool))); connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); @@ -127,6 +129,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_server_alerts, SIGNAL(toggled(bool)), m_config, SLOT(set_server_alerts(bool))); connect(w_discord_presence, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_presence(bool))); + connect(w_discord_hide_server, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_server(const bool))); connect(w_discord_hide_character, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_character(const bool))); connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); @@ -184,6 +187,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) } w_discord_presence->setChecked(m_config->discord_presence()); + w_discord_hide_server->setChecked(m_config->discord_hide_server()); w_discord_hide_character->setChecked(m_config->discord_hide_character()); w_log_uses_newline->setChecked(m_config->log_uses_newline_enabled()); diff --git a/src/drdiscord.cpp b/src/drdiscord.cpp index f5658403e..1eb404d78 100644 --- a/src/drdiscord.cpp +++ b/src/drdiscord.cpp @@ -100,6 +100,11 @@ bool DRDiscord::presence_enabled() const return option_enabled(OPresence); } +bool DRDiscord::hide_server_enabled() const +{ + return option_enabled(OHideServer); +} + bool DRDiscord::hide_character_enabled() const { return option_enabled(OHideCharacter); @@ -147,6 +152,11 @@ void DRDiscord::set_presence(const bool p_enabled) set_option(OPresence, p_enabled); } +void DRDiscord::set_hide_server(const bool p_enabled) +{ + set_option(OHideServer, p_enabled); +} + void DRDiscord::set_hide_character(const bool p_enabled) { set_option(OHideCharacter, p_enabled); @@ -202,21 +212,27 @@ void DRDiscord::on_update_queued() { default: case State::Idle: - m_buf_details = "In Lobby"; + m_buf_details.clear(); + if (!hide_server_enabled()) + { + m_buf_details = "Lobby"; + } m_buf_state.clear(); break; case State::Connected: - m_buf_details = QString("In: %1") - .arg(m_server_name.has_value() ? m_server_name.value().toUtf8() : QString("")) - .toUtf8(); - - if (hide_character_enabled()) + m_buf_details.clear(); + if (!hide_server_enabled()) { - m_buf_state.clear(); + m_buf_details = QString("In: %1") + .arg(m_server_name.has_value() ? m_server_name.value().toUtf8() : QString("")) + .toUtf8(); } - else + + m_buf_state.clear(); + if (!hide_character_enabled()) { + m_buf_state.clear(); m_buf_state = m_character_name.has_value() ? QString("As: %1").arg(m_character_name.value()).toUtf8() : QByteArray("Spectating"); } From 1e9fb8034ab0369e8707433d10613c80f9bc31cb Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 10 Mar 2021 17:20:35 +0100 Subject: [PATCH 253/842] Extra space for extra look. --- res/ui/config_panel.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index a262da382..fef38c8e0 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -79,7 +79,7 @@ - <html><head/><body><p>Enable Discord presence.</p><p><span style=" color:#ffff00;">[NOTE] </span>Changes may take up to 15 seconds to be reflected in your Discord profile.</p></body></html> + <html><head/><body><p>Enable Discord presence.</p><p><span style=" color:#0000ff;">[NOTE]</span><span style=" color:#ffff00;"/>Changes may take up to 15 seconds to be reflected in your Discord profile.</p></body></html> Discord Presence From 5f3e40d189f7856aaac0e1b506cac7f30519a002 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 10 Mar 2021 17:23:04 +0100 Subject: [PATCH 254/842] Default values modified * Changed Discord Presence to true (this is to promote usage) --- res/ui/config_panel.ui | 2 +- src/aoconfig.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index fef38c8e0..2de5380de 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -79,7 +79,7 @@ - <html><head/><body><p>Enable Discord presence.</p><p><span style=" color:#0000ff;">[NOTE]</span><span style=" color:#ffff00;"/>Changes may take up to 15 seconds to be reflected in your Discord profile.</p></body></html> + <html><head/><body><p>Enable Discord presence.</p><p><span style=" color:#0000ff;">[NOTE] </span>Changes may take up to 15 seconds to be reflected in your Discord profile.</p></body></html> Discord Presence diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index d12a78b89..6fbe0578e 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -108,7 +108,7 @@ void AOConfigPrivate::read_file() callwords = cfg.value("callwords").toString(); server_alerts = cfg.value("server_alerts", true).toBool(); - discord_presence = cfg.value("discord_presence", false).toBool(); + discord_presence = cfg.value("discord_presence", true).toBool(); discord_hide_server = cfg.value("discord_hide_server", false).toBool(); discord_hide_character = cfg.value("discord_hide_character", false).toBool(); From 5c9a004a63028d4cdb5731f7b4617a9d4e6c3daf Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 10 Mar 2021 17:45:28 +0100 Subject: [PATCH 255/842] Added favorite-list check * If a server is picked from the favorite list, it will be ported instead as private unless listed in the public list --- src/packet_distribution.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 4d413ec97..bff2c36b7 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -201,7 +201,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QString window_title = "Danganronpa Online"; int selected_server = w_lobby->get_selected_server(); - QString server_address = "", server_name = ""; + QString server_name, server_address; + bool is_favorite = false; if (w_lobby->public_servers_selected) { if (selected_server >= 0 && selected_server < server_list.size()) @@ -219,6 +220,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) auto info = favorite_list.at(selected_server); server_name = info.name; server_address = info.ip + info.port; + is_favorite = true; window_title += ": " + server_name; } } @@ -234,8 +236,23 @@ void AOApplication::server_packet_received(AOPacket *p_packet) f_packet = new AOPacket("RC#%"); send_server_packet(f_packet); + // look for the server inside the known public list and report it + if (is_favorite) + { + server_name.clear(); + + for (server_type &server : server_list) + { + const QString l_address = server.ip + server.port; + if (server_address == l_address) + { + server_name = server.name; + break; + } + } + } discord->set_state(DRDiscord::State::Connected); - discord->set_server_name(server_name); + server_name.isEmpty() ? discord->clear_server_name() : discord->set_server_name(server_name); } else if (header == "CI") { From 0ad6a07319dd230f0bcc36566de3c8fc75bf4a0c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 10 Mar 2021 18:08:40 +0100 Subject: [PATCH 256/842] Always display a public server name if available --- src/packet_distribution.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index bff2c36b7..5c76f2be7 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -239,8 +239,6 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // look for the server inside the known public list and report it if (is_favorite) { - server_name.clear(); - for (server_type &server : server_list) { const QString l_address = server.ip + server.port; @@ -252,7 +250,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } } discord->set_state(DRDiscord::State::Connected); - server_name.isEmpty() ? discord->clear_server_name() : discord->set_server_name(server_name); + discord->set_server_name(server_name); } else if (header == "CI") { From 73e78738a9eae2dce2c39c7578ce70f50a743ec6 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 10 Mar 2021 18:41:42 +0100 Subject: [PATCH 257/842] Removed Discord implementation --- dronline-client.pro | 2 - include/aoapplication.h | 1 - include/datatypes.h | 10 +- include/discord_rich_presence.h | 39 ------- src/discord_rich_presence.cpp | 175 -------------------------------- 5 files changed, 1 insertion(+), 226 deletions(-) delete mode 100644 include/discord_rich_presence.h delete mode 100644 src/discord_rich_presence.cpp diff --git a/dronline-client.pro b/dronline-client.pro index ba108f6fd..485ab31a5 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -47,7 +47,6 @@ HEADERS += \ include/courtroom.h \ include/datatypes.h \ include/debug_functions.h \ - include/discord_rich_presence.h \ include/draudio.h \ include/draudiodevice.h \ include/draudioengine.h \ @@ -98,7 +97,6 @@ SOURCES += \ src/courtroom_widgets.cpp \ src/datatypes.cpp \ src/debug_functions.cpp \ - src/discord_rich_presence.cpp \ src/draudio.cpp \ src/draudiodevice.cpp \ src/draudioengine.cpp \ diff --git a/include/aoapplication.h b/include/aoapplication.h index 79b994f4f..4ff285ceb 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -3,7 +3,6 @@ #include "aopacket.h" #include "datatypes.h" -#include "discord_rich_presence.h" #include "drdiscord.h" #include diff --git a/include/datatypes.h b/include/datatypes.h index ef8621011..196e46df7 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -138,20 +138,12 @@ enum Color : int32_t CWhite = CDefault, }; -enum DiscordRichPresenceStyle : int32_t -{ - DRPSComplete, - DRPSMinimal, - DRPSDisabled, -}; - struct ColorInfo { public: ColorInfo() = default; ColorInfo(QString p_showname, QString p_code) : name(p_showname.toLower()), showname(p_showname), code(p_code) - { - } + {} QString name; QString showname; diff --git a/include/discord_rich_presence.h b/include/discord_rich_presence.h deleted file mode 100644 index 916af626f..000000000 --- a/include/discord_rich_presence.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef DISCORD_RICH_PRESENCE_H -#define DISCORD_RICH_PRESENCE_H -// 3rd -#include -// std -#include - -#include "datatypes.h" - -namespace AttorneyOnline -{ - -class Discord -{ -private: - const char *APPLICATION_ID = "818850172330442793"; - int m_index = 0; - std::string server_name, server_id, state, details, matchSecret; - int64_t timestamp; - DR::DiscordRichPresenceStyle style; - DiscordRichPresence *complete_presence = new DiscordRichPresence(); - DiscordRichPresence *minimal_presence = new DiscordRichPresence(); - void refresh_presence(); - -public: - Discord(); - ~Discord(); - - void state_lobby(); - void state_server(std::string name, std::string server_id); - void state_character(std::string name); - void state_spectate(); - void start(const char *APPLICATION_ID); - void restart(const char *APPLICATION_ID); - void set_style(DR::DiscordRichPresenceStyle new_style); -}; - -} // namespace AttorneyOnline -#endif // DISCORD_RICH_PRESENCE_H diff --git a/src/discord_rich_presence.cpp b/src/discord_rich_presence.cpp deleted file mode 100644 index 5f4eb8caa..000000000 --- a/src/discord_rich_presence.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#include "discord_rich_presence.h" -#include "datatypes.h" - -#include -#include - -#include - -namespace AttorneyOnline -{ - -Discord::Discord() -{ - // Set up the minimal presence - minimal_presence->largeImageKey = "danganronpa_online"; - minimal_presence->largeImageText = "Sore Wa Chigau Yo!"; - minimal_presence->instance = 1; - minimal_presence->state = ""; - minimal_presence->details = ""; - minimal_presence->matchSecret = ""; - minimal_presence->startTimestamp = 0; - - start(APPLICATION_ID); -} - -void Discord::start(const char *APPLICATION_ID) -{ - DiscordEventHandlers handlers; - std::memset(&handlers, 0, sizeof(handlers)); - handlers = {}; - handlers.ready = [](const DiscordUser *user) { - qInfo() << "Discord RPC ready for" << user->username; - }; - handlers.disconnected = [](int errorCode, const char *message) { - Q_UNUSED(errorCode) - qInfo() << "Discord RPC disconnected! " << message; - }; - handlers.errored = [](int errorCode, const char *message) { - Q_UNUSED(errorCode) - qWarning() << "Discord RPC errored out! " << message; - }; - qInfo() << "Initializing Discord RPC"; - Discord_Initialize(APPLICATION_ID, &handlers, 1, nullptr); -} - -Discord::~Discord() -{ - Discord_Shutdown(); -} - -void Discord::restart(const char *APPLICATION_ID) -{ - Discord_Shutdown(); - start(APPLICATION_ID); -} - -void Discord::set_style(DR::DiscordRichPresenceStyle new_style) -{ - style = new_style; - refresh_presence(); -} - -void Discord::refresh_presence() -{ - switch (style) - { - case DR::DRPSComplete: - Discord_UpdatePresence(complete_presence); - break; - case DR::DRPSMinimal: - Discord_UpdatePresence(minimal_presence); - break; - case DR::DRPSDisabled: - Discord_ClearPresence(); - break; - } - // Note that, no matter what, complete_presence always holds a - // fully updated presence, so it is safe to use it any moment -} - -void Discord::state_lobby() -{ - this->server_id = QString("").toStdString().c_str(); - this->server_name = QString("").toStdString().c_str(); - - this->details = "In Lobby"; - this->state = "Idle"; - this->timestamp = 0; - this->matchSecret = QString("").toStdString().c_str(); - - complete_presence = new DiscordRichPresence(); - - complete_presence->largeImageKey = "danganronpa_online"; - complete_presence->largeImageText = "Sore Wa Chigau Yo!"; - complete_presence->instance = 1; - - complete_presence->details = this->details.c_str(); - complete_presence->state = this->state.c_str(); - - refresh_presence(); -} - -void Discord::state_server(std::string name, std::string server_id) -{ - qDebug() << "Discord RPC: Setting server state"; - - auto timestamp = static_cast(std::time(nullptr)); - - this->server_id = server_id; - this->server_name = name; - - this->details = this->server_name; - this->state = "Connecting..."; - this->matchSecret = this->server_id; - this->timestamp = timestamp; - - complete_presence = new DiscordRichPresence(); - - complete_presence->largeImageKey = "danganronpa_online"; - complete_presence->largeImageText = "Sore Wa Chigau Yo!"; - complete_presence->instance = 1; - - complete_presence->details = this->details.c_str(); - complete_presence->state = this->state.c_str(); - complete_presence->matchSecret = this->matchSecret.c_str(); - complete_presence->startTimestamp = this->timestamp; - - refresh_presence(); -} - -void Discord::state_character(std::string name) -{ - auto name_friendly = QString(name.c_str()).replace('_', ' ').toStdString(); - const std::string playing_as = "Playing as " + name_friendly; - qDebug() << "Discord RPC: Setting character state (" << playing_as.c_str() << ")"; - - this->details = this->server_name; - this->state = playing_as.c_str(); - - complete_presence = new DiscordRichPresence(); - - complete_presence->largeImageKey = "danganronpa_online"; - complete_presence->largeImageText = "Sore Wa Chigau Yo!"; - complete_presence->instance = 1; - - complete_presence->details = this->details.c_str(); - complete_presence->state = this->state.c_str(); - complete_presence->matchSecret = this->matchSecret.c_str(); - complete_presence->startTimestamp = this->timestamp; - - refresh_presence(); -} - -void Discord::state_spectate() -{ - qDebug() << "Discord RPC: Setting spectator state"; - - this->details = this->server_name; - this->state = "Spectating"; - - complete_presence = new DiscordRichPresence(); - - complete_presence->largeImageKey = "danganronpa_online"; - complete_presence->largeImageText = "Sore Wa Chigau Yo!"; - complete_presence->instance = 1; - - complete_presence->details = this->details.c_str(); - complete_presence->state = this->state.c_str(); - complete_presence->matchSecret = this->matchSecret.c_str(); - complete_presence->startTimestamp = this->timestamp; - - refresh_presence(); -} - -} // namespace AttorneyOnline From f59b4968fcf8b9461ed58401539c2fa140a824b3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 13 Mar 2021 23:42:49 +0100 Subject: [PATCH 258/842] Refactored to be like the usual coding style --- include/drtextedit.h | 29 +++++----- src/courtroom_widgets.cpp | 12 ++--- src/drtextedit.cpp | 110 +++++++++++++++++--------------------- src/lobby.cpp | 12 ++--- 4 files changed, 76 insertions(+), 87 deletions(-) diff --git a/include/drtextedit.h b/include/drtextedit.h index 40a49c52b..f4ff02059 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -11,25 +11,28 @@ class DRTextEdit : public QTextEdit public: DRTextEdit(QWidget *p_parent); - void setOutline(bool outline); - void setVerticalAlignment(Qt::Alignment); - void setHorizontalAlignment(Qt::Alignment); + bool get_outline(); + Qt::Alignment get_vertical_alignment(); + Qt::Alignment get_horizontal_alignment(); - bool outline(); - Qt::Alignment verticalAlignment(); - Qt::Alignment horizontalAlignment(); + void set_outline(bool p_outline); + void set_vertical_alignment(Qt::Alignment p_align); + void set_horizontal_alignment(Qt::Alignment p_align); private: - bool _outline = false; - Qt::Alignment _verticalAlignment = Qt::AlignTop; - Qt::Alignment _horizontalAlignment = Qt::AlignLeft; - int previous_height = 0; + bool m_outline = false; + Qt::Alignment m_valign = Qt::AlignTop; + Qt::Alignment m_halign = Qt::AlignLeft; - bool processing_change = false; - void _onTextChanged(); + enum class Status + { + Done, + InProgress, + }; + Status m_status = Status::Done; private slots: - void onTextChanged(); + void on_text_changed(); }; #endif // DRTEXTEDIT_H diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 178a85978..b86bfe738 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1437,24 +1437,24 @@ void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier, QS set_font(widget, p_identifier, override_color); // Do outlines bool outline = (ao_app->get_font_property(p_identifier + "_outline", fonts_ini) == 1); - widget->setOutline(outline); + widget->set_outline(outline); // Do horizontal alignments int raw_halign = ao_app->get_font_property(p_identifier + "_halign", fonts_ini); switch (raw_halign) { case 0: - widget->setHorizontalAlignment(Qt::AlignLeft); + widget->set_horizontal_alignment(Qt::AlignLeft); break; case 1: - widget->setHorizontalAlignment(Qt::AlignHCenter); + widget->set_horizontal_alignment(Qt::AlignHCenter); break; case 2: - widget->setHorizontalAlignment(Qt::AlignRight); + widget->set_horizontal_alignment(Qt::AlignRight); break; default: qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; - widget->setHorizontalAlignment(Qt::AlignLeft); + widget->set_horizontal_alignment(Qt::AlignLeft); } // Do vertical alignments @@ -1475,7 +1475,7 @@ void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier, QS qWarning() << "Unknown vertical alignment for" << p_identifier << ":" << raw_valign << "Assuming Top."; valignment = Qt::AlignTop; } - widget->setVerticalAlignment(valignment); + widget->set_vertical_alignment(valignment); } void Courtroom::set_fonts() diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index f9c27d9ee..81036ef43 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -6,109 +6,95 @@ DRTextEdit::DRTextEdit(QWidget *parent) : QTextEdit(parent) { - connect(this, SIGNAL(textChanged()), this, SLOT(onTextChanged())); + connect(this, SIGNAL(textChanged()), this, SLOT(on_text_changed())); } -void DRTextEdit::setOutline(bool outline) +void DRTextEdit::set_outline(bool p_outline) { QTextCharFormat widget_format = currentCharFormat(); - if (outline) + if (p_outline) widget_format.setTextOutline(QPen(Qt::black, 1)); else widget_format.setTextOutline(Qt::NoPen); setCurrentCharFormat(widget_format); - - this->_outline = outline; + this->m_outline = p_outline; } -bool DRTextEdit::outline() +bool DRTextEdit::get_outline() { - return this->_outline; + return this->m_outline; } -void DRTextEdit::setVerticalAlignment(Qt::Alignment verticalAlignment) +void DRTextEdit::set_vertical_alignment(Qt::Alignment p_align) { - // If an invalid vertical alignment is passed, convert to Top. - if (verticalAlignment != Qt::AlignTop && verticalAlignment != Qt::AlignVCenter && - verticalAlignment != Qt::AlignBottom) - verticalAlignment = Qt::AlignTop; - this->_verticalAlignment = verticalAlignment; - - // Refresh. To get around the fact both height and length did not change, we set previous_height - // to an invalid -1. This will be fixed in the onTextChanged() call - previous_height = -1; - onTextChanged(); + switch (p_align) + { + case Qt::AlignTop: + case Qt::AlignVCenter: + case Qt::AlignBottom: + break; + default: + set_vertical_alignment(Qt::AlignTop); + return; + } + m_valign = p_align; + on_text_changed(); } -Qt::Alignment DRTextEdit::verticalAlignment() +Qt::Alignment DRTextEdit::get_vertical_alignment() { - return this->_verticalAlignment; + return this->m_valign; } -void DRTextEdit::setHorizontalAlignment(Qt::Alignment horizontalAlignment) +void DRTextEdit::set_horizontal_alignment(Qt::Alignment p_align) { - // If an invalid horizontal alignment is passed, convert to Left. - if (horizontalAlignment != Qt::AlignLeft && horizontalAlignment != Qt::AlignHCenter && - horizontalAlignment != Qt::AlignRight) - horizontalAlignment = Qt::AlignLeft; - this->_horizontalAlignment = horizontalAlignment; - // Refresh. - onTextChanged(); + switch (p_align) + { + case Qt::AlignLeft: + case Qt::AlignHCenter: + case Qt::AlignRight: + break; + default: + set_horizontal_alignment(Qt::AlignLeft); + return; + } + m_halign = p_align; + on_text_changed(); } -Qt::Alignment DRTextEdit::horizontalAlignment() +Qt::Alignment DRTextEdit::get_horizontal_alignment() { - return this->_horizontalAlignment; + return this->m_halign; } -void DRTextEdit::onTextChanged() +void DRTextEdit::on_text_changed() { - // Avoid recursion - if (processing_change) + if (m_status == Status::InProgress) return; + m_status = Status::InProgress; - processing_change = true; - _onTextChanged(); - processing_change = false; -} - -void DRTextEdit::_onTextChanged() -{ // Do computations to align text horizontally - setAlignment(_horizontalAlignment); - - // Do computations to align text vertically according to preference - // We do these computations every time the text changes, so we better be efficient! + setAlignment(m_halign); // This stores the total height of the totality of the text saved. - int new_height = document()->size().height(); - // If the height did not change, exit early, as we don't need to do much formatting - if (new_height == previous_height) - return; - previous_height = new_height; - - // If the height exceeds the height of the widget, aligning vertically does not make much sense - // so we exit early. - if (new_height > height()) - return; + const int new_height = document()->size().height(); // The way we will simulate vertical alignment is by adjusting the top margin to simulate // center alignment, or bottom alignment. - int topMargin = 0; - switch (_verticalAlignment) + int top_margin = 0; + switch (m_valign) { - case Qt::AlignTop: - break; // Don't need to do much here case Qt::AlignVCenter: - topMargin = (height() - new_height) / 2; + top_margin = (height() - new_height) / 2; break; case Qt::AlignBottom: - topMargin = (height() - new_height); + top_margin = (height() - new_height); break; default: - qDebug() << "Unrecognized vertical alignment " << this->_verticalAlignment << ". Assuming Top."; break; } + setViewportMargins(0, top_margin, 0, 0); - setViewportMargins(0, topMargin, 0, 0); + // we're done + m_status = Status::Done; } diff --git a/src/lobby.cpp b/src/lobby.cpp index 85377b81c..9a7d680f7 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -261,24 +261,24 @@ void Lobby::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) QString fonts_ini = "lobby_fonts.ini"; // Do outlines bool outline = (ao_app->get_font_property(p_identifier + "_outline", fonts_ini) == 1); - widget->setOutline(outline); + widget->set_outline(outline); // Do horizontal alignments int raw_halign = ao_app->get_font_property(p_identifier + "_halign", fonts_ini); switch (raw_halign) { case 0: - widget->setHorizontalAlignment(Qt::AlignLeft); + widget->set_horizontal_alignment(Qt::AlignLeft); break; case 1: - widget->setHorizontalAlignment(Qt::AlignHCenter); + widget->set_horizontal_alignment(Qt::AlignHCenter); break; case 2: - widget->setHorizontalAlignment(Qt::AlignRight); + widget->set_horizontal_alignment(Qt::AlignRight); break; default: qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; - widget->setHorizontalAlignment(Qt::AlignLeft); + widget->set_horizontal_alignment(Qt::AlignLeft); } // Do vertical alignments @@ -299,7 +299,7 @@ void Lobby::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) qWarning() << "Unknown vertical alignment for" << p_identifier << ":" << raw_valign << "Assuming Top."; valignment = Qt::AlignTop; } - widget->setVerticalAlignment(valignment); + widget->set_vertical_alignment(valignment); } void Lobby::set_loading_text(QString p_text) From c73e9b450f010029c0fd50193a627886837dfab3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 14 Mar 2021 00:02:55 +0100 Subject: [PATCH 259/842] Replaced magic numbers with enum --- include/datatypes.h | 14 ++++++++++++++ src/courtroom_widgets.cpp | 24 ++++++++++++------------ src/lobby.cpp | 24 ++++++++++++------------ 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/include/datatypes.h b/include/datatypes.h index 196e46df7..0b8162966 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -122,6 +122,20 @@ using SplitBehavior = Qt::SplitBehaviorFlags; const SplitBehavior KeepEmptyParts = SplitBehavior::KeepEmptyParts; const SplitBehavior SkipEmptyParts = SplitBehavior::SkipEmptyParts; +enum VAlign : int32_t +{ + Top, + Middle, + Bottom, +}; + +enum HAlign : int32_t +{ + Left, + Center, + Right, +}; + enum Color : int32_t { CDefault, diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index b86bfe738..1db7c3362 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1443,18 +1443,18 @@ void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier, QS int raw_halign = ao_app->get_font_property(p_identifier + "_halign", fonts_ini); switch (raw_halign) { - case 0: + default: + qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; + [[fallthrough]]; + case DR::Left: widget->set_horizontal_alignment(Qt::AlignLeft); break; - case 1: + case DR::Middle: widget->set_horizontal_alignment(Qt::AlignHCenter); break; - case 2: + case DR::Right: widget->set_horizontal_alignment(Qt::AlignRight); break; - default: - qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; - widget->set_horizontal_alignment(Qt::AlignLeft); } // Do vertical alignments @@ -1462,18 +1462,18 @@ void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier, QS Qt::Alignment valignment; switch (raw_valign) { - case 0: + default: + qWarning() << "Unknown vertical alignment for" << p_identifier << ":" << raw_valign << "Assuming Top."; + [[fallthrough]]; + case DR::Top: valignment = Qt::AlignTop; break; - case 1: + case DR::Middle: valignment = Qt::AlignVCenter; break; - case 2: + case DR::Bottom: valignment = Qt::AlignBottom; break; - default: - qWarning() << "Unknown vertical alignment for" << p_identifier << ":" << raw_valign << "Assuming Top."; - valignment = Qt::AlignTop; } widget->set_vertical_alignment(valignment); } diff --git a/src/lobby.cpp b/src/lobby.cpp index 9a7d680f7..65b030d03 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -267,18 +267,18 @@ void Lobby::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) int raw_halign = ao_app->get_font_property(p_identifier + "_halign", fonts_ini); switch (raw_halign) { - case 0: + default: + qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; + [[fallthrough]]; + case DR::Left: widget->set_horizontal_alignment(Qt::AlignLeft); break; - case 1: + case DR::Middle: widget->set_horizontal_alignment(Qt::AlignHCenter); break; - case 2: + case DR::Right: widget->set_horizontal_alignment(Qt::AlignRight); break; - default: - qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; - widget->set_horizontal_alignment(Qt::AlignLeft); } // Do vertical alignments @@ -286,18 +286,18 @@ void Lobby::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) Qt::Alignment valignment; switch (raw_valign) { - case 0: + default: + qWarning() << "Unknown vertical alignment for" << p_identifier << ":" << raw_valign << "Assuming Top."; + [[fallthrough]]; + case DR::Top: valignment = Qt::AlignTop; break; - case 1: + case DR::Middle: valignment = Qt::AlignVCenter; break; - case 2: + case DR::Bottom: valignment = Qt::AlignBottom; break; - default: - qWarning() << "Unknown vertical alignment for" << p_identifier << ":" << raw_valign << "Assuming Top."; - valignment = Qt::AlignTop; } widget->set_vertical_alignment(valignment); } From 0fbd7961b1f069a23c5d2b8980a9103cc08909f3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 14 Mar 2021 01:20:35 +0100 Subject: [PATCH 260/842] Added logging to alerts and removed extra timestamp --- src/courtroom.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f6b5c7d2f..9e6548ee6 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1089,9 +1089,12 @@ void Courtroom::handle_chatmessage_3() { m_system_player->play(ao_app->get_sfx("word_call")); ao_app->alert(this); - ui_server_chatlog->append_chatmessage( - "CLIENT", "[" + QTime::currentTime().toString("HH:mm") + "] " + ui_vp_showname->toPlainText() + - " has called you via your callword \"" + word + "\": \"" + f_message + "\""); + const QString name = "CLIENT"; + const QString message = + ui_vp_showname->toPlainText() + " has called you via your callword \"" + word + "\": \"" + f_message + "\""; + ui_server_chatlog->append_chatmessage(name, message); + if (ao_config->log_is_recording_enabled()) + save_textlog("(OOC)" + name + ": " + message); break; } } From ef51827e54a0e6b817de954a95ff03c911b83876 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 14 Mar 2021 01:21:56 +0100 Subject: [PATCH 261/842] Mod call notice less aggressive --- src/courtroom.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 9e6548ee6..eee7562e7 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2252,8 +2252,9 @@ void Courtroom::on_spectator_clicked() void Courtroom::on_call_mod_clicked() { QMessageBox::StandardButton reply; - QString warning = "Are you sure you want to call a mod? They will get angry " - "at you if the reason is no good."; + QString warning = "Are you sure you want to call a mod?" + "" + "Be prepared to explain what is happening and why they are needed when they answer."; reply = QMessageBox::warning(this, "Warning", warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (reply == QMessageBox::Yes) From b666c846fe968bc4b7cbb8439933d60af37a5411 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 14 Mar 2021 01:28:57 +0100 Subject: [PATCH 262/842] Added space to mod call message --- src/courtroom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index eee7562e7..c76e4e195 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2252,8 +2252,8 @@ void Courtroom::on_spectator_clicked() void Courtroom::on_call_mod_clicked() { QMessageBox::StandardButton reply; - QString warning = "Are you sure you want to call a mod?" - "" + QString warning = "Are you sure you want to call a mod?\n" + "\n" "Be prepared to explain what is happening and why they are needed when they answer."; reply = QMessageBox::warning(this, "Warning", warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); From db458b2f2493b0e5f49051ea80312179488c4bf5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 18 Mar 2021 18:32:22 +0100 Subject: [PATCH 263/842] Fixed short audio issues and tweaks to disconnection popup * Fixed short audio files not playing properly. * Tweaked the Disconnected from server notification to immediately stop all audio upon appearance --- include/courtroom.h | 5 +++-- include/draudiostreamfamily.h | 5 +++-- src/aoapplication.cpp | 1 + src/audio_functions.cpp | 11 +++++++++-- src/courtroom.cpp | 11 ++++------- src/draudiostream.cpp | 13 ++++--------- src/draudiostreamfamily.cpp | 9 ++++++--- 7 files changed, 30 insertions(+), 25 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index adbb3e607..2aa733336 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -848,10 +848,11 @@ private slots: */ public: - bool is_audio_muted(); + bool is_audio_suppressed() const; public slots: - void set_audio_mute_enabled(bool p_enabled); + void suppress_audio(bool p_enabled); + void stop_all_audio(); private: bool m_audio_mute = false; diff --git a/include/draudiostreamfamily.h b/include/draudiostreamfamily.h index db209d733..f88cc8202 100644 --- a/include/draudiostreamfamily.h +++ b/include/draudiostreamfamily.h @@ -18,12 +18,13 @@ class DRAudioStreamFamily : public QObject public: using ptr = QSharedPointer; + using stream_list = QVector; std::optional create_stream(QString p_file); std::optional play_stream(QString p_file); // get - QVector get_stream_list() const; + stream_list get_stream_list() const; int32_t get_volume() const; int32_t get_capacity() const; DRAudio::Options get_options() const; @@ -63,7 +64,7 @@ public slots: int32_t m_volume = 0; int32_t m_capacity = 0; DRAudio::Options m_options; - QVector> m_stream_list; + stream_list m_stream_list; private slots: void on_stream_finished(); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 441097c9e..b8170c8b4 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -274,6 +274,7 @@ void AOApplication::server_disconnected() { if (!courtroom_constructed) return; + w_courtroom->stop_all_audio(); call_notice("Disconnected from server."); construct_lobby(); destruct_courtroom(); diff --git a/src/audio_functions.cpp b/src/audio_functions.cpp index 84bd8ce3f..34bf0fadb 100644 --- a/src/audio_functions.cpp +++ b/src/audio_functions.cpp @@ -1,11 +1,11 @@ #include "courtroom.h" -bool Courtroom::is_audio_muted() +bool Courtroom::is_audio_suppressed() const { return m_audio_mute; } -void Courtroom::set_audio_mute_enabled(bool p_enabled) +void Courtroom::suppress_audio(bool p_enabled) { if (m_audio_mute == p_enabled) return; @@ -15,3 +15,10 @@ void Courtroom::set_audio_mute_enabled(bool p_enabled) for (auto &family : DRAudioEngine::get_family_list()) family->set_suppressed(m_audio_mute); } + +void Courtroom::stop_all_audio() +{ + for (auto &family : DRAudioEngine::get_family_list()) + for (auto &stream : family->get_stream_list()) + stream->stop(); +} diff --git a/src/courtroom.cpp b/src/courtroom.cpp index c76e4e195..1b16fdc99 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -36,10 +36,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() Courtroom::~Courtroom() { - // shutdown all audio - for (auto &family : DRAudioEngine::get_family_list()) - for (auto &stream : family->get_stream_list()) - stream->stop(); + stop_all_audio(); } void Courtroom::enter_courtroom(int p_cid) @@ -133,7 +130,7 @@ void Courtroom::enter_courtroom(int p_cid) ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors // unmute audio - set_audio_mute_enabled(false); + suppress_audio(false); testimony_in_progress = false; @@ -155,7 +152,7 @@ void Courtroom::done_received() { m_cid = -1; - set_audio_mute_enabled(true); + suppress_audio(true); set_char_select_page(); @@ -2186,7 +2183,7 @@ void Courtroom::on_wtce_clicked() void Courtroom::on_change_character_clicked() { - set_audio_mute_enabled(true); + suppress_audio(true); set_char_select(); diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index 4369748c8..1683f01fc 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -89,13 +89,11 @@ std::optional DRAudioStream::set_file(QString p_file) // bass events for (const DWORD type : {BASS_SYNC_END, BASS_SYNC_DEV_FAIL}) { - HSYNC sync_handle = - BASS_ChannelSetSync(stream_handle, type | BASS_SYNC_MIXTIME, 0, &DRAudioStream::on_sync_callback, this); + const DWORD final_type = type == BASS_SYNC_DEV_FAIL ? type | BASS_SYNC_MIXTIME : type; + HSYNC sync_handle = BASS_ChannelSetSync(stream_handle, final_type, 0, &DRAudioStream::on_sync_callback, this); if (sync_handle == 0) - return DRAudioError(QString("failed to create sync %1 for file %2: %3") - .arg(type) - .arg(p_file) - .arg(DRAudio::get_last_bass_error())); + return DRAudioError( + QString("failed to create sync %1 for file %2: %3").arg(type).arg(p_file, DRAudio::get_last_bass_error())); m_hsync_stack.push(DRAudioStreamSync{sync_handle, type}); } @@ -124,14 +122,11 @@ void DRAudioStream::on_sync_callback(HSYNC hsync, DWORD ch, DWORD data, void *us * the pointer we provided get deleted mid-way through the program lifetime */ DRAudioStream *self = static_cast(userdata); - for (auto &v : self->m_hsync_stack) { if (v.sync != hsync) continue; - const QString filePath = self->get_file().value(); - switch (v.type) { case BASS_SYNC_END: diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp index 1be22c990..acd1d35f4 100644 --- a/src/draudiostreamfamily.cpp +++ b/src/draudiostreamfamily.cpp @@ -87,7 +87,6 @@ std::optional DRAudioStreamFamily::create_stream(QString p_f m_stream_list.append(stream); update_capacity(); - stream->set_volume(calculate_volume()); connect(stream.get(), SIGNAL(finished()), this, SLOT(on_stream_finished())); @@ -106,7 +105,7 @@ std::optional DRAudioStreamFamily::play_stream(QString p_fil return r_stream; } -QVector DRAudioStreamFamily::get_stream_list() const +DRAudioStreamFamily::stream_list DRAudioStreamFamily::get_stream_list() const { return m_stream_list; } @@ -161,11 +160,15 @@ void DRAudioStreamFamily::on_stream_finished() if (invoker == nullptr) return; - decltype(m_stream_list) new_stream_list; + stream_list new_stream_list; for (auto &i_stream : m_stream_list) { if (i_stream.get() == invoker) + { + if (auto file = i_stream->get_file(); file) + qDebug() << "removing" << file.value(); continue; + } new_stream_list.append(std::move(i_stream)); } m_stream_list = std::move(new_stream_list); From 58f3ff4b636b9afec7226fccdd9cbf5c4ef4160a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 19 Mar 2021 11:25:20 +0100 Subject: [PATCH 264/842] Missing dependencies resolved --- include/aoconfig.h | 4 +++- include/draudiodevice.h | 2 ++ include/drdiscord.h | 2 ++ src/draudiostreamfamily.cpp | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 391258339..8e3298718 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -2,9 +2,11 @@ #define AOCONFIG_H #include "datatypes.h" -// qt + #include +#include + class AOConfig : public QObject { Q_OBJECT diff --git a/include/draudiodevice.h b/include/draudiodevice.h index a4285f708..c142f8d60 100644 --- a/include/draudiodevice.h +++ b/include/draudiodevice.h @@ -6,6 +6,8 @@ #include #include +#include + class DRAudioEngine; class DRAudioEnginePrivate; diff --git a/include/drdiscord.h b/include/drdiscord.h index 795a5ea68..198b5a7cd 100644 --- a/include/drdiscord.h +++ b/include/drdiscord.h @@ -9,6 +9,8 @@ #include #include +#include + class DRDiscord : public QObject { Q_OBJECT diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp index acd1d35f4..51a8bbd6d 100644 --- a/src/draudiostreamfamily.cpp +++ b/src/draudiostreamfamily.cpp @@ -1,3 +1,5 @@ +#define NOMINMAX + #include "draudiostreamfamily.h" #include "draudioengine.h" From 15ccf32e78e59ab80102972cc2278d48866c5704 Mon Sep 17 00:00:00 2001 From: Chrezm Cada Date: Sat, 20 Mar 2021 09:30:28 -0700 Subject: [PATCH 265/842] First working version --- .DS_Store | Bin 0 -> 8196 bytes dronline-client.pro | 2 ++ include/aoapplication.h | 1 + src/aoconfig.cpp | 2 +- src/aoconfigpanel.cpp | 8 ++++---- src/aonotepicker.cpp | 2 +- src/draudio.cpp | 8 ++++---- src/draudiostream.cpp | 8 ++++---- src/hardware_functions.cpp | 7 +++++-- src/lobby.cpp | 4 +++- src/path_functions.cpp | 13 +++++++++++-- 11 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6922b76e2a0c4b825d145f47666c77714bd90a6d GIT binary patch literal 8196 zcmeHM(QXn!6unbW78)Z-o516~nE22(R2ncbCP?9-i5eqDAJnwFKw1*Ii|isQVtDZ% zs(;|0=ok1Qet@1kGsWGdm_DfqbCa1fJ9F>dd(ZC9?gb(e^-*V+s7OQ>8e@JP-37wy z+!ka_&ujn%?8$Gtec#1VenHnv7zPXjh5^HXVZbo(e=vY|HjB07y{}eHZx}EPtRw@x zKiFuDHH|Zca_c}RO8{UR(^4>&AOAp`CxA7LGliIe2xSUXrb3q(Le~yLnPa`C^=Ar| zISF}Y_|az;x zo>Z%)EqkwAnNG7-;l}RWgQL^8?z_P58ls63gZ?eg^QJh)3R2XOVl#n#Z%FM)C6!v$#zE0{@bw zsD1`sJ$bc8;6HJP3uY&-eV-0!pPKZLTGWik!ka}*J4=tDd5;?O0JuY^$?b)+O>8gB zauD~+ImWay@>&G&_zI(TT$?kDX{T`g7PXLBObekKF$}C811Xhqk?a5F^56egj~J7& zVPJ(A5a~v%QAfh(-^a<^xz@JPj?h?GZ>CV1pp)e|P?qDs`5%TD+fZejn#P$z%s~G7 S4*~pTW8VLgd(*8h1HS;-wPwTs literal 0 HcmV?d00001 diff --git a/dronline-client.pro b/dronline-client.pro index 2d8366f1b..423f1bbdd 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -134,3 +134,5 @@ DISTFILES += FORMS += \ res/ui/config_panel.ui + +QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.15 diff --git a/include/aoapplication.h b/include/aoapplication.h index 4ff285ceb..db71d2988 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -105,6 +105,7 @@ class AOApplication : public QApplication QString get_current_char(); // implementation in path_functions.cpp + QString get_application_path(); QString get_base_path(); QString get_data_path(); QString get_character_path(QString p_character, QString p_file); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 99b2334e6..bf73b77bc 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -85,7 +85,7 @@ private slots: }; AOConfigPrivate::AOConfigPrivate() - : QObject(nullptr), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat), + : QObject(nullptr), cfg(QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath() + "/base/config.ini", QSettings::IniFormat), audio_engine(new DRAudioEngine(this)) { Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 8c8d36af4..046d1e83e 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -256,7 +256,7 @@ void AOConfigPanel::refresh_theme_list() w_theme->clear(); // themes - const QString path = QDir::currentPath() + "/base/themes"; + const QString path = ao_app->get_application_path() + "/base/themes"; for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") @@ -282,7 +282,7 @@ void AOConfigPanel::refresh_gamemode_list() // add empty entry indicating no gamemode chosen w_gamemode->addItem(""); // gamemodes - QString path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; + QString path = ao_app->get_application_path() + "/base/themes/" + m_config->theme() + "/gamemodes/"; for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") @@ -312,9 +312,9 @@ void AOConfigPanel::refresh_timeofday_list() // gamemode chosen or not QString path; if (m_config->gamemode().isEmpty()) - path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; + path = ao_app->get_application_path() + "/base/themes/" + m_config->theme() + "/times/"; else - path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/" + m_config->gamemode() + "/times/"; + path = ao_app->get_application_path() + "/base/themes/" + m_config->theme() + "/gamemodes/" + m_config->gamemode() + "/times/"; // times of day for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) diff --git a/src/aonotepicker.cpp b/src/aonotepicker.cpp index 738308c69..bdbcb2648 100644 --- a/src/aonotepicker.cpp +++ b/src/aonotepicker.cpp @@ -44,7 +44,7 @@ void Courtroom::on_set_file_button_clicked() { AOButton *f_button = static_cast(sender()); AONotePicker *f_notepicker = static_cast(f_button->parent()); - QString f_filename = QFileDialog::getOpenFileName(this, "Open File", QDir::currentPath(), "Text files (*.txt)"); + QString f_filename = QFileDialog::getOpenFileName(this, "Open File", ao_app->get_application_path(), "Text files (*.txt)"); if (f_filename != "") { diff --git a/src/draudio.cpp b/src/draudio.cpp index a2f5cc677..91a449e63 100644 --- a/src/draudio.cpp +++ b/src/draudio.cpp @@ -30,8 +30,8 @@ QString DRAudio::get_bass_error(const int32_t p_error_code) return "SSL/HTTPS support isn't available"; case BASS_ERROR_ALREADY: return "already initialized/paused/whatever"; - case BASS_ERROR_NOTAUDIO: - return "file does not contain audio"; +// case BASS_ERROR_NOTAUDIO: +// return "file does not contain audio"; case BASS_ERROR_NOCHAN: return "can't get a free channel"; case BASS_ERROR_ILLTYPE: @@ -80,8 +80,8 @@ QString DRAudio::get_bass_error(const int32_t p_error_code) return "the channel/file has ended"; case BASS_ERROR_BUSY: return "the device is busy"; - case BASS_ERROR_UNSTREAMABLE: - return "unstreamable file"; +// case BASS_ERROR_UNSTREAMABLE: +// return "unstreamable file"; case BASS_ERROR_UNKNOWN: return "some other mystery problem"; default: diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index 4369748c8..0257c8945 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -87,7 +87,7 @@ std::optional DRAudioStream::set_file(QString p_file) m_hstream = stream_handle; // bass events - for (const DWORD type : {BASS_SYNC_END, BASS_SYNC_DEV_FAIL}) + for (const DWORD type : {BASS_SYNC_END})//, BASS_SYNC_DEV_FAIL}) { HSYNC sync_handle = BASS_ChannelSetSync(stream_handle, type | BASS_SYNC_MIXTIME, 0, &DRAudioStream::on_sync_callback, this); @@ -138,9 +138,9 @@ void DRAudioStream::on_sync_callback(HSYNC hsync, DWORD ch, DWORD data, void *us Q_EMIT self->finished(); break; - case BASS_SYNC_DEV_FAIL: - Q_EMIT self->device_error(QPrivateSignal()); - break; + //case BASS_SYNC_DEV_FAIL: + // Q_EMIT self->device_error(QPrivateSignal()); + // break; } } } diff --git a/src/hardware_functions.cpp b/src/hardware_functions.cpp index 3af0b92a6..40775f189 100644 --- a/src/hardware_functions.cpp +++ b/src/hardware_functions.cpp @@ -50,7 +50,10 @@ QString get_hdid() } #else - -#error This operating system is unsupported for hardware functions. +QString get_hdid() +{ + return "macOSHDID"; +} +// #error This operating system is unsupported for hardware functions. #endif diff --git a/src/lobby.cpp b/src/lobby.cpp index 65b030d03..c5e72796b 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -75,6 +75,8 @@ void Lobby::set_widgets() if (f_lobby.width < 0 || f_lobby.height < 0) { + QString home_path = QFileInfo(QCoreApplication::applicationDirPath() + "/../..").canonicalPath(); + call_notice(ao_app->get_base_path()); qDebug() << "W: did not find lobby width or height in " << filename; // Most common symptom of bad config files, missing assets, or misnamed @@ -85,7 +87,7 @@ void Lobby::set_widgets() "the DRO Discord including the large 'base' folder.\n" "2. If you did, check that the base folder is in the same folder " "where you launched Danganronpa Online from: " + - QDir::currentPath() + + home_path + "\n" "3. If it is there, check that your current theme folder exists in " "base/themes. According to base/config.ini, your current theme is " + diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 1548d95f9..f2d7e1f30 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -1,5 +1,6 @@ #include "aoapplication.h" #include "courtroom.h" +#include "debug_functions.h" #include "file_functions.h" #include #include @@ -20,6 +21,10 @@ #endif QString base_path = ""; +QString AOApplication::get_application_path() +{ + return QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath(); +} QString AOApplication::get_base_path() { if (base_path == "") @@ -27,7 +32,9 @@ QString AOApplication::get_base_path() #ifdef BASE_OVERRIDE base_path = base_override; #else - base_path = QDir::currentPath() + "/base/"; + base_path = QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath(); + base_path = QFileInfo(base_path + "/base/").absoluteFilePath() + "/"; + call_notice(base_path); #endif } return base_path; @@ -92,7 +99,7 @@ QString Courtroom::get_background_path(QString p_file) #ifndef CASE_SENSITIVE_FILESYSTEM QString AOApplication::get_case_sensitive_path(QString p_file) { - return p_file; + return p_file.replace("//", "/"); } #else QString AOApplication::get_case_sensitive_path(QString p_file) @@ -135,6 +142,7 @@ QString AOApplication::find_asset_path(QStringList possible_roots, QStringList p for (QString ext : possible_exts) { QString full_path = get_case_sensitive_path(root + ext); + qDebug() << full_path; if (file_exists(full_path)) return full_path; } @@ -153,5 +161,6 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList exts) get_base_path() + "themes/default/" + p_file, }; + //qDebug() << paths; return find_asset_path(paths, exts); } From 9d64cae6c98a0e859f9f2a42beda564bf055cbc0 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 20 Mar 2021 15:28:38 -0400 Subject: [PATCH 266/842] Remove leftover functions and reintroduce some bass commented out code --- .DS_Store | Bin 8196 -> 0 bytes .gitignore | 3 ++- dronline-client.pro | 2 +- src/draudio.cpp | 8 ++++---- src/draudiostream.cpp | 8 ++++---- src/lobby.cpp | 3 +-- src/path_functions.cpp | 3 --- 7 files changed, 12 insertions(+), 15 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 6922b76e2a0c4b825d145f47666c77714bd90a6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHM(QXn!6unbW78)Z-o516~nE22(R2ncbCP?9-i5eqDAJnwFKw1*Ii|isQVtDZ% zs(;|0=ok1Qet@1kGsWGdm_DfqbCa1fJ9F>dd(ZC9?gb(e^-*V+s7OQ>8e@JP-37wy z+!ka_&ujn%?8$Gtec#1VenHnv7zPXjh5^HXVZbo(e=vY|HjB07y{}eHZx}EPtRw@x zKiFuDHH|Zca_c}RO8{UR(^4>&AOAp`CxA7LGliIe2xSUXrb3q(Le~yLnPa`C^=Ar| zISF}Y_|az;x zo>Z%)EqkwAnNG7-;l}RWgQL^8?z_P58ls63gZ?eg^QJh)3R2XOVl#n#Z%FM)C6!v$#zE0{@bw zsD1`sJ$bc8;6HJP3uY&-eV-0!pPKZLTGWik!ka}*J4=tDd5;?O0JuY^$?b)+O>8gB zauD~+ImWay@>&G&_zI(TT$?kDX{T`g7PXLBObekKF$}C811Xhqk?a5F^56egj~J7& zVPJ(A5a~v%QAfh(-^a<^xz@JPj?h?GZ>CV1pp)e|P?qDs`5%TD+fZejn#P$z%s~G7 S4*~pTW8VLgd(*8h1HS;-wPwTs diff --git a/.gitignore b/.gitignore index 769cd5cb0..497a24fbe 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ docs/ *.user *.dll *.so -*.autosave \ No newline at end of file +*.autosave +*DS_Store diff --git a/dronline-client.pro b/dronline-client.pro index 423f1bbdd..8d9852f17 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -135,4 +135,4 @@ DISTFILES += FORMS += \ res/ui/config_panel.ui -QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.15 +QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14 diff --git a/src/draudio.cpp b/src/draudio.cpp index 91a449e63..a2f5cc677 100644 --- a/src/draudio.cpp +++ b/src/draudio.cpp @@ -30,8 +30,8 @@ QString DRAudio::get_bass_error(const int32_t p_error_code) return "SSL/HTTPS support isn't available"; case BASS_ERROR_ALREADY: return "already initialized/paused/whatever"; -// case BASS_ERROR_NOTAUDIO: -// return "file does not contain audio"; + case BASS_ERROR_NOTAUDIO: + return "file does not contain audio"; case BASS_ERROR_NOCHAN: return "can't get a free channel"; case BASS_ERROR_ILLTYPE: @@ -80,8 +80,8 @@ QString DRAudio::get_bass_error(const int32_t p_error_code) return "the channel/file has ended"; case BASS_ERROR_BUSY: return "the device is busy"; -// case BASS_ERROR_UNSTREAMABLE: -// return "unstreamable file"; + case BASS_ERROR_UNSTREAMABLE: + return "unstreamable file"; case BASS_ERROR_UNKNOWN: return "some other mystery problem"; default: diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index 0257c8945..4369748c8 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -87,7 +87,7 @@ std::optional DRAudioStream::set_file(QString p_file) m_hstream = stream_handle; // bass events - for (const DWORD type : {BASS_SYNC_END})//, BASS_SYNC_DEV_FAIL}) + for (const DWORD type : {BASS_SYNC_END, BASS_SYNC_DEV_FAIL}) { HSYNC sync_handle = BASS_ChannelSetSync(stream_handle, type | BASS_SYNC_MIXTIME, 0, &DRAudioStream::on_sync_callback, this); @@ -138,9 +138,9 @@ void DRAudioStream::on_sync_callback(HSYNC hsync, DWORD ch, DWORD data, void *us Q_EMIT self->finished(); break; - //case BASS_SYNC_DEV_FAIL: - // Q_EMIT self->device_error(QPrivateSignal()); - // break; + case BASS_SYNC_DEV_FAIL: + Q_EMIT self->device_error(QPrivateSignal()); + break; } } } diff --git a/src/lobby.cpp b/src/lobby.cpp index c5e72796b..1e8ff5a9d 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -76,7 +76,6 @@ void Lobby::set_widgets() if (f_lobby.width < 0 || f_lobby.height < 0) { QString home_path = QFileInfo(QCoreApplication::applicationDirPath() + "/../..").canonicalPath(); - call_notice(ao_app->get_base_path()); qDebug() << "W: did not find lobby width or height in " << filename; // Most common symptom of bad config files, missing assets, or misnamed @@ -87,7 +86,7 @@ void Lobby::set_widgets() "the DRO Discord including the large 'base' folder.\n" "2. If you did, check that the base folder is in the same folder " "where you launched Danganronpa Online from: " + - home_path + + ao_app->get_application_path() + "\n" "3. If it is there, check that your current theme folder exists in " "base/themes. According to base/config.ini, your current theme is " + diff --git a/src/path_functions.cpp b/src/path_functions.cpp index f2d7e1f30..d1b3f7cf1 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -34,7 +34,6 @@ QString AOApplication::get_base_path() #else base_path = QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath(); base_path = QFileInfo(base_path + "/base/").absoluteFilePath() + "/"; - call_notice(base_path); #endif } return base_path; @@ -142,7 +141,6 @@ QString AOApplication::find_asset_path(QStringList possible_roots, QStringList p for (QString ext : possible_exts) { QString full_path = get_case_sensitive_path(root + ext); - qDebug() << full_path; if (file_exists(full_path)) return full_path; } @@ -161,6 +159,5 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList exts) get_base_path() + "themes/default/" + p_file, }; - //qDebug() << paths; return find_asset_path(paths, exts); } From 1a74cc6272420b91b14942b94f5596eb759095df Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 22 Mar 2021 13:59:29 -0400 Subject: [PATCH 267/842] Add MacOS specific routines+Implement AO style HDIDs for Mac --- include/aoapplication.h | 1 - res/ui/config_panel.ui | 2 +- src/aoconfig.cpp | 6 +++++- src/aoconfigpanel.cpp | 8 ++++---- src/aonotepicker.cpp | 2 +- src/hardware_functions.cpp | 32 +++++++++++++++++++++++++++++--- src/lobby.cpp | 3 +-- src/main.cpp | 9 +++++++++ src/path_functions.cpp | 7 +------ 9 files changed, 51 insertions(+), 19 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index db71d2988..4ff285ceb 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -105,7 +105,6 @@ class AOApplication : public QApplication QString get_current_char(); // implementation in path_functions.cpp - QString get_application_path(); QString get_base_path(); QString get_data_path(); QString get_character_path(QString p_character, QString p_file); diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index b451ff59f..4e976a6ce 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 0 + 3 diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index bf73b77bc..c887f1cc5 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -85,7 +85,7 @@ private slots: }; AOConfigPrivate::AOConfigPrivate() - : QObject(nullptr), cfg(QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath() + "/base/config.ini", QSettings::IniFormat), + : QObject(nullptr), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat), audio_engine(new DRAudioEngine(this)) { Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); @@ -237,6 +237,10 @@ static QSharedPointer d; AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) { +#if defined __APPLE__ + QString path = (QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath()); + QDir::setCurrent(path); +#endif // init if not created yet if (d == nullptr) { diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 046d1e83e..6f6957386 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -256,7 +256,7 @@ void AOConfigPanel::refresh_theme_list() w_theme->clear(); // themes - const QString path = ao_app->get_application_path() + "/base/themes"; + const QString path = QDir::currentPath() + "/base/themes"; for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") @@ -282,7 +282,7 @@ void AOConfigPanel::refresh_gamemode_list() // add empty entry indicating no gamemode chosen w_gamemode->addItem(""); // gamemodes - QString path = ao_app->get_application_path() + "/base/themes/" + m_config->theme() + "/gamemodes/"; + QString path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") @@ -312,9 +312,9 @@ void AOConfigPanel::refresh_timeofday_list() // gamemode chosen or not QString path; if (m_config->gamemode().isEmpty()) - path = ao_app->get_application_path() + "/base/themes/" + m_config->theme() + "/times/"; + path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; else - path = ao_app->get_application_path() + "/base/themes/" + m_config->theme() + "/gamemodes/" + m_config->gamemode() + "/times/"; + path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/" + m_config->gamemode() + "/times/"; // times of day for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) diff --git a/src/aonotepicker.cpp b/src/aonotepicker.cpp index bdbcb2648..738308c69 100644 --- a/src/aonotepicker.cpp +++ b/src/aonotepicker.cpp @@ -44,7 +44,7 @@ void Courtroom::on_set_file_button_clicked() { AOButton *f_button = static_cast(sender()); AONotePicker *f_notepicker = static_cast(f_button->parent()); - QString f_filename = QFileDialog::getOpenFileName(this, "Open File", ao_app->get_application_path(), "Text files (*.txt)"); + QString f_filename = QFileDialog::getOpenFileName(this, "Open File", QDir::currentPath(), "Text files (*.txt)"); if (f_filename != "") { diff --git a/src/hardware_functions.cpp b/src/hardware_functions.cpp index 40775f189..ee3222abb 100644 --- a/src/hardware_functions.cpp +++ b/src/hardware_functions.cpp @@ -49,11 +49,37 @@ QString get_hdid() return "gxcpz32sa9fnwic92mfbs0"; } -#else +#elif defined __APPLE__ + +#include +#include + QString get_hdid() { - return "macOSHDID"; + // This code is from AO. + CFStringRef serial; + char buffer[64] = {0}; + QString hdid; + io_service_t platformExpert = IOServiceGetMatchingService( + kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); + if (platformExpert) { + CFTypeRef serialNumberAsCFString = IORegistryEntryCreateCFProperty( + platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, + 0); + if (serialNumberAsCFString) { + serial = (CFStringRef)serialNumberAsCFString; + } + if (CFStringGetCString(serial, buffer, 64, kCFStringEncodingUTF8)) { + hdid = buffer; + } + + IOObjectRelease(platformExpert); + } + return hdid; } -// #error This operating system is unsupported for hardware functions. + +#else + +#error This operating system is unsupported for hardware functions. #endif diff --git a/src/lobby.cpp b/src/lobby.cpp index 1e8ff5a9d..65b030d03 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -75,7 +75,6 @@ void Lobby::set_widgets() if (f_lobby.width < 0 || f_lobby.height < 0) { - QString home_path = QFileInfo(QCoreApplication::applicationDirPath() + "/../..").canonicalPath(); qDebug() << "W: did not find lobby width or height in " << filename; // Most common symptom of bad config files, missing assets, or misnamed @@ -86,7 +85,7 @@ void Lobby::set_widgets() "the DRO Discord including the large 'base' folder.\n" "2. If you did, check that the base folder is in the same folder " "where you launched Danganronpa Online from: " + - ao_app->get_application_path() + + QDir::currentPath() + "\n" "3. If it is there, check that your current theme folder exists in " "base/themes. According to base/config.ini, your current theme is " + diff --git a/src/main.cpp b/src/main.cpp index bd426bae1..23c1467f8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,8 @@ #include "lobby.h" #include "networkmanager.h" +#include +#include int main(int argc, char *argv[]) { #if QT_VERSION > QT_VERSION_CHECK(5, 6, 0) @@ -12,9 +14,16 @@ int main(int argc, char *argv[]) // However, many Linux distros still haven't brought their stable/LTS // packages up to Qt 5.6, so this is conditional. AOApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif +#if defined __APPLE__ + qputenv("QT_MAC_WANTS_LAYER", "1"); #endif AOApplication app(argc, argv); +#if defined __APPLE__ + QString path = (QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath()); + QDir::setCurrent(path); +#endif app.construct_lobby(); #ifdef QT_NO_DEBUG app.net_manager->connect_to_master(); diff --git a/src/path_functions.cpp b/src/path_functions.cpp index d1b3f7cf1..3cba212e4 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -21,10 +21,6 @@ #endif QString base_path = ""; -QString AOApplication::get_application_path() -{ - return QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath(); -} QString AOApplication::get_base_path() { if (base_path == "") @@ -32,8 +28,7 @@ QString AOApplication::get_base_path() #ifdef BASE_OVERRIDE base_path = base_override; #else - base_path = QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath(); - base_path = QFileInfo(base_path + "/base/").absoluteFilePath() + "/"; + base_path = QDir::currentPath() + "/base/"; #endif } return base_path; From d7ed511ee7f2f861a9e7b5604428e1c5c5adba2f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 22 Mar 2021 14:08:36 -0400 Subject: [PATCH 268/842] Remove some leftover code --- src/aoconfig.cpp | 2 +- src/aoconfigpanel.cpp | 4 ++-- src/path_functions.cpp | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index c887f1cc5..376aa3649 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -238,7 +238,7 @@ static QSharedPointer d; AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) { #if defined __APPLE__ - QString path = (QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath()); + QString path = QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath(); QDir::setCurrent(path); #endif // init if not created yet diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 6f6957386..8c8d36af4 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -312,9 +312,9 @@ void AOConfigPanel::refresh_timeofday_list() // gamemode chosen or not QString path; if (m_config->gamemode().isEmpty()) - path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; + path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; else - path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/" + m_config->gamemode() + "/times/"; + path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/" + m_config->gamemode() + "/times/"; // times of day for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 3cba212e4..765ad6716 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -1,6 +1,5 @@ #include "aoapplication.h" #include "courtroom.h" -#include "debug_functions.h" #include "file_functions.h" #include #include From dd08126fa7cc33b9b516815e5fb4c38f8675e7ab Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 27 Mar 2021 12:19:58 +0100 Subject: [PATCH 269/842] Q_OS_MACOS in place of __APPLE__, improve pathing slightly --- include/hardware_functions.h | 2 -- src/aoconfig.cpp | 4 ---- src/hardware_functions.cpp | 31 +++++++++++++------------------ src/main.cpp | 23 +++++++++++++++++------ 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/include/hardware_functions.h b/include/hardware_functions.h index 3fcb27cb1..5aa8473c5 100644 --- a/include/hardware_functions.h +++ b/include/hardware_functions.h @@ -3,8 +3,6 @@ #include -#include - QString get_hdid(); #endif // HARDWARE_FUNCTIONS_H diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 376aa3649..99b2334e6 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -237,10 +237,6 @@ static QSharedPointer d; AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) { -#if defined __APPLE__ - QString path = QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath(); - QDir::setCurrent(path); -#endif // init if not created yet if (d == nullptr) { diff --git a/src/hardware_functions.cpp b/src/hardware_functions.cpp index ee3222abb..ff591504e 100644 --- a/src/hardware_functions.cpp +++ b/src/hardware_functions.cpp @@ -2,7 +2,7 @@ #include -#if (defined(_WIN32) || defined(_WIN64)) +#ifdef Q_OS_WINDOWS #include DWORD dwVolSerial; @@ -19,9 +19,7 @@ QString get_hdid() // what could possibly go wrong return "gxsps32sa9fnwic92mfbs0"; } - -#elif (defined(LINUX) || defined(__linux__)) - +#elif defined(Q_OS_LINUX) #include #include @@ -48,9 +46,7 @@ QString get_hdid() return "gxcpz32sa9fnwic92mfbs0"; } - -#elif defined __APPLE__ - +#elif defined(Q_OS_MACOS) #include #include @@ -60,16 +56,18 @@ QString get_hdid() CFStringRef serial; char buffer[64] = {0}; QString hdid; - io_service_t platformExpert = IOServiceGetMatchingService( - kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); - if (platformExpert) { - CFTypeRef serialNumberAsCFString = IORegistryEntryCreateCFProperty( - platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, - 0); - if (serialNumberAsCFString) { + io_service_t platformExpert = + IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); + if (platformExpert) + { + CFTypeRef serialNumberAsCFString = + IORegistryEntryCreateCFProperty(platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); + if (serialNumberAsCFString) + { serial = (CFStringRef)serialNumberAsCFString; } - if (CFStringGetCString(serial, buffer, 64, kCFStringEncodingUTF8)) { + if (CFStringGetCString(serial, buffer, 64, kCFStringEncodingUTF8)) + { hdid = buffer; } @@ -77,9 +75,6 @@ QString get_hdid() } return hdid; } - #else - #error This operating system is unsupported for hardware functions. - #endif diff --git a/src/main.cpp b/src/main.cpp index 23c1467f8..26def361d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,8 +5,13 @@ #include "lobby.h" #include "networkmanager.h" +#ifdef Q_OS_MACOS #include #include +#endif +#include +#include + int main(int argc, char *argv[]) { #if QT_VERSION > QT_VERSION_CHECK(5, 6, 0) @@ -15,15 +20,21 @@ int main(int argc, char *argv[]) // packages up to Qt 5.6, so this is conditional. AOApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif -#if defined __APPLE__ - qputenv("QT_MAC_WANTS_LAYER", "1"); + +#ifdef Q_OS_MACOS + { // MacOS + qputenv("QT_MAC_WANTS_LAYER", "1"); + + // pathing + QDir l_mac_path(QCoreApplication::applicationDirPath()); + for (int i = 0; i < 3; ++i) // equivalent of "/../../.." + l_mac_path.cdUp(); + QDir::setCurrent(l_mac_path.canonicalPath()); + } #endif + AOApplication app(argc, argv); -#if defined __APPLE__ - QString path = (QFileInfo(QCoreApplication::applicationDirPath() + "/../../..").canonicalFilePath()); - QDir::setCurrent(path); -#endif app.construct_lobby(); #ifdef QT_NO_DEBUG app.net_manager->connect_to_master(); From c6cf5da95ccd402e12f3ff7469abb4e42958cef3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 30 Mar 2021 16:43:28 +0200 Subject: [PATCH 270/842] Added emote hiding * Added emote hiding hardcoded parameter --- src/courtroom.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 1b16fdc99..61a64089a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -978,8 +978,9 @@ void Courtroom::handle_chatmessage_3() return; ui_vp_player_char->stop(); - QString f_char = m_chatmessage[CMChrName]; - QString f_emote = m_chatmessage[CMEmote]; + const QString f_char = m_chatmessage[CMChrName]; + const QString f_emote = m_chatmessage[CMEmote]; + const bool l_hide_emote = (f_emote == "../../misc/blank"); ui_vp_showname_image->show(); @@ -1006,6 +1007,7 @@ void Courtroom::handle_chatmessage_3() ui_vp_showname_image->hide(); } + ui_vp_player_char->setHidden(l_hide_emote); switch (f_anim_state) { case 2: From 298953270ac0d1d1502aec028ea05f16ca661e94 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 30 Mar 2021 13:33:41 -0400 Subject: [PATCH 271/842] Add path set back to Config (it doesnt work otherwise) --- src/aoconfig.cpp | 8 ++++++++ src/main.cpp | 4 ---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 99b2334e6..a45ce6b43 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -237,6 +237,14 @@ static QSharedPointer d; AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) { + #if defined __APPLE__ + // pathing + QDir l_mac_path(QCoreApplication::applicationDirPath()); + for (int i = 0; i < 3; ++i) // equivalent of "/../../.." + l_mac_path.cdUp(); + QDir::setCurrent(l_mac_path.canonicalPath()); + #endif + // init if not created yet if (d == nullptr) { diff --git a/src/main.cpp b/src/main.cpp index 26def361d..920424f9b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,10 +5,6 @@ #include "lobby.h" #include "networkmanager.h" -#ifdef Q_OS_MACOS -#include -#include -#endif #include #include From fd6925909cd7489897b3da40815f745b4effd531 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 30 Mar 2021 21:46:09 -0400 Subject: [PATCH 272/842] Unify path setting for MacOS under DRPather class --- dronline-client.pro | 2 ++ include/drpather.h | 22 ++++++++++++++++++++++ src/aoconfig.cpp | 9 ++------- src/drpather.cpp | 26 ++++++++++++++++++++++++++ src/main.cpp | 8 ++------ 5 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 include/drpather.h create mode 100644 src/drpather.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 8d9852f17..1d8c31515 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -54,6 +54,7 @@ HEADERS += \ include/draudioerror.h \ include/draudiostream.h \ include/draudiostreamfamily.h \ + include/drpather.h \ include/drtextedit.h \ include/drdiscord.h \ include/file_functions.h \ @@ -105,6 +106,7 @@ SOURCES += \ src/draudioerror.cpp \ src/draudiostream.cpp \ src/draudiostreamfamily.cpp \ + src/drpather.cpp \ src/drtextedit.cpp \ src/drdiscord.cpp \ src/emotes.cpp \ diff --git a/include/drpather.h b/include/drpather.h new file mode 100644 index 000000000..71eb0f505 --- /dev/null +++ b/include/drpather.h @@ -0,0 +1,22 @@ +#ifndef DRPATHER_H +#define DRPATHER_H + +#include + +class DRPather : QObject +{ + Q_OBJECT +public: + DRPather(); + + /* @brief Sets the current path (as given by QDir::currentPath()) to be the directory containing the base folder. + * + * This function makes running QDir::currentPath() system-independent. The only edge case really is MacOS, where + * due to the way applications are bundled, makes QDir::currentPath() return nonsensical values by default. + * + * @return True if the current path was changed successfully, false if it remained the same. + */ + static void correctCurrentPath(); +}; + +#endif // DRPATHER_H diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index a45ce6b43..4e6024a1f 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -1,6 +1,7 @@ #include "aoconfig.h" #include "datatypes.h" #include "draudioengine.h" +#include "drpather.h" // qt #include @@ -237,13 +238,7 @@ static QSharedPointer d; AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) { - #if defined __APPLE__ - // pathing - QDir l_mac_path(QCoreApplication::applicationDirPath()); - for (int i = 0; i < 3; ++i) // equivalent of "/../../.." - l_mac_path.cdUp(); - QDir::setCurrent(l_mac_path.canonicalPath()); - #endif + DRPather::correctCurrentPath(); // init if not created yet if (d == nullptr) diff --git a/src/drpather.cpp b/src/drpather.cpp new file mode 100644 index 000000000..608f4026d --- /dev/null +++ b/src/drpather.cpp @@ -0,0 +1,26 @@ +#include "drpather.h" + +#include "courtroom.h" +#include "file_functions.h" +#include +#include +#include + +#if defined Q_OS_MACOS +void DRPather::correctCurrentPath() +{ + QString oldPath = QDir::currentPath(); + QDir l_mac_path(QCoreApplication::applicationDirPath()); + for (int i = 0; i < 3; ++i) // equivalent of "/../../.." + l_mac_path.cdUp(); + QDir::setCurrent(l_mac_path.canonicalPath()); + return; +} +#else +void DRPather::correctCurrentPath() +{ + // For other operating systems (Windows, Linux, etc.) the directory is properly set. + return; +} + +#endif diff --git a/src/main.cpp b/src/main.cpp index 920424f9b..cf9fd29ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include "courtroom.h" #include "datatypes.h" #include "debug_functions.h" +#include "drpather.h" #include "lobby.h" #include "networkmanager.h" @@ -20,15 +21,10 @@ int main(int argc, char *argv[]) #ifdef Q_OS_MACOS { // MacOS qputenv("QT_MAC_WANTS_LAYER", "1"); - - // pathing - QDir l_mac_path(QCoreApplication::applicationDirPath()); - for (int i = 0; i < 3; ++i) // equivalent of "/../../.." - l_mac_path.cdUp(); - QDir::setCurrent(l_mac_path.canonicalPath()); } #endif + DRPather::correctCurrentPath(); AOApplication app(argc, argv); app.construct_lobby(); From 3f2fd59c671d1ea2d571852461f8c53c0dca1022 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 30 Mar 2021 21:57:03 -0400 Subject: [PATCH 273/842] Remove unneeded libraries --- src/main.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index cf9fd29ba..e8cfaa34c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,9 +6,6 @@ #include "lobby.h" #include "networkmanager.h" -#include -#include - int main(int argc, char *argv[]) { #if QT_VERSION > QT_VERSION_CHECK(5, 6, 0) From 092177574bca20c24449357fca988c2416d3e841 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 31 Mar 2021 13:48:00 -0400 Subject: [PATCH 274/842] Improve debugging output of DRAudioStreamFamily on_stream_finished --- .gitignore | 1 + src/draudiostreamfamily.cpp | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 497a24fbe..dda04fb26 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ docs/ *.so *.autosave *DS_Store +*.swp diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp index 51a8bbd6d..b5b2b5b10 100644 --- a/src/draudiostreamfamily.cpp +++ b/src/draudiostreamfamily.cpp @@ -162,15 +162,16 @@ void DRAudioStreamFamily::on_stream_finished() if (invoker == nullptr) return; + if (auto file = invoker->get_file(); file) + qInfo() << "removing" << file.value(); + else + qWarning() << "removing unspecified stream"; + stream_list new_stream_list; for (auto &i_stream : m_stream_list) { if (i_stream.get() == invoker) - { - if (auto file = i_stream->get_file(); file) - qDebug() << "removing" << file.value(); continue; - } new_stream_list.append(std::move(i_stream)); } m_stream_list = std::move(new_stream_list); From cfef8ac77c5396885c497423c135433e0c4c2b3a Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 31 Mar 2021 14:39:17 -0400 Subject: [PATCH 275/842] Add iconset --- dronline-client.pro | 2 ++ icon.icns | Bin 0 -> 1089544 bytes 2 files changed, 2 insertions(+) create mode 100644 icon.icns diff --git a/dronline-client.pro b/dronline-client.pro index 1d8c31515..733f6d00c 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -137,4 +137,6 @@ DISTFILES += FORMS += \ res/ui/config_panel.ui +# Mac stuff QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14 +ICON = icon.icns diff --git a/icon.icns b/icon.icns new file mode 100644 index 0000000000000000000000000000000000000000..c03b205f75dd7fd3a87d2e66d52d79120445b33f GIT binary patch literal 1089544 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

?8&&5P&FUYHyaS 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

J9GQyK!_l7*bS&aF!H+(yH(*}1qXL0IL*N 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⋘5%O3ZUt5FBvH6QMO?9LUjQEfalnj~ly?*2z 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

xL11h87TMMa+Un^wQ-np zWT~=R%!$vGNMp>afg%Y%Qp{-hYtH=d<*hMe8HE(g#WJJa_Kdy({ zgJB-dG0s6&GaJ7l=~J^>Rb@F0c|=OP+-%%&_mrMJx=E)_ouxS}r6ndMaw2);R-X?_ zEl^ByJ#;24S+W%O$SY*)j%`v>S|;IG$fKZIncVtH)zAhx>gpi$gr1x17V#N>KB6X5(abvtlXK&d0PmH)J7 zHMLH0B<7QOloTC-_RDG+4-2VvF$VRQC;lX3KO8R!sid-7)EOca$dHVPCt${S?3*u0 zpI+ur^Y7=Mmlxg`qr3~OJojR4gQ*RE9nS73r=V)iJ&4J07Kb+kL140pZb#r1B#S$^DBt6_bw2srwPg5W{`+GAZPgGB$r1SkQ+MJeZvZt18ystdS; zt47bgJGaV}BQBDtm;_vn=0F+Gbg9HIR_M;QjmZ<%-KL*6+4$+Q`@6d@V=IE7fR`&QWVn_~}ho ze)hPrvbR6|JT(TIr@5Ut4z;G+Hf=iIr%#`PPMtdJL4w0gT`>^*x~V%NF)2$IFK#hu z;-n6#na$In$4JMQy4OdKhNiD&(DM(28#+U*E@#vEtuWAm&gd@Z z?L+91Nv{kX!G~c?C=?t21%vy`vw!=Gd^GMo`2!de2MrmlNQNsD*Nfc`_R|$mhZo;8 zJb`rU(L;Xqn_uHJ{;@mAm{X>yTf=H&=V61!bSHR60Icn}J}L~tmG+_iyQN>zJ}~kb zegLB%z;rLiXJSgKOq{(`#(iiQ@Oqy$P;R^VD&YKOI0*3AWIMcZm&TtB2ZoS1$*X{g z(BMZ?ip<;EMZ=0TYd<07Ia z(NOoBezIm>US6Ljnx9`iCjuZz$%%;}kx?yQd*!uO5F({s0Ez@Q=|gl74og2h5Wvd< zK52OA&%f|O>+<8rb5l~%l5zEKw@ZhP89&a!;Tl^Q6FB{2K`O!B|8>`03-LIr5eR;F z2zs)stAY8VrED3OvQm)BJGXBmciw)NHelt-mGatK?|`3yd@FDU5N6-!w><+ae|pi` zpA62E}&{uP#c<7pgwsJF8-Zwh_8?Tlr{N!!c1fO@$$&Q z5pG`D7dTCU(|qf;?TG4#;F0jueh?8(0yT|HZCuuhbj*e6V5sEP1BG{+Tp4IM z#V&b8_m5})tqRG!V|#yJ2j=UJxy>;rph}q-ggOINA|+Hj_U7B;z|?G*3va$@l;q|# z1Jg5*5=UmvcZc^$yt;Zc1jfOlGn!?k&zwCwd&R=#>`;n{MV&;ZL6gLWaL`n!o)sbe zE_KJ29qHeE`(1WgRu0wv#$aG#E$0vjGz{Qx1@RrQnCu-j>iT*Xe`carfnjd+ zJMZ8G1JWIv!c7{J8P){kx4-x=-H=K8|Jy&Fkuul_ha?Loj#pg+fQHc+M9Bh*=8^I3 z;Ql;t@0ZKnKN%%oeEfkt_2iS%t$TM&Q148=%4qb@z~xacxo`h|^4c4($~)kF7~Hdm z9DyOHLfk|0+JZ3hbl%^dq5ewZ>6O;)3V?ilGqLs)jc1O&UlHJ$7;)((Qi;WmJO%{3 zT`T+Po66URaqAP`H-}T`V^_{mLH5-ikcBDP;dYJ)ZqHNp77ytYik)dc`|E6$>{TV_ zr^maM-aHJF!&9qa2E+?AWPX_e#X#0I&nauvNo3sfXt_MPV~tb;8LA>vq+<}Wy0K9G zdjQr+|NhK#SYs+6C6Eg1d=CkftLU2JJFhIj8DQuVfs?bWSiM@_#+p@|OTaq($iokU zgYu{)7U)btvA`HvvTO}hbDQ}`lk2zMay@c=PqP2yThL7{I`4tt&y3d1>khKnx(=>OZ63Xmhaea5J1lId-&6pS2JiW<)XnF3bze;vOyd2oO7bZ@Rp~I;W+Sz#16kneJO$3O@ zY)BKPlYszKR#KZ^!`!7%1)TxybX7P}>2ien)r`D4%X4*}(^KDlLwtRF5;f3+aI6uQ z9EbQv=?yB5IZ}dCo`r52pw=*1QV`gYhO%sy?ZsqOXRwt-k4R zF4!;U9Nw!}0VJaCtl8D&+oOY2=v9E(qBLJ-iTZ?+3NU6IbaIqy1|y+e zGbbw(DuO+!0JV%p|Ahzk1z=!t1uiZrIeo%+6FL+Y7Pdh2;F9wUTz|hIO%?&Lu&!7j zt}QyR4Gs8G&z|m^4Nbps@2`GeVe>K<~`^NNHq&Gnr@I(z7P)9pH5!;-FTBC!jsv- z{5;78PsA(F|4Uwfhpv>ez^-nS6>>4W!4 zi_|2D{Z?sIB!Iss98FDi-&CVxdHMV0;q2}m|Y zgQBoOCQO{5xsVDl>e_37M8i-J=aBiYVFsD5p)dsbtF_-0hzR60G&LhFdDq^&Ht)Rs zUMp}x^L~|zfvigUO&PSFO%?$FL|OOb@#8R#gM^23R~j${N!f&^uuv-DM-{NkNVU! zc;cpl+3TtQ`(JtEg=ZxL7NIEub{trfUJH0t(}s=l#=_-*n=@)SlpOGCCW zH3TX7-)sp$2GHPW%&jBPv0v_@$6hyES|P~_e?!drPr08dIkz5BsuIINUu;Pv3&Y6U7lHN=tC1?(KuFkqNT>i^du zb!bm$VtibDLP}bT_uhT4Wp#A~5ddnSZIU{5nj``MY>V}#7Ljx)W@s566PE*0cLI!B z*o>?m1JT*vZk(U@=GA(Ke)FxjSOXO~2CNpaQe6Y9W5=9!Toy3HCJD! zHJ>zXnvDN$21x%@WQ6HrW}ru_obce3<)k8qe*sR(sR^<2pVwcKzy9UVAlp&8@`vFr z95F&Z|KtO?=}AUedO3lT==vgHXIhIlM#hJb?62ZkV-Icp9~qBi^#OZxyl zyLMKRDy=Ej-Feo{JDqQ+ua9?{n!oE)H)l@X<%Zx&28y3760S4m>3XLcW-cQB=7(Je zSFIh*cD&lrTuTVKNrM2o2cGEf^c##MV~)M3xBnYb6PEZ=%+uG zSRf8m`RnOG1%T-6sx=#B%2dM(kPhbUQ8!$t8c!jvr_t>qYU(pP-@3$MT=D`ynN@UZ zMtaKfHLIFWojRG!fc&E-+9v0kBmw{^6WHl1m#s{nJMa4z6Nx&9}f$1etLxUN}%-5v{}oIU&FL@5i7mD@x_^UwHiu z$nb(O)xGlgwoSo-A0P*W1-0-M?B55I#69KXaqr0a=bi7H<_F-@999?K81sfadjDNg zv@cJJ4;up`22}1bVip7ElHd;GBF9Z58VZPZLPO!KIc70z2zY2riOhi2AYr=g129J& zskLP7uA8&Qb|QDE?Q{B`9`&&+f+yie@l{VA3P#|cN2*g+*(y+QRF<6xGK&r1>{ejXr>Zs*P3F&3DA1ZbWh!`>X8XkR_UHv& z5$6o)4<_wG1i696a>xrniGZ==KGsd2X|EnRQZiFhlp&DcZs(_Fe!SfA1HGXR=tqp- z(Ae1MsLJYN=^woNZd)|I$ua{rRRn;de=Xj5_wCk4jvmcSNJxqY?;l7o(A0pz7^rV` z;2@!HddI4vbG}+`x%p;Y%ma0Ku=syEe!MJSv0ja1uude`mtkUDJ>q=n-KU>c^W6_V zkkxB9tM)%-`onnjfrZW^_hcesaTwx#Ke^#r8S~~F(z=N-LY{2DP({SAfB(3={MNAMd&MUf2x@Rr?^GnHye!r7PFTjG5Cl zn`W@!N+m(%;4kr|0yt~tzu%lOYN{7805Su@dScqN=`-3E9x5RPfY$&`&e{FjWGZem z|DnRozy5YY+Zb@V;XWLu={egt&8)HJr$w#~#_6L1N@H?c=Ey}Cp6@4SaCsHsUjD6j zK7j7@1if$L$%4jxnx!SlLk~TqmEE&I=0=0gL?22|LBJuCmD8DzF)8qJTw!n(7+F+ z0>Ji0*-@JRH55z6<{{2d0m9_{PsT%+i@7Ga<>pb6mYjg=U4Iff+8dfWaEY~rXe$ge zU}`Fu0r%&Q`{bjRAjk5)w8`-SG&uwSKxUo6{5L)a%0zM#lTwng`9tltv5Ci_;dC4{ zboAw$k#7eA0JV~rUUX4V8vS8?o$fuH>^E%QuHt8$MLhjimceMzE!RoQmbn`Bt+&U* z&iWy68%Jn6O}p7*H&$*hc6=6s(GQu(^UwaTJoLZ=&f@;RdGFKjEcpz2%Cj@lWaqXm zF7YQr2{2&r98C_;0EUr~VNy_ZSSC%K=?FOa*Bh_D9tvvW^?qE-#ZSQU>rsrE0rf~w zpQv@s+^Zqr*Hvi%-=gZ8p>N9i_|UKvsX=Brcc-*Vx1a8iz;C8$?i9|Z@|{#kEeKJd zeL#vsxe5iZivx4?qq{arD4vx-MhPQleR==BVaGms`@Q$I5$)P{ksGeP9Fi>1XMu={ zzd90_SNPUwu;zfYk&Er2^b@m&8N#_Bb|mLfPbJ-eRI-WzmwAun)3K zvuWM3wOn~QECrT8|A%fBTrW)t)}yB4JlYv3BJ<&a6IKG_Vp9^cK74;%i|P<%2Bc=d zCWQnvDFlGbe|dR%)V!4P?%1)}P|KK;0!uStU@^5jS$oi5PuqUjJbC{Yhm+q`5DyD> zw-37R$A0)3PMa`xh_L9>dqbMyMNy&X@xR`vqv2=aM#$Mp%S(c@&(xihsl z2oUWg0vqs|aAf{1fPRZ`s0=a8Xmw~;9irH6(Wz#2n4vGwDggS0n;$mS3IL(39{Jh% zp3dhvM~dZ&ecQnhXez1-$Fu_2RzdxWhKb&HKlw&>Zr`lFw}X*78=}b7piHV`GP{kA z!K?5<`edBamoHx_UqbhPZLWLIUedpBH>e<{)QBTfz>#&SsHl>UJ{#}KW)#GKX^GTO z0UU+9VpuOV1bIjG{6-ys07OCVJT=JVMB*`M*sixt7T5MeD>)l(iTjD+cvHT6wIl|1vl5CLjVz$_#;e#0Z=vY8dzss zy=pB38|288Muk)-pyJtWd!1RD(OaL)P0zbiu-1+4YRXhA;EaHG`dJTd{xlSICR|~0 zAs@ScupUwnMcst)2$Nnb%$g}@WT+>Z}$=zUg59e){bWjPYCRm5O zHpH|(5?U13P8W^A)>@Fj+zExA4hYl9YyTN5 zjV*e;;SV#z)Vt?ZIcjdBGT{ zjaKLcSUE!E(BVQ@2ArTt!_cocUVp8G6V_m4RA-g%s)q6P`&*YdNXR811ggQqqvI1( zCQSOa4b(i4xr5^AO~wWH4(I@*g;kMDt;JVge$_f0qL?vI=Y`HTZSa~*&Q9c_qoW3c zm{?&uaJml}a<(J);Cy{|-21XO{~$=EQ2K+x%mp;$M5Wwz%P5fh6Tn4VCI1-wHpu|p#%pU{Wp7Mc1|1p)}{1_9(|1-tM|0G+<

3RQ|3`1(w+x^hP5wHJ4~+ZJB$|0Vg8E- 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

xp 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

_u>*jzxa zwT+MFPiPaera2JGe4W__*^-hDJ*`Y=U)J~bGsRECgdDL11nfEyP$zn?C^t00q=LGn zNfc`ZKa|GhH_T;9PNjk{buSzrv02o~NnG138E3FH-R}=W2!fd7(K_A9dPyaWWdx-)NHi{Y@I{+}q=6%|W zHPbkkuN@vVu|k$h-=&(x4FT5VVRf;%Gocyhn`y`o(o8dnRRQjV5IF1U;wVJ+uXY}? z!_z|c@7)7Uve=4<6!k;FsSTGR-}bz3lsG;H&S)!cQ?h82mrzPyYn<5lT9-8oGcx zw8MNM+0xNruESvO!yo!s673rDdGcRB z)qLb*AH~(Z1v7L+cLL1XZ=&eLp?o?SwfV}$XAdZw%ge#2SZW>}VQQMEIkf$}8g?G? zcEYZc%!3~K;1Hw=+Y}~)Zn*IlGb`Kwu?PYg4U@Mv@RCiRJP>G7%az?Nsrq!PAYwC0 zO>jV^kk

JnIi#M{c_HCckK2N@wW~ zx{DM0{Tc3QKkckhAZ;=h-$l?`Ux=y&0|WRQrG;iOq&et{Dx4Q_XHiKxugIKipsrfL zz@XlQwFXtnb=O@b{VInbUU(c>ID3W+KXH_N`Q2pPC7h%82~+If-}yKB1#>m|X{H|4 zj4K#oeF9!=aZdEV6^sd<0l)c{n`Kb-DkgF_6lMgb9eMTzGV-jmRWC#JfPuJzr&uRc zPRqaJg$40t+5i>l^bI3w`e4&dD7b12F0o8$z0sv5^XePw0EENiLR?XcilsZ zia1vX4v)fq z+P-3QQ0$EZ4MYN2*lZcod96rS+?pRIqk&Z@`u$cp@Nuhp^I)E(~!vf)+~S+#bikQ&=?6KycU&~C=e$~0%T=prj?YJ zLSi_LV*jNVoO>}h3%Pm7PKCz+xSpU?vDx*3C1F;43a~O0W(B9f=+UED0)Fr4W^{$- zME@zqGjP_z+5v|gbTCz;muD7L7M7G2=R)y+2K9hI{|=O*SFeoa~U&cw1q=hzM1^fFS7q(M}P~OEhR-I0D=6e zvCbKjg>yx{*?}5Ed}(JnDW2XSwYBrHcw7pDzE4kV^XJTyefR#e zOvS*TgN1)t|0<|!0dI#i1W1F89c$cdEuubMJjQbP>(L3+6?fT-^V8+p>n_KFYjdE) z=H(t}8LpixCyu;OUVHl^$;Tu;zaU?)4Wh`nfop0ne(7R`_&9U`MB&Yoj8%@ zGTC{DGBdMWVi@vP2fbo=M@Q_TPY_nn9+5n55Cd$Wqo+%1f?S~UHE))k-!)Hi)YaF^ zYC~3+_10S>9BDbb$hT~5ZIZX%dP66?oM36c!5J%zwqq2>l*pEg{u;U%KQ4i73*Du3 zQDepFE-Y63-^|ZL)U$Y~MEiI_OJH#X4M0_^^9M+{-S>#&s;Wn1-hu@(ZQ8H0_n&uP zf;{_Lk^t>cv^X(ccRbv&!)H;2;yv zu0aem;{fLY8DNYzrDe**j6B(?W&!#Y3c>eX6D%9;84&&73i@<@%IK8uGH?YPWD>a` z$BYGJ;(iQiX?}x*byQV≺YlR~H!DUGnqv*)ns=blGn6^(7tBQQ8Q2W&rJ=u%cWB zm6ymjxanqL6BwpC$3j~EYojo8t;kw1c_f2D&g&(UWjemS&p#P2`|P{7GKC}KdyOGF zpwIeX>k7A(+>VWfb7l6dIZ}ch|NP<-%xS5|1hqy+ijo?3Q(YWUdqfW#y+?7EB$C4i zX@L+Ju4oMH?On;O&GlXHzV}Ysp@$yaR9aeAgYX7CEqL6O5G+9AT0jtCMK^(!&Wc`? zl^1_70N7W!#lI8%r=~eB`UhWd+UeCFP57jrBxjXWRQJ!z%FfAzm^-fib0dLDk9qx# zXNgHtgvN@XT?m8xK%OO|ivwI27UK)Cd(+t1B)e}vOm-W#y0o3y4|4S;JUX%&CkuIL|)wW?|?$2Rw!c02bcW+{(hU3;+%J+;evAbvB%4=zs|(& zc7KRyrEC5!4+D{@oXyWoVW9_AAN#{aB###~g27Q!yAZqQjdIoHqh-%scGKv+`O}8o zd34b7!ABD$D>qmA4;qZ!6-qA{967KPYsK7nMP<*Z4ANjk)aeJ$C4zoboS2Et{!1@B zOLiEx3u3XWFbgcM2s@kW^>FZS{o`I^iXh+@s4F;?>@7|C{Bu$ zNk=?Yb269`+{ItGV6N=2)fTwT;YLJQbQku)_zAKnME<||`djHgZ~&wffGdwE;A|=` zzXZuR0SaUYsHB@~zXprIueb0I_yw(DP{EMJwrl;lRD|0N>~4Y_gqZ0~I?_F0v%tuR zt7F?fDm90F$jAOhmRjv)~~neqFkWtt;2k;2Elk zC_t86QT{#iSi00b%cBReLTH2>-8H)&0PZeIARqS);;p{;F*Ztx0S3l{ufO?0R$Fyd zS$pl(y92rsgaznkOIuN1DKEeNnl!g!o=zHqk!`*O8&~Pc$o`jZToyotBRYpbJZp7v zOw~D;&B4|uanL{Xv)t*AS3yfg+xXv-or5t8g2r%3X@J_=76|LKwte~OmrX|>drU1tn($ByD;WT6 zI_!-*$BY?ciUUW&te6y7^z3=XEZ0hfH5dRKGANtP#Xm)LD7Hfpo~o}u`)tV3C!NUc z=Zf+H0}HWBn+4r}>DV>X!~a}xMRr5l8Nk;cgy^uwc1He}kJxY!jvF*gzsKH$X}BYf zUE796oQJQ~#qeEs|3kLjZd>eL?u#2(pML?1|AAQ0fONKO znxDzkrlTX|Vf+zaL-@v{3uokC2PubzxR-t}43bx0afOA^yRVBbx=`-9_kPKMh#s}q z<5CCE8;c-^r$brMCLv+$i&j?}4gtiS-2N_}9Yxnp4aH53HFDNzC&|Hw4o7S@E0+S+ z&L+9>=D*7=cie-W^dc!NEL3qsTrHy{G+BF?AVIWYdx+S_4WkL?eoZ5_vSwjJR(OOb-dIpsFmsggP@=g_Z&k}61DtuqJ?6s(e@Sq#l?S= zQ7;d(s9;69o8agKz3a0$SqI|=?Lmy_@5s=JZ>9qbvl?8vxcxR;;kXQs2QD9Dq=CX% z@`i~z)ai%gKg7-j+C6t%0+TLcrYZ0fY>8ngy@&s@g4=r-_5e~;A;KPER}bLDjS>mT zcRQQ;9_cdu6Z?xKZ~+Wqj(b~1&Zf3-(+1$#ul*d^m9e2%#iH(US%%m}ZF z5ZS=9q(-)VQeIh& zO_)5@TVj}QtQV3#NZUwC0WRt9470Ju_f{ZLzAoP4zJ0iUO5*9hOCrtt0kK)v+1yyu z_RG{?nh!nf=*H5LLbv!Q0{~}tl@ZVm1^^iW39|xIK$mDMF#ZYD4F&jZlbORA5m!ZI z0PrkN(IE%#$9G{yVPPI*|FhF^a12~NO8dm>4-nOwK!@bKT#pqF;&R;@h3L7of1_7B z#Qth)>ttZn06k0YqNinM%3;SJCy)N`e%WBHwPen((`5FH8PbfMMs5{Ql)uw#{b<(O zsbWPE!koPq9{po{-6b|!;pHB z|9L0_K4lL%lNHP1c?QCWw+>DoMp$5z&fMn3yS8Sb9COG4a?EkZAO;uz)=u<9y$HKCp@hG<^0ZAWZN!B0VPcB@_oPzyw? z3}((d^zJ6er${J8j~v~C?Gc3dkz$od+eI)_wsX)!>4r0<_4SSN%Bye12f7SGg7p&w zzwdtggR9vOx1XVXv_rI&+$*%cAo{`hOJ`p?sEba$=om|f|B|G3U-FWqUBWaV11aJ9 z4qXi4d*zKHycHlD;?2KI`B`qg`SvBupsyrz6!Oc1+kCO`+ZYVoR_v&wH|F=i@zH$| zJ!xVQU?ZG76aIhXF#D1D?UU6Qba|@0`-aG;@ zNJ!6E2Vd^-!n(7#8CG|3qQjy~5RAj|9S;V0y%8WIBQ?LUAVX3Md5pN=sKbt<9sqLt z`Q}=J=Sw&#Owld z{VE4#izFMnvvY9Ptg#u3J*>)G+i-IL7S_zibTn>~&xtq~C{Pc_@x$=rj&{Ls_J0$J9rLkTvz3M6~C|e~9N{K)W3yV;Ic#PRd zWIt`_1Yg8uo;-HYfCXypg87nx{QvduJEgRYLKwYkew;j64mupW@=%;Opn3=t|Dmd> zOo-*AAz~PKh_7eVVSEhUejTx>ys>~Zq zFX*ulXN8YCVT9cF*E>`~p$Uj{GU z5?W${kAT&aI1x#@aRM3$;U?{zJW>P{w}$Aq^!l4`L$^kAH|dv8(11bJveEkMq2t4m z6;5`kOw^G*^aXgHA>YNvvUjPx`1$`u8uaPV*MY?& zHK5-I=I!`TzwAA9RyatGNMXdc+Hy>Xg%i9H5Qy^_HdAL-4$K}Xj%GxD`xE5h z#uO716HDy9D_}wFC;|#dhuvj+`@iqZom<|!ZwtGy3+(LPH~q|+GkxaFnYAD~H;K%g zDp&$$EM_vSL#7Sl+8nMK;@LRJOiGn;<1UpiKl^ecFPs3CTg=PX-+e1%E*y;!CtivQ zOR#WGz(Ezp9h8gEwbrE(WUxr$p#8aGNe6 zfdT=ubMOu;)(z8VzFzR+D{pfm%a7{{9x!rZ$n^#%oy~!Ucem=w6zJXn=*p$jdfZr! z;33o9IW|)6*!B4Ru%mfQio??0=@599g62)3e>@!UHCnG*L0F?goOA%O{;uwTfyPdR zP)#>z1`QSZSH4!tOH1XLBZlH0b}ZpdkPeK~o%2SIl;{8VwETAe{bc$4`LgoorMMcf zMykN}S%(F7Jr=0s3a}@Ac0+*6exQ=ga>4?#GZFs%jwh z{McbfV&kF)&)4;lEDS&mkOHj2o}84-phJtZ3E-aq2Cy-OtSA2n(}+!$6%efVi|x08 zP@C&P5BC3lRWBcZ_<{Ux%%#!PF)Ql z?!nNs`9nqfIxaT426imgjX|6~MB66JjVMd4P2d8!53kFtWDp0`rmoq18oD+}4i^2> zeq10!$`@d6Q!a`6IsoW{vQwD=tox*2NJ-eyS2qB-2>`nC`OIpp(I(&gXL;wPcN}s$ zsbXU&8Pe$|P8f-&eYaB1sRVNNVH1EQ!q23`(qSB?0G)_&7QAZvb>8G63DJHWUTA zHvl#irPpsdEJw2Cu)&g5r{@#@_-D`0=X}YHfUK;X%oI%A&8fc*BwptN|CW^#hXJEn zy86-8_}Br7C*oCDsPg2$00Yvs*I(OAIv&(*@Wj_&{`Hvr^}f3$4}DN&*=ni6;*Tug z+!g0yU#|v0XQ#{G1}t8A&7c-()vYdpq}J6|<5hz-*mzhYd+fEVTz2KekhI(9RF8LjfB*ZF zie<&{{CR-8hrdT-g9)!#;8{j;Y2ez#%H>OC%i;og^s)PK zca(j+Up2^&bLPnD=Z}~2iW(`#+rTN904R&Trhut}OBa8WkSn+h~$8zPzjUFUS!HLz_kbCJ>JF#q6 zmf8@RxsJy4{|-1n`u8qDJqD);PhwD?x$wd4*@R{kS~Vr}+lEAA1Pw2N)<6%B86%ku z>D+3>!rD!H5RFWjRAkMwEzt4T#SLDJ;T!1KEY8Tt#M4{TvC;QNdlKKcmI| z?6MQ`RS!0MhlU~HA~W7nke2)d@HDVlN@}O!6d|3pYeiYIL#{`koQgjF=hRGjw0Hp8 zXoAyD1JE`}i$fB(U&^aZkZJSg%WhT6q^zL|;~m;SExGc@34qYLk;7PKneGyWq-4ho zciz{h3?=r}DH$13TAwImue?LPnEsI)Tn}v#F0-Ierd=~p`t|A|)fi*7Rbws_sZ1|w zwgOm|3?cgHm)Eh%jXz#FBv-_GTKI9Z&Qud{KUs4$)N`yB!$I=5VQ%ocpy6% z0HAU6gyn)iXWVh2v)t68zCOB`GV?hEj3z$AY$%1a6eW~zVq$!HMp|-SVIf#^67$Q- ze(v$$!;kYtF|H@bQOxTBTyOX{0buP`8yf|>Hvl%a5(B>4$*`kog4nyJCYB*j{~-ci zcjvfESA6^_Co9WNJZ4Ie5}!=r9_*!@W{x0KZa1a2@a`t)8jRue84Ai7BnT4&35B+; z#p!=7ScOkH?R4qWySJv%+Ml7vA180W`jQ+y^l%6dTrDepUW85;cN+;FK)4COd;7`} zfK&f^2o~b~WH`taoDwH-k`bY*aY{ep;t4p#Op+`}ZKdRJD($>=fMq8gp?nT5L>Or1 zNA~%(*q|uKMnyg(;Xm}ieb~V_a3dhW>H`+-*{sVg)2^4N{`aC}=iv#v{GO5s7JQy+ zn-d;0@R&)0Nkg*}lAihkaXtA50r?WbYP|7VkeVb9|MdaM$znh6SB+A-^k+G9%tTqT ze3cXx72v5q!yTXkf}Au6H#~QwQY)1&DDmT_P950)E5Jc=_F1RP?t8@+Iy7^}$8yH- zQS#l~xl&M6h`YiN%7euMpCrV;eysee)WF6-2a9<)_D+mw&B6-!v=K%iZrd!O$+rze zGogd|&`tvhQc5)^mlOr{#yf9;V`Rgg{Fpi&L^~6wS;rnd41&dKAs>Qr0DNsYECQkj za~FiyUfcSF-Co?9jo)U(+Z>_U*JH|K`^RN;0lYa8t~T35JwP&Ss0vA$YT4qSFg?abQG(GS`YQW#P&RWm$ul z01_~wzfe#jAEf7K+l*X>M#2b9ZzFUhwY|JGNFMG++_B($*{5<5B&}f+fSdpj?59^8 zQP*_>XY;8$DaPV%37|gc)ek+;z-t3w*I!&#E2A#GS(beNJpyy8$P|$>{!0c9kQ*-_ zC%HJFsl!;Ci1J{U$+l){3^XXt?6fO&qQ+w!NX|@`EL?F+!A9H@|9MjGyyZRt*db#z6Q_W_3PJLCS5rRsI*-b7MIAw58fwF{qrA^i#O(%%wHgD zuozU?0yyR4i62k@b+O3PVo1L|`NUze@4n#+#Xnqroy^C5yS%(YP=fsya*ctMSK#a} zI^CErcfj+b4Z<;(mR1coIj_N@qXN&t)MBA}?X_3QfWH0wF(S-1W`uJK*0)}JQLek` z7TmqdlI(&W5cb2thsAI4R--vxAuKHz{Afn#;7^y=CCteJaV`Vfe+njwci(@TY%y?K z1`eniY;K%$(PWwZ^>?6K9wy(As(_; z&viC+B&^mW;mcO*BZY*ta7@iMG}U0%0~XTIu$f}pAGBTa^J4k%y$?d5tv@G!Ir>vM zcGz*!qo6>qu{59`XhQuq4VaoQKmJJVHv5RvZdiT*>kFk=UsQ-U3%axa&CN@yX54QvkE3(oABJN zzoUnEggCj8ST=51xvtg#Uta*OUHLU40_6aJ4^3+D=IlQqQ-)hPk&{wJ-BcCx>e01H z3ab<3t{=XW9c!1%YHq5K(9Cuj(@>LqBqWQJSG=FA~#qNDm5!d=B=!i z^Dn$cN|!B$n-&Cgrrany?!1Fc89Q83u@S>-maH?ZA56n2s}oCn^kW)uPEF5{Ow_YD zyxw%}HP^}8Z@%Zm*|@bg-+fn(JM}b~Jmm_hsDYdWS5eSkiX6QEzPQqQq{@9t!XZN= z=a6m~8Vs$1ZZ?3Wr?vZa`Zeco!iK~3!&z4|AR~E{ZycTpr!0WvoZM`jG-YBFpgecv z@R1bY#gFR@ZVWh_0QIbFEhCOV-KuL*AhNIRTG(}C6N>_DY$!YMB$@j-T>P^^=AwVj zA9>MvDezUEh9ZO0T^gjcAF`su*e29dDwKZAYX2zgxTxr%Q z$bSGSh={;~O;g*nND@(K3 zA+$Ea+k^x(?8Y=vjze{|e#t1Csn=bFXNP#ZB~r!7`|r+>3nxsHMOgf2<>ct82L*<4 ze}phGzYI}k`V{RE8aO(b>*7+|b=mj~%(MMG>;n@(^~MkXL0HxU3XFXA`B%IM8^lCJ zqHA19-#)0nzf?hyP1pcH1#&_$yr-gLdVU+y6vS#nrrfb<8G3dERhy@Xuj^F~6xzuJ zkb$?(DfsN(haQ#XrDY+RYaVV$giXRJuSXBrZrg3pmEu|qHsdkaAz!wftR9`!D4!^Y z=r}wTn;VM*5RWiwvI}t2v$~LulX3xl3j2 z*r`%q#dgAi<%dBM&-ooZ^k6yv)L{^$S|xR;E4B>XYmqD`lcGTplL(Veqom=QcwVnw zQq@#1=luR`eS*>i+W3CUE2?D7rIYcz-zoCSD{ssF4?H4w{PB)JMwB+7??o33my(iR zIHbU)31|amT&0mx){+-7X>3`AgbHzL8BTjcpz=8Bk_!MFlu4P{87YOmdT^ni^^dkH^?tY8Plgw(WdXx(aKq5v0#-D=~b0Q-OLx^ZRgF8*Q8AA7+FHqcyr z*?D<68EKIGkAWZogMxeJ+3LPUM;W(I07xiA_N_Z@e*q7i-0EF?EXHvXUtNK>X!qDl zjyd`$jn>|uTrBRczu|g${kf;*kYDbBiA}AnUb#wz!|{qhEm(lZ|6wd7vld{?GdZ-U ze~XtalIw52L6RXIS)XeGC3x;&gb5OJC!;ZtwEhgmg}1pk57u~Gaaglzwe%{=m%H!g zK+!y$7~o?%*I{vf<{4+n!bPiu_u2DsZx4?+;N*)#j!`o*mB*j>k3b*{7Jf;1ii?v9Py--2$uI`Q z(Ini29Uxx1&C$h#vWR122Kd#2djxknpjXM6r=KYM?00~N7)@FMMLzgord)gpt|ly7 z4jggcpKQUL_$UixbgGQT^!%6w0k_r$Km_KYZOpBW-sQs?3TeHEQ73;abkLP`0G>c$ zzmFf^gr5G$>pHEwiA-IG%wFP3Q zhV2-ltcNU^K8JuwUUENCASJnxGVnCns^v@MjyoS*59w~CAhAf?XRp0MD_fvugPV=O zx~yx?nvWk=`5iZ1Ihj{i2G?< zRNf#D|M-pcZY-5G^u+amCLB0$&ccPcos+nPbnqs%^+W$mmVO9z*ZwdiJ5OHw;zzl1 z(k<91Ek`KM90(Yp6O-kfvrm!V?zfvjh?2sG2M{7ggoa;ewAqga&V{(gkeQt&`Pc+l zymGaia{Nh<{Zy@SqJJ2BL#r5RMmRtG_cW30*-}*WBfRz^SV@nQrTM+Hl zS={Vyp%O62m?+S_0kAQZ zmWL~wDR?|r)X@6oqEr0WQAV%QkY zIwBAWGe{s51}5IWuR-Tu36}U23=EfFHpv-27_ehh|82IDhaY}GuDWELWZ~X=McGP7 z{;!cg;N<^=BM#R@ba4t3x@O>CFW238D_EA7DQmbLI$Z&FCKFC~%G{b9s|i0GXgT=l zWQrX&2E{mXIY1CvIRyX3gH7+&TW^s946q%ouAXway!Q6{l7#`bxOZQ$y`rB5GAiBK zmzamK<7gZdZ|29A6HnF~AV;AR3;$|J(H?f#finK`OA#re8$kSj{`*O}`+*0+agdHN z%sElOU}sqWnT|qr5^RU&;Hr1GF_LoAqaHZ&dc{uL4w5lrFAM@_c;GW*#w@vL!e#RF ziskw?crpYNap8|S54@c``%u)yzac~ro@?_3cHP1Z4#JUMt&JWT&u=Ga|L6CMVUA2T z26i!Gd(IY4czkUIYzc^fJG*4R#zxdiY$E;d*_S*ZooM>yp8Lp7I}X-sL5(~ub2b43 zC?Rv}Y1-r^6HY+S-!WNwM|r42^S>wRUI)N^Rr%Kw z|CI#`H}qyyvl-fep3(H*W6!+=Pye98v(mFaWSDRoN5^hE>V6m;FJ0w$xc+`mn9W82 zt^#1b5RWJ8%5kUsk%9dcTdu1jl?x}*V-v8GZ012ZlbMo=CVA+mFQuezh18?ZZGt=i zZp@nt9kx$KSVKe@yi(>E$c-}P1i%4pJh-9~a5e7luTGcy?syboNnL(S72(n|<&yEE zWydXgVGOQ<4>=AjHVngg5OrIN(37%W!Hr)!UIxhT(?{O_c$SPEb+Lwv{xR{R-Pfl} zj5HnRIY0g+haZ2kj2=El>c9cet9Ku?n+(X1N{}=hC^X=f?N!%J3q|nP``~~JR{`p3 zA#jr;iyd>YLF3FoEU^OdbqtDu7DuSS$M&mk0T8Zb{j>)lhhoL%MjZOs_>|P-r0l$0 zU2D`=ttfc#fk(Lb=f}fFvS#uMasuiDWs+&Vpx&*zJ_RCX{#_s8ZVZ(IEI01DaaT_7 z{X;JtKYA=1B>12+GV=<0q@^T720Hk~u`ohYW=9R*)+z)JYr=M{aY13A!LX#e%(XQ* z`N!L`e6#(O(@&MX_Sv(wskJFw5(ct!&pK0HeEJC)f8qJ^@IAN6C1XYchKrSf2O&C7 zKJ&c1`10$LnURg%S}gw|_)n!Fa2K64VMr1tBC2twyAy`gz(1dT(I@{(S4tH&08WQ+ zx}$~;HR-L>Zy1$D{`bsNa^vlH=!tu8EdEn5nTw}TAZJIJvAmd`$)cdy;Q;LnAUru} zz@2}VOK5 zR;sYSB(S7&vSWnuM5c~<%xH>oG@T6EE#=2%E>?W_(<|0zqc^9Afwu6Pi?`nWoPGl& zLRHWc4>TiV27dp2{zY*7)U0PaVKR}{4t8%eD1b`v0sw1gMS-Je!# z&8$JKtwpVc*(u@LNE9!)XX<#oO|N0#n*Nx55EuXP+BYTO6_aK1z(bF8e9{{!#Qp;Z z$~IdKGIf$2p#FPh6g1IhKF{6Bc$7oy+D&kXa9W0Sgq6dX>G`39tBjk>wQ$Qo|35P| zTfT;%Sx$0r-W@1`pe^9FNmww#2h`RptK`82vn4&QLaLIJFg~JhgWz5cYC0B!J~~s= zaw2v7^ER^ykce@coR9ICyCh;fz3s7Q<;5pnKv*vD?bsS8Mg0cIrQ=3OUUDO@9Z((s z;2|9kVsPwt%K`?zsFaCKfZQI%@)(5z{_)O0KDwgiPk;TV{OVT+$?Gq?hG+c>qzC3% zX(~Sma~TEnx0eU7P}u{Y@7~ zw;MfDV9Jy!T>N9@rYHYBq4!!_S>5Z%qkr2Q=AK2p`{!k5XM=AiDH#(bHfN3!p$#+K z(d+U7&sC;lvoV}KPWE_EjkSg`!T=0dhwS*FyCQ^9g2ljmGm31m1N}i0P1Y2(P4=@T{R01h{#YZ zO+KSogM0NgkfD$R&WQ*Abg%U9Kgh%|1X1&XdGqCW!$-*S)vF~9gCDApZbFdNjhq#M z#eX0nOEZFT=w_gY{s!P61UFV1fg^KSUsEB6A2vkJAHhu>|Ckadvp)Gu#!Q$jKS2l` zHwUr#aLxNR2e(7~+L*iv-o{96K-Ax( z*ydw5f|x3Yp>OBTlM_xDw%*5`HlV#>f+|6RwC93s+2X}A_H8~71Ny0J zmgS@pzn;|%vVDE2yqJyajS1cCLDeVhnhO@7oYxPqXFf{gYcJM zdqei#eK#4f#Q+V}IX@nj{`AW+M?L49QF7ZYf7C-2a#v<#=V~2P?orJ%<8p4r%c3Hm zeLh=GIOYg&kp}0qDVQJi!!xumzxgJED_wN1!~~Tlgk#Od=%<`@X;CMcTTrbC(gFl} zD->;#Fss76AJwJ_GCSiMD$Br@0zNxTCF_O^`8Dr)l;Nwu$5Q|Z9P!juS6$^CyXB_#RX(W=uFip58d}Lo+`sa7CY0tr)BoC zaWTNjf?{V^&gMu$!x*gD2YQMFVLeXJtHI?^QBjKdRHNK|&s`WSg9CyWW8F**5T z^UlABLa-8C4Ht|Z3s%~NIC;;);u?#8vf1+VfQx@WMKlcqLep`eMTCkl98% z$Poh8(rRo{J@?$pF=1{CSXz$#m%G9<7|p%Y%)J`@+_pkZcA-qw{?UcUVF zGOq21=|S%_QyVkUQPd32uc5L2;>YxB<{E%ub52x2v*oK+$)6tjdpL+q*_9ofU-9S? zYLDG^6-WUB`!`C517M&b1g)n02qyv-T-HqiqO&=z@l66STKMQy)acluHR7uwt2>rl zpXBDsb44A0t!~}&vSDM9BAnn^l2QNF(lv6!ypN?msa_hv9Z?5)0F>v$@l!bg7|4Dw zQnA?Ss9~^KK)EfH-k*T_QCit36Q|rFYnLoU7>={7A52-~m=lkc6OK9rf}G0$o5hGF zu!U`P4Qs;FQ^$S4hvYacrXi>p3*(Vv#>%3FKP$}6`LXypsRQ?a{p|_aeef^jnWz3I zg+2R9en}rm;-a1$gs6M0lblaLXcp!@u;WVshvA}yOXaRR?+MMuuYdI`IdK2|adn~^ zGzKM*PC!lrW5Laohhs5Lv~-wa(R75;@t%Mj@_`<>@dtr`nd#}7`Gveh_NUlf71;EP0>Kj3PxOA&7M}h7QfG$@g(Fk$z>3qCn!!qNMGgqFx|6j0l zRe8~iFTX;zoV?7!qV%M+w4^xlLtAARxz_IJUI4#CtkZuJMmu^P4cMWP&6cd?5ZHz- zK?-f#ZI_+p)YDFhNNK}c>#EkuJ8!)SB(VsS`Ns@4p;^2g_tQB=hf!=axEJ-FD;kDs?}*uvpUbdPpO<3-ok^Xmb!TnqzPr zd`Q?FXog_nNo_sKhnoNuc&*^96OWPOPde2XgGMaEaq2#H@)UU+JO4R&N-re^6Gsl{ zfWZm3(#8U}y1K$bq(W4{*#W^|1|Fk1rGc`bTm`Zz)}s!c_PgIpzrF(z(wnr-_+YwR zc%r8XnT;JE z>mcQR@OqL8288~fr=ODLt9U5287tn^f!b=&w$i63PI$4Ytj__uWTe5dxNd&h`^^d} z>c@@d^9ge^tZ9)xEI$VW>L^Qw;4-oiWr`sSZ8$~#-lIg16@5u;Hb#~JKb}5Yb@Yld zne^jqeHkDwIUUdTkyg%}#@TG~W-g2knu3R1MnhLj&dyM0Y_xUkXh0aYhRsfq-JEupCA9P z%%3+WG$RwoUnKe1fUd8p0~ts!(uxr9=Y#DIJzC>U`9uR$O2gTfTy%&H<-(E!{&*HJ zArZ0wQuFe2v*Oe8xCxMd?wP~60l+KBT!&B?Ib{KG6QFxt2_kF^%74VRG2pvd;s}<1 z=9`U*<;BH68yg!R`ECascHoe{KQ8>SrzEEr^zPF?Gc7GO744P6|F}7vO=P^ak$kXVmSOaA?z z|KeIgHQvL8U>BTN@(Dog)UeoOW|$rAs9|ukMmNW%lM=+?CPz7h8Pp|?t9=*a?@S+OMYPw$-?PBSxC77z)1s>^vWGJrJIwEAB<~` z*hKQ-Y~a8jbw}B>;nxTp`PE~SqNJ!$rcIqHc)Y?uL;l}+=WQ7~X^JdcU8c|cu^bIo zf)6iW8a^~!qch%WUd)f@+17U!Jci2$|4I0=C%=O`=Eja+V7}o&adE-_2X~{Cw5x$rK47Cnh+)W1Dd?2=| z;Q=shv{<5qM{C|UJWiK$Z9^s;3a?`pnoG!o>@PE(*fpR(;YR10(lycpeb^qm?;Mpr zdwzesr~sRoLkh0oef{N^GH1>lsYO#E$ByayKu2vl4mF&{U@33)069V&L!1owKtv}= z>xM{f+lxq-9;S>i!NGV?j#rL*;%ed6xV9GE(k;MES0{Zfjj59cgUiaXSza#h7UJX@ z*8<6v7zbWYox`}31RK%Dwsu2%GsnZ625n~cSKmr*N}TMr$6$ZKNCE_F?z`)b^45DZ zWEGxVj^lhu!5EkpA9Vy`woHi!0;9`K(9MH6UwkT9>pAltVc%8x}HtkM^>}D}ZTZ{f>?%cU@?9r6@#F5Wa>FMcG(!003`qInb zoJ_%1imA&ivoOUpjWQ|ZQDe*2}YF2!?!VAE>Q zg)vVE5gBCCF^f6WEIL%sOe3#6NksS>Tya=iQ7$Q9{lDj~yP!u9sh!rfYgWmrXP<@Z z1$C02SAeH=z*)rwxN>h8nwu$rO(-(sc4}+##LXGK?}zIdHF#Dp7vtTncm5H(+2q)= ziZJTsh3B7>N!L!3T%?k_)~2Q4`01()*bU?Vr9N8;uW3K&!b1%fM%&Yg@}yi73i z>~kasPwa4GiFY;Ldh0E@bn;awJJ24Jk|Ye=WM$-Q1jJ40N2^SY8fg>P6{{F8gq&DQ#(e{ur65Rxxqba}rw%Zutt;iiY;7nWSn5;&p78|ZxqKKsN zSa6P_-Iga-V$y)MW?dYFd13&%x2TUS!L-L~zc$w9p_5Ad(2J=~KI<`ymdI&8eMr0_C>tt_F#Yw#&{t}o`l&a_>`y<}aGmi(E{T$o z0^D536Ucl?LGuA_2(tJCS5p;1AkO)aDb__^OJq$oSw@aA=O9<)TOWKP&p-P@Xf_TT za-i(<%e}!NT8S$f9DBGHcIL|wG2G&ba90Gv++s(F3f<8TAP1&ucuFiG0n3X-?4e|& zCTA9L4U&Druv5AA;9(?nPA8K}^GEE87@$&JfAIVP(7KXxFUyi1hMwjCOLk^NY zy}(f5C6oP zj_J;#7Od^n@fkAw(raYl*IytuVVDg7!rFDu-DLdmQzfYpV>0GR>~q<+of!gyKzRvq zBY=nL2^bqv!GXe80oLM>=&ZBO1}8zRt2thL1FvI$6EKh3Zs%R)iYXH@&nUxzRV6US z{7x6>z@F`b3x3o$({B+UnCQ-GNm>C0UqRx!_6EFgux8crP$Wljs!(4%Z0G#0vNCXxCr~v%r-B0Q&%V6)Gt`0~0bFLVoq#rv-1l@eZ#a z>lI|^ln20r$_YJ>($ST*zM5y?&w&m3sjIEV-To>W{`-^V7`$~GsmfO_lT*&W2z02Ep2bCa*G&gI z1k{rRv+~zywgyDPXtP~2VW4qbY-j)n2{vSs;u~c2dFMicHx~Nvp84^|GU=MBvZex8 zOK|6(8xy?m%d*kMe++~oog?jrh#n=|jDM^M1lf^biFjuy&4W@nkh)F>d^oxF1U(b_ z94W!&|e-ilkd=on~!7vf&KLyJ1D3mH6>$yeMEx;+of&*ARteQ(r`U; z!Kzw0XT)TwUcCgV*i8T!A=Gc!@kh!@Lk|Q?e>GwP7Sd6;+*CpkG(P^iie*bD&LMbr zz5p)`d^2~xTz=^!H%!nFj34ytC|iPz95F)n-+y0OjVD8!uvtP{G9&CejB@4{S~;0R zd@!55fd1H&%0zy?oA;AEaNh$?oS-&%n;qrwp@-wjC}iTG9|Cm5cj)8`YM}GiQ$d^O z9yD28gRXvC>)`lf#4_8opo0jY6G(s^fW-L3w2Ta_MN;)yz%x!i!)^eO6MzSl+@r#o zpxH#RdaP8s-8+v0>{Gf`*QWqmA~yiYUc&`H8(P7!haaYFH%VD}=~+3sDG-cCSsiGj z*sC-R7QuqbHfh&2%E9$`v0&w>k z3+txZ3fW_qtz^RF2?!Hx>J7--`Qs+aobTsJkDfiTYt24ehj)cb;Wy~=+3CX_pG60@ z)~Uz{jvbmoom>j_Dxtc*wn`2;=s?+b@7=LrtCu(5dRN9>aSfg#TqDU~Q^b&?8zRb? zfJp@vCo%l1negU)Yc(pb(!?iXnf@^g!Zmu-K%n-a^zMHChPVoVNmUYnmI9 z^??%sBectyxhX?=3{5D@mtJ`d)sB!j{4S`>*H0X|+iS7+d0N?_E>=)%onJ(XbH+O@Pq^l;B zmtTO_WO{=_?Au8z79PJadRo8s|+{mirfVGHH~;_ z=Yat`0%aU5HV537&ZcU#C4?gj=~WjmlfIuNTdiH9^OyKW$T&hjY&HQLN+W_xTN1@r z031(sV;ysYtb!i$=})CH@zR^{q;n+#GL|!EfRy|ZBhQdMx9%m0jkRcJn4c&j{4oGo zw>;2JifEL)nnPJeS-A!Bk0+jzfB)-A#OXZs&;xf$kAfVjEL)9(5{Cm3=;~BTb>eRD zw;AFT$egKRP6TsGTDm;=w|~p$pUn!*$i#`4=u_By)|eFJxYL9=hdW*K#qy=*xH@V3 z!UT*hcn89^)8ZzDJYybc6bO+3AxtT_kC~R2$Hji${6#+$&7SpzRJ?VT6ot&svDaq3b35WRzvn0viui*{@LuJxj+8EjeyLY{Jd10d?ay$4(-i0 zI$y&xP1)z*_Hl?074T6P6T|5OeBS0xJO&xCbv58#Jnw2Bee5s@0NYCAb@iV_uq=-p zb*{Yq)@yS1$tOtFie-2&d6ASukXao$0k8v5i>>q;EE=l8;ZO$QdFA-_|G)j^h#|k~ z(Cjs0v3}+`zr#s=Jtk@WBrP)+6CbDFblAPTMJ~(2*xcg4XylsVr)F|yU~{Gpi|Sr^ znR4UJH{oQT;v$0T-aq{nZ}q+^`4GC7fD>Hw?&eeg3v3Q@u5zk?Yj2go55Pun08xXt zZn9uRtj8ePAKV8ceh)T7Y(l*B^6PThjenHoYu4!LK@v7bcuK*?p=_m9Dp;#Ca(l~F zCmow?EZvPCD?p?>^Awh^$Z#Qce_E|XF9eE7z8z?U!fe4YCc26Teo!`vnB*nl^mW!3 zv*q)z!lfj)kW4$hqp{p_i>>6)gAN6!5;nq6$F!ONSGNSCqu6O*J8_AK9+FN(h>gtZ zWT=(-^7^m#WYcTi7K$lH)<3vVW2c3GUIpN$VNFGqJoUflZ1BxQCozHw*LG#AEeGo6 z2&I$jX`Brb2;Ry9wQ*ae2NG~&#B_y+yD5P3Ui?7* z^3daqgpbD|CbjGyJ!JgY5z;F+1(1<4UzSh~MFfG1*;6+qt8Edkgh1{bOidxv2om@C zy1>LMW!{gA8L5pbhE-{;jkOiYJ7|X_~>k;VO0N{GXo@99hg=4qsIuu~*?p9r& z0xTyU(Q?PF0AG)@&N{0HbY;28NJ~q`NgVdep-fXX9T)9tQiJznI}mM_VQrHEoIrZw z%4r;2P2DR9RzIEK+s7E7H{MKFkX}O66pG-KD~R%9rxZTFZ}m$*>S7BQdznZ zC;b>ik*``T$SLu)9*gP9wG~nb4u|_OXmqr?`kJd`#>`n#+_NuED~m8#^3u4y;$@f0fPvdaq5at>pU7=@-zk~6^RLg` za1!90tP>7lyA_GC!>ULWRO{di?9?4^)Fa{uhYav90riRL4*Tulxcbmjo_gw8xoq0) zvU=@Wy+26#yo!l4`kGiyy|l?uCruENKAx`5j#~?6#&7BKr`KMyL9~5PlrR9{=w<>I z4wA{WS`i+UqZv!Yuhut&P}XZyS2M{=)R#bBdikx0(CgpIvI|qEop!op;2vrtcJ(OB zqX})p3u;WBJq*ISyER9@SWwp`PApg(#63W~*w_bxbs%CUMW=t{)D->ZtJWzA=#Z6(&W(7}}1U#qg!2fFFY(A%R-`GhaW zBZ?URR}MGFO<%5t&G?6WR=T=EdH#CUS~-8&0!ghY!>KmP8P5Z9an5TLB&!)kVE|zx z`%0c{Q-%wL>vB9!&dQgmcRng#O#cKCI2Z9Y0k-?a?sDFlr$|ymrKZjLqzh=)D}u*| z!s0KK(RH4JMKg|!!3mIoSCmR+^!a0ep9vM~*TD7rTDX3l~f(TbvvKKYnCZg~*>pPrJM zhA029ic@8lN!D!ikl$we5O_xRAkGSjl1*!Q_aFDR8^Inw>cWvyj8o)|RqeXZK9K7A zl#Dw23``c+%IcNNAtbN@tmqAp=2|XQI7zyA?1i#LzrG!s#g9IkF1O!(58k59m#myZ zyhnY5SO5S(07*naRG*8Tbqv@_c8B)VSemhv>%)l>MpQ0z7^7_UN;!4dv2w`a#{{Dy z!dm!^9Dj+FRpQ<~WDO+a^j{~3tPjp600%!0HbJ(yjf|nc*A&3lzw5dnp5b44o5lO|sPBUr-b|R@Cf;!GIbq51) z@Qwk#EzUL*e!VU6+Yq*Ov~d8E7BQ9MX-Nkib4dKafJx^oufHa%*Q|{hdwqDA4XbF# zI}F}l1`pm5?V?T^>u`S+eV3g9$D&6p_h{&`Tpaqq+nU-BlWAFKvC`d;?y+(d3&-Yq zv5vqS={jwhb%8P|_|2U^Up}1vaYrPuk-{+L)W2_k$-%qqDiD+Xt~y7TVYJJ-XWN4n zUMGUC#>R!eiHBaF<%vFpV?+}+b8=DtKh4aMpMjEwHc{3c!Hqm7S5;JT7%}nKshqTI zshnCmPZFDO_nZrTNW14aiz90^4Q>Hgf*D=uY$}2Za^)u+3G9o$`e8x7Z9cdZQU+egY*t&n)g#~{tPuvbdizQln^E0w8|keh%3 zkDN8}@yRmnw!6R~T51AB{7#;HsiflxL_TlKd9%)C6c_VzY0;MfrN1j&t!}3rX#RwjLzEJM#?n-I#w;v70*uaseK??Pf_$ zPLS2BRzmXp3OxH)DkDdqDVL5N)1ld;?1a%{FP6&c2Fb(SJ3ep6(lec_p*G7PFp$#I zDJ*X=H%<*W*;J|jSb$Yx(_-+TEoAbPYoc++(`Z*uyGdq!^))7hS;#Ae>tWF63JwF4 zAFhLimuNJ!5Em0G+QYgdkD3RZP+(4xj%Rgt+HPBU_PN(&^7Z$~nsQ7k5R_b>%&Q72 zg6?dFDOktLNy5;~r4z%NsFqZ)F>T*xjSd8(Sx3Jf2;f>6V!bM6U_Gg5+O#eqKT4W^ z_Xjlw^+u}?su1haPd_e@k3O8)I>75WR06J#9Dc|Uls>L2p}(SB46T5ngs_h58L3ST zyuWg2-eR|qsGx12MQo_rb)k&$#}N<#`SC@7ddQ>r-wTjSLFs6lqjDBWJ;1(Qh=muA z2>hF1rn3w$l~C7E$2Jf)q7bds1M9d@a|)7vjaWb?pzYQsXUac&4ulIf1>oVuYXc6O zSWqs(c}kyLA1`ATE|h&LS4cVRiA{;v1i-ui1FdrPfmqz%HxxF%hJI#qj#p=Js;grz zj(27DHw)y38*j&ZjAahR7#f{?LW+#J@H}u$_ScP7GXk@?3`{4ms}Z`kNBocrAQfBy zsn`gZhIz=$k3Vz6wb>9Oz1G4>!8M@=|NOA#m$IJt)U#nru=6lLLK>QklQqsm^u-Zw z0AQm83t(9=e<7ZQzArS{Ee38WM<4lHyu?%n%9-mo+E3ZggiEsG)3R_$s5ToahK|53 z1Z%-N2Ha?8Q$u|2C~V06!8L%Sq_p(3wA?)0HO1QGli4$JzL@nTHv_m%AtwOWES3|% z%^lpjRTrTET6DMSf)rq5VcGZ>|JulwEMAuPeItas~0Kj+*EF(vblyuzh+Emmr#~mYgOuJV4_wOya87Xqr zl@sNrsaIJ-bXYd;zVi;5@zHGQS<+Y1(s8Q;0}6*7UFaeemW!5+EgEw? zh;sBtQv`{m^ZET~w(dbJB13hEy^AE_Czq*z1~E=zR6#QnBy*3H6TDim+CTfkD;+|H zwgZVK+;0vTB6;~as7kyB!26iI2H?vXqC~Z=wkI4B!_-wjHZ=R~5fNOg4>Q-zGc@n( zh1(D&Uv7LxlX7f4qk^^*Rg;qaMa{&hsUH?J?At#0@B>-0Y-KxB*+`?45Ksl!04Of% zp?xare^gEMjU^gEf7(`3tqvWtYB(N_X4)v)5jvO#WTTcxAvkeLpTz)qY$9=!ZU!@^+|PnyJ;;)C+cQ5^zzQlnSmu^y7(X zS@O64z9cU_^AciW1tlQe2R$FJ-Ab0D{sE${j^byok!oU@fbNNFfng2PY(Az$usx z*8iw3aS%HO(b0HzJOg(Dk}?W;JvsZFGe>Z3!UIZf1aK38hnM662m*max9WTfm=U7$ z@#w}NNC7q$_U+s~<1QK-67SmNUNZ3#Hmuylba3BfWu>DNO2*ws$VtOU*@h+-t79*8pK!wUEkN3znv>wizU+oN^+>{+v9;w=UyPY$VE$n@gQ^qC=}wI#{l@xQ+%>C~ z`n6?p$t4%b4m<9o@%=wPEtn_gUN`{)vN9zb?4Xq7t0(`2%c9nmoQdVIWP$D1mDXT=+YR6BouIHqi>Mz9fmhehBDuhK=C}q2-Sh zZt07L6OEa1L3(bIuEB>#tay$)qqIQ<^W27r8r0(Fix+S*$OKTb3F>5qERnoR3f60^ zx;~saLzXUH?gQ3&#*)7MWdD8kHFW@+OuEVD)-flO(ENGxn>!EljV$FTqS*=(osX!X z(Lvho!sPVhW)wF9aN>wb`f{*F&-`F|+u?00G0=e~Vzak@zkZldu&?LmF9B<|`ufOt z?79%s2Oh+T!y=cM^%$=>#i_!^{9VP^(4*h_Ky@R8>CvH4N^k?<)*n8{K|qyM<3yj& zHB%5OsYUw5BpQIwpKT^F31cyy-mOnamp|Namn`^>f?e&n%xP(y>;QQHBhNTd;_51# z;gw@GF)<}-eqn{O)s3g;C`Jg;OO@i@y=B3o#SjX3xe25JJ)^&X=l$@F{Nb|k^7?yk z`2uu|@zU|5q#!>XPa&7%u0F;GY>=5Nhe7z3#+pN9j``!f4Nq@@#lJ}|x$G*q_%}*S z{4B`Gn|%4@;6Q^c0PHX5ivZk&)|UZ-X{?jYBDPME+Y%{qd4bvjh3gOr$;pXH=^5x` zuuhpi>z({hXMVyv!Foj*I^B61)xFjP5xSb{E|jiTA_gD!FWfcb9UAVeaq*vrz0$nD z{^e1cGkf&xn}OZO6i7}7`@b0-WAz0D7OkRPZG63d=XB&64?Q?f|H<}W2f=@MTTA|M z$tC^}(cm{?m+XvO3{vD`>R45W)6uhr569_uk_^~l5Tx&;crl2$BP5bz8JKe5V$_RTob)hwVY13eiy~unpOr?! zq*p8z;pah#TXD{EK^RwWNdf~195#u1IapAJsQ3(iV&YrryvfYL0WJm zE)mK)S>5Mta_-TePp~DRZZBQ63Qq#Q;S1Th#*SSLzzX$F$nge*!20tV!;=f8{pbgQYsdx;G%F&x0RwOd+v!F z(wScq7YlUI8baQ6j*y<2B|ojGl#4ID0S64c3cyed&t*fRoP7ET^6R~K#k`R6Rj|>Z zeXAK26rO^{h!5TLxA0T9CZLrnCD@eu=RY2od+&b~@SxPs7}X5>E3dyQ2OM^!OuOR& zIpU~e<@r}%bpmvZmWh`SrcAzE>Z;c0lh3AEm^^q00TD&qLs02NT7i=>Eh9tTo&Kpj z_e}V!BZnM(i2QP&y``+Q6xWh!b*@9~nTkgzLtRr2#G7eI{UO%H3> zXSfnowwjx+xtV%ad{%Boc5Xg4ACr?Iivx#l+*@sBM}S~nClj4|U|zsZ4}mFNUhn>6 z1E3bd<969)R~b6=(CEmUluIk=*@iq%`om=T^4ssF-xk~85-RCgeVMlL)%mFj>4yum;%mZ%rR)z) z|H#kyM?$t4_3IuaKMcBByBEU5GbKo~qap*+9#i?#_QCKOV@6%y9`1~s30XW19Y_ql zdyO7TnE`nEiZXuo<=4RBL6*u5slo}o%Wk{Lc3Ta?wI#L{JpIItvkgSo$SSZQsj+#b z#fpd=JN*|Y=oHkIh`sU8W`8Ejm#)~1$Prz+dz6%@%onfcG;PHTY3%EdNIv*JqfO7l zgDy@0YM9xuOxa^o769iW36LFg_rM*cIu7FR5YZnu1hh?Jlh!$5^ymG!CCg>>PoGFN z`r~-CgF4RPaD_r?#W9q0^Ak>e;GpCNHy0MRoEv0h=gSA5&zIZ&_-CYm`GzXP1(1-E zE#q+&peO?iVT|Q$YZj>DLU0aJHYwIW-PA|9CW8~8XRltkvUrR9>mN@#u#JXZT5h`Y zPC5Ckb7ggDrDWoDf?CKmIOX&+<@0a8354}5HWkmMHKyZEI9~SLa}Njv{2B8p=gC-X zazsJ%<%RiR9}~wu23!;I@kw&?T@OHL^y<(gM~^;FlH==eg|FO!3b+8 z+}1dD92_4cR1PZ80M#ooAtf~>B{3^cHvryw|Mh~EOP5&=0J`xY(^Q&ZHEj+0-C@=v z1-dr?)+2G-0h(P6S8`;Z;Vv4V{wtVt#RP5uWESKXreNzW$zlD6tTc49{BxDpP<@WB zZ&(}D82IrzY*^4s>24kFk<~%+KkxsiU_hBLZk#_cAKXpF=+UpEa`eJ;FUUiW{z>wS ziX{QU_v$Teodif{9X#nCktoa1mZR~*Aj$;oZuUTV}Igx~@b$PhQ9jNK$b zGfd8BjdsBF)6I;aE)WAM!pitAFqf(pYNYOgAQ%hE;ZHCw*3ksjUz3LCKsxjOqYJ@j z^g>0NS`{cVlAEI_j}V6t)T2U0<|QhC^=VqkD9&h|_UL z9R(Q~Bu96^& zmo1T*Gd^k&pG`SHufEv)1-r62eKS2E^V^X%DXc8R+?X!i{b|!2*p}sZz<^qo(9niy zY{aZ69unxk%gvJ4o!5u_a2RECR+dqpG#IgDg&elx2U&yhH9ih=4tTm-vSt$CkjkMT zG@4Y<9pnK}h$`DsVrqum`sXL)&DY+8FV}HYWZ(HEJHo`(s|Uo$zhzJA>oS@#vZ*lsRu zZv8J^mUS28&Cc_Xr=TF;TEml4=@n$!dC!4Fv zFDR7E>@4R%-Cw&5oi;dK{3tky`<=8_!NF;oC_BtX(7*8XxDn;s2-dM@v+QDYgJa*N z6SOp=^tCJIeIbL_E|EHH0wgvh>Qw-D6M(s}gl1z=vx&|2lZeeTNP3qfOdOiPZ82{0 zR9U%rJ|fvG-CWZp2!$sfwEr*l8X|6i0Z#0erhP5jsHPW(w%LU1mMLhb33B^GkIImt z$H)=CJxX4D@l`w-h=qS%F}{3oB4kMx<}q1VXwUd?y8Qj||01D|P^Q*xv;B55?xKrS zVt*qJR=7W+0%MgXCLNuA5pVs>xX&>U9Jo4_N=eU>yC43WEc|I+C}Mw@gl7RE?}&mK z$@bX4L43r8<*tPrh%5-Vi!Vy1nX*Raeus4(s6HV+_KJI@zw1GR zqm&kaVrgRCp$G6}6t*Az)9E6t)bU4m`+;sMXb`#%@YGdM5(Z-q$ozbMG#k4IL({SF zm1JU&QSQs zkJ_0f)NM{0`Bw6aue>HTogn2om?uMlY)m4LKjt_zo?38u;$3EzlT!pH*Cu1ECsuhT z>(W-;wDQPU?S~r)v5VQ;n87v%Z$7guQ};ugL})%I`pN9sQd-J;%$ujmz&+(+yau4O zv5mM3kixV*2N75z&yMNCGE{fnw8rKRGLefO=c|ltfy}#5)uDANG=;9|$O%1Z%8$$N zUUkVbart+WSGPuAXlde;3LLArNv7eH=rB1-35OpzQkR z0b7c|HU-ROaP|Ar2^UB&NV~6xOrLnnOu4XSWn(C#XyOMC_Pwlw>WQbNVa9WE5}y(; z3soGw$qF|c&y8V7dAXd_0QFN3!m zBxS2tg5#Ani?xm!F-lENHms{=f#ERT;YB=Iv8qgNzvCVoC7|~%=_@ClbfQ$0p&wFN z04O1Ci%f+O9pHgjt+ZH#S{b|}gJCIxmm?D(H!?XjJD0nRndAbs%ubyt)Jz=T{7;LvB7ZnEbqR19^n;$JUpbDlbc(RhWA>z0l+g3 zrxT@ts`*}kzM&!1F}m~NP9;0^8Vdgd`~Q$3LuAM8w$YR}pC6_2I%oHW8*Y?0-+oWB z^K!9!ouh)7%#N|MXwo6Z3^N=A%s^nuNsGlTFc|PpE{rDJQ{w=Y+E6QZ-u*`ml&l_p z)hKt~|B$=@l|X90{_Y1DFtPZ@i7ei2#mAj$q{~^Lqg?RTp+((`8y?iuBf<^9hO;K? zkM5XF4D;*3G4@ravtSn+z;W&pV$^qeXbSR)y!lwv^<@^&`QMZgv3MQ21O zrW!ZPAJRi7_n_7O%S+{VRjVPRz8+ivaIHfN!GJ_5_9hGnueA5dhkYpfcg`6SQFjuO zAX5X+13v%ihw{*aj{rIs$mVqcM$YfqSFV_NktD~lUqxKjFQgGZw$K%R49J90ADd9V zoTH?qrb~KuE;bw>gFx#j-hhCA0&wN?gelwv$jrnmGfQOhq$?1=<5Y4=x{Mh+MjC6@ zV1DIX7Xx@;2t{gm^ie10$IhqdpNZFSPLZ^H6ni0WkR~Q>IX3qhNWE^`8rWvd@&@8?eWoyY+$IGp|?Q zyo}6@j1(wzmXQ9>6&-wD| zh=|y2jYWa)<}Q#k&pt<=xhv{ff`K#{hySMUGm{)s7G8;Xr9_x!$gytFF;H+Z+JMPV zZAF=k9R7PbX4pwl$SRVD8Y|hAhg*u-@Y{bqw z^*GRo?!4ERjJlhK2}K5;Hp4j90Fh|=JQHiKvGxyfd|phMI{%Q49^9o;!9dM6u0GI3j;nh z(l?4Qbc1qY8cX2UPeph{SOY0vm(KgmEC-0GRpMWX_!L^qK;%Dli{5FPab9HIo0=4`p4<7N!^1PczRoXlhNE zV2sYRgFVkm! ztSt28LSuhJN}2lKhC12;`NF)z+(;n0t6xL_Yqd?;V26vHasvN)H$Jn z4c z-#se;zZxG-UUaVxJ+d@jb=6fY*>2T|6dBn*iCj-gZ0L zXmTYvDLXqWGcy%DQINTjh)zU%4X*`xCWKdIn`yL<4Z)$5p%}vITwa-pQv*&*`Sc$b zoQFY()=oR`U?Xa=`sDt*?!>8clN9zW*2TY0x|~j&asZl|SmcK8W-4Y%g2lnX zz__7=oT8!B|2RnUjjykQRPz$KaQx^fELN>tCTEYHfB`;1vhlhO(d zJX`9KK$u*#3U`eTXL1zn9-W%W7IH~1N3lNAY9@IwsV8x#kvh&cpi=+_Pw?X*2AWdc z@f5A3;q;&&H&1#L6-i-!zT_9=NgnLkSm2WdHa$H9CkUxvD@@kY1U#alg0XnY0V3&y zg-?Jjqw=w;BU%S2TaY2o#LO z*hnK62+E6X!=GuIHH*gJTHA@bO~Qg{EnJ7XHd=(xRX#Qpp83;Xk{6-pGLkq z>o=m%!ph%uLs;boL;u1)u?_<2lJLI$yzC^oqj#}f3OPGoLvBBon+gO&zedTdjg#x= zel8~s&Xbi1V6Def&Yag_E@{Roj;*91=*%u$X{owd{5N4f5r=1s(=r;t!d@>UCQOlM z{_|%^&9nzXEX*Vs0fB(;z4w94T~Y>a#}v#%IHD6M{S7vRZp%tXdv2Vo3x2s9;08k+ z#5eu10Re&Yz%K>kc{&cKDrC&)QS#awuSj7Ir^%7Z1r+HK)6RBs&@isa z&WuD{abP^=n-t;~rrMs9Fen}AMobMYP`)f;Of%yWl2A_|qbwyoH#@CzO-0rvmyFIE zfAP8bafvDAh=6w~brXQBBmBBmCsUw%1E7-&z)7C1iK{ej0C4fIEHuz_#$J38S!lA0 zd-u=80wygHqK*0T20E%(eFcKY>PUi7wE5!4>7q4~VN1ebfRkq4`{##E2N^Z$0)^ZC z(>e;&RF%ohPiJBQnuC%}!aX-5*n!8y7*2EtTOo14b2?Jhbp=TuuY;voPn)qgZNQ`_ z3HO+%UN;qRc(ux>Fj!8x{ucRp@z2tuS8pVX>cm3kAW8#3(qvVzHW=41H-n3f)spT{ z!53DY`J-b6@NmLG(=1bW{tT83$1vEC!UA76j!`EQKtEn-$i!)WK~5o_HtZ=|ZM}_b zz0G#gw@)7_DlEX8?-@GLCF>$NE(nQATMU2nQ>Rw`SX{Yj1EH9}gBt{ER+q~1CClWe zg+I&u1wYE%ALhw|g$rcK(x0Ua_xx&b*E}H}T`VS95R(fHlNYRt2~F9Pfd_S-&~Twb zMNGB0ti^GoHMVl_bMnb>wj3j2#fG6NyWZYJ&~*wLoFqv~n>quqTOzjxt91eO8WKbD?j}7v%bc`4J5NE=H-GkW4>%X3;^gri%rp% zRR@CJY(Uu(4%A*#c%~7+OaWtPwDR@W-^kLX%cK{|fAdnNLYLrPHMzp7*Q`S0L<7}| zPD-#5qxWUoZ4F^8(OVO?g)p)E8DEJS^t5kjL)H0-5*IV!8h6>*SVOZZh!#zl7R$wZ)(<<@8fek^BGj zs1zn)I*zLc%E^GlU3?s{4z`Lvop{)S1u$+M*2>Md-wWZ)he`@z_*61BMlLvSxJ;OI zIi5nKAKD^!%bkA9v_fCr-MEpdyB>}KFx)mB>DRG#OIgjoT1=~q!{jVEDIqB>BQ3RZ zjUHm={^!Y;@{c?INNxb|-iZBbbgco07M3y{i+DG*00r1`yHzJs09ODw`zPx^S9DzP z^I}`^EjQiO`{h?&=ogor*R!~|BoBg8(!u%{F0VEsQXn)Uy+*WQpDZ@XOw#$@c) zJL8D~$;q8{Kkx#JI;|JUmn4n;@ zBd)1diomWvsDFRiZ?8RM*wIJH$tRzTH-UdAM;>#m?7QE7GHB~T(gU0plysc}VU>ws z-zPg^98cgexauO$4zg{|5+mT_#FO_XaiW-@s+;^a6_{Bx4|?>+gp*J4K_-r_KiO{l1op*q0dUO5hxuU~Hnu;R^_d*7*Dlht_W&mq6R_Wa zzOn=|H$MIROG(0&I$j4e3uu-BMt-M`nG{VN&IgPy)5V{Yrt1oW7oM)P@o=C~KKuN0 z*=w(Tq<;rq1=xM}o#h{Ye^jb*btoBmAkU%2TM<o2VlF`4TYRmTKrF&Qs_BFa=T&BFFS?w8bo zeYkv0WewjwocYmLH5Xkt0+Ttt22knfwDU@U;3@#MZq*4CXhl`&1gti*B(dn>$v+qW zlX;zty)h3UIK^%E7(W!%+vpq*TfKHTQ+OU z%4Na41@g(qvt;`859Q;}K9yCan5aUI03|CoVnZka^&uV~X`^>7$qj(I!RvHJxm1HS zn)24y3G8Um;avY_yxcG}0I&@jorZDIGG-uKkKt@yRY3i(ZN&A68kO+;pMU;U1`QfO zsGY8UoHtKSJpD9TU0Myfs<`7xAy+&RHNC3A4|%eAr^z$72mNR&5?X6uhsU(IH1BIe zZ5TZwHxbs>;LzGAHrM{}#tpx$kNC`sDbOL$rW#|~1;dBSg%_QVqPA_TH5hH|VMrCQ z%~w^)s4*AIN1uHH{#%SR+$455k4z3x&y|>lqJFwYQJ`7R@H!ogVoMKUJ)MxEl84SKmqRDsBR>Y`MkJtt)|Sf7+w_wEKK>U;&*Zd~g+kRRr7M1t z<4-tC7OtrVOFZT}?q(BVA&3UGiNIo`ItJ}=Z8&n2X(R=X5ZqkGYdzqwt3sPzjYE-b zx7k`=e)&1c#KwO|slPq^C%FuZ)2#fy;1)~6X0gSW_`3*ea$1MsO=tDOT&6~^8;QL7 z)MHZ8tA7+)@4WSvoH=5Iq^9RdJmvy=tJg_F3F1r6MmA1vAnP2V1Bn)bwBQ{9FSeC! z(xn74PPlb-bv0$>6=h48E?-``V(}t4{q)s$^XG53RsSDg{vIFQz=ovU0B&?=RRia4 zwV^0r`-BaZUUxbn6qqt)3a?_=4FF!MD!Fdz)LyT@{yK#K_RK9R&PxaXKiL11Ov6TR z>>sIpFvA;mh^uXO3WtrD4*0Mrr0{A*eVKOQOosr-(J0B z$bkod1^Q?eLN)XK53%ql!wGdN1_}gV(SYovKLeVvB9kFr9~HmpBpH`ZbSo# zl!J??FD5{{?>^Xz-ifB1ygZrp@kcWE`|r)hm~x4j_M&Jxnf3DQHHMT;6!AmC#EKj04#!Y;pDX)79hw}xjD&c&q0dZ2 zcew&%Lk_OT?OD18A#5cJL2NU3B4x1cBRfMV;JfLWQks$_$&f_^(ZZ%LbPB?ZHR$(f z$B!)^-gW0(f*USWix(}Cg!)R^_y6`*yZLFtqBctg^zA7xz4R)=gSk}!A$8TE`9eEM zs25Z6QbQmO7;w>;3I;!H*Y#-EKQCM?$!OF2?YoZ;vNbZ-{uHfy^ZHIZ?IJI~@H}ML zERhuC#~^6oKzH&&D6T8qNIRxTc{poUm&w}lQaKneXho|20|v;eufC$!k|;D$X-=5t z--T~s+xXGcus%LLGLp~B8e0*>292o`7Z(l;N)k-)8n9CY=EjD~ivMTtIsoG+uJ)63 zl1@4mcLT;$hi-~(z%WGxO$6d-LWEL?~JtTE73jtZDqQhj90w96Ea(AWjcJ zC<%}f%{}I^Dvg3YhQv$(v(xfFh)+h0Jpw=}fPt4?dg(yC`$bCo7v|+dkO(84_APF& z!!9P#&@IS4D4ge}4H$XiN{tK7W-KMW`Qxsi5vdSvaD+sivMYoGSQ89%pZt|d8+QX>4hl;vcB3WxacJU7jnsY=gIwd-y{G2_nmU&`0+A$^`Xgt<6MF=peZ9%NAEa50U!SO0p9I8 z1?9^`=r?GP9DMlUa_8N5$kUHKf*r|U$q1CQrhJK1;tpgUx#88D++k_t1DrLJQKIGX zC(8V_XKemcgz3QB#_A{)4d(>XQsajc8IqiCE^?s-I(}{{j>149a78iWk30$)<&{;2 z9;vL#v;pRjB{7pY>y{QC7`0ua7>W-8z+HzO05?9BUi@mOH0^kGR|exSfe(Hg9SdPE)qOT-?H;>z`+nOi8UHY9AH z1I}cy6&07rW6!)O-+nhUFrl;0I1`ddxN;E8D;{KK-cG{Mp zWYFJ2s4gc`Sdbqr?uYA^aE18`&)wI5{=zb@Z79WTuLI<|$C4(jq3LZ5_$j%!(+<-! zeHrV35&+!ZWaH?6*Bz6%_~$i%r6r~P3vzSwb9wIH^eXs`>(7FkAYwYk-;ua(+NpDb zOF$;R+Q71-s3aAfHAA!c4m)fwTW-FojTXMUhymhw>^9rV-h1q(Tx&BFoBCv(+InR; z4lzdvZ^~*zisbC`ew~zqrDY4{tPB4L_gsmAqi@cr+kO>rw2leUF$TCv%%KD8 z)zMp{`UCk$&rB*UHJQPX3=S^(oB4oCPbr2boWpIXDwmeJDjC|ZM2atOa+se5&QTvXze!V{CY`n4j?vmfj zi_>0^dv3W+wi~rMnB{d+xqO*4*5TG1F8t9RiMM7P15s%Zz|a((N$UtiC%^t?rhGZ$^PuRx;FeGjq`2LYl&6qN{v4Zt_^XxCe8xVuK!p8&atG z-3Hn?H!+uq-53GsZ_hZqW&E7aQCEDO0a+`L%#h6r#6~cl0_MC7+ z3$YwfRw4UVERi}mGgDksDPd#WCG!|I?+H zYq;+E*=esmWzTUt;fEm(cVY2H09d(>EA!T8s9Weg;6l(|&al z7yte91`IAOfV$IM$o`Y71~(hWd3#NdMew&E`o#M(M7-gG(}XiS|KtQW<6Kn}@9##? zGfoW^|HWgh3tvu!U^tL<)*B8+3zXaGAK5SoY#L5(Fxc?yKd+W+s9YwOUGfJgI^xZc zK7#?`@_$?{3l}VsAwve^968Rv*-B;25{@r;hgLrUB?NBA%MGbxBU0opzb^bqOAWY5 z0`$}bC3y9f%P~W%ldVT?BLDZ7%jNkg&*~YwjW*jwMb@ zLWl89fE=9v+hyOq`%PWpX4}_yWnfxRGO*3l8u{sWsW>UQqtgFa$;wP5c4_2EY`xORa zUkFbsWSujAe(#jA&r|VZ!(?W_paBwrB2yY}XvTM+iP+R&!9U5MgTp1b21Y}&iSFKT zVDc2#r8@O#V{zv-2h8%Oydrre#;Fv1GAK$y7Jt^C9&5cBi~bxrd(PLgPVHi;2g0*k zb9AB3x=<7o6GXhN99D|sX^s|VLw`*kt^zEP4?q82Zo2v3h`@f7##BIQ|JCFoY+OXL zpmhQp0@f$4di294qCpaVe10BbAO&;8k;fgWsILfD0X{ors=WQ~dkOKo_W8%l{v-t; zD;#ade9VyoNCJsMD1J=IIR4Q_MZ`P?_|AVnm3QBIJ1G_&{&eZ3&}o5j-C;y?V~B`0 z5KI&w2At~fyO=PY`RyJkrp!#=fd}2o*|>x%l2;fjEa*3MkX~ti<+WFqpk$N?;J0N0 zVA5)MQh0}^moX4B0D2kWzT~ujz&A%MHUf&p6g~C$JCmTX1xw2@BSi7PMI~iA;Mpi<@BRHjng8KR;vw_+tjc z4stbAsFaBEaHoh@A(HX+=9_Oxbp?;{z5l2}4A;7Tv{rXQ4U( zlKLPzpf6Ex6oBi~CiF9{c(U?hGI7lfaKIQXzRMZtY{plPctlSd7M-hOQ~HYU|0lUw z(8B|b^%!EL1&Vk{8~WJdC_eGyaEjFra~LkZxrm6yO68`zC(DdaKE#*ZtKvIb_S%1M z88>zenAo*o^lQq1-R^YOrmAC*`#}pU+#15*gFNucs;642;7TNapDRT?x@Z^jS5-L=*ogq5!(Y1|H^f*>cmJlMp^x3Sjh@F|r+$ zY&X_cVoc|;G%^_sjhj;V6EI13TskLC#{mt%F8})Sh&lmXK(8@mMDp_Ui*S9pWEWw< z-&k_b?GLIn0FomkfJ^`@1>ghF<;;Wt(!>B;XlQyL10;6ho{N7r0A2>l#!z(8?=EBm zDJ>ehdPz~Ns1V{!`Q)adqjMXGY13ZIc0CX=wQ<6G(b!9Af6C61yI{KekDW#wLY6bm zI9>a))bK)|&R}59qPgC-V_;a+$hCIAJTaa}}{5rg_^)5TpY7JW5PiWikXTyk+z zQe{gQ%B6q50^g z)znE{WhF$}%cKBX4#ysTs66q=BXZZBcga>`MjMUm@yT@XWmEOPJBZS!-hQlgE+fT* z2g+H$J71)lnE8>_}F*TDR0A?L4CAiju@+U|oqi-=%XIat+doC8Fb4&Wmb3<0^ zT*g+RSj45p?~ZkCa^mcnQiFwOBqtwj1XDmgdPsyIq`T-Q(v1^+l(~y>iMpxtFrR5` z%9cO;bpqxsVEDU+sT9b?7o8=g1%M3(HgEW$FaU4juz7HUCs=HqCpdebsCWEB`Y~Jb zppzhmBj+D}{4uWZo9u<@2HW`;{!WUbx!BaGM%%$6T@mQXQ`jU-emSG|a?XCIw5V9# znGPv{6w(RjgW0dQ;w9(;@M_7R-lo%?Z!f3a166B;rvPAZ1R^(zUCda&QYtwwI`z!o zP!9kfu5%~}z>QC?gTl2akkH#EM0#&y+@!~TkGSW~zg_$ne=_~^l5c`BWYS#YyI024#|7cIutUZ|f};OT(8#|2`s>R+2kgV-LetF{ zxa&W6OZn0=9WaS=o+~rP)Kwdq0ptR=;F{FNTKV01zmav;9g&cWCb{yu>*a@^=18m{ zh6S||h2yA1&a)X+oA#odar9AA zkcBIjmMqm9Iw&&88w8rj1fW4Bv3^V=cO2BC=YRYp#GZe3lYY@aNF;+He2;Bvw@5;FG&A27fxib5QAp?pYg>%cytn+1&IkbmAf{;}2-< z_U!`jcGJi(Td;5u+D-CKoC%&3*@g0RuoOXI8r2E$m{zRR`V)!0_2& z7UBaCJ~iaDqMvCaa|d!pIF&AsmU0Ren)Wb|IPpIlx3%rOaHiA(m$;9{=k+u!^K zx{IozI~LiJe?8tN4%NFr{aob0)cB z#gTr43Jb9K&xNX0to)}-bx>);{pocwZrf3EB(B3t zH1EFqj@E?Zo5cWoUiB;)=h^>d-nJnyzDDz_8WjuUAiyC!s9y;GM@w zJ){B12&i0M4q9nY`r+b+3s`@hncA~8X!DWyZQZ-;O`-!VzWAW`;w}jFJWT&+y3C%_ zeG{|Ha-3Z54K~_Db{M<8GCuVTCK@8!BD1f!gWbg9ud%6Dmw!wOnS$wDtV~l`F~X;f zZGS-1k=hDGLC_~U#nHYIw8(3{>ZoM1L*obBVc?Y_0LP1bERs~rm^og-im1E$K1|xW zvlZ%Pmp+EEV+J_b!3ZaV^!r$UTybZvjBWM+_@IVXivfpqj60}vZiw^JK`el$;Vupx0XjspBRbB2=yr>H$!+_5KfAW@vgJ18mb=VUzf|`0qb&{=)w6e((+%05K@ez~Vnb&Hv{1KeNJvmo;5$ zu1s9JTy#+Shh1kX7i(^6ku`>{CPy5DE3!h=M;DZ|c&m!ttc)Rq-l7AOcdk zf@8VPSYyV255X2$4$jhfwO={uKuAA~X3xu`4tH4e=^M__4a+x$4ibr`UogAUWSdbTHprPCo(Gu#_wckllcRa1Z|I- zJvZHcH{Qt%$PG5!P)2Pr5?2n^sZJU@AN0dZNGd4#gdnz}5}bSN)6fd@tdABwD=Rk- z?2nRSWdOYY;p+p-7AziwAd~{28yNt-olztinvem|H#9)JlL5fh9T)uruDxo4<%ks) z6cps6vxty;h6xK-c6#0o;o^*GF+tr!a$8EA{HmxhnkaYwTOrcahQ&WtbaLzo$IF0H z9?c0&cVghrfBTEfpSM5;3>b{@5ZZRp-9{^$|jp{+xeNHaD-~uVhZj*&S z{h;~*TA?3+#{!xm31Bx%-0JTjE9r(KiKJ)D;5otflMo{cwV~oma zll<0t`LPn+##8JDg~V!>9JXMNjHzFWs{z5Zb#DbQ_~VR4jjcQ% zTDb%7F2G|kdFPXv^5BDyn()5gF=NKcKD&?8el{B$38Y8EVDJI+g-_3FN&waKMi&6+ zyQsKCYN~4Fs;jO|4@6)nTTsvQ$YYO}b=Fu-nz2YX3;?99b19a@Gd8BqyVV2ZF~+`P z+=}|2vuOF={$ZYp}l7?5HbLI2Cp4KWtU*j|EmPR z^*7#V8brx}LMT+p!!??am?Z}Q%?CXsBP2J_WEaz&r$|07IJvu6aANnr1>#IijSVso zR|JheFsPO$@y7(lmMIxvA1`;4sWoSnV*V3mqKrZoy)$g_?J;htU8{x$CSp zST4EzuNut%^VR2{%R^5-1vo>%O{VBSYn!{g7+gpoIt0POdUggSj~+CD0q3*2acp!G z0>6e>m`A-Gf@l4yeIC31b=W^NqWl;e7{ii%|_o=9dAnE=$1WoX2$(suJ?YCTnuIKoL4$aOd3yht6i zNbzgP0zwQjB45t>O1_-&wHvOlEhVL;vd3<_U{|LO^zASJ6n9R}T%@r41_!QFA7nnW zTIhoeW}0LM$jl(9mkS_FafTl1|u$ zF#t_Jf#@~2bi>2&kXTn;06de9Ig{i-6lz+bQPM`{b!ghh02#8xQw(P&{`CE463cFo z=6vV^fQP*mfamNf_O2!Yr5t03cWz4Xs8>#Ifn0a%U9x0e;tqVSTyoKGWiS{toPs9KuJ+C7y!8t|EK1E zZ3t`(cnh_3$%C2(;#1t^e_Kpx_bP(1XvKmYtEDFh>F*@DGVxs1928g*_$=91M7 zU~K>OMTybPI%R&rb~L?*WGf)kWx`*jl(V`X*ou$Wk8=R2+r&DhDS z04t!gWK}iP13>X>(s{QSuM)ZiTzf#6l+J`$y?AWWS`vKInR0Q|F7|bx`3?*{U}JP^&%{(SWX?ZBTWw`SLkBRwLntgf&z8ql{)`M4);e{dozb z;hN>jE3Yy~)_!V7_Fj@e|=k+Kcm-P{7OrTahtU2yCkxNc50C)~e`M6x5%{M`JK zl46J#=VIYsUHaUVX=DIU+?Yq9$poNOv=vH$H8eeqfsg^v(-=;M&(=XA=W39vywZ!# zKie<>VkM=qSON6!MRKwE@3tcCNDxqs>uMdmce{<3@iZ>FBVGJMTn-&f3(o#GU_ul{ zr*tC3k3xd*@Yx-<+wQnizWMq)DS}uW802PM4ZvDgx-#MiuEuF=#M!SZ*=vs-WcPjc z_a$v=)jjo9 zRk8%vxMe{wyoFYc`H5}zi z>us=s?7q{^$O0sBcpM57Qk|5d*jjQIe_k$=G2;1!ww}X4I(Ro>Ky+#$0h;`9GTsQq zrPb9nJ(s~17&7~HY{uzAMHePIjyM@k2eI8)w)T><+Z{GJr}~_FG{Wd!eE4#3WhJ9L z*;qUkLXxK*%>xFVscH`{cpj}?shxuARBG+i-g$WXIC(=2+G-R{r6omp#LC<#2Kx`tWmeK=4gtM zk7w%}(Aneghg|l@OOui;FJB^m{pU5(4@zi2AG-KwUE0Ce3@nW1)vzCkrGdmrm2c#9 zf*X3{B#{be`0&p8N^rU>vFkr>>==3R`RC=#U!4wZyQVz;bV7SyW1vme-egmGe99AY z@wsP79;8YZf@#C+8F}3#??BgGzl7O@S;#9z#TCHPO`K!WM=;GXnBRxAnvE?RGOsA2 zxnRK}`S=s7DLM&gpE#Uw(rFR{qmH@&c(tXSu(BNSxQPGiTxR{)$s9t*u{#ctd8(Pl z=ngz(nr1+FMTMDwlk}7_GO&%su961(F?E!yLjmjGOS|0)I$PLZv_Cp^;Q7j*X+I% zC&WAIfb@ii{XFL*IP?us_o&pj2W0B442_u*Q9LbC}RA#X(mh4SS$KghI~U$Y_N-oHNm3>@VHa|Adu3;@m= zsp-PPL6UJX()>sxQW`1v4H*DwV7NPigbaZ0z+!qZ{EL6Co+$QGblpF%A{VSQ2l7f#5E6rUP(C#E^87!$ zQibd;82Qz1>onmtQqT{)&GOR#Y2&$WFaYv!F7o8lPgl-FP_)pka~Qbr_ZLGkTfO8L zlwjclzK`PHgT<>}IB+(j9%{>%$(g5}Dr*j3&kLZoR=MBnWG4L06 ziuB7#OvPa<{irrEXWVi><1DuCh;!7BxKxcdSEO);8Zp5gI)IC%Q?x{G8@jfM(TThz zr@QL7aGtmscx1xH}mLwr=g)CV(hGM!o%EOg@yp!D!CG^@Q1Y z&;H>jx%JjNf*`=nA3rFF&5P$peKwa|c9}#mccv}?)!2`I1w}B%zBQ^7pXJj(!zjqb z@rs-OeGkT(Y7ekuqb`6kqekJ@C@`V{i*Sh_;!a<*(#;psZ}&Mn*5ooIhv^v?lvz0d zybh330MUZNXkKgp7yLzkJpXbs0Q`~wRtli+GTbw0SRtXZHHN0=F+gG_u6f4AF8*U^ zutnEidz})0X}^I*QM8O)G!cr+C8GhPTlEAaONC~JcvS4 z`QL_{jg)P-+cp7e_?aFCg0cVJ^!MbIx8BB`wEcl~blMK{TRU-7q}B@t4vZYqh z-EbW_@zj%jNgBBS{rDq!>V>IN+HU|Bq?G9s_M{<#MWa z$>m`A_uF$1h=-SJyQG5D;8J;UJ)13Orf;T@oJu-ylVc^O-EoM-7=8AgPJz?STtKls z+Ge-cCiV5n*KoyyClCv$J2MxbO0Rzb5ltBPSv21%LsL>)+zd12kLK+RX9%RD;HZK) z*!b3QmGO`PW9NP;&Z}eZq!F_%u*&6?Awy((j7N=W{5dr);cq+!?DCu}6yodHCG%vH zsztaJ0AlN{*^o%*u?mAn7n=;qhX!w5d?P&1yEAAcKPD6Jen{s2NQHom#fO5hIglni zS%wcCC=IpH6F`P5$0ZzVRM_4R!nSX?u=X3Xe+FqYFaU6^wqAXC=YQ^%g^*-SHt}(j z{exXbVu8N~< z0FI=o4j(&#L`ar34!#>Z*wyvUz>+Sc_A%%Af+nzUh1&pfvLktUk-R7r0wO!HN1nN- zw4$<>w*gpv09+q&o#aKvM`(Hm10e&TXYiT`mBh>*kK8TY`NzBD@ySmc&R2e6QBg6J z|KWn(9P|ooD2e2ss>uV6V^Xvi$5|)&Y}K6bWARThNGe0hLZ>_an4=|@%j|_Fa||To zy#;5&uDZJ>18J@c8N>%EJDra}#Jm?ol49VoIB{bAx z3mgAsU4!2Mh*^(Ld_cgYVQOfGqElIh$=GYJz8xH`$6Z;_*Iv@UUkR8bXbb2k zl|e!URp#HkHCl4q?#^;LDWs^b?#{{z2`?S|cp3L&3Jgw+eW^`+*wo=xJ|7IKs>H(309>|732p`W%a1c~qynPuc{vmUfI>Y;kYMG&zMS=;_;(NkCuJY}K0uw43w3vmrKByyTC2hn*5k3}Mc!bZ?EjWPk4 z0ms-pVAENO3Zq!4FO$DrF~NFg_%``XjCAaY$4Wsy6(!eWPHlGU%|L2Ph!{>eECk!; zAm|HX&6YdwxgUZ2iV{sKV@8jb4c1>D=rZ#%;>N)CV>~`bI?s=HO8&ALN%rX(7t_Zm zX^;^Bg@8HvR66G9KzsUuhj|2=57$FnAB7A6AfY!*$N=aKEbH*(G5~P?MHOF^oOb#N zB>1Aj($d0wEdD7T!`*+3%F2i}jTx=g*QsTi-T0`SUGa<-O^jn6v*5FUQmaM*=>G3sHS2$MDnWS z^ixlkC!c&wh7KL-0~A_UMhu`sk;|_9hure7Yo!=;RK9SrEML4>>${b5wqOF9iG?X0 z>B3Ny5{_GBOKYx!WAQ?Hw!|=mN3eWwzlmf)ER#&9$DVu^3Z_y)PKMH*EVa4l6@Z58WgQvB@L7y zV%%qYood$6BrOcSQMcO3pj@crEZn+N0_W-d2FW5W zH)w2d^A-N4z<|LfJ=Wjm?D)tpTY^{6_(jvaytXX|aYmmRhpi}3>a#vDo$XSt0HmV}zVQ6|D18jNZYEis}+q|VS zXU^=0bGc*y#0sDo6I@ap#RqQ;25rQ&RM5U5|MojUr^f>*&-j}Sff0f|^Giiwx*Lr} z=#j^chjKacBtp|27^rWkkxT!6CB~jS9sCp~tCRnDv-8%g#wgO&+9(A%jdK3^=OCD= za+`-`%NEGrubJpx`PV|0xRM2WQH2*D-o{IMYcMCQk0{u$x+4D=xEku}qzXz4sdaY3 z-~J|7{T&hs5K#-w&j$lX9Djm5_4s5NvDPrDMqQUJSO6wq1Lz6Lg*dU$$MiX*F(tPI zy7aK&BPP!=$wtaE(;>$T4n_(9M*+YHnDND@^7U6Ulj8M;gYG5I(isL zq=F0P&5@Cde!xC87S-6brrsh>N$?qHte4gU^cL|PesfMflo{VB%?()4y1D?k1Sybn z&pu7il4Fj5FP`FVN_fC_OZTy?FDklEWy$c;xm z2fw}GcM^>tpDI}kW;zhVK1TEJW+}cL`~oJ?2C1wNN(yhe9pg;0V$#D7KU9XTu{w_J zm@9o1tdkFY5x$d@&6F9D+=Q)j#_z5}V3JO`ow+L~uP8HAGvwyx7Zq{0zx0dmKOL~} z$GKzz@E8EylmUQuXnGg}Ap@X?5$u2`p2+|pcZ*6d`d$5xE6llA?c;xBgoeE?z1{#bC~0kZ{v8 zBZvF#ekMVHgVK)v*lt)Ui+wRCf|LZ|M7X-L5?cP3%HV;ea@QTV%PA)vPgZRZ#L#Ue z$H1neM$6P!UXTO!-AgKQ)MMTcKS(VUqEWlEo_SYVHH8df0w_7VIPj(j9<_KjH<-_2 z7GgLL#feX}0BP6Dl;>aah3G}A;cz4+xar7EWF+*WP)AA|CSGi>w*BeEE-6M2I<${Y zO*oApJ($660^Li6;DSUM06seJrV^|aU|KNt*F*6y7cOr5Mmr$-d#MT%@MKaZV?3ov z^h*4;5fv25f^Lac_D-$%D8OR%NsMh@4H%>$sZHG)uLrX(;oFY^vdM@eTw*L2m;E3G zb;~e!#7?(W2nb9QoAFE^uY$~M7GWUR3hi(5!Hn8GAU3_`Z`&?=j5wxt}f&u(D4 zqQ9jAR+YRm{5(k$=G)OSQU(IfvZAU`?8EAWHJEA-6D|>_|JV04xl}&xE~aW=HS{tthmtH zl;ZUVzHp$`KIyh!ZSweW=QwKN!jn7y&~S~3EqDIL;cWNtVXHBr&}57Ox02kL6_pio z=iT?>EPtUya-*m>gmLS^*eQL{f(Z%FVXiTFpq%@MOWgR@^5YNR%Hz*ICnW<0V(p)+ z^-n5bHJZL(Nkz$lb)DLnp^*OmaOMg~C?ABlT}9avEdCcmyZ?If-~;!`?z`-q8eHh} zv%o;VfrDkzeUs!jr=2V{U;@nfdY07V*asH{rmFyBNNL9OJkES0uIwH?dA7I!2_NQs zF5vMWlO9e8c^^5&kY9fFHC$C_b|n*{^~q;;POj{?-#*Bjxr5n=UTW5nAL7Iqf64uJ z>!11CgPnQYRW(NXIAd?f9YNNi*E6nKcxz~AFbWXOTwHOX7)T$Dokm#4ZKQLVh=a?O zsLpEfMrWq|4EnXYNP#@jpL;GeUX0GAXrQbLzaFOAWar8T*{5c?)aOMcHw)8Q6#)Rc zIY1b6C!vJ+r9aypCb^IVh{}z3+$)u3bKpZ?4S{#Aocr4|r8Jf&&GldcQ2&bRVDZ(K z4bFFh@fO)8#~K7b8ij96uX7=V09jjwd)Z}w0)%v>fc%Mh81Y=QWc-Qar63PSYnv&- zKwSWsZ`%N_-MHBtay`r(QWrpTQ=2^a$dmXcvpL=zaRj9RR?|&8Wkyqa)CC?N)_x^> zC%7bh+IYS3COJUXIfBaoqWPuJGK~$XBAj<)&t(m3(;mNI9q2WCxCZUa-<&D^qREqz|NH0F zvb>@OBGNc9qNHG;Ww~s2KYf@!((j__vX`|P;2R$tZ&dUZw9tr&VA&FA_k}{Z1NPoi zCQp7?Hr{X}e~i%nGsZv!B9YhLc)eV6?LR>iE%MW>Z)ExM<(RjiN8!X7k8%T)Za9I9 zqb9|SM{}O7Ba~@><lg#jJbI%1JuK@I_2|W$mxa$JZO34QkU`C;-bHhIGvtq7P3ENee>4mc7 ztna^+{t@b6`v8$C8a{GR-RmW5CPn?@gC)zW>I0y6=1(5jb)D zc&USg0TnesHDzg>DUSAG3>L433zsuBR4k?2C5 z+(NWuue(Uytb=^48#`SZesl85HpJf+C~yI%O*Z_+1~O)wF>nb@cVJ-QqIq)tEq7q| zpM4h$agI7L;{Yd|{GjH4OujHktUav1oOJRDCanIN3j6TG59F0M-j>p0^o4*ySADcX zS%>B^5ltMARvd8bVI3>$oDle@ix{L|jYWUi!gh;RHfR z%Tbd--v|j*J%W-}q@P)!iV7^=u!vyevrIi5TG}b6zk2-kcA|9ADo8ffHJ~tC+2uwn192tM9G{s%^6M(7XJBY!_a)x+lBbq zq{lX7XNyOc*T`07b7i@9g3xnB=~7gP){DB?V$(%ipVAy%xlqo7^x2ZidYO3Z-Ei?s z0kleKzrk|GDaQd8lninwopJQn{FvFSI7* z-!gzR`Asoij2ykCY&&L@G}YHa5TIViP7~D2XnYnyu?S#%sT4O)rr}y#ZYCv~7?>s} zop3xP^FYVY;o)FagE78~m&AzeeaB~}y^!7DXG&?3#0(h*>z-UNW1{`~m2$yf^!zJN z7S+}@lNsP=0bd*R(Uu&o!bx}CMKPCB^g^X2Ma5tM zT@vQG$$VRK~}Nh45ax<_8~MtAa%!I@BhdJs&V&fb9RvgMt8t zZk@ou?74I0@hLAzw73+DcPqAUP|wP+=l&_8NCh|BjU6j{?|*PoO3wktCo{g3SV@0n z0w`OZ%mj`iI+C!yz1sEL4JB9#vY;7{`2Z&x3%Uwi@wa^O0*Qii|JTbeh4$Ltf;hu2 zz+!7vejj!CVeWfly)QRK}cn!F+**Be6jaY%5=$-gohB$ zV`317N3}Zn!Q!6^n#z_emUmNJh2&4CH|;}l?sC)?Bf&6jQjt`9CN((^9zNXSCkIN? z#e?OKkPh77n0_d|ZC}B1q)Y$|$Pl^3ox8n_jJ`|HM?8y_x!0mNU}IF?vib+L>fkJQ zSgRcd2!=~nUsjU>=`a`&C^6@R;XaNf2U13LS=fP2f*Lxcg8@5BApW>IyJX%hDXfFu zm^Msr!BfOY7bJm1T${x%Y729kXtYSCz5QSL@=U=zM;vjmtUG*&-U!f! zTZ!UtVFBcJ=qo8rK-H;}^JX4$!E`RbPCc*3{oCKKfCO4+^v@)w!~`O;-~M}JAQz01tg|3{Cf7 zz_#n|ffhos7$C6{xBkUH#s6d1UUij4R|2|Dg*UI7L~uV!e|v;TPO zA_^j5*;q`R{p+)Q$(mio8VsgaUUx0(JVJ?M74y=;Be2^eWG@{HESX+V_Y_0JQ&~~2 zJO8l)oc+eR@6%2{0|ZL)3{8jugMmGE9Vd@H{iNhWnbG{8X3N6)xF(Rqh#j76hl$9L zb|M#uw>WzBf5dffrgMWgwH)W>4e5l- z*S<{$ZI~LSJqE+bJhOhhd9j0Az31PtEf{Xc@p>pkm$Q=!fa4xe9rU5F9WLJKp+c>I zU^)1xX_GlW{R9LUkz?bE-&ANsoOZk%n8B~k#v?SF+hE9Px-tWJF#0J#j&xO#e3@ZS z=p|&ZThdH3Yb$dwi?-3#^>R@8T!DrZ$>BN|dL`^a)?Yr$G^U$b_!$O34s^fNHMGgZ z+wa0;r^bmw=-Co0UQK>=^6}7oUyFGYZ>|CpK>J;rx)Z$JSA31~OQG|e0-k*60w{tm zfX}~}CC_5xC36#;mtFVRN7i3wEsUFB8bj(*k6i||BIZX~=EkC#!%_hIf`#(P6VFI( zJ+Dgi41*0BxSEU`x05uggaMcgWB|}30lqppQT=wuDgM=AfcW4YeFbC19}6Ks2rxJi z!mZDoNIrH!3ySqhz}WBpcrh3MmI**!6E{xTd^-k>K%vPD10e$-Gkj$dnz*$)|GN19 z@V_&P<}FzQ20%9Yo|3}c{QM~H!+`idRFovlkJ}^n;5Li)VLe={ap2^Y;{QzzP2f)E z$oONARqjM4g$ct3W8m9wX3FEwOp!u}l~JUbow8jLXr*#h&cy?!A~|i%vf~b0%eLEZ z=L1GE$&v>rKPEr^I0u~Z0@MxR*vh20zwh(g4H&whLp2|?lX&pYoqsOiD$2{H918!6 z3-aaq>#vogp!_j3AqIjlaM1n-$n#TQk_c#a&UZh^qJ?uvNTf)&e^JWx$PHll5$E6o z?e~LS>C|z9oqSKs1JEyJ%cuYQM85s{2VdOYv@%b=Hs>V0<1V|%dc%i7OEZ*Gkr{$E zMUhtx$~LM|{PFzup2>fEDXwb>Y^BwSeAG7g+e2 zaRA30-}>+idG)1N5ZHyR5f3}+NEx~D2yPn9pI2G)#XLsoFRiI7HmjS2!BM@l; zw4VhDAnF3(*@f$Gz7tvXr;jY8WxSMZ@IvTrI`Mdl>O)x{*XPWvF4T3oufZ70#5DQQ zGN=P2Bl4rNXz4PU@^m~?!WQq4gAW4pGDojsG}qIblwEiV%;qM|w~3r);O+^g8~`u@ zc>Q@^UQTXdVZKU8i_{i>{K3b(4S)|fOX(gS0{{|w!mzD|ru#9#_DZf582~EUf_L!^ z*G?co$Fhn`3!?e?kV4GOB^L~x5)=SpjDn|U;=eq1YqMQ~`PR^Ma-G@X;Ew|zJewgl z)Q0Q-HXFIQj2b=C0tw$;#K6rr-!2V}Evns?=S#WJXNQk*Mh82Brx+VW!t!vf*m=J> zJ1NDb6-(sK|4fpS(o#K}%D85Tury%a_R9}IgPI7gv(`Hna1{IJUGK}w7D-VoUv9d2 zq8$3mL)>toCB#4w26o$JC%OOO2OuUXvhatWWYMDe*ae5kA!v%rC`)dJ=|l<)qRb%3 zbYplt`cFG1j9C0{NB@i=ViOrIUduv@;)m*ipDTwN9EFsF~W7)hs6& zD7kHBf{nOxI;b&j1~;kw<9xyo7Xt=wwhq`A(wH4Y+Olx(c0@``Ry_&0^73Ued-fcR zF?rgC6*EfomhhAvKqUE$#iY+ELu&vfcL*V!RFmMs!K~}84_YZYt5z@>8!vg$D11_5&8WE=OQdLH$b|;YriH~P+zS+frR7GgAQDiC+mP|ObLKi9QoAwpbe9lam}xX7jD*3GV{L4 zk7DyC`DntnW5&p4n{EP-W>ZfU03wFlatYu3+Z2`3-H$F<0#Ta)FP$)mLc}r4-1_7a zCW{}f;2fYXZImtA)rCnL8U;iQBQ0@G~Bpuu1QP!|BsU!r{_PDqm?b;ZF$ zQdgj)h})yUeQ9=(P6ysTKwYqc=*+*u(6JC5b6#Gql!Dp1s+q-$7D5k9C75m8b8sra zzFlN^72_XuTe>H&j`I`YSqKm~TN>x*S7uCFVQiNm-d-Y?wf8$Ep(?95B$d4Av7vIg6 zr}f-B>w`-YPMt+|+oW5fY{ND(C%aw{Sv9wG{-ji z`nw-x`ukQt0>c>-!7-L&#~+2T%{Wd9H^SD(VAzJs_?>3-G=sI9yrRGtf(p=c^Kv6u zxD*Y}CHFjVUw>%cWh3Q!DP#cnaMj(`kO9!$7&Y*SS8}aLygbuVI_<^hIOCul(udJ} zbUu)J!AMF^7&H06g&zJ@n--I+Sez@o~{)pf>8ZIO?sy4~$YYvw^_t?z` zEwpw81J_@FgOpd*;x+~HdC;e$0u=4AyJ!Dthr*UT^wy`IdV0}&c3?9=kv^FYVEFLNf5q~pQV`9<&i{>a@cswdgu{1;fwVAi z^f5=sgb7!IrrTucoVk!DS*9BC&FuhgPve6HH+Dj)jN@e{fX*BUGafzi!Z*lz_D^%< z^UruaR9_gjtK|F-S=~E(*$O3zRPJO$d8`oICixD6qR#34Sm24F>7L5Z7uMv!=dg-d z4T^k-V!y19nJgFM>gfUf`mbVG!3`g?XXB3bI*cP`VH`hp1fj#ybdX;ky7{V>?ZKmW z+xGD0O3JLJl-a?t8B;Dyz_R3j{RhiZs{_)D+=m5P`I5E*^VqJC3zmH+k;ZDB=VUcw zUc)*8?iIDZx^k*R{kQ<9C_cHrt=ai<>pc%h^|A#HA{diX=$M>&>WR=@g@rliHDJE# z@he}A$6+pwy0qC*@&MC07xSZ7tVq82a+bU}?d3Rd>3>+LUW_A-I#vet$8;ZKMJvY( z1Ti;L*i4!?V;W|%>_IpU&Gu^1I9=j;9K1KbCSyL z1Jy@X)BkAMQI|>yI0{f)64Uh(E~@VL?)x8+0l@VVr2^Pmvn>PHzTp;{E@FVKEHvGZ z0k&o`0Jt)w*njC=x7}_yS~UL!4Inv(XsIzu$ydPWHB z?-q8PScHz-aVHrJEux|6ItIS{{4<&I(#ul7v;PFgtx>h0J0mw7103-W^^0H(G{~;I z?I@dXxm7~KZSwDX?!&^W0-b&o(NS+&nx?k#q;J%U3H~lGI5?q9+n~4^nfJ< zS8-huE@&N{A;dBxQd3(iFTOA>eR$m-GPu3{@{nH&RJP(46&wSgSSR{bqYD7xwavP( z&gaN<+fIB3Xy>7|6EnDs?L^4XtEb^JJIQHy7&A8iFMCK%99u!OMVRmp=(nm#0JO?n zNb=M{GK}KSY;*bq;4uid84^HJ@Z{P`3eq`;1i+RCc%G78Iu^smu`~+{r6}%y&(0~3 z|E%*1O-~nQolAL@j^#)D9+h>nT|<>LK%}4hS=^DP;o?HWX{DGLcIn6ZP#sC2MfUsO z%w8goKKdkLKw{FB%xIH6_uf~w8MP&(0Kj-GIZE5IrzYXP)01yoxJF#kY7PPr=hsO&=!YXLd+o6+7%p{~^I?vu$_5!sp~Zzyc8T-u)JIog+W72~ zSz;`LvWzl7C_u=GanWCT%f!1982}Uuu?Xr^zQah#7zh~v$vEi_7ZR!~0njMY@3E(! zRR%z$q!dy|5dY`M#9jzJAp$#uZJUNa3!a;?F=#u6f$$imDSU%-ihDz$Y(NgjBsMhN zgMpiFz7*@x@cu04E#zXZ%h{0KHIVJJGjEdEe|G zx%l8t@SuT%a0Fo0VuUwiEc*WYpQI7YD{MOeL54b6Th54Lk=FTl4_18~Xp?CjYfWaY zgD>kgg7d}*&Dw`}dYeRXl;^&|gJo`EDO_m0Y4EDqW_@@|(;A1dRwxAg_G8JetHDt& z=$F8E3&sbIdoC4>{mK)$4=_zA^!f#Hj-} z8N9~xp~Hm}XX!9NTH(v$$;l`ULPk?dHy z6eWaDXfncp*N!ua76$GB1H`M#0C?%yX8t}DtFr~ zVc?(NX4-?!79TAT$!f%aH)7p&Ws9vgH|d4HUBbXOUwIQx%FOtc4c7c@Y6 z+}Z!8df9uA-DRVVH;YTNMH=PmYj2inbn*ZK@i0maP$Z5+6O2=rUL;eq_S@azfCIU} zO{n9lswx%x=S>8^zvx0a9=AP&Cd5GJFu?vqDN&=}di%|?-L_+-7CZFImzLqYWTP?{ z*~d^35vkKN@wDo>`)!16($KC83;-&I%Ee@G(b6UI&YPW?i231n-fnl8{19>>O*8(O z@nBHE6@4lvqHO0hrnHKP1U#sBpZs_=*C)xhpJ*><&YN80CKeFe&^1;U=;3KUk3Tct zhC18;00n@gRifYE#A&x2OyUfe${832&L~29u>H9*cAJntvBbW|z-D-NOxAwPiOW7F zJ0eT-3+0*Bha_dh4>`#*bO^)%rE0dSYn7wteJS-oI&s(piBmFLv~S0^-32!QES4FT zb}9sn%KT-O^1%I%;hP5xIDMvHtBl!Zl#JbabIfHMu?D%HnMRPzsq}Tnw$d zl8c3ZW68@;J#CKwSO$QV0$2gduyST8WB_zSz3J{NTAlM|TP9w4Zbh|P{^S2Hr@UdQ zL<@_d{XYtE7tBN04LNb*rj1TV<@H>M%8)_Ex|(j(bR))1&;COTJXo=kgNf?FhaL(} zE89cf-PE&&p&Q`nVg-pdH8x09<#Jt^oOR}Y9)pAc06+jq zL_t*Pa>04$fe1}V2T=G5G0{k7JV8XPgHudY(26Y0;BS|NDQTt{SQ z$=T+HQSf9_5g;aGP0(TS^z&13oQ0F1-Z$Dc2PC`mxSeI)wb#|_u3B*|AUFUBl~@=X z(YUAbwV_hKJ3?aWnCbcS1pJtxlH^*&-M2pR%%^bfI-RS3ymKxF@1jUQMKN~lW6?DWq`h+ga;a`moGoXRFP6b|%cQ0W%u#xv&FOBo4Fmj>!ZZRbQ~=AdgJH<5 z$&N(j-p8MnWeeso6bz@)P*@3F0B4+hJeYSa2#>H}F6g{R0r@K!!TITT(gfiaXH0sW zO)>2(NS)=9X#-Jjt|3FFs6yJ8+J$#c>i%GI81;7rBgy92#uh4V~10e&TQ)o*W zi9|`9l6}e1rC`pSxyA2(@IDy;C55G>vAn!IC;*H^AU2M$4h^Hx*$IrDuOK`O{hcuS z?f*$`sq9b{k^*60)2#PbbMn65!3X*yhxSfl;Ja^T$#XBi3gvn{vya-*4jp*sK;oS0 zpm;Pn01;`DefQf_ezDQU38^*9U6byELM99h5Si1-DL|-DHQ*4i-ec@ig^8}|(c5`= z)~*GMr3Q#*S1ey9^|e)U&;k3%MHgR~l4=q%5`KmlNE-vIuQ5z+zv~WM514J30Jye~ z;_!ML0Q767AMArX!!PrYdVR(Xrc~|HG|wkfLiy7fpUCVVfAU4>duwrNvFtK#duhXt zP74@r>|b>v>Xs!jk10J81E%;kxC!#?D&!!Tz2cTY23=0m2hWcawL1>Hx{-^2D&)o_ zQ#RUYL+xX`oc2ltU^Wy0R#o8!0F^Sc8Ba}LHblGcE=F$`w&Q8~6kjE`8!0&)Nx-;r zv55zxY(B2zEyA_DOE{3a{%Q?>GQ@zlfM}KBIHrGOb(u8tTs;|0(6_+OUpFG~j>9%T zSUE6U^l|{8VC=l56*5_M0f52kj2x5*7`wx+vi&w=p!kkDl@Oi`Jv+~3G7c;Ss6btt zO~!#|-5o{|)5cUh8^_P`F{f*2Xp$RmyVC~B;O#4rFV@&yca@Dttf#t}&;_Yc9SO{{ z6V8?+Zqf!%9#6>5%9W?4yohnO$_JI!R!H=2A?vL*3<{<})nJOClk*Ufgph>jUHr6p z?+6UBlG+$w)IS&ixC<~6i9}He(9yz0f9#D9UWqOEaXushl9&J<*`)>^`lNw@5Rqvh zDa|0HX5y0^DsC@O0bs#n58X#DR*6LNic5-c8#`2dsscb|0Kg~Cbi~1nZ%*0>@najK zY4hwDjV5?rTh&xwC!@C+D??Ws6qi!?(K!s<^`CpNL)i+2V1+6^X0Y!}Q!totJfV0n znK7k>`Ev4!r+5h|vmjgk_r(l({mu8FeYR9Pc@6+tv;Iz-c2=xYxqz&&Jz%h5M4^Jm zN=)pUp(%IQop+LdP5hU{aGowSAqIK^1KW<>POh9VK^MRcwbjzpfFmqiU^z7GCt(tA ze2?Yj$hiA`4SjQmCz6||*Ok5T#yehw6~wk5lu|{bxq9q|w{7Um(_aiWTOh${w%wF# ze*zxJj?xH@<;AyCa=%OG?H#AqdDw|DowPbk(u{>V9+D{wgq47av+lZUB?A0;e)|Yy z=2u@y1Ef}{aE)V$)_oHGk{nXwC4-=;fu!T(D+H~+&-P+_LCtP2*Nj~9@y3w|#^EUF zp(ULCwui#B{743VQW*ckOc`3e9M>&Er;$$=0CBGiT~v&Y3i>vc$vG3J!#NS?11Kn# z2cCRUmM!v20if-I32^RNXF%!_dK^%9s{9U+Jc2N=0yEu?m*yQA;9@&pbpgcWjkn*G zkH7pX5LBj)+!Q+Tq*Ih>?A>DE!wuJ`_uci~U0R;@h8O`t*+zlE%@6NTKY! z(+*%-)L;w-j#TWz2iq4z$7Lph$4S!f>$96M@eV!nWwOJy3y@qZu~>{PVh- zp-2rJTZnkE;yL)R@<{_)i~O-hLDSPXDBl{)1)e`{fkLMmXl1XfE=M}U<%S!tlj3N& z__wGF-#vqY6Hh%wjz01TsVrLz`l~Rz8=yy(d|{G)`t@ZY(bw()UVim8 zFfhSj=rfa;W08?i1h~bfo1$CA?mwj;(7&?3B_qIuPL7l8(t8DRu-n54_D(8sg-9bw ztr1oY>W5MQH0!LnrVJjuiZH_+9I@mxzxq-c*JQ47jzBLm0$gSQk>i*&P-}yB-mOoY%=(%uvRH9ZaUr;QIR78P4FJ00Gh;72 zU6TPxyN!!+{P8#u#bigX{=-?U)mB>#SI~`flL#%H$G~0xzDt&tl`B`)ML9ZXcDU{p zFRU1FVk!QA^btq;5<{ntupj^LfAY~MpQ^YS#GX)f_yaILarNlLw9^@zp5>xb^tujr zIyXS`?x2DFDkh5 zs@!<@Bcm-LF#1UF#v=0BmtV;LW_+0{T93Q49Epa^y5o*NQF(Vwn3-`W3LUOCU6wN_ zP9lu%{O;}alJnkC?fPP55Y!hd!3rSl!J(h}12Y^8Q8%hN91D?ri207*Vk9(vb9i2L z%(Akj^6l4OWADt|G?P?v#aDYsO^A^}(1e&Beg@Jrt1>Yr9BegnsYAPtqrPx94*Qf= zthSUPL`XO}>3bF3{6rluwhi7H*nR}R3NQZFgqMt)Jb0gc#ZDRGrYJ| z$blrlo9}-lbLY-U2rRPBIwNG8tw)2To$CgP4~1*;k(?U?B9(hm0_`|-2MiZHDujlY zqemjyxD^{(8L{L4{hZH=zy9_|%K+e7Dr5k-Nb9;K>;b!uLqcF5!o@#(DX|Wg$Sp@D zB-t$Y-v1C3_BCSx0a8I^!?k5}GXVM{Fd19JOpkWD@gDNVhB`#OlJT} z-#n8^CO~an0~8>=p>3px3ezrFldt3;JMX*;c65fLe}yP2+M-JSps;4=Ca8qTFnZI) z?Lz_+ZQnOAitCs`+zf)c;M25_&XTaMZv5d`2(fR_Q~?wKjvBq{^^=rbTex_Mv_boQ z^2tMsIs5Ja={-@8PSQ39_N_T_KRByNu4f(@VwN~A#uI_bsEy-)bJa%%KrL@b(nP|a zu42HykSGSS&sjPL%BPzpuML|V_+~BXE4N9jHEH+MN&|ogO|%H+!pox^Dq1SJnJz>ABLk5 zKpnx7;prlQ@jV!x%{a$>d*F<%T~9A?N5MMsw~)B_M-_m-o|T=8YZ>xzRvLa3`7e5L z@?8CD2?0B-47!g4$W}N9mQE1IsEX$G;H{jE(U@tVbZ<# zNlkqNG{wf0e`9Kl!w#$7JXFL24ki@fw8*bd|CN`ZcKF%y#jLO7OE{>@j`$}xol5^|>uRN@rV8ntCdVFjv<(%$Lk#pV20WTvdu<#$nRuhpaMh9} zSPWCxlm6!6h;HohSqh|eC#aG+?PRKeicSZ6a&q^w9LgZyL=wEYsM0fdY=FhX zs%Ad@_+zQ4ty2YvI&L{*i5bI)4d5va)b=LcOxt0mY{r~7Jm|-^rhj;I=T?mq-1Ixf z$^At*N*ZB#q;aVbum;nxcZ)e?1DV)loCqd;=OcV&jDbBDER@j|OQebQ#FMC;lM)K> z%q6!)H`zLdwZl5)gB@>27rdFT4b1NAi)lQKLr7y6dllu8y}akTGHzCV;FkGtF)9hIddvWYB;K zfCIMq;KD?TxN}*2|9y{|Thu%T04L8DZM~b>pIK@l1E6~FS>`%%g6c=+K1|A9MyAY_y5| zVuN)pkno)r2I6teD2o;@lqa5>imPI<_>ZuP8OA=avL3m}2M3uQ{ElPC%9f+Ifrn!d zlqcbCxbb#~?PJGKuVO-fN4YZ>jNwEF(EurirUhJYa=z;!`d?MPT(;kOj9h-%pN(hu z8)BdjFtE@5d&`->I!$V;tD%dc2K3KeadS4)OlV9Gm6$c#b6Iz13WoN83BA)Mz$=vn z^XJKbp_HlbjMMlMGH}2_2TA{a#YX>uYG7s`U|O&KQnr-ON?|h1X9nRj2-+!B1$CGN z-VS z`&d8y*0~QBYZyN%(V($3y4#C51@Oa!-5E$5`9|Cv=<@l~S|fCiIVT&OLo81{ydsd+rLUM?ZBZ9Mt}KBrfJYvA1|hicx0}Ar zvh%pHveCN3FvZ1OijPWOCRCsgu4|18LRxnmG-1$=Ln64P3+L(!qy|y|_uT!U8$E4{ z(r0q$qmMpD2J|aZW~5oDbJlL~V6)<8(L)CE1qFpr1~_~ELYe--^d!Ksf)d$lkKHg9 zL0W>i;IT9qf}pW)T2{;<$>xx6)&uT7z-DQFp^7dSeLU-<(xr13kO{yIRWb!g4B^@p zNa(5wk=RuXn)sw#a-}Q-pyaVf9=0MYQF5~|_;U46?iN`9O7Ys*JzLjuqMeReh*JAM zR*JZ?Y8Qdh)X;Ph1Cu9Dmc?buB|pCaixUT-c(MEDP7}IZtx#SGnA6%ICmeUYFGctw z?3c5?l=nXP07nS&Fln*7|L}qzAu23iEbC4mhR}0#*v;bNzY;tDLkE`1tvB6>^I-;- zFKTEFG0>|R_{-n^B-@YKLdusc!mR*x*l-2Sqg|M8&MYcOhN?Z^7*^Do6 z1|H(g_RKp_5{FGiVxnUJCgLIm_;=OGlZiBtG+%z8>?bhZItwg_QmUu9=8>Z?jJmZUyugmP!+X_m zabRs`90(5SIA8W<#vcYHJUyigCIA@$4?psVR8-a4fa$#*+Di<))`<1Ngv7B>Dgp%4 znDQubNJz-5<_94xKEPL(2PZ$42v)xj+-E--0OqFZMZoyWa_Y#dH1DM1Af~&S0CD&Z z%$-LQDZJrq3Qt(50}4v##BgaEKDp>GoigP`G5~Z#6>sGRaqjIMcYteXGRD9vHXLS* zpL8RTdaVQi3AW_H2OI`KG!{iik_&D%*Z*ja>|zYDdkx$qV_XxH3~<#J=eX1Ed>iH; z8b5a73i1nO@4fa+1Qvd0h=JwHm&*N*Jcg^u^39wANVfeuYf>i_>~Jx}MKGz@Y~zh( zo9(twNTpRCn*6BL);3_{0Y{s(fP>58Qm*IRZMG+|lU?%0Ji!zLW_VY^WXM3O&0Cwmw z0fXd}O#k>JnLm4e`%!z)H+a%0a_q6=u~QUPQPdVp=#=rne4xPECHF|4Ob_8;svA=< zaN7r%P8c7A41@b(cFAfdg{(J<9=CuF*IsA1Y&>$KF2P+HBhQ*SQx+{*th#HG^N$K* z9raN?CFjTI63~F?qz`^5373cS+G`FSs$($Ap!Mz_Pi@)g=M>b&$6QPq@p-sP?Xe*g z+N5!q2<9XF?F0t=q?8L}pHVhfvaw5_N1aryocWr*-RK1%=^=(j(wZ@ID}F<=gA!nK zmsH3zQ>Ma!qVHb)9DL}(vf+qzq^<#H>YYk85D*L&?Xz76xcLp;9b@O>wjpp%mko)9 z94HK>E`YgnXUo&j{nR}sTXx6IP+3`6{G*K7IzBo%uxOwMjpp4HgzO0})F@^8<_90j zq6I%`5dY8c;cLsTJM9FxpmcCUlqtq}rautv*1<_8(L3(jhK+OV$q(QH<>x=-r6bS- zkS}?qx}KW+*t712DssScJLCqXQ;MF<@JbImWB_ze6_OB1kjk-|y|AqG%UQFOW0hZ2 z6vad~0>ZY;QHFIMuZ1NtznLx1mtV#hw|KgDcRkqfvD-nB$Mr{ykP*LF%SH>|9bzB| z+agn@Opzta%Xy9imBy}^;qYij?bHJ}&}8jox75iQC;N8)oeut|1+!(^)K~TFzmfr? zyZN!Ckl^BsU`&taSDP@%abJ)mD+wRoZfzOQW9h~-F#Eo1Ps46 zp1Xu{C+@yzdHuotb^vk97zD6~-4e1AkXm~o+Vjg0R6y{~I9Xis?RLjH>E#3%5&&5I zlMZvRa5(V5{h^G9!}F?T-hT5<96m!^0yB-{moB_qWTlAMOs z^)hV{R~)0B2b7e^i3jX0TWzwDG-A_3kJKTaS?qfNY+Sl%uf+1hk=oU%u<6TzRsz6_ z6WY251AF86tRD{gI-PP)Z&!5RiFr{!Gs32Brk^R9#KQxWN)}haGwd=BfG4 z;@{|8paN7=GBb14Yd>w^pIP6TQX_fL&d+xihat%|%4t?S=kc{gFEVd^<_M~Lt+wc&XybO@#RKbroHXfSDX+x&ENK4cB*mQlho|H3MsS>Ter6r9 zsAy`e$9?PN(tvAZcGzKzTzJVv);D~I7+7I2aNMtcC420;t5iT_vk40FTB(2yL~h3B z1Y%G4WFf4RnZ#itfb*jiTV}5|<)v4p0k>zYNG1nb<@ele7xcGS{8JAA9~PD!yLRp< zkt=W6ac|$*vUM+HTMPc&1iAVBd)l5leOq=r|I=@LaJQ_vk@tl+OTUs*+53R~dpeUV z9Q=m*X8GX#cOmYM-A3$|6E_xL+SpvQ0INV$zq+n{Uxrj!aVprn5C~$_(P$3Nf)AJD z_SsEV@82JF?kofhmqW`!a0Vm_%Yl^&MjR`T>_tHz|Iglg0LWEb>B47ncTZH#8D$9p zB3nWt8%)L+;{X_AY;aoJ&)(ooc(MIEtlvI+@w1=3>oqL)l8j9dN(dnok%SP4C<&C~ zNSYZGW^UiQRi{o>oyw<9ovOp_2)py)7@2OP0DFGYofG44 z9H1@$NJ(O6y$Lj@M2_j~J(_Dd>BI-ialEtGZ$fGyA4hj~>_05eJWVM8_W#^xGs~9C zFTF^H59}i?4Na&HxX%G?-W0aWQUI(suILATOnvaaIEPciDDi0g7SIJ00uN7jrmxkI32{+4evD0K8Vb$shBeh7ld-Kp!jiMIwv26UsQ+!kUWU~ zw-zsc`e`x%r~`lu0Is*J6o6Ne;24?ADPUSs=I{|DC}25G_6;Y}krk4av*?#U|CwO` z6vPW-`S}IVEK1G)x@JsiDBAcDszbw&cw7Y5LN|1-y5j+EC?=srr6qFZm6vshF#>3x z0?S@nChu4CtGT_(T8w-h1!J z2Rn91PJT>xGFd;&xC5$@TuBu0XzB9^b@cgnn>B-okYF8uk%?AF-8EvaQ`Ojj+Z_hV z`Dab{)_+J7^X6YbnQu6f0M5gHDI1GS^f2=H;Gvom+*%izZlC~5)0QZ80i1&!{6pv; zTR?BSPA6+uVFDG=xEVeT${E1Tt=aO>6HiNhWx10w7E-R1_8lx&Tsl{;L?$zbk1>XY zY!jg9ZDJemmS;L?-c$1majC9o1)!-~^ zlRWVKe~}SG+dKDf0~-|4Z$yFPO@W~!N63%=_5Y|IfSSrmy}3dc$6y4QTFyF}R8M`? zpXv2~VjiFh0jqJtz=9L53^0|NLr0Fnj?PE0Ai?gGvjb_E05&HkH(s7RTPn6pT@3xY z15>6kW(=29wCTfz)B^>!2-5k7VKn1uD;EGf1_!1oC+(C#NnjHHna_SYxyFSd`Ovw| zf9^Rs3aObqXb?Br@;#dN%DR(5DRHPokANk6mK;5LRF2>&!127Zr;L-5Vy+F~Lt#sO zOkHALbwe4SdikcCz#=-59 z2ZXfGJU#C znJ`}RkSL=khY5voUS2|(9^++Rlpf8JhNf0|>gng!5N&bhpJOzw3BS3Q^w3CRhNx2>_Ph?Lr-T9 zQGECe2^X2JQGgYWk6}le${1TT?-_EbAhMEOTvS+;z;!0P0uVX?ZFlF7EPay+^?{>x zci*(o9U~*PQD&OXBMS6N3ViH_kIBt9-2{bzN2CSJEAHZCx(jY5a9%s$ahW`bV&>%K$b!X7 z&>kS_*~`qQK6?j5MGL^BLq4eoKu;Q{Ph_wsNe|a)ACk=>U8lny*8m+QvVDKseFl;Y zN7gakh0W_y+o&=dV!@Z-daQ|4Cj>xGg^!xrI$5-2F+{H0AQFtp0EuSD7fS%Gi2mB> zw*#pCVz5|x?9J2!A5+FGXa3bgFU}F%)6RA`e#|HtH*}B=(=GHzKoiz79#fO4{#Z2T z0eDYosjMAjq(tZ*neL+i>3RO4!!o;Kzb;v!15ev2>zAowgy4tsh4P@4GiEM$bD@w= za^v!cr{+V_lA`Zga(K5w&(&#i@kJj&n{U)bzLuSL+7>XG8xy<1&iFYNQ5j$!Zhxw) zsh7u|cp`ITE~KW&{h|f(?YKea5*+QURX!DDA~Z+#lrUKkh7AE=t;;6AtjVMaSSYe& z#cC-p-xt8kkC`)O$nc>W^M?Rqk0C%Tj z=M2jzEcWAA^tUCRdg>W604Nc_H<=%Xs8 zQe4otZ&5rJOW;0BG62wx;h&wH8e7es&!m@WC>A!!(|(%Pq8$YXJMMOH=c%-~7$T2z zEL8OFA_Y*6P%P924ls5Fz+Kj!6LdDE!e34@s8UFz9~hL^-f$gyA$;lLpM{exix<5t zJNFz!4MFw5LPmRgjx1^jLxXSgj~D9&7ym|qm1_Nm0<@t6`^ry#_OAw3!$dz31$s9H zzW1a1Wk|mwsjfOASvY4)xleOOm+5BRxAou2N5F|~gpC5NfiI{~$<57?Z98|!E34Nz z0Zzn==}I*-&OA#dLFsB+b3K@LSbSm;s#lfSECzxFK2nio?2?4X|LM1f;OoFUBt&;Q z^xhzoN6l?##i-fNSmQRTz8rNIVv2OVV*g&sFPJY7TUn zZ0l>Pch9-_s)Nt!i_c3G=Q%p=;+G?DBaRqVRaBz9o1o{R3{n8&C7z#a=0b#Y;58KN z{0ux`vU8`G!+TR}p1cAnK4UbXV~<|PLILhk+j9Nlk!{k%X)tuVG-EuVB1e{po{e=P zQ5TenmH_*I4GmoY&|&fRwmq_XCHsH+8mu^}FQ32THeBKk#zUVa!M?7wb6?yU#`ZPd)RD9IdQyuw?o&=}d;6d)Z}rq*Lpk$t;qh7Jxc7 z`>F1R610*DK&jGYOP433g)~f|Tr%fEq#JWr`hv!sry5lJ^~aJD{FoiwBp(YVuKuxO zii^`A1%Q7X1&EcDa#AUVx-(ojz+0N#{g>$=kjv_)9E+X6IqgtR{97(kxXVS@MM zpPmf@9(P*UJoq9;LO*rOEmB{16iVly_ka^n_7mvxJ!s~}=BT@};NkN{)LqDZ8SE~r zFTGtvsoifNtf8E)Np#7Pqa(5K4Bvrj*5cK>k%f+F+D#+C%% z)*gTfbqqlC=!e&Cyl8Baj2}dy!R)E1I*Ka+8^ClEnKp5P^yM)C9yiiOU~sYHuRHYN z;*00dwSU2`@fNNkVQf5J%#p3D6!6!)3v27;wA#ba1waV^P#Nn3ETm z=jSg1LMlcKnrpART5xn;8C!Po@1j*?E=D(C8fVu(P%xfrPuj=^(AxlVc_ko4ak1cE zx@uuzMP-#`0+0bMGEEm8)fQI7+EG8PEN&REjUWs#V9}mDs9}xvk1`1q@dV2{(9jg!R z#jOBGl~Kubb0Y3e#I-qISxtE!ORiI??u>!xi?vV2)ZO&_RP`$bddjlFOu<@!7Oh=AOHtc+=`UdS^O5ct1iMPZcz*)A(Z4 z@93xN6d)~0Zkk*a$!BmSAoVcBaIBJg9OzF*jO7jU+df*rC|9kmc$25Ia^qXF_3gI- z#f7g~=yUFM<8@a-VIMC11e|*vtHnfwj6sW4Ml&7AXFJL)h!2zm*)a;39 zSOiAS|UQ=q726_B{o`8rOhc1L=K=VV3>BI3-XQIaAymTOJ zXlRg{nxoREq)6`n=ldBVG7$w%G74zzmir%g0F#`A)E+skyRv2*hLbDSc+UjCeD011 zdQq%r@^R+3N?v$znFnwJZ0LGZOuVp2ZvXV{n3p!-AG^?w*rm?fQlO1P2%0pI89EbT zhDg)VJHwdLgJl?hdIaGf-;hyo7s#1Ey5E{+5iZmxPne(!bm|O`%c+ z4)z_Mke`NshMgVNKW>X5F=&3(FYp+@7dW&w+5daXOE5?ovT?uC30G@ ztG-~-i@L}T4R{e}nCmqCLDLKgt8po3B>v#aAYb|*LXD>>|m_)eFP?oUfM z#FR`p{RUpVTU*SLDV_zdMI9SFaDaT{JKy5`Hp!&ABmtcCpG{3I^7x-{^bF$=^=5F4 zG2;x!9%uY<>Y{C7Cex%%FQ$)88p@kR&;El>JOV%)=RrItK7GPCEYvtH)6rY~@Z;p2 zA$_^lPmb(ZLBQUiH}xn;^wZ50z%pOUtqgSj(!qD7UguIq7XbJ1bsp=BlklcGqI7K8 zqfGN4(elEIwQ>mhEzA_0g_wn_So_Om7tewQe9Vct@j}LeQ_qzl;Mn09Fj@uZVkc#O zUq#LMQNPfZ@*vGwU)Lc2^Vk!9s4m+%KRoZ8nP3{%E2F_I)ZNSm+OE3VK@%~OH9);z z3toH~Qcr9HLG#f|E|Gp^Wmp$r-OPO0jGE5Cd|Edf)Qj4>oRGI^kzUkXLqQQRI(*&<))o(y3z&^yf{cv zmqR0V|7)wObZ)W^;IeoqG_f zy)VFHSP3DP@|0U4^HlU7CvYHt$I6R#>NYPU z=7Xl^w!Y*9xS;Cc%pZL~rePSA*fqu~lj4sFXgt2~!t;D^k+riFc;od=^2U2uSb#GN z{i7%%&>H`ys4yU*F!+$Clb78p=btxI1`Zx%qJj2*j1ltw2is&N&j07;=PR5kP2zDL zKpT7pu})^-KqYC-STs=dKL&`O|NIw{pHGz%k%=g9a#J8HD_6excmGTBv+8l3QU%t< z(ALbwo3+<=T&Z3};cZ(Uzg8iupwU ztbMqfr=Cr=L$Qn_=`4JQhyw@%AE7}zg3xN9KPI>f4>umi;^LoYavSUG!91;#$)`_| zd%pC=(6~+o$HyOkRBu4Y%Y*J2jyXy@Qq4X|bPOYkOXM)mo^`WP+qWB=uMU{300Q~r zO#pSy)u5~oeargDq>;mPxP=rZK)D+XG5}l`bbJG;KI*~t*@|T(t`$r@tnEf0S&oeY zTxi%L?1T0CZPWz-$2_}34S&M;%bj*sVTR<6x0WR~Nx0$r#JojPd4N&?G;nKcmQkZe zLJ{OxWh9_y;G{}@9TaN!$Ka34@pNA%tp-Bd5fZ{UNKk=kqPf;kb!U7yu$;Haw$o``Svl;`&t?7XeU)oA+*`YOie5nb;S>*o5+}BkGg2d5~-}DG{K29 zHicdq&N=%$nQ+DE_8~_d#~TN(!BPrc9E9ChX7D9q8AIxJapEX2ddr2`|?1ng}z?~3> zb`qvuW?{gwlnj7s951589vSnvcuA&@KMhKG*}pi8e>V^2n-z(Q==iA89n>RA>oj5! z8JV7=fLp2{`zLn1Cw;4pV%Y3f>H^@rmLjUT(4CiMt>DXa}g=>(qU#X3}-cqL#>UGk#a z1nJB#7xC9#bFJj#=z?Bhs0($Zkag2p{oC~NZSAy?!NvN%3`ZhraNVxomuWa>=DErc z!=;1jcjV&a zNJYc|=vq0kry$pfm9KE`fkVYxwr%5?l){3-q5?E(X#QuH;TX?m+NiH`TaZlO+Bn#p zVWG%42X{m!v*ygj!ZlMY5nM6_4(#76i(YyOXT7*{g8cw2$U#;ZQ+T?AfJp)gpCe5& zVfu>f+i^A$U>*zj5EW$E^W}jQn~+o z|6n{nbiXF=-J30olkdp%3JRntiu=F+Ln$gQfDGmOvIy=b<1dl=C;q}*E2T@f_WvTA~oq!(GQYa^*2=5I24wQwD9W_Eq<1t*Z#tBhy>7#W>T}|DiuQK&z2( z!VMPvF@J|Y=wO%{9$F`5a4?K7`Y5GPzw@ysE9lT|>V*0U8=!9f^bw5=~|5Py(4BFT@!PNL;_T;>F_f z$|`#VfNL=}c8^r}h)l;6AkiX|Aq8~eVOj|3My``G09LJ9MsAdHq$s9>yW`PKIF%6< z*fy$;8%1*c^@X4pxq@sFK&U$)WB_3C2(5!MckV^-i%eH3@cg`aQUlRqDjQ`dsk`zZ zwUXTyi|!Qg3Y%8B=Bg_NWvv}{8#-6^KQAs_Dcg7MLkY2Jn8Aw(e5L3nPf_v*Wy#LD z9#{Ug)K|-Q|L(7)wD`lS$dMV=oV#>5c8kTS#5{I%5r$)@04Hc6W;iD1_k8|tsX1DK zx(+EBGStvIIFV0oBgq)-H#Do@7(jtEVsX7-(UOo@j-QjexJruh$}2xA1ECDC8U1rB zjsb9YF*S!tfqLSTM6-nJ3H-@8-MCy!03zHq0227{*9m`Ej;B$N_MiEp#`r}#f6bEr=ZfUnH+(d=X(%}JI<(%n+{)|&m2 z)7m5rWCnvXz_Q}zi&>O-WyUOOIA>hZqhMh4Y}&F{Hg4egcd`{|TBNX~P_DXsuH z86cdvptcd35?4r;eYkg)g+r#kg~+#|g6lj6S*wfF=gGeCmdW8f4yIRxZ+kj3-j1HMh+7Uwr$$1x^4^Ng6 zSlJk9>Kd!%@h6{_Mkr)tp_roc1~Goy;?#;k)F?kL{_C(9s;#b;v(7$OZvDh<^f}25 zyKS4>u4lzhv&^u#ly$&6k0G;oBB-uX;J&ZnW{aWyqz*>`vQYoo25igF{*IqIwJ|a1 zbO`NEr!YDp!mcJ2e!+XynpdQJf4KqdMZY6PkCOAxo(TznI!N|3U~UFsaX6| zI6d^=zn2;)k1a~%sYIa3H7SP2d9jW;)^MWGv)34#n6Q>%{MdCgV>7!Br9D)dG+n&uDG1|wvQCgq|H_NSxaZ@q4l(I0<``9VqUvP|unv`?us-y{e zU0NYQiNQc4AY;{E`YbEUY&GtbY{`~7NPp=0_m)~hAUGDlSPWlz*=*@k9CyYS%+JVO zu=5cjvvyh?qt5mt;19e=j~#y~X_ABECRmHgOG}r^At<)(oUu6zhMG0|B8lN71C`wR zX{1mLqQRV0vO?Pg4T03=^Hk=u>p)bvcLYo{g| z+Rt?oM~Rh-bW;WZ&Wsl>UbvVy02Jqz78im`Re<$Br1G+uu&@ z{8Pf83=SN?kG(#I)2}o6w`nkC*qM+KY|l`jAWjE=sB_nW1F{s?AoWIb%Z)eU7yy!vNvZBc>I}=5CuYmdRqx0_Fm;_w zlLijQ5boMBeulFTsihPH9a}N+Z@{^4>P%??gXP|PzbLbC`@*Senn9X}{_x)_4qOn& zNkoiOq?@}7W*bj3f&A`WEu>_eo&ENwAvA1w!gN81y1<^JqVlNL4#)@#w1ugnHr@t6 z!-b+}2ygzlsI#@JRTp?|pkG{mJZiLHU8>`-S&n+4Bipf4AW)hp`_JrqUuw{vx$MQ# z8jJu!z{6iJ949*3@VxpH>L28VRU72M-n|He#XssjpW&lM$=PR3$7T+0j)TOQVE}k( zx2Wyg^XbTjaR>>>h4eul&ff1nSPo^hOFIe_;-4DxnP;3OV^C+I*4yMg1;pf>wO8qY zPr9%jP%5tCNR6yswOalB5srpp#PO$%);TJB1zpSd08*l{7{WYkxc2-kjPq^})brDv zB!%>Saxn#iqkm8YdRPPu002M$Nkl*R>EEX#PjnOCtBKCc?+AB0sxo8lLYW# zh^%QS;O`yNfJDwd3fP)Tx{(2pzxC}m6FbTek^xWzlgQ7@&&LUX9MoCu%4}F~3Ts`G z-$d;*Uh6Q9{8l$MVsv*{JPLM?c`eFim(1}6jI5odz+;a+3GROr&JSYYj$&|0O&cc% zoOBxE%Yd?Cx#G%8bf2b7e!<_CJJ1A-3@a3v}sa*^e}d5xI2z%4QE!436zlYVHrxiMTI>pnE*Tn z@a%#Yq>0M*PN)f_SW|NDg|p<0sZ-73AL(l&v(NSm?7;OQXlmH5WhJSecM+{u3Y|!W z21*T?l%fDaCa>KjCVS$W^y5t_brAWlfda8cFaXXy{~Y7vA+*NL79%wOk27-d{$m!WmHaeLahF@bb!z{8>1t}0R+RQNkF!3=bnlNs(#PH^B zZP?sPn;*w^5P^c%tyn}Qz$|zZSNr~c@DPoH?Ec8~3F6_^uUo?Fo9RXMr>wgkc-{RFw;bbuh>t;r9#KzUekiCi{k4tDC12R#qL zd;*Ax<=gpWF#UAg_>Ea8jsfJzvX@uki|i}E$&)!BIS=C{bcHz^Q=}h*dnWkhxwm8M zLXdW_wd2K+pY+G73X2`CuVOJM_>k=Qk(vXESC+4&6o6k6z!Xe77$Yx_0#Og=(V`<8 zn<5|fDCh#HdQ@Ud7A;`qE0X+JVPQNGgOmys(DM2})A7I$#7`w~EH(Vx5X zZdw2B|Aj>>mKeD$Dx%EvvkTB}!1rcQ>qdK5j0x|fAsu?!q-joRoHI|29|0$9(m{|?bv9@{!KO%rJHPyZioKFBLVKN-h@2TL8!J^b`@NE6~@ zeA0UKE}DAPnF|6tjJ9D6t=FAd9Q@WZ+RXk?)Qz&@LaC{&(M)I-`Qssh)oF`x<8dT1 z0rH?p{x3rY$QSo z5*8yj{2{j?3tn0+H{Eo#te9LTpYYaXR$? zJU)nleK?s-c{pzJ`Wvsymd#sb>XeBgxWb)Scf-sjmt7%$dTu@>3o4aitYabp0nh%= z8m57Dm$|1ik0oGTEbBMEB?tHIlL3RrSWvuMW%h*^%J2X9Cpmn$Lc=heNrECq+exJh zW7Dat0rcVPUaQHK#+HKE_%R_^O>w~I=?hdEEVJ@+;-#^cx+C%5{rY!_3ue#a8jO+v z90<4@z=tV!QNSLV5DM5f7~&NVz|BOoO-D|LHY*)@(_{nUu(Mg#s&#zh3PRj@2H?^OW%Ug>b ztlFw-x%tMA$<)b{8O2Ftyu4+a=%ZDYvhj^K2;dO9B*RMx2a%h3c!+;k9vn38OygsERUp~xhZn{C(P7FS2XGfp8Y zg(|v2|4Hc=?St-jXxVr(PzSZ0S>0CKp|Bmb|xZx8SM)C}cGfBF5kRdTko6V#z`+ofxI-dXB9op1 z$+aLoB?BLiEKlw`FWJ$5zZ%9t>U;o|TLb4-AC1J??Vf!(? zChAsFvDnsQad6R{i(LZv0eve{Z7m~DZxlYej*{4&Yg`b>Z&9Q{R5ePY!9a8 zSomN&vaZLThale0;LAKDAw6i%mOeIHjveJwae-D`{OdJg-1UbfMMFcqG(qe8x4!W; zx$&y2(?@g)_%z`h^sj&W|DZT5UlK)qb$g6slPL$UG}6-A1L>92>L_{aD>xtGO#oaj zu`i(bKV`Jz7*9?cJpzk*N<@GrAcCoTcG-u(aMy*Uu_|mnj-FNF7)4}ymI9ne+j<#m zYLgqwcS?nh0W2#N2&T|H?iFTqoM32{8~&;xG5GxaC72g;?FUCHW=7dwJnKR!Mm=cJ z9R>Ek?0ds;hQ*WYrad>%6F2odTS@(|UAZ;5KEsBMz|QfcAZYZJF$H#P z-ys_|y$J>Y7ZP9qIAw-$LMQ5c(@-QW56?Jg_Z~2yA6y840R`6qau@qmTt%bdSuz?1 zZ{kvXEG=W~WP?DB(EQ)pR4-rr@|R@7`0*B`_rEPsI!GyZ>6W~>SiW%Yy>bI?Hu%{u zekE_bwHb^Oh^s+SRiY4!`b04}*BC^hd?^>ZzMV8?eZ#KUti(DKP99KLh; zjdww!Q1asteJv@KY-qZzMDu)o$1ZvJ@yF!rU%5}de)m1{H1s=E96sDmYQ6Gl2PpD7 z1`4F3@M)8#$R!uelDevcSRjLGkIB5YAE(`+0fod0H>Yh;3==1=WXrZ~vSHn8A+X2O zDJ-ox$(1;r$T2Nnj1)$y#f=bET047cthYKK#a;4ANI2 zS$XK-J^}^IY$&eo56MU#ThQPPLI|fHS35rKI}rG|e58+Aeei=8%#@J#kLfl!eYzG) zY*@XTi+?f!A_f46&>0gk05YQ%n;&UqcmH+a|DV786?gyRIk7^t*?jP%Ac$ia0NPx& zZ94dEC3ZeLd?`GkED|;V+UbEO3mx2~iIb(cm`R9CS12&=xo4#U3WW6RKM=5Iv7Ibw zO;Q{BXGsyn|IeN|Lkg&W1V)vSVGwwE`D(?%i5CYf<*I5x+q75}jB$J+4%~t(tg2<` z@L}@#yYIH%C&$|vFw!NqxuqWWv(J}XZu^wn`GtGs`9(`Hd2hiLU45mbqz~v9$AX{N zE#iS99V*kwhVn(#?2a8A{nniv&=cZbJm01}I~cgQ;L}MiC%QTS8yHUdsF@dhILV49 zq!G_vObQpSTp|DX-S5ihZ@o?a@T>ojN<9OYF6EK?iJ-t&zw$NQU_yx)oa4m~kal-k z$85X7TiX*;XWeLbj)y=Bpcc|Je|~yC9Z#@v6Y8d8`sruLOlWv+f=&xgRyjX4`LKCm z81^;h?FQ^Ni;c&RFwD*GA8vO;Iemcp9S+`gS6|=Tf=O)?c6b}1Ae2Xl&Vas~2Y>N1 zOz1iPJ9W(-EOvhK^Pgb{vQW?eHtUhJ@S4^z z3xHH{@3c+Ej2tPfG8I&V>2i?A4#deJfID8uYHGWW%=?~0E5DVG$$q!5{E?L z1=#s!sbkHEv4t!}7B>r>#wmRB5%O!{LcD-U=PHmEsf8Ynr=RJ<_@ns>;vPR?qFyuI z(#-2)k!~bd=cPU{2Gg|w^Fc0BhH-g$p*Qd}i42;WBOV1h_!25<`T< zZNW(+HSJ~J!W^f!X`e_q{&>V1^F=iDEN;do<}F-gB>=b>qFW>ZkYTylRZ527rz{wo zf@J`31sF#oDg4X4`K)lU!bH3P>wYNzUtcGGdFDBs8Kn3>`U{7yM4+@L**Rd~or7L@*r5J$#>~?V zEP7quJIo!a7hily8YyCii3eY%boi!WNhvn9QEGtN!0WL1ZveOZrWP|Hf($wJNiXZiiLlyuo#17KF-YH;i4alf2hhdi+=RuoVc0YD~Dtx zn;J~x)K2<(bkso)?sm}gaOSBPEIIR(>S!w}mTE{8yuNJ*&I|uQ?!56PdHBI!N%fIR z8$t9wt`yJ)5{Pl^q{;G`TRtI8kci3BbC2)_m_llir-rd@3Ufa&@1P72^=J8-RdTqz zJxPy1v_0|>Hm9QW&~0Fb@tW2wh-|a{b7H3^ENjSlsB?xX&OEH!$U4MKNEcdX}Q7sa-!&)K4I#^vDZnOW(D6(Sp2u3@4z6UK4cDaxEM5e zkPPgL@l_dbrd`>EKf`(tG4hZf-oSy;NuC_SYkslMqd0Ahiaz@3MhdVD>>}jj)qAB$ zi+~IO6dMMAQr;Q*HC9_@a5d!z2Uh#0mMnQ`#X4!i+{zeY&6dk9o+SfuoY2hifQ|4D zMEfts>;Y>!+yG|bosEQX9>dLtWviAuO7>i!E?HubXq=6kO|ajjiYYNzXxlc(m~y7X zGbS%KN4O0DbXdL;41l&e4+yp#td%b~Zzkqkm}{ZU=mr-EXG0R$wfi#bx&^Q~AHk%A zL8x21I5I?~Nn+dQgC!OO05E|pR0hDy%U6N{&}t(K>YFe4pzK<)`(Jmo3L5R>a`#=I%Pg_I9IC3i zO8y3V3_gC_XJo~y_0ZBv?Z+%Y^A6dki3%e zgNNmnPK#ltpg$%DrX|dLWcDl>0cC*P)oR5#RZ2Q&zW{Kjdk72GWw-9Cxq+t1hMFdS zp$T;o(V4WkupkPRf;QDR;<~>Ysl?8IKj;E^{E5e9X#YW>(Va?;+dtSLPyG2WQj8l> zI1kXapjFSMppHl0*U3vA%@&exMg~}%Ou2Ovb&@y6G;_shmy#Hp+rR`Ugd(^R7_-|@ zmneNhEOvahZ#b&q_)IM%RQMZf&6W*=ilrRl-4rD^`^FYBdLJtV{PmKPYHQA@ZG%iJtL z1}r@l%D(k8%?}Y7I6na6HcuAj8o6>{|9;u9p_9kZOa^>tO+0finhV_pF>HP~aU*`F zUyUyj+V%2>3>4BTKr98(uIrN z3I}Nv^6RMiA5F+#fiq}Oj%RYu^x+RvLwOzE>0B4`4@mI3$3quBLKWOEm*STWvRxX#q)>Q+gAh}Ty%hO zkg<5-gSm-W#5Plz6?QCcz3o~1`i)8S6y+LG-C26 zRu_O4Xn5?}o>+C@&Gt#gPb0K0;j-2TCE>i+2pGz;k2wAH|UqdM%qYJ=v3{F}# zQ(n50K0S{NZ44jwWEk85(?;dAsSHEfNPQTRoO~q<{P5In!2H0Dz7%EIP+JF)|0+3( zGyf$xHuSrPelIg7P0BD4A7-#N9Id)ruSzW`DZ`jlfT0CURx+{iu0QLd8W3_{vb&qRFaG#_KQfnU+Malq` zG~N6|68nuUk{npBt33i0cF@=lsZ5UWsLWa>{xCYnb{sexpzbE#^l%i?LMvCjg8ric z!GH{70mp{R=3JmRWpR^-HwfesAtbr<9$S4%P_b*WG_y{a1)x z>CfMFmj`eX+72B#C|~~C*X46}-Xq&~?2^6%2B{YRTzkIXS@;|JnhcT-$(_676XLGG z#@q4rTwvpeA~Zvo`GlQ}7L*5P4SaI~KphVp^e7QffV-h%{Rc=}-vP2@#WK0$)?4L= zfAddz#@LN4vK)5`+OJVI^k8b&S*X*w5qe)>e}gk9Bhr=1X4^VCttY z+m^5Ab23df5Gu(R1Zcr|TAumm&VNnKQN8Z--+%Q>)#x1vdWwEL@#LRmJ^2@US;K3x=&VS~`5^^?_0YVN!13R(d+oXrT zo{b<(yLq^~--uw#US0(^whL>rC7xf1TLz{86ad;7JUWEt)SASqe~Q4&kU$ zQ>A$)0{|SN7%pCiP&Rk{3!neXe0M_@-`uQ4wLlx!d}fx0QxPg@^f(nskWrpl@3`(( zjuZB8idL`Xxf2`#C@RgTKnl3;XrEd=oyM6F-Vh+C(g#Fz*vYZYwn{EI*>MbPXP$j- zNZ80J6AG+dxe7{SD!^$5tx;y$`M_$Y1o}qD!NH>_mM7=p?0;#WQXN3p36nFue*K%W zecLYVZexUw<>reokvG?GOin=b-6IsRz?B_x{=WsZo?kcE!qNNNm@kqfGW&djRw8u}>x=Cz2od;J82m8%$_Qcqh_S61 zi~okYda1(Q+BFdS&&S02M?d|M-1xDplCYhcpOux>^1zROj6N3Aj-oPUJd@H~5+!$m zhC)i|UPr9%NtK6%cf6av!gIr!FyuQ|1`Qo5F{~5x3^-i8dI?zf!wH9asMN$KPYmo* z3FBTzz7K7JxLh4u+aQ-6+#?NbIp`)^Rk{*|hdCFDOUsAZH|4|*gyp4u5A2kV-M*(> z-hT6Kh0zWM4WkrS3!it+8Q7`ET+LyaQF2U+Kd^J-)B{G5)*uuc8$@2ktH18`u>)}Lsj)E0T7QT@~M*&4O9Cl_@jop%``oBtBila|My{4)j?6ecE^G#m^p&CP_=BE%UoDF2(u!2=z! z!l0C!u|v7|g{4x*vxWX1QUSDsvIw-?ROuH6rv}_x&uPg$(EOiV@@{}$Zh7pFf0R#r z<_>x1-OWxXOkaE+oa{LP}Dj1$`e3^g!N!=i33rENw@H2YY$$W?<*9`ZFyUq;=ruM1l{t0dk?lk@v{w<7{yZlf+kXEZ~+Ku9e^X?+399>2b!B zQxbi52L;>`3n2USop;Dkv^QS))*4(lSsa7dj0m~bbBzcVFIdo$D50PD*da2=zlF3 z0AwJ3{|DcfyFUBrWE7|D_rL!1Uu6rg{e$SgE@pH))9F_&VO{E^H~tXU9w!Tv z6p}1|>>YSJph2isFxPprP7t}tY+sB|l$7KcZ!@o<@yWM+XyXKqPc9kP7$^2|!3c9A zNu~)n1%9xprgI}^^^TgoV8EbcP_C4k)bjGvz$rUZdK~8gf%4_JUf0+rFTA`4^JFku zS>`B5T+SsIo;?E+X3z%&H@jH&P?{M0zn-6C<6=3*5I^b+7y#LrOEhABK7amVM;}EB za61mExF<}QBxA>nLKtYKptlph2Z@N)aE;*1uN+FTuJ1i?Sl)e$jF8}5W;__tg9i3f znt{A9#7JBYKGLj*jFR$plX#MW$m4u;y{?rV;=fSFhskzlxk-HxUc~vYWk@lUmhMU2K$|kALA0UwU|+ zXr3ID%-}R~>}cFa-^Zee-kqSp-o3k^B;|GNqHt1xMFR&KH5eE`VBO3|XUnN+0Tzjq zCykR4qlPOi;W@}0I8ZLHz5b>&qu5&!hB^R@$g9QW6`wtq+3F|&JLkM}AOUbeCsN!y zKwEHb`GE%>ko&&zEvc%imQsk&CpZxW=Q|$^02TcQZJ-L%EV$Dp+l*#tE})g#JwOiR z;g1=f{KJ43%Gg{#Sah<-#=^%jx=2KPri5^e25BXCU3CQDYRCA1{!)>ZC;$AwKgqqH zyG!=$-fM$K@8d*)e*FhRYx8Vrsjo!U;5m6S`C;GZ6r5HB))5-hN=-4ds9C0)H@_=y zp?#lNzG~rfCy#jvaaq%uxU^bORGhg!9!|f7tG{d%aXUSi`dr4B#Mg@i7b^ zI@rf?!poEz-0cLMS?A)dnP>j%&CdVPiX$k;EV=)C|14j)^Ro;V8Bk#Ls#WsyUq2|g zZ%7Jol!*)y_H$-o8z48JN&FLNY}qHh?WaFwolmK)WCjcS2CYM2AbG~ZVD}e#U#M_b z#h!IdV4yhC7-M1C^o%PH9$}=Hs$6r#GMXfNly`yhxw-opg_M+ z-_RosQ3k_^_!BvZht0m;tMHz{ByDq6!yC(8hcFIljF zyZ?A{Y0Mt%1R9F8wF1RNvf}CoHiX?tnwVf6etOy+QG?Eppici_CQ_umom6E>872ukPy2}q(Smc!U;XOW<=_7O|6(^KAp-^t)y2Q=`a{b< zMg7THXU0ehKBSFjNDuRAOSUt#>xOjDZe28)=7*yQ>H(D%=HRNr zn3N2FG((^MGH>AmxfNFiE?)eih7A8PWgPAoxn@RzR2ts?**k^GecMpylxYSKZ?bN% zGE^WWKC~>M)1$7bMdmMfA+zXv6e^HHH>PW^x)RsgRcijSU4>MRH!7;uRbNn_)y2^r zmSe`74uRj@<_8H@XZ+a3Kl4d_DthK041hXFM^#r7vgr``-p8?<@N?a$}je zMnCw&`5Hp1?gloIY}vWHTwZx)1IiqWU(DNhyeJN)*|}#;#|8zi2u3L0#A4UIHeMHZ zz!HenNrUIc>coc|LsSMx-B4>+t_fi1@FTfSI+U1o;cO|zEguj`Q5T(8+Nf>ff!r_w z;NfJ1Y01LPRBK*+U7B&-we`RZR_*7X3x(F$xS>1I`18-UL1S?kELKpE8!O?$KmPQ*g_Z%p%vmXbhzZ~kGsPA$08&U2>cHB^ z3YFi|){2ud9)p zZvB)z^vI)9TwErFrTwKKk$~7gH0xtvA`Ns9M4oM}H4G4&fBSaw1PEI`|eLt}eBj@WG7y+^+E`Mc%8|MMU)g(Z|2!rV<7 zqTjAjVECA^IErxtM0y(}8weIrW}6_9^y)0EPAX~inskzQrNPS88N zeq3_-v!;nBjkBp7-4HvgRFg6QSmPYyM9M{3e<(~O6I%^|wPre> zco|Us;&Cc1Uo}`nMdtWXz#+Xxcsuj}6hR*d7q^tgpwYUH@*`ZR;W&V&0iyUXE?bVi zr4G`W=IT|3ojzrp>U82UK-0IHk|!>$7NJPnanyw12gtbu$is~sO*j_1^hL}uaW!Sv zjmhDd3FBqNFzB$r980f`O^cB5=HWQVjc-ae?tntvEn7d(Zw57fZ8CMr1Su`X9FDt) z$~f`pKsfZ$e9;m#$=`lo`nBiphdma%lT{q+1TQBS7k%XC7ZyW%Fc<&}7rhv7X|1IM z02u&myp{>TP>~6tK*RtDAqpoc?xfSSEYb#KC9MppXm-L+3PZF)y{91VBcRFKpjMoSHm7!lWI>~P(9 zJ#t{f+X{l9y!2&|%Pb*%!M{ad4L@eu{kKQ65NA=Dp9PTAlQ1N{-NB zPv+(7g%1y7u;bS@QH{?D?HRC?2eyBjROTuj2Y?cFOYa2?c% zHmNCP|2GE=wS$eex1}^U=04JQ=*XPQB{{3_TWt}u?1T<0< z2A*#AQ5!xQv0EIO<4XaS0R#YKD6aIotb8wa@bM1^&c|8SIAMtK08schQ*PRgAKiIP za}#a@SpUWr*}r=?!hpe#ah#F?BS#LGkwXV#oZwh*#((C{-~000^D$rHzr@TG!8mgW zUVBU>$m`axlbySE8^2@t%`J$_EM*c6LDwoL3a<>m8)NsimDn6AXo%r5~hQP78N`L{fsF$RZxV9 zZb@N6Zo2V?&L;VU!B^Mfwt|Z;k+rMV$-rSFB$g;qE&tiv^#>tz;h&T`Zz4*Mr26lE z8af%uDfL{cnITn6#lvjL>bZDiwtoJ%A1=wRU@Syh%#RZ{%hn)4dsa?<9(E%WCB;(I zw~zek+2`bz>u!?$`^%FsdU!%SBBw4=fHwln{m6yVTvsJo&^=*xDSXJZtbE^E*G&85 zSn9xa|At-ysiSgRU z7ek1X&lgrUB?rhd(=~e-zo`6>3xD2-QjaT9sqn9FS(!Za@bBg0*IgULbBcdeRveYD zf9Km!Y}O*hrF~RvS2r(7VzdE1?jj_NRxAS=?eC1W$9`>GNfD%RPJ*(ZolGc^g9aSC zTd?z*l2IMYv%mo8k7bWXsbDOmA*w4J^$lVXl1~$NNF#GxDZnzYRR4JO0lle7XJ;r& zmWdItBoxcbmZ>Q@y0L6It2k0sCu`Sk#P|VGf0ZQ5k^~t0)2B|*TY$(cHl=K8L&}gs zWUjF_#=eb*e%XMS5OHG!GU-x&q(U~V>z*5h&N%Z7DF9P}HyA17!^t6E8nu%oFJ%sG zwC?A~#y8&qmHfwJkj9+2TyXvc8Xnh|Ia^YsT6$x!ruTP!G6r-*WNdL!qvHb$fP&ZH zx!?>o!~i6_kPH6!yl3X?aRAu4PV*lH$T*%;2{2*+q%Uo@2R_+gz_CwK2EgL^P6fmD z6IlNj;LIYF#)2UQ0TR=u2ZY4!R`eE;S}jbVgm5b8&tLYp}?4 zk^)pDvtaRy(ga%Ospu1mh@Cd^py+Hd@XZ~^!Tri)3I+>kcyRo1zbcf|EXDn*4Zy+k z0H%}-4zK$(t|obyoHS$jYN(gJBSwxz=>8r~Zrt2V?84Z;-@%TUyz0q?PXp1cDd8!;CLTinkv}il=c+RtX=S+oesWaP1=L9iINW=XBYcFt&|mhqX3Of=-jc=!^sxxbqhY)a;OrSwrI3o_QNR`#`&rvp z*;!0#rkzTX>fyMUu_p@~N4SVUUV3Rcj)}5g?~HL%JY~_qj7?N--pgxRIk$0NPf9h=U>ABps8bBb@E~qnrT1GUkHI zkpjRpEiW%oT1*B&!Bda^#m@ksTXYPdof7l6w|2lzSufHj2Y#K-w^nv;fXb z+Yw5`B5wbdABz}_1dcwaAae7qw>q5G^l9u3KHT}AJ$tUa_s(V+HgY6H{u4U!RE{}D z3q3#AnS|QvOge`dI@gWK_FP(pyC>hd5>9t!R!RSJF4u}|DR4)M9xH8^~ zph#*b2GX0Z|ClU$aXC>$rrRixT7VO#PLq#bdMTzoXl15oI0Z6At=}dLe>o|jVfV}O z%z+}gio+GMd};42C-y(rU3(1}O~@UtJLHvBW|zw3Lw_}6zHeHso0H!p-%fwF(>y|P zwHK}8(#B)bseLI61`ikh6wz+R&Oe`e9EYmHng1!c5nD7c!lU}0sGN*#-1h#X`DNQIU`tBGfLWp8tecMg6@PE z#_TpopSYa`dU~dhV>d(EzD37!^dbCi2E8M5d@10svLz^>=|>Nv9I~*7Z8l<<8Z#p{ zi$SepC@&qk@zyQ~j%_H&54P=*J$nye3(u4zeJ4#AEn_jS57KSvr1^Iu(LUE1BuJ4~=&33% zuaI~2IzT_9;CSm$nTT5vm}U*4FAuSWwr@t^xAXgi8l)R(*JA+Ku*rq44~qL2EL;6@ ztiG+qG65_DfE&1$WG6J)y)E*nhYK$eRuv7XdgF@R4!ViWO3W zb7fZhKX(M!Cvc%>`UZBcW>6`C7}uHL6c-hh=nX8?zd-N}&>v8Fv{ok@dSn1@_9%fV zb;fI8b)o?VKr1HqqsEMt^Uptzp1s;MVrTBU>u;8KHgA=oBS%9i9yAtk=N~)b9+`YZ z)!gvgy4g29lsM*1d$Qr^8{(V{rr(51enJ659BnwKRhqO~N4}k8=-|q4!*t{5!i6LU zb1IU`hP?#G0jeNs`nfyql&2p5i-wJUj-3LA?UIV;3tzlf3S(flq3@$E08=)paMnc` zH~8RLOvp^ibMqEjuU_~5=;c?+h!MjzUoF_)(r(^u5T;)GTT@B~+VxC z$XzZOHhh%iW5<#UQ53aAxuEODFxo zG}yQQ5N>RGQ~Ow5>|-8RTpX9P&%`ZSIO@lerVc36l59n8Z|3g61egF?TR2_Nhdb(= zHyp%O(QjeH=~xZ(efC*rfMEf81k%dfo5zeDHvM#i4-%4txmyFo5!S4I4KBg!04d>k z=8S2ILMaU-fov%j=f_6ncROzO}|q2mD?i1n~PFT1Sp0u4q8ED@uo>}vp~JE81&18@Hd_Aq)+%l?ppp)(q_(2c+1f!B z-9|B>p}!vC0^Sxwz%ktiarmEfaCA>WOb7cIB$_1e$CNEV*tDtPHy7gzCk@mK(11Gf zjlcSaJo3;V+lw{w%a8&}_7KjBUJMz7>5+le2RS=g1s)3ZO4UfqcATzx1s`AA%hS z|8;<=D>~L6%Xowx@Qpz6%KF#Q|1^M!jALY&i*f#U#_1EJC|-a$9I&{HYE%9un_k@s zW;?8oq8?diym@?(bEkS7Jy?O;Xw31hAeQuB7LkQ8={N%tQl+@2IFH-~^r^}Uk-$(`kAIkuUBmh8+FcYmN!>D3f3R?>+4tM`m?0?=ff95-u6HDOK zNp3!_(XitGgte^;)^<#W4T$ZUp!$X=4=1!!&|o9ec?ujThvxp*jrM=86*vjdNw$`S zAvC*A_91yVLp*Nm2pNJK064iYllNBH4^gr=aVDNYsE|ds5nwmnt*_`UPIT05nAjJf z+-|t;8b;b{O)GZjzWlXs%F>mqWa#h_5H~FV_c&iU*-9^W!SB?`Ku(xpK28(R{09KM z4+q4+d(IYv=h98KWW;X#VfcMz==0X{MZGZ608qq(0lugNyGf7=`0n5Sz5LgI{I?G} zvUZ&U;T7jrTnX6FPy@7Jrs1r-e?jbz%Y-%sPR3OhM1GGpG|CfCoOnr%bc>Yhal^*o z!9$FMgI)>9iJ3D63nz!RG=IK((w-&>8l|=te|rM32k(geqyKBh;=cwv+SL_@F;Q-n zAN|}hEmn*e^?B7SWNbzQ%NFUW}Z1+aJMM@*{5LO0KnSyZ^)s;?97RPzvz7! zP|MwxDc-~hcg{1Y;rq-pPM3+3P9tEiG|jC|@;Bf8dwJ^5Ps@OT7-VqwF-88#$u>Q! z=2X+p;|^q$nA|e+N&vi_{M*muoYLC?@xmCObU~6)bm`lT(+M3K86E9|0A@(>qN5Z3 zD2k^HB8d0#CWJV&xHp5j^AG>(lSHQo0{>2xiqT*O4VQv)NZW%gasLY&ky5v9#pc%VVde?Ib2~5t? zMxFwn7P3vL#g z-UJ|>E;nSfLmKzQ3lr@j%ciDN-zP6!29dbUJwKDtv{Fn7xW z15p>iC>NHDHy1JXC%11Of$r%ezj@mA)VACJwfQ+w=Z+P9ev zAvIc8kS?YZ?KT&R=|9-EP2S%0cG|F+dc=!MWcsw}l8b1}QazYDua=T7{4@On@iRTO z=!ZAHz6oEf*)*gqbc*3h+OeZY;c7)tfb9a039VMZSc)->A1lAp7%wqt04&zD-tY+K zFBQ_J1?h$rePb4uW9Tg7TAB8X5ID2&Ng?u0~MNd z(EQRuV?UU2C-c@x{hc45x8M1=+LO}|md>yQIoYyVWeX=0fX4txAISblAtV9X!0rEs z@BV{4_J=3KutaX1pg>sB+>FIPCufuzLLcNono8(*>4~H>zT_u49@`w;c_19jq|phC zS1TkmsO*oT)(z0|Uvc=b)Z@JWC9^M(7Zxs%IkRUaqm8~D3RE1flG||eMRj$Zl$DlB zA+DdLw2{9b_Uc7A+1xS;_wHWTb`sN$SNEpDVq;rJzj57@U;qF>07*naRGiLmEa$O4 zj>BwA=FdRe_Gaq8X=#=bzykx3IerxI*Bd{J@GQ)~23MC$75YcL4S;*cL97~4@{bdLiZkQp1`3!? zM>%g?wi;=)3-4s;%)h7O7=X?{tTd1Yb(ZV?q$@>`e*HK3$jZ)x9)MR7!mkGaOp2TW znL2ecx^zejD5wJuNV@f6!yM}a3h*Dxux=?JrzA{|--dC+kNgF6@ict|HI z*+~=^>V47Q`Oe?Syk~o_(z%Xu-W&Xt<{=6B=rz~Ka4ez8~dy|b7l01VGwWSb%&TJa$?E;$a!oSvox`x zj{U9V3r$fFH&bn!frc^3Gs$$qv;XAczXfGOWq-90`L8T5muy^f_|^OFk;ne@xC|R| za$JEL%>D8CVzGbWEBDEJTee6sn2IG>tWxKQ)xW|yE@^MxwkD{Hx*MBGtKZ%|sV7GJ zbPqf3@)TaQhK`nAV7(a$002-2Kr?6_F#wLK za5^2>D;pEkmQ5bo#}!qFr3I2wS=ne8ZYi2E*}|!0r{iZ!nr-Wy_qNLs%;C)346&eI zoHll(jD!SM4sVA*be0k1qNbZBLw!wxQhv>x$fVn*9`HK3q!PY-%spR&^6AEuknKB| znEfylCr^|z+(cxqur~f0qm34Cn+Mix)@D|J&Q+BL?|vZFRg{DSLDVo{mYhC)x=P4U z&yg8Row(txsEOAJK?mib*zaR_yExO}XwToYTpLj9fKdckpf>@;ckSMjIJj#M*K7VG z0Gb^eDta3VL=1rNn!w6J+PD$`YnHEMWzq_TT|jP;62|mEuy~cSovPc87f?9H0Yd9- z!`c6AydiENGp0=qoL92Y(Yt*LY<+LD>@Por(u8O`hYl2*PN(do3c*>BEWgr3fy|gb zSrX8GuLecI^OsxS-!A2cssLfG2Qr%fO^MrI?QH=;r83$PL*wv_8D~mAh#vMvvuE!< z`SLfvC6(2+QUoPQWRQ3}PELxQF0oCfG<-V`U+7;_NiXiguBn8U)^_Q!zyc~DOLy-{#-^p0JADk1iWJ7V(TWv6^lrz?hVHhvwhctc zdYH-pU^Gq*2IHSP04M=S%ISEfUM96R&3yAQN&vwGAd1MGFbcT+4+`bRLwf|<`IuXA z0SieEpHwB`%pAos%9v!#*5E;=z%D4%eDkf%%A_O&;Y*5QGG)RTbJUUJg{@2sYU_>K zlYiV)`8<4XurgjU@5rcl|AY5sC(ht^%Ge6&3p8lZ5E(yVEbvl`0SxDvT3aoUlLj|0 z%!NTjpYlVMvUT(OutAZ6&x{>CTKa&oKzfk|4)4N1C`y|czOCJdpvSE3$7Os?p7G0r z*g5$Q0{}@%tXreU066e)4adz~0}M-KJPK%C@;)P*j{=kzqXYmeQ37|!I}!j{d6fOf z*?+ck)GG7q3)dy9L1rtx(-7Gy;lDIqg!@aQ^Z#8T?AorJra@t+$&(Of-V#t?dt!Zz3%|9>!{8?Ti^E8?n*0ZRd!V@S(XdN#(-&t z011Q+p#%~VNJ5Q60uBKZ5+MAcm`Py$@OK|M>VIK;tNYMW!DQL|}DkCyM&R zMCIAfyheMTndJLS#u*!%kn7&_ZrQVEpVafLDbB+2nmeunldD-Y-^ATPm`BUNFA+Ej z7RhYrW-?6mw|?3x1I_I%uTgc%WAqjsid(sMKPirx%)kU;lYuhvHj)OmHIqvD}liP0nwTm%l?;vZj9J~Z& zTef5g#%8$gkWvm&&=s_brgf8WYiNb{Tv}X%o}(8c|9X1{@nGS z!1b7Y*kL+##x!1sUrQ~CRUqEf_^Duk@8tWqVj7+;va zO2IkQcYNU9As$zg=^-IZ{fSGd{#M@b+bD|!L+*p&+?NB!{ z@^fHf>b|LZqa|Viz#;G<3-+@~0oDK)VjhP+{PBI=dIJxuh3$d;DJQI^zcQ_WV>5L# z44XIn4P(4>AM|Tb%1ZlHyvwmWzj6Ia!ChYXmZPod!h$Io)8ubj69eMLkQ(Fxa|DbH z%A_Cpe(>&lr$rS3Fh%ja$m1pQ#48>z73IKf%jf}Uel_1(ct8K_t3?J!#-Ima8={b! z;Ud7|#fxR#npXHB4Y#Obi{axx6ZY9k1~pCz00?vN{$GmvO4a@M|Hesd7Zw1rR9;~L zAelI9Mw|tbc`7jNt9syp`wT->)s~@C;o=^<7~9-TpOcKr_a(SBE~ay{LksNIfLdo?O|rPLQLC2z z-0$x?*@?Skw?O<1JBqA!##Q4g5N@qF>yO^H2p^bwXmGyjs>fT{`StqR*S{g(htj`> zhDO|rTZ@k*1zOyPG>5#hsE!Oicp3xyf%C!cWze%G(Fo;9@BDW^=n1(Ge5Yzgi zOY5mj{`aPz%Lvs%Uq(#y7F`Eq&s)1M||BSR<~uRF8@G{bO-IgVh7I1<5Qo`$Dm zp6W~rA$r&Cx&D)jFgo!EO~;_Ad!V~Zx;u``%U}E=x#btP$c76qNJ3xm^C+u{(_4n&r^NBFqVevIKJ|z!6vR!W@QF(aOFYA46>q=?F(Gc=mrmoiT1|f1W zfU}4=HYmkyN>qq?&)xUu$w|rSUHSMcv5GtGZiJxCw(<1|d0IR^MR z!dfQK(2l^$yMBFd)!5{SlK?2pb2HGtK$v90kh@45T>vmf;Sogf57K_et^?8z5i-K)J+uf5yB`3!F9-1>j6d#*J^pc*K&$@Ze6o91 zU?0Mb2!Hqf_hG@;(|0Zhp#wr&A(V!qo&w&f&GY_L^f%DcEnP>C zNHP3A`^its*S_{ubAyUQRCp&+psTxEuDJS1m}eHtn$|X{tFH0-Vbiym&e%malmB=% zJ8p{myIA?ZvtY^+lz%!VhRc6$@#D~#s(?4y|5AUAPVORkgu`~&6;)M`0Eh&li4mW{ zNjt{}X37sZEGCTRIc>|W3DSQMB&{o!LJFV^6Jd_Iqa2YrpN3}~?-;mn%&t5KzAJ`QN)aaOTem$b z9mh`R2%1T6<(f6px@xsfO7un?dgJGEHaH^a?Qx+2+x z#ag9-wiRxYSwp(IzCZMJp=bCVFIBeWFgl0$0#0U^;${a<`YX3=*-_cu(@Q(ok2sXi z2O+>0h9O}JCY=Ig7M>f?1%O>Ue_M$U0}qB*ltbPZ+=gatzbxr-^Yck!OSU^;p~w>N zoL_grx&r&=M^%Ns`>wmB3)laVn5s1#RmP7z4e1*Y6@InQy1Ze-dOZtI(P4fjsLh(% zQwOnX%Ioo1<=UYVU+|}h%mbYz#~9`Km^|~D&s0$@FLchWBT&lqwzs_*XN||D1xmTf zQebI_M`Aq-rO`sU{yxZSz@8)+f2U~ze5Z+LwxTmX+LE(nOo74_^#}+krd&XtzyOp= zyUK8b0?(py5#Tplx67Zs@vS;04P!c6w!|FF!hIY1rkDSUE3cGII10tx9o;EchIC9B zzReI1@!1|hYHSV2(D0bta_gPxNaw`IZLamq&pyd{O6?qzpnivfOQ`Bm4q}NtXloy1HcWAp`mek03z^Qz@+|wDU5q6 ztE*+h`gItuVd2$Bgou_kh%g7^pfyE&a}0uFj0tc*owP$T>fwielLMaYAXF;2@j@I0 z25*{)8|Z+1`}8ujF!?Zq!8Z`&Q6BN+FKD-Ob&9E&!x(mb?s$O$^gH6W$3XiLHqe#`F!wp5e*Zo@%O#gylmW8fos9x_|LR^Dfyfw@+xhLCivUVvJaxSjJ;*Zb)~|tPSSoija%Cd@ z0|WB#)~zzZ4+a)ufC|Reqb6*y7+)gggri6n)zrxopZG+Dm`{)QzUO_i6=GmZm$pDs zss?$*#GkzRmM8vxc-;Fx=V)8rsvxDeUZxxC2?N+>OaI?&zO8*fJ+g3y`2R4 z_RC^9(nU*tMbNKOL4TXsv6qqF)^iRy8C{(E{JvF*SB$pSp7N+ zeP?Q$23tJwnI>c7MO&>a@$^W8{+Z+gIDciBWMS}cUCB5qtEpRoQhe+;LX%c34TSU` zbd5meob;;-s1Uwjn>z|nA@-*JE{w;D@GC{{k0CfKQ0vSL#|=-=4p08$$9`-Kx4}HN z>!1t_48g;EuQ6YgOE11aAk~X+xW_+fOfg-V6CyBA_~?{iQ(zQ22zKmvOnG%WOw)yL z*su}1^^n>`e%&z#CS_WJjqS2yh+&9;pi$^**|BT49)-1#`f7k4fc5LvfmR$RBa#x; z;nFXGaX5T_4=+vm(gdLks1o%~eA$1LfAsz+$y0(?lysR1E>?dFV)_&}m zv>!SOS&%Z_{W4vliAB2_yc|aQOpcGqg}AbD$ClyNbDuhL&di&F1UZg51OMDDM4H)T#?N@MU(A^q>*cU1y#<{l+a~a>wnr$xB}TQr9Hn z*b43#1-O9nH`l#aZo2svSpiYrs;U}u1Pc;GhK0i~jH$Y?0z+F2>8v=nB43 zU|@6%<22}XVohOeryCipLs#{lRXs71=QrUX#&SoGcIdJ(>zsdWE0P=kI;E>Fnva*Fj>TlDAY*1OmaDALV7( zjKd&hRCV(kNCzZZ!S~xSCZ3$&wS*3Uil58n;)^d*aE)TiWb6iA;*%)m+-N!QNiEhR z0JsStRbGuN9x!ja?bmnn9gzZnyRZo0)mdQyz$1odCGV3qWn*LgshwN5l94KI>*i z0^`R{)B07XN@eqf>oKvYGTkD=I7sKJZ68*Q$0(9Zc>S5^Oxz6D3>x;a#7vQqCtmdg zTzy4|`C)o``sBLzz86Z+aMS@xWT*j`sR3cbfFFXI%3Y2hn^0ljqN|B>BLw2Zkj0;PY015?l{hf&~F8S^U&z z{#AbP|8DTJP_WOC0_!$zlFK(=jKK^P<=MJ-1Dps-tru1mfY1SOab^eu#~=UfcCD`n z>^yN)Lm|~upL`XRh+vlr6A=FV%@~tSkNI-Rx8vCutR-FGOBzz*fb%1x;eSHU`SYy* z(BP2tcXi^d|51DbE97fm`m%iIzy4E}x3mQDO=Wn_f;B>c@$nIP-~0bj{_ES{fo_=P zIBHgjBSYvX&|jzo5jzmCC-mu=p$a4TXYR-WyMpA&ciA#DV^o~X5V7fR)dqJ8yR3RY zx~7?o9Mb}P_waR&lm)Ps?u-@wplKb8a1;3MADp7_XTh9J3MAJcE3Qa>w+B~@4)cZv zRHdy;Gl|hX{~()gk=2A@l!FO=S9h=M-gAK5qqz87w5V1#t;fwz&;x*_1jR|b3ZwV~ zl*tx_NdN?Lir!P|qyl`medkW;z}3sAYnrjKv=mo`mT*w(3^UC1=++0{3=b#&+OK0% zasPoMGT2YuMEJgFV<3f!2`D{Uu08>J z4%e~E$BO_^kL8tB*df3Iz`gfAkeZSK+9?GP90h>gQ3=+CZA|-E*fa=A&q0tGOe%wL zE4cL3i4&=9+xMshKvi`mjsW0~2lIoOQ_xxhJULlB7Bp7N&TP#1_|UHuoyTPtZ^p{C z-IdM>aRG=Fxc|QUWE`T`II$7-TwYZ<^3#i&U@zRT4xL|#ir#WUH#$0kRofkU{U4Wm zxZ>*KL@12^q+il69p0br6 zv?VwL$em|u`F{8N{!t!!=n(@{c%B9YisWTDKg|WU35cU}#Ku0`R7bp);Ew=uH{ZNI z>F#^)!7k8=(@6i!M*aQY{XO(q$Sm6?&#P*`V@j8la=gS57)Hw!l@aX^)(6ogp6Y6?KQ7>iQIX|FXa#Z;AP6!CYF0r^Yfs{oM5RE2qxqPA+z5b<)ro&yLomm^qae$2CkN-|A!3d&bg z`t6S9Avyr9rCW+M#ov3;|h_jGm3j#*GbTUAvp>(;gcFN=k$^{oXKcU=Tl;>Ug+<@H1wK0Vfkn1YBmceAuabM_)`h@X@9YHtn?Pj0vDIv_`;on9^d=uDtX%{s zheBFa5Qqhr-ripM>udi;P8>fK&t<`#kpdopJ(wq5eYLc$Xu;7POpu|J&gcRFfL4MB z2oFU4$n1RBukcNyqa9OB<6)i* zO~aH-x*!56qdEpOF74P=gT%o&&OT3aVFi=ceq8N$s=ZxSK}7b3?|)A||D`X=lBPz3 zqwvg$0=zNdv!DCC{Ogy$A{E&6FUG{5SJqK!s2)wxemH@3wO(>c;@kmBf-{Gkg4cvA#^&%VRZ1JIBDbxdzF z;kM3tjMr+bupp=7Kf^B?k?})Jfj#uIfk_@@BrqDkIi+PO8NpSl4?f7_XKBDe3h9*hFU|ZbOBUS|gSuq<>_ zAOtudw)0Q6*4=-+%O8I5A)WkVJ1>O=fO0fwY|KO8B*Oe|pqYW4v?ZG6Xr6>)oY19K z^j6%hVyDK=t$A^?TnN#eg2|5p?d^y3>^?3AF&}%7+0YtK*x9|AUBM#kBo@oY4Qr(u z=e2pB3;`OtXYTOi@HtkmmwcOO1$NW|HLO=B~{pwm=A^n`ggtiUC4j2G%sI) z$v;`yF|Oe~K8=kb&gFbq3g6H3cW4NbW&r1bMuIc>J0OO;pYzN3&KWQxhP(n?nf)?Y zKfYX_pmr<*l$IAm!*`WHGYx2;!jY0)a@{-Mi%FJ`Ag>%OK;U#*)igB8)1LBVJ+=qESbOrJS7=_Ny!kiS2E{TuKNzyK9z@AX@N(gY>3 zkxcobMM%WwgV;!>FEnYXxIZTQyt;G(J7W_#>pwCyD7~GhWMrUE{_s_=klSy+U7qxm zt9?ubdkz%f&i_}w{BQC(O#V~YVXT1SRrbNW($cVMSYt`H9FjNZY$Y_TX(2z`Ov`Z#eqKc!?Rd-+4?DdEIXb?oF?l zDv`aACLZK`p_r{EF`}kac+5Tpd}{j>m1~B&q+)nL#&}~27Xa9p08DuIGr2JsG#DUU z*KEfE{1IHq$9|9B3#jRnrX|hNh8=K9+~TvXSPa(?slj3J8T`&3@>%F(Y$i7C98;EX zJ_^JS-2Z?K4&iv$jE%|5CMW`|fg}JooDzAG*;YzK^UHo`Y-|#Wt#@GVuZJi!C&bEf zU%YvvlG5cqTJnG*Ciieg!)IKmS%`=>%LM=|0jLlFR#;0av7&<^`Hz11Q-1+~#{f9D zV_{q|VO_Y!Z&wSK3Z~?aDT=W$<`w|Te*5rm$v_n)P%MF~X1D+VNm4Wy_8!VYt{J#@ zW-^*aNjTR$2^+>!8#)ZIF$R7jYuBxl`ldyh5f(xcDX`^{Ez*fAmW+}xfba|8wRuwm z@If`!*H+7>%^T23V*tTk5F-Sh{d?#UC~bilEM#4Qhp`c+Z-+^13l3ef!N;Q%ca&Gk zRaZSZF;8>hN#%3fw(o-2X$y9rsx_}<+n^jIOJGcSY^eg@X)TNp`N5G5iJzu}^W#sO zU=n~sln~}{dqI8{LhoeXFx=V*76;>K+>}CtY<$* zR<2lyb_7La5XUzQ!pS`D_$>{I?*Sl=WBJu_Sh(oT{&50}giM1A0K+)z-;1;U1D&0+ zas68P-go|6zVO8_KoM=d#gEr;CS6&mEXI}ArU~Sf>_3%k8?$o8AOUpd?jMLB=S*{np!4Ba>rh z6BlmyallZR_ksvC1wt4)@JT)*J#a8jnWSudY_j~`d+yQ|q}tlLa)^${(Uy8wk7hJIBM1e;!c^`v_mzf-RJAN>GT>8ZUV-dKrlS=J((F5H+?9?vw; zW4m_Cr#}B_>_FFIC%POTN-NmKJd@Ks{*C7)v8+izX@F@S(O8;p)GH)xQeX@=t8<#P zZQSgsWDm`nJfq@gUj`Js!vR1Uq)e)6tE74nCQ`6}{9~V#U)^?BR&<3xp91N1%f4~* zrVFuVhk;5k>M?^IKW511rXS!UOh2??-f+WBQLs64rI(Epm6yEm1yI)1gRvPV9^C1% zVCLnxjPKAyTm*E#0EGP*C;mpezly=)ZtbxVO#Y!5>J)BHX{xW5kNoooUZ z^yLkB+%^cFv8M7e9;dJ?05K$pflP8tMO^`rtJ7SgvN1UCaU9NJ7nW*dpr%+lDhh+J z7*XfYY3kzH&;!tiev8LAS!*2M@U0shnmaqvGYw8FmevtSIBnUv4?3Q3#7f6^+;A(F ziy&5CU0sd%27kb3r&Ch!6LUeGOT`$2b>ry4gZDlNj5BRDhC6Fl%ys{PcI3l^>k|u>tt(eaLtQ=S;zr314?r{J*#{xmTp3h6 z4T{Ly07{_iiVFZ`M^7F}_4ami0f6&3E&$M-&3^wU3dhVW~{nf*mg4OjrcwK9l9({T!7Vn>eZ zM}_raO#H1?hEVg-p@!i#YuaQ{E&H0eH}8GVyCBLtqDl#==K#eH%Fo>K+viB*avOI$ z|J>fw#CGI!$P&Q`TShuudiBc&)g8>?lfpigrx#l6E4u#YqCJ9s1V1SY1u)^7|) z3n=9@jPein_etlmBUob?k+;3!4RY5n@08d7>FZUIWr(j}%asC?kf`{N|M;eS{Q6IU z&f`)JvHwy`Jd5#t(9tRS0`|pf+}k=LP75+8FEUNUBjKp~ESIzCA^^g8=X@#J$N+Q;bh49)83!NpE;!FH1+aXn-T8~V zI;3_8l1r!v_KO^+nZBJF@M{Nl@Cpa^9QRG&_}u|ae!IJ|xN7rC=(bhMWZAN%dXz-_ zSr_J^a_}}N`tl=1Ly$bi02qVuxWQJ4-*dnH^h`0Xn!ZrVF_||DAb!q#yG_LqNFD5h zxWTn+&tAAKEa*d9BcuwJu@;F3rx z1u^ruROj*Iz61al01ES7OS%FfEFthB50M_aa)fv4fd}ruH4<1kDyt~R_J0``0I>TH z_9B}RTt$cg88LZqH$a1S20L+TR|OTPjg3vR0jrV)lOF|+LTT0U6Q|I}m>qBJy!1gl ztecSNHCQCPV0{}jz+!O_6~W5qN94f4cIodO)Q<&MLk(--rH1a%5FYHH;#eCuUcgRh z&drwJJRmp!@|RLyUndZS0#}2pm@(!pWB$T`&^E)O_y}nC-411vp9|A>ric-s1V^-f z5&+JumzV8;7vNWn)uvMH{#W3t$wiA7%i$9pa_w8L4cmVtSJM(F0)4(a!hv11q)&U= zGi1fm%Fk}P zNk059AC{W>!oBA+<_Wv=|Itr#-uX_q~oBEBNU>sO>_5FcWwg-00$6@4sYsVQ52eFIvjnt007Byj0Nz(w|@+t z$16D`%-rNOu<$r56kyfyFh|>B-&^b)|Jkry9CUof=+&8-0KK;bXFI z|6%m06S{azsgOG8dT48H(Qk=lOgm0nkr}`gkvR zqx`ersbB~|)^w{zy6gXon{Fb*q>54%od0w8A9_M`0f1j}ua(V!g>4i!xHWG~)QZbw z6v~)3Z@LIOK-54xogt$5Z@M_=S-403T@ZaA7#Pq&S5Qjr$ehbm8wX?6xGiMW3f#%8 z=ZJv>ew;{b-MU>ya0H`>;>Frw;bA*fmy{8cGE)kEfhutA+vShH)Fm=!@8RKodC99@ zja91>sjjKRt_RLXsxrioWevBOXEdWBOw>Q~XTZQYs(7)y*5Ua^t+~KxLyI)FD2_x| zLVgmw5~Hf8R(j^(tRa4(kZ#$6-Rsv*_ch@p?jcq{oIwajoA;>4v!fMQhvSN6_y7)vtd| zE?i$&Y<9Gq$(vUve)X&WCLjIyCuD4NOvaq;R8oxbYvI+Tuwm%%8Y$;*>J%Iy*iui%6?D+ zEDoFX&}e}@lf}8Ji6Nii$ZO!mvSZF@An06-QxLpk$vH2>{|1pre= zWCs4o7?k(I0>D)HBlq9O1pr8nLH{3SQD_tt{|C*2R)%1olakm{K^$ZHz}9ZqmJ>>5 zfL+fxI;bnJcs$!=4#s8Of9DYGd;($7VYmmlb?Y`2u_`S^HM;6BBv$&ev)9_gIoL&Y zwX%H45;GoyKQZ+8^~%=mdvphfoi=ee%BlrWiq^3Jq(F+WXwZl|R9E8~mAN)I{_ua~ zXnTj$EnW%5b9KnP-`ANBfypE7H0Eji+|v@&Ivs?2JE+9BAJw-f0>d*G7#lYaP{hb7 zx0!O~rN~rsNI8-m;M1b;tHd*SC3+6899qGvpr7FrpZlWxbe?FXpN1H792_BVke4Ss z;Yx_Lk76Q#!5s%&T3sRW87ugqjq{I^7#ttm_RBk@r?)S_HD?yrm&7taeIlDL+6;xw z*tN%^Fm(ZFX+&V?kq`cp+;Yp! za`{zHa1jgd8B%~diC_BSm*x7;ejcR8S@Y^z?EK>jP<%ghLW255(>Gm$t2?CSSy@GA z*yNA`L2>|_aYl2*odjmSRxZShm6#8<`#RjDz;q0&h3qnqDy{j-?h zJ%KvxEH}9Q`pTYGA#^q=;8zvvz}3UkhI^%A9Af?G@3`2>Zq!VB*$4WpOt-}XpmE&r zum#GI$3`&$RtW;cMLn-uy+SHgR|iut9Oug6=fU}9f7@I#Ph0@Ro65b12SZyO^PS<`w{8y=>fQ108py(O2lY9;Wgfm{hX3akORrjQ$R22j>wqVpqWe%ff!iScTVDeTs8C2nQQ z5+rR*5Z`&^{U!$Y%RBNp6QQ%~l#!d(}NT=_;_s5(8sfzE4L^I{AhAo5B$ zd_Z`P_~?<7a`-UL#M-F77?c|vxY{R4bww2)k#%d=09P&&8-9*v-~QW2wm~Tp$^`AW z{3x;*wkS5SAom(#!tk_!uG~O?aKoX-ZlkBYqS(`dbMf%Alo8(KCu4A8Ks0YrS7L;j zdj@5&ge3i}pAJm$&_D2H2?mtK*i}lU%5V*3x%~Az-Yxt0ADMaD3nC_onO=jJUUnH4 z^6D@Uiharq(NPDC$GWhUK^(C2UhpFw9rCL??wP;T!=&ap&w2(VGKbA>4Hp2y1{&ns zQH6aKul^jH;2D4H{NoHd7nA#NrC-m9W3mhqW&d#9-^fpX^dtF;H@yirB5*w9jKkvG zg?BC#7#|;%Ph9^Ax&G6iRRN7Mp83ZBnhSmW29YlV)^jWf#G#c#^fOKS9PsA=;^>hF zsEabqJ2OTT5`P*uX-G-F`g+`ygvnL1AuPsOfP)aHsSDWoXH5J}UxOcCcl;GeKl%z9 z2dR$2V@@dG_wwr@X;Y2 zl8bbTYvvtB^16Cq@}vJS)Y|H<4&bVmS_rH?587y5F?GF#wxqyvTdh z3JkR(SO_SGcF_ts)!B_jfa@@En|bbH37((M1oA9ju@c9nHlmq>=Q)bAeQTtqyx!W@ z1eAmptEA8mZukkJB_N&?3a01dS=amyj*L{8>-xB$7tgB}2;U71WO?%mHTzFtfHwv$ z*Mt4NGJr+V=7u`C?j6_S$ivO@rnkRc7Bw~`l+IBmA*kS$NP$Vv`u}|RU*z*&`I_ol zsesr&)K6pZX_T(&XaQtZ_6x`xt>dUGF_|;(nMWq_0L_~3nc}hR4&11v#9yTv$r-i{oP{>RZ_^Ye%m|F^9aSa(*JTd^Kx|pczOw?^y zn0hn$V-1?RWBuv)9SyRpyI*!6IHY4E_W%6!*3{NP8Q?15VjTP;eXYUcVR z$d>>RX;@STMS#u9>(o1=CPeGd%=$Z?&wLYL{1695S(vZk>P4N;9o%=IVq{_v3jkA` z+tH76y|Yj%q%zMv}j zSM>WAUByG|r1&Pq+WzC)-kCc}@+!;9agEHzvsKKj>0JGcCka)f4-a%A|4`N5%Dom?79raxUCl2aky@W(dJ>)uMR zD^QB>)E&S4wS4;e&!nYP@bD;*Msm`)NS<@e)1|5mOa5r6I(X9t%P?C9!OQrW^PDi)amfhsO;Hy7?M($J|iAltR4|rzqSoKewe81pNXrn6_LZ{{Ik6c z2E$uOl;3^!4Ui@|bm*Ynea{wu`moh)ZL%CU7nHM1LE*!Aer{d)SyK9sLa)ruJ^NMS z*bH#Fh=AM2upzc`TF63jFuuW8>WwPgkbM9a%Ik9KgUg^ixR@|58;L3Equ1-w()CKDjTzRsr z1y`P6ZAW+VF+do^l~}uX@531}0HDfMv$(D-I~=ZjoM_lOLj^8wTr6u=uXKTP`hL#U zPnY_ddfZ~rF6GtrstAxf&Ze$ob*#+CvYC&9hU!Gwhf6Mwqsa7-$?;F$*QM-o62s_% zW8SWfno?&3N{f~3!T1@{cM)6?7R;oJKu!|S`&K~vuuRop0@ov-`qby-nrB`kPq^xF zE_UG^p@1W%^EB?Oo_Mt^S@bPA)X}B7J1GAb7)N*Tx{J*>cVsA%**7pOx8C+ES$n~{ zAj*8Ry!53nk=yURPew7pFB!)m5_~g-cFF|+EC)ck5q5-4LhOGG*9KN%5ch(YzDWM+ z+P{>w>(t>YZL)FnMcenQlX2DUdnRLGZH`AG zXM!vE&jkfG4-d-!a6uGD9!n<2&?mY&#h6C>eDaL-L!YT59ejJyUCRE0M{pJFFqB9_ z>>nZUmNl&_afGr;21YoJH2lW)%>I_f!=~=^M>+&kr*ARz&J>rIVUwy)cH*k)4I3^@ zkDil{DNjv(gRH^P(``F_odW6U2jzF=VLyc*Z$jwl8^AT#Ji3Z9a^nl*=nH@o`p#kJ zl7Lc73lKJgq%?BM_!$Rc&X`#oxhTdlzfx$1_8Y14(m^RSmZ?lM`u2ItXh zr#p{sI+0wn9fgaR`*d(cG0)UB2+jYGVe&qP72ROM%WQ$j?DqMuxdz=y`{m1)NYmm* z?dWV5Cnr0*sqUMn}<{J*lep*{iCoDeu6AM?wr zdF4vEc=JVawBt@00f}(R5gGKp@wSRhe{1q?yi6Z_>+N^S>;L#wn8;D+Yc5SPFFx&e zo+(Sv&mTY42jQL)9um{yk!K+DFXIsRA0Hc$dVDjU{ha5?U;NpdWW%Nl=CUB?XxgqF zC)WcDH3E_2*Sz*m<(6A-la`fjkmA8DE0~1x3_AN(#^kU$-RM`y>p4A$S#8#?3?BYW z)-wZ|DYe=0*F1BwhPI1c&lKp-YXCDEz^QmR;dLwkNLG}sDf5OiMhl+0)Q^w!Ee=y zW<4U+iAg#aRB3eR#2_EAIuZ^kP8qP+w-X3o;`bmd`*ua6GV}0SieDjR(!MK zCR8Mw9^qJaEkFDS!{aa=Cr(H^bRjHiYJm?&vbaqFHcG~(F!vaNjs+?>Rs9;;eh`S* z22Yq$Xk&QHWDShLW^)n`jaMe)!KVuh>RBZKP{v?Bw;RU*u6pt$=5~6Xhl7JeOd-yN zVe>{w3!}hUO6e~Eq#oO~%Ps(5-HyEjjpDh+f0?G zdCzAl#Dpub2vFa=OzM{|lSRv0q_%mfq#BxJ4Cf8oyL;rmN4Ls1zxzFT$8~=v&wt+U z%X6RgEP3Ofyk0*0na|3ve|5j~4nUj1tQuEF_nuYcGeXvR@}#sMJAt#Zri0?gJ}}58 zo%G?a9D}tLEz7VwUZs;T^L;OpBgc+QfB&G0gn6+8G-|N{T8Hq!y^vUiZNngtea-wZ zt6MLSkN(RCWCT|Nj`sCqaxf8yxtZ*9`bp#Jk1qU-r-p=&M9(ldZnLNqS1wU=fw5_1 z26eR&&&>30E&`l2gKTO}ZFV^*;59xarA3%Xlx7fxrJihAi2L8A2Db_miId zB(!l}2c^Rk+e3q10m*2$KXeA1q(P^vDg)f|*iLz5%Z_l`@%E-_lq?zjF&;I0(kE_r8P_D4C198|ESzLK_XRJO zTYi44tXS11l{kM}jN@PC%s;<7s5{+djN?eJQ$ zlF~7-j+=cxL;rvXlsLtdTDI@l4XHouPv>PE-Z0YYHCTjXAH;7Oeqp(tJ&c23HXJ^5 zA)-s5%D(^rKmbWZK~$@|SN1{=f?Kd;{utJRV+BnOi($75nITdF7!#s(KBxW82W2!% zQ5Q0TDy9SgC;Lzc1}T7T+c=+_P>F813kv|Ywh9XXh7jZ+(gIs6Qa(8~nmT&qpwR(< zi@V88yyy_J%mZw~GmAv+Kuaocm_qTEOCsaN%Tjfa0 zd!h$ZbH7A@)ABrY;IQ=J3P1GRUMEPDq_BNy2Z=NLICH&X#WJbHjvxC&o)6>|TYLBJ zlfhxG`eHR%VKt_GczI{tSd=MD{4azOviWA-_0D(5D_;2u>Fzp#qT%C!zFcnwK*pK( zWRvNW7ms0Snv5js8;=?dokf?|lJZP5Q>kY!b6$p!P0gwK+M?INV1yG<3}SKDi0)>6 z_xm@n3vq0ppfbSBf+{KQ8Fgx)N?Q%$*w8J% z_>~7Rm)2NbZB}0M`q#?Eo7UreKNS1*bwhFFNvU5{DerjO8|23CeOKQ1-uGeWrgbhe zeqN{De)5D|eCZW(-(C01inZ%7`L9z+GqzoRKDZ;Th94Bec$OY?8xz{#_8rMp?xM~4 z+t2FR%S>{wO!7JsQ)IaoQZFeSkxVuqEbB$T*H|#X!kJFVTwSq`$iWl_$vF_|EQ63I z`K!e5shvl03JTL|NQAIPF@` zhtmNV_*(%=>o$JI;bJH8lI3B?u!z8ZB874M?)?WaUWKyh(=;nuTCkB-!x^v52&ke& zAYxL;H^>jm0xv#CAUW3Fehm6$+(h4SdIOZiauFaXv_%t^(3Xp+4YKs4{AACHJzLrXPB|C@Iwnw+6#>U2JXQf7g;jvInp>H!aPlf<3COCR5AL;K)?b@?XSB^?j zmDr@yL#2R;H>e!g2C=ep8$&JXIC?Z{c2xX9r=^OkQ=A)03016Hj|#wD4oQGiMV0PI z)Gcb1hGi?IxploXKtDin(_-nt4)CqF-zC@n)jQ?8-}z3E>3F`_tU3MV3gtOrv|}|n z-LYf4j6gwRF&F)UPRSC~xdBRR4R-$9R;|!0n)IAMc5Ozmv$kii(F)65S+kqtsDM}& z6$cYaH@JB$tO~eV*Q|Dc^XmQ8uYN(+t#6Z_P8@51qA*nyLz>x-K#JZ-#LG5} zHT30^N-`PAe8KH^Jgsa7p5I8*M*v=RZKMGfJm!!BlSXB z=;YlpKEAOS7R%m)?cgO?;7#yiEFf=KyIQa3<@bT(Lyf_Q{!B|(#3sa8vpvak%o8%q zcL{zcv7mUQor@2rXBrwY-iLBmJ@&@@S@zYJV`X@28B76;3mzQnx3?d~x(OAF+sMdd zMOC$|S<@Cs75F74kqj(Zu4G(^1a>{i~*dq$wsJPCmz(cRc-3c@S87rHRvdU^1?CzAiAAC^$ z=#SqJB*kR=J3Hk^KmA|1=brnd^JKRa;Y{PQ=4QF-@mI)YkGn)x;JQ32N||@Y(rk2W zSa$E-1Lb|FL6n&tI@-S}qdu(*@Tf>VPrIynv989FsuQ4DcXyv0!pC5O+Eg(qAn9#X zyof9CVIOFGumiCe3K*9yUuIzE*)xT@|H79(C%^mb-^Jn(&Pw7a2{nito?(ed7h5hv zE-e9lJf|&H+&4;-`BikAw|JJP$s~Ef@y#@#rxEPY4g9EdMn2hA%kZrlhgfUHSW1o_ zJ|geE?!EGjZ+`hS(mGG0D$)G1fq)peT=UeY%H8+hucDROjyXgGtog`?ksMpF`(PLs zRgWHRm*3p?fIRa#&-4+`A3O5;ge#vY-~Yegk-@=1SqdelC1u?IDws1#fgk+nCV4%i z=SDDFxM0I3sls`4P6@cnNZ!J~_&@!^DjzB>M4J}-*xmW_3VmW&JgkBk&DyI|xfN`5b5%lD3*mwaX5-}dZLL0}& zG?9fA^}!>+?>x@XJBM@#qO*U}8FMyW04OahN2@PCaNtmCXlSS$H-_@OkS+k6p`4ym zXbTGfrn<;G{HX-Mp@Vxj{>3YrEXjg3w6J5RfYNP!g%9+TZEwCY=LyInr; zFCW9e5}ha{4Y(7DjWqf1@7*9(5KV4dwOp>a{4y*CTqYM?e7Ur(TLZD9EQ)tDh4{ne zJ??)xZWg{s#bFO0I*h&xZ5y4H4){UGfP6M-4%#@ir_#PU+^V2-RC-U!;loGe#7XYB zvTuN4r>F5uWImai!Eb`OoVdDm6(kDg??C*@%dU_&zv*rAwXc5#*WB<3Nfib)EDK6x zipElT#?aLBnAVyxMk4-c(Pab(>L4S8A7G&~X3mG54SRmMb15==G`eIB;k5F~G91~c z(1n2i{qaxa#vj}yFM8?o3{c@|DG+BYZmt39dEM;5F?&?77}J#}kBE7IYC3Gk6#eHq zJ#YD13PrxT7}n3&*l0M3VP3t&&7{%uut?c=phwOni2}?SrT}Zfv6!nqz87IYvuvPC z4wN@aHMiMe!Wwfi)H{3tfSVy5Cp)DJdbH{&M$ge9CcyR3xv~nP^+!88;0MkD&yf$p z)FWs@N1{zJXjFp&x6eRgcVu8(wrt%luS|@S$uqqy6i#l%7?fL-1<~X1AysJO z6^MtAbx1#s&eqgWX*j1phJI_;u7xxr^EqUO!a&6lHRHvx!fRfbAaBSh76)-LOKC}Y zd3o6&=5j+LeW|XKU1dv~SMmseI|jg_pn45m%rtLa6y>aCqObrEk{VmbQ(P2FJ^I^6 zxchG;0CWKWUwpECz+qt|+LFBuQ)LQ`Vu-mSEzEWxuGyKM$VAWqtc{E0Ob1}oMW?8J zA=vo7s=}^!qqMDQll9O*df|l^;R>w_q-p6AsjIHCnVC_kgSeS3aY`FPap(Rd z6q-;1pcv&Qlx3B>=cQ0<+`O`POMQ*s^MyZ0-RLiP1)rvg{e&Vh( zLGjt-$We2tUmR8mjC~o0dm1q3GVesdC`fi!64LB3G&IL(If8`=o_2XcRJ3JFUngW( ziQ!SbW?YS&lR;gLp-0b^%##1Iqy( ztZIx!2YPTTIs{f@G33Gv)>)YO`(lYc_nA-O?3qZ;>xNe3d$OmBf%>S zRGU&lZG?5dz(XxLJVG=%0Ot!r2Pp}7>qQ5CStn0y;tbh7Cc<5qxF zRRxs1Et2-*$K;>?>BC8h!Oup_5V{b(FTM0)kk!aRh13acq=~BmwPP_)C4+G6sU25B z-E#Bo^gahnv`ptfhUZBZGtkX?Kl11{dD8DZM{b1Ze*=!ewXDYEpGxM?FnLsjZPa{8 zOo!t46=|3aNP4-`0MkLT`}iE39WoM{W3(K>BB*EI5hW7o?9-Ye%$1duvUG_zp2?C4 z>c5$Q(zDPIe>@H2Wvb1Du|F==qXS{gf^BXn5Us6f9-h$)QTB244Biym7g@JHYG)_q-RAOaRZs zBy_gZq1m@*pY#omXum`W1}aTtb(z(E#AY#V{+D89vl-F~9MBPttG<1>^R@lx5hy=o znYr3YqVeY$;80)SWkSTg2oe)*xJ}?(Fq<#FNZtcY;R6u&8XFzgE0m_d6RO{c6-(pZ z#+C4#7C_?!P=O@@Rop8^?s%~8&I8noW@F`;Cx5W%A<4lS&t2S26k#T`t+0TDTsDg=5Af%)7zHsA4C~2#~WUW+p`2tdFyLC`_s~hn- zU_^h%_B;V;sqg>jXGU3=2U@T$3<`YxKmS{v{k#{*-d%g8WyK0ef-D#408(iw-2$*| z=M4O5N$L(F0B69FEk8LWm#HurB4;XNCjSg*3=wVSy)3YAp2QI! zo_}s?T9h%iAHW3wJpy1oJ#^}$V;|UzKWmdeEc#$PQ8Qw8l;d%H`~~~mP{54qisb1% zr(_i-^FunLK~0g@*vG4JZ(G0cXAmVhN5{tH5O%;vutCPL5GVhHUbU(Pn?+DC$?u4d zC$L-8NiUyKh|Dn;ti+`Y&iKan_V&rHoqLincs#$2n`Rb=NOn7Jb8iO~a-eCx#A%H0c79$EtVy`g#0?M|I~r~Y6ngMnPsCt? zKMw8ihmRc7&Oj&5nE0!BIHKd*r;}vuEmQ^qO&++o%BVXcSPZCyQlzT7MN-|+EY*vb z%Qz$m?zrPl={SBcl2pzP_Ds{7kPagw{ZN4Nm|khZ+Ywl9?XZ<7`cO(T%j~eYs#&+F zPNgDbHg!Lq<~2c#zrV{T$pb^u@tG>OAq zzWFeUf=X0p1H}U&>?-ej{|BYBcOYYmg#bf=)lgX4+|+>0ka1nyQ(wPN3GAp{v|-i^ z`vvxOybWL*lqo&-*xmv;7Z3$TpdJ1#xJ}{hZ+|=FVkc#FTN?({jkuYEN8un92PVUi-va$Z<(oLKrYj52F{3u%r+|~4@BAWlaQA@!4oi8FJ>|R%M6_m66PeZ!D1x* z*fUS!_KPN|gG8UL6c0xbBkoRobpm+_sZ^*I0kCRr=|u27Gzd~dbc)C#5tBddhDd$0K-p15yl2=>rM4F(g;PX z+(aV`$CZYmi75|b#~NX5;DVzBo6;j#eAu#e2XZr0&LURaM1|e|mQ^bQJlaN(XTQV> zs1MV35r*Ro7Ix3R{W8k7Vd!zF|Ql(7XbW4fa&p`hY;X^f&m3w`z#q58Ynw-;3yXW%2O4P0MH`< z>=G2l63eh?gi+z(XDQia@=t~ieUswR55I&49?K-);KPwEmNvPg$a79{Gcr6V$2(5w zq?z&AJDCrlDUCigz^03|*>UQQh=s*M8#)sJL7ITq6;45fU<@6|nKS%A8~le39)|e- zK@8T)QCj+&GMMVpjGKWz=unOhkJ|sab3jP}cJ8DlmH%}?hXC#BMHKdjml{{M_&jJj z+!vjh<^1*5hh7`Yyx$~4}!9k7DcHT6e{YXx8sEXBx)fm z#0eOM$n0Xg988X2SunW_X2+CX_ADqPLD}&}K$Y8!kK;dnoY`h^2uURY_U+j#AN}yh zqtObkGz##xlFgf-!5CUhA-ZKK3&ze?8w^uvDJ%dC4i3rBesW71-h#*ZNdcboebSSj zCg1q_H)QdWWzyK(0v#MxI`J>V^>Y03`@F;NK+q>>7(?-6hZh7S(28KQYH~oBEkCoG zm)S+;D9r3fihv?xTyMTr*y&udtQmDh1-#SKViMoFF)Vr;-0tb(xwlD=y#;sw`G!hl zX&y3TjYwW*x5x!-Go%3PgtIuBtB3nAS%)Y;MB(XAUNIv=y705r%$J5)UOI)NUng-J zfVnvaaQI%-;MOE=7AYP^q{TcK+2B${0a^mU&@;&xe~p3%Z>yq~{at$xK-Z6dG$kqS z^nMpX_l?P>DYJg@m`@n{ie%0CQIE#);~hd8;45h0F}CKW;p&yEp!k$aD`+n|I1Fj1 zfZjvN*+b^769nyF8~-~&1J^tAngUc0AMCP z%DSw4(xL=sq|1(tLGKF~rW^_+AdYL4=-WomPe&TFr+FmQ&?!MG#1>AXqmwuj#@-~z5$vWT<2~lkFb|PsNYP4b zTbnFdIy<8A!()Te)8B(t-l)-|Ug+q5`|u;kf47S2g|tBHcRiez2m(&*)RDiHE0&vU zgYcm+9|4gg$ByI57aSo#flWsjVq&TemC_duy34RLv}*M#i=9yChS`XFtl#<9e2rnBcq&ub61Fk0d)1#{t1dN~Ghp;-_l2gT4O14X`PnLsl8Gjl-(nP85IX2zHh z;^Xw~Nud02TNvdr*zs{L!%jgZ^Z}GZG0}hg=eOm--)>EayimQ8Y5IgKu8<1oGvPqc z@pV8Ut#h_>r z0ltw-RP#0T{^GmF*2rUYuK$mH`RVVd67(a^BlZpeWBzB-IbEI-(w;$-1T1I5i#ap7 z(^^5}9EPA3)Bdfjkpl1qKJj14Wy z-;w+LR4oqo0#1k4yq(Jd817XJ4ALJKmypKZLkFe5zb^#L$(Ej%_3PGQ(Xw0>W4duI z6Y|Hw4mJBV{^=$j79u*kyHWn52sYC}L(@ssuhFoGH_mVY%u>K5jkmEn4~Kmoyg^@- z8&%5{02~8IsSbd$q28XdEnBv$GyrnRx4!@|4>ErCvtVlc?58{5Qe=}PElN92oFZfB z0suOJQZP{oSc%^q!gyx37(YZyoBsV(1Du?T5T4ndM90?HxJYVhYrN|HSc}p5UV7OjxId9Ahch)MZwL18k=MNP zRr1uUua;N6cT6?f+nLL`irIc|t0%@(11TIlR~L^8)j;|ZIO zX!3l(4==K52^0eI`o=0;4>$&m#DDY7>traR%qoW@77U1?<;53YB27?Y zR89h7@zc~O!gSDQiQ#B%`wZh-WTY+H56Oe~KD1z{w$KWC@Zb>uqd;80`GZ%!RzCE> z|ARCB6|#EWM%Bd4&pZ`$DNBGT+7fN^w6Bm<^oX%k80gsy^LZY#X-z7k|fA4^UVdXfx$sJ0x4hlmB{&A5-%Zkl@CLyYDxTy7?X5{E<+ZQ6cbr zHa?e}&*hAiEw4s5{*SnFF@`HM@utnT z%RLY}NcYw>*l_V%whTMpoFqrggd9D3jALZ1266)Jw*?Pz5XbqM6xbI!AM|H>UZ@P7 zmtJ-mVi=epYF-xTCKw-<_q^{P|@@JMOvJ_+rOXd&8y5XInB#yS^u@nz!5(SCI#y+j+KH9Q7}~)1fA|v* zu3$Z1C~(7#KapoX{{`~vyY7}HxcaIIKQ88S;-@=eDq2Pde}+GjS|{m|$)92Qo0($M zjN0!~{;4v?OjDj&YSYDv=LT-;6^z@Ur=bW(4O?1POouE8AH#WTDm+sh>I=G=>J4D{ z>En+V@!4F`-7CHH{3(D918vyUI5X#=VhqjE9|} z4eSIZ(8sYrG@_ejW@$jA1;<6JaSN6XA2~oGSGc&0=ihpYUOI(oHFHh(OkmrO}0noHScDOF$!s@W5-Wm!E4+^A}D_46)9N`NnSlF z3%8+*qn#2Unv_fOEWX21MH2;ibv+mJ0h|GV6n8mxP_O`S?>!G;b9YMZaB~5GlOkJU z*cO;skP84_9kb#|1D*WiU3%bZVUd>?HGuOi znZp_4|6HszKScqqXUAv$wUKi|%r(rGl`B=`$i;VS-R)Qa;Mpe~{MfYEs@X}HUo`!s zbSYj>922-kZwdr~GWKG>)_bfhQ%%FdFS{F-Fxg29Uufu_a=ob9SsJGBDyXhe5+pPW3|sU0V)jF-XC zxOp&a>Q=-WfNl^w=5@Fqy0P)xQ2=NPp#@OuN+^x_#3w&1MJUSvZb6v90BI89bCd)z zC3bHY)6&5?&&JzU-*idk2$mU5#-c?rX9UZPc?KcoqiiOtY|m)Fn4pwk0UP}h*0Iaw zb6@zH>^VS@y@E-iKoJJ+k9*wZSO74g%E`3j=lV3$OR$flVa-AYrkFo;C)oA*^?mmk z#h@Bqc%17L=pG!9xBulkOZC0386F_+X5r zlYQ+F!tWgMbMill@G*2A1K^7lIDV)X4PtR{4ynK*6wI^~n3N@uk{joEmcx3T?tKuaL`GG@8}5Pxzzk`8K14HoLY_AA&m0DnZr`!ZNB~q+l~I92umIp% zQM}}5oS~x$xJ;XDuv78EwbdK9`oqeJE+-Bx*sV57?--LXgh1y5@gsilxP$I`3jHJ+ z5Z51gjh{IPjebzs(efW3<~m%RvaES&2rxt2Zyw$X{mXE%Lj_iOG0EqOE~ElZoH(i1vAD9EqOkD3 zF%tzv#~{$O2|5)j&oKpnrbHouUhsnF%Bx@f8W|cGkjar@b1MKA1RN1VNJozdxA_Fh z$?mY^1bnuEa?IT6QNR_7lntqCe3^kT6KVqp7xcI|SBCTHycUpmUw554Dc67OlMY$o z?a|E<9?C#mw3o{4mRhUO%yqdt2&Lw6n6z{-9E7Y9iv&V}A{_XA2>hz#;sF!PoI@ zK)ED0vsm2~PzJgj<7N)4l{bhdf-3{wq_jLg$MIZf=7!7Oy$7L>Db&L6fx>Fa<-S-Z z%wY&2xox#+6RyBTp1H9_x8ddxCT`={f>mbm9!wF6i&H6RQ^o?op4~gj`v(Vo2>@;YrxP?Wh4aso zgxw0i0_c&c zF-UM=5eJ9iw5%*I%NjLtx8a$PR#E7rxwVR zioH*8D*%rJKqBCV8*Y@FZ@n`E?i>q}A@=#;-P+cw3x3pctW`xuX^b#u+V)(R;+5p#GmJ6(PwcH8An9;IU!4gY+)G% z9>Mp{@lGCOW0Xj=G><%xC4oke#{t69#6z4rYjg)RAKqS3_&s84o`k01MY#U2A z)d9do1P^CWM4j}Y&Je~0fH6$Y#xQ1Mq@nK2RXe#gnoUyV-1b9M5TG z#zB~Et$+JnfRlXH-!~{7xDATq9kW4%*&-BMQ)zTy+*n9)I>hm}g?8RSWY@PRjy@ zCh9W&E(N*}P7shqaUo~|yXx#8sTi+fraR-Vg9Yji+*MzUk20t0oOJUnwEj3^vqrOE z?aUp2Ce(zeMql9MdH-r${kOaYiy-{a6B2&o5DPqlBM>8a8y4^}TEzCHv4{(+tL09j z0(m;uO9y%p{%S9mOE~e{0NgsjRfEXuXqwscOv2_2wgdX zrAPFA(lVC`YZ{=BB`oGH(F zW$WAlP9Q-&B#}1pIzaYqWMt)J2WSZETrQL_JSxNi5hX6}dEkE8xBu|DLx)06BNXWA zIw5a<%eB(gKZ4z293MkPk8_8RMtMRlk#B~wiGCv#D(wFpllX4~5_!5{^>hxtGn%Y{ z8DmW!khu>yQz=fLr`#dz5FQb1IrL+M_Q?r6eoYw9RH${2e7R>yn0BdkUmW~XfI*ViiV@BN?(uy_xJ@fC^Gl%u~S2H>W zeONpP4?g23mE&fHuWZ2t7?W!9BRJ0h2hX_M1!VGPIK%ie$8PyO=QZ4?Ac-2y9h^BD zQ-(?D!cop4N)m9a;FXPNf+>tEFv`s`@edOq9Dz6mNzi^|fwBWM@AZFW3gYPz!E3 z7#yxl0gW4m!a3+)8h0pBmiW2QU z*dX+5K6bLIv6J3dhZ_L!0o1++QP`awM~Bysb5H!>nN@Mb!au`8tbVS6RGSrHXqXDS z5Pp;=Rnz|3b!)KT!1gr*6C^b*T_@Qgl06N69$zq>7*b`jFxnv_j(*hXq4DwHoBsBX zqW%qwYE^8X*ZU9`2l^vW3dByI>F5Fgh`_%90c^&Ya%#GO;Rm6mc?ks(&I|MOXFW?^ z{<4=z|EUfc8y?iU5{q)cFY;63dp4gSTS2?BdN*g`_VM@ArRSda;;1g>L`^-L&6l-%rEEYkkLi zH}&Sj{0x+WCO$&Q#Nf=H?n9OtA~|Hb3^So1nA2uK5qM)^{Pb0%=}tCHw|*>|lPH z2a|Zf%NmBo0&cMNL(=7BM+d+t0pLs#6h+se{M4J}l`F`krJ(~qqi;v)+!G1v83jdM zGP7m@pr_Mc0H7bkEOm0ne?CB-CsUXiRRt@Y z2uHDN-sx{myeu3{@mwCorf_M3tZQ3++AV1mXOT`qB#9FpUAW_lCTH84Kja|HPSBYJ zZP+7+=JkGbwvAS2|3ykbpxq?K{j#{;7s>(~`*&lmekGe0;MxJlzWlE8D&&*U`!ayzpd25v_A&OC#`;=JfNQmrFmZs(q2qWo z(PZ*>z~sMRDN2SRm9%^Jexw0E9BXw8lny%n9!FmH<4DsDKmDnE=;I%iH~z)jf(}jNLq0JB-br_0jwh$0W{$Q3)cIy0Ej;H60A}doU%dwN)*!?rRc*@Jl zM~0@6dWJvgtZg0r0~H_b*}Ge|JoKGf0MVp>)rCdKm38D zniivNW8%-5zb^Km#%;X^otY~>`GgDs<5`WzX+4SUY#Un;Ey*mT?a<8xRgT4Hzef|y zN#LZNceAfr?w!rfArFK7xakIwc}tNFTO#?V!+5a2W&7v!tQ7oFWp_j);5npM06EJP zKwYs(Z@@YBHPB_W56P#v$d^=cfOCf9Wbzr0iFWt&Ne_OD>T$ah{28*SzDDYyZ(`~Y zBB9?fEEz{igi$|@t=?W@ny7K>ll+Ki7~#s@zkOI<+24!feOPSp=c8Bt-l;nx3~(s-0G-uxv^Q&Y-rjx`_|5vnJ1LUD2{)& zLlK}(^i3fZDuw=&#{i}t+l3NxoD35$lJO0neuANIvMoxsWSANFMzVlHVv&a_=>ovv z!}~C>9m5aT92h@_ouQn}AoFK2%0gZMa14R_EudZ=+^}uyqkQwOEgb)cz^$48bH zcTCqZa;sT~#pg)p7srs~&kr08C*{1_V;K~AQ0uYEK%(`s(NhuLzI_K_Hv?UbzyulB zPgn!u4|H6BW5-Dg)8TQ0C5xBfF4Fuy%yGX&;b@(7CfWAfTKM#vkT@wzVct@MK5`Qx#!8d zV##P3#XL8FQ~6myr|52XKh^x=H^0^TOW|RU%Qx2Paa%$;@z{Via{{aX-?{7un-c&& z&(@k9b>_qGy~q^Mc3=9v?_md5oAd;^qReuqqDa2gxZ3RGgdr}}`8n8X-VRqaacvxu zQ3V=};_^@hf<=&=9@Wv343)kGvngbkorTK;oLN}&jHE@4fQshHa=)>Ug(t`ovLYKfxAg`LWSRKjfXmqNu|M| zQFCNq$Z!qIeRFwh)K(0DMOJJY#1J=3ou6i3l;SYzRv_(DaY)VU_Ug>dx7=#3{P9oC zeeZKB2K>Y3z`i@pA8)?J+`7-aY#auLNwsn1BtW=s^GokGt}FSjV)}GUz9ziMtfC--KIc zM_peS{VvLpx9Nn5jJGxb3EA5nJP3uJ+xr7x$x1p#$C|t8lKd z40Gr@Y!PZeI^EU}FInY5!`6bl^9TTxI%xioH^Lr*;+64^x{yG=VLUJB@J$%h4ZDy8TSS=i1T}|d;OctH~#Yr z4t(=%CGwQJ-5u9;Hkomp|K$pxdMC@u4g0t%%t-?Bn|B}2;(g`Me`bb`4w}y1u1&tK zQ4X*Td;cZxlG|Ip_~kFl#O4C8mc<_jd-5F=@jz{cJCqRG*e>wbwS8fz1Z4a$Ruoe4 z1mMxYzY)&JJNk|^>;i>tfOy#-Vku`NB?|;Q+Z=pngl5=>heyo^ zKl%wXKRqEkQzxcoaU5X+-z=`%VS_UI?uEuS>|)hpu{^fGZxqpRfs6IR$+b@j^XMDG zGhY(X1rQJHRERx^9TaUv92cem51Ga`+<3)Xaq6*7%005=fiNkKoN;J~tduDiUMg-C zVZtft#A4ON%oL6uIfT{FDLmZbDjk0o!wOSXMMjn(imNlrfV$>UGVgqJ)gOX!)Af1; z09y00D1fzR2#r>PMjfcX||0=9yBo+m-d1EoQa&pfE^0F?4Vu39Drsp z7Ta@@Jmj5>F z9cuzC8)JzO{e)-YV$yi{@#m@~ue7AHx>iim{PbtPG~fB|4@%(P2ti5C@jAd(9{u5Z zko0`OKk|_52pUY=b%Lj~fD(wVBibJF_jX)KdgYZr-v|s`*Lh?|`gIq)%AEV0XJeI% zAAXiLT7FCtJIM*(uyG_9GxSmDWLOMToC>eaW+FzyBMrlH`iY=tBv2|WBgztZXR$y}***8WAn`Vu0fTiln@`0ED zXq@I`kxl}zotH4%{s&pIZYOgSP{wx%M?&QqKmk&WJGS?6Q?Rxbg44waFlAd}pdwm; zraY@1(-aUm_Eit{51IqmfiRBlRvi|9G5|LKU`!~3e~kZK)jykS9N1+12xxa%(V1M6U`d*8W#KL(w%qUcQ7 zZ#f{edYSamvk~>(uuCJ0hP4~p^*ga*k0wA2cLC4g>c4&a4v3p+NdBd$6^5*EPQDce zf`aW*y*<4$FtCv8?seb$pKhLa?(@yW&=E5=F=iGp86#IHp`6KMk*y%t%2c2CzC8f5 z7RIHC!^GVRmGP@dW&}pPD*#1ALZ?uY@Jhp)l)oq?`CM8LldvHPDy!_sa&@8s161Bu zynv;Gi{AI4H0qPcHjqy*jwSW=_R4@y>aoka6k*6!=N-P$&f)7&j}F)D?7X@3^8eKx zMIpeZts)MT;&nxc76sgZI`fM2UuK^FtY@K3N1erYhI(?S$W|J*KrRp_?Oi(E;wz0= zdU$Rk89n~wj=U6r%y8PX6u2wF5$9vnEv+6`B*F7z(4T2)%{u`PJh<9+0K25Pnkow* z?juC2XhaPckarW2xJ0_}c6DqUZLebEK9L-X}I z5zvUqYCeUgCOLv8lh8PZiT%j>fF9DsN4{rxtA%;6b|=s-S-3NMCqx1q(0PU<5JTo5 zcE!q$DC7wtcoxhyd_$WsArV;!65Ps7Kl?$Fi?l2mPO^k0NkJ&+1i;`xe) zeslcCtZqoNQ6~Uc*erB60Z<$p9Vs53)-xfp_|LU}7)p;+u&TtPaxr!>+`E!wwZbxE zh%Fzn1zdl}Swtu`k?ayHpILL~fdkO|IRh{k%hdw7=>_A5ST7Cme%Vm+U{W1c>rdKs zk{42)bq@E@@87#m&TonGKQ>S>@{5L2jeA=sUkNXE?sz-FjOp9j18t_dWnaM-%-Gn3 zYCQ#J7PB+XmUvrHz~m5=qF_rHj!~><^YVZANAsr-INgl*A2yS`9~Lu3!hXkR_x70J zdpWz~qX(8ODjVqH_^_GwG{l*}l?LRYgajms$P=?1HEeR^WA1TEQ^T)V@Pn3%a?2Hg zCfp_52Km0`ch{J&e*MyN=r>e&TU#6SlI%c-3H(Cgkh7Vcp@1{&{1J9uI$AtB^qZ@% zHh0(w!vJj4QUM1jxV2&UT{-{nUSiID*58;$$VCGNmiSmmr(XvIq?0un55I3>ryot| zrcF&~b3SSB)#kuzr=P}?a()44Vd0U!MwH$0yLO>ovg?+|;7UInCp?VZj`QfF&`AFX zb9H>XTy?tWDOhR6`zg0^&Ee9%HS}%CSqz)4RgR(1B z`n3q)V7RBNQ*Hu~K{wh133F|NqVSL_HqRqwRf!8dRYw)K@fRW6{Pxq%2Un1(oZ~HL z@a2v8cr#gjLYwmup$-#Bu{jzwt_*YVjT;>wH;0b!Izsp}Eyl#;0F@wib@2Eb^Uiis zQ(~|h;)&aw&nmn_a$Hu(zdGo%Vxv*Om4`(f_i#D@=oc@PRUmqGlFZwRt`2p}n<@)k z1Oo5E{LHjH3P7D0Zdy$xV{v5o(pVakc1%Nf+Co?9xUNbI6e=$p5N>Vk!Pa+nf_+4+ z#0PNRX96>&0I^|O2N2EHh6qLgX@g+d*axEk06+jqL_t)W1&JelBZSOh#7;tTSy+@l)41%l=F5H0B)`t4-O2NpZxESHxe_e z$gAaJ6){&j3U%VV^t_jv7jP1QbGh@lIt+RuAC%JH3<%mp3aUPD)f*fFQ7B zW~DrYjp~w$zcVrC4@KMm@B*#`Ely2LVgg{EV?I{(4Lc6rs2QhN@L}QeDNK!yhQa{J=RjC%C%f9tPD|WK zXxuaA&Zwn9REB53#4zP`3i+tSeOUc(EbEdaVmv)PJ*EpYrHeJ=qobIu=hb|ALNg%1Pr7U(+G*qYf7bc!*uvC^Ele~V zz_TSkf|<@S%y%WHCS*a zkZ09-U-50X?=_$Q{1+p5HoD{FyWADJNt)42sS2ZzAHq#>dJVn})}aCU%kz;?x?sKw z9SciQu3`G?&E^ENntZK1XSoyLr7wAjdGX)=t!c#sT0M>e%!`_B)P8(B-M2x%b@4of zvJ+As+Tzga4&$VUm+>u!zD#r%yhth>nk9XwXHCE3+?vhZeDxR^@tU;bPdqMX+A5E{ z;%sDm0yhA#*<`8Wj{_WFNqs;T}gkWI5DIdIzKsOW!^)pC?)SlTW-l(fHXH+xbwLE^7Pc zDhG6RRO^n!3KQcK#bN9Mz|tjm0dV|BM*56}b7Po|Isw4aWr2%8;9Zy)2`2zVVT4r6 zbeTx^CCXC9uXKQONVN8qikKM|!)W2)i+^l*;Uu1H|K}NDv@SMLTn&g$&h6OF+JLh~ zG9b3Cos!}R%S=`Rm^L47v7*wH?k%0r&f8u(QjXAmoIx5H8I|jL*!gBREM!(30mGuk z(M9Tb-me+kqkAz4qFboI17wdgoQI+CWMXoPlbXUOVsY5gI6yy+wOXN3yn8Vd9WD_e z+d5cKHEN#v^e3Cgo_)5N!eq%bbVg7q4A0Awt^{!^ zf-KLxh_m4;%14Y0e0s%4al|np%+64<^T2b0)NfNneJ<8FnvZ|x-^>s@F&o}!2mZtp zPB7ga9isFNjTN{;e!6nw+u6x5$6ITMn{5Z%$!o9wqq*+)*E3+#1RRLVW)M)Jg=Mf* zxbi1mhn)Z~c=3zOi=Xox)82$N0PF;q!PU9Zod8Okws)j4Mkk!1PiRY{qI76lj0``L z#HUKLh^SSjSRz^}Vr-ZUx=;Yr{=Xec0P`Ch_ru_S68ELo;k!?CIthTc6bAjAzp|8l z8a_oXZfu&GB9r2d3p|tA9gO^i=MBmb~~ zQ0C~(34mj_Zw)FrEr_t0?HG_ zNKe^DcUXvBOyHsM*<+L2!QkxgAHoE{4Degrprr^sV%$Z`GxHJWGk?Q zlz?}b%h3sd(b2KO=*Soc|6KHS1EUal1KF7QjcO<+%s+*o2raREi$F+KlZvTzPU@bm z-6^t#Xr3W}dpvKoYNZOjc3)%@>FTyMEbWj}6 zQ3g#hFj*nN2a+8nHi&)D%1ecVi#7ZA?UxCPI__GqjFh5a<0tN93Q>x+(PpE@2D1&b zmtCEmawLNFpGPBj!vO9AHB(UTwZMrIhcd9Jhh!N)>?z5EbnAn*f<$hbwZQ?bWTWDqGC5{W7#}m!=$x{}m*b#uf%gRtcJ^gB zQPhtcvZQ~D0Y3ZF1x&1O#{>WuklEJ_=7r+;axutHlo2A5NJ{O)FlX4CW!s}qI*fx& ze}|;{>8o!LhHu^hER4-@2LNTC-$3RW-@fCH!>$`L1BN?tLb}DPW`BfYWP{^>l%I_E zrY0wghmRb^1i*rf_iScrHV?~57#Xq=4P~B>2n61R(a}ID1a|sJ?~ma*jRlRzpxZq@ zCV}%Sq92&go)z0Q6vV>LOK-QXeoJLWc3F3aMnf@>+AUFeg~vR4v@{V0A<&n^Mw% z7ppHHUJca1qW}#H&<9Y&1i+`wfrAGfiVgY(W5B#tY#Tml*Dk#YKse!X)XAWGTTUUS zZS<(5iTu@x!vFp0PjRIflwxiuL*~R7R6+iyv7+>ufBTYo;3Lj9yY6}~^MF73f9AsX zyxWY;OjUqonL#lL@bu?C$DH@V7n;8I7TkO?Wu`Gv!EY#cxVdkI@IhD+;|ZKm4#mPF zl)yJ!k9*_#zy2dfaK?Cn?#&|rOUv#xgN>5y1fbYIFcfSF zfwbtNZzWmDTL(CWXe+#rzI>f14Iv*)hY%ky{9{0Xnai!vSRb}FurA=P&q2&|vU4L# z94=Pwx8y+5(9Tg2GlV=Nq-UgZqX_|pCv?u6J-6LvrqN&*ab{l!-FX?y_#uxNiw$Z! zjwSG+E&v{jV9`z^?`-7frT$y|E~;cs*d!Q;mCPhR*)izFovc+&bl7BEZW5gfCQ!rX zFQ52i^Z3U<(aeqwnTb(M9^qr@i*l6wh%`oWCBM@vTG$Ouqm9e z942qkjtF}(c-W7fL)ZM~niTl;?4@#6X%3I#PU{bT^i$?;_kSQ(*v~V6_|4U38oP-0 zX4)P*QbS-A63oDCOECmXI@h~Pm%;us%AqkvIQ@z7!H z0GJacfb?@BQx4U-3*V?2r&RM1fxx>kIyS-OYuOUw`h0K)!mh&TMAE$pk<+*oW8lP; zm{5zsyQV)iJz;LYV=wM+ZNMO##|iXf?9fEoB#A;g^YRoIgRBgF@Yu<3nge#&CSbNWNi zFmHM5TXBoXkU8=A9cJtHV{k;J!?ZymV>8<1fBxvl%~ikrRYmBR9T@fKFQ4`_bHU%g z-0bY@GP9G=eS#fnvJ-#f)D=LUm<__Z0p^M@6U#IM>`S${l&sgS!=(SNZA-ert!H0=29tRrCy||SfiamKKK(=vNB12iX0swz@zt|U^LmeN)?NC#w^Yo-YzpY)E5Nt)8 z#Xhf0wl5~bNS3lKkKmyr%R~SJAm+h;7(jQrQT*BA2th`*T@9d+fPF}J3x zyIXJ40+iIr#SB`q35oH=rC#D)igh-c#;c4aM4;Pnm(u5I9iiQK4vJBX=x-$%T59`V%4#?doecvXez@)1G2)#Gey^SahdJ9Hns2iMGTGxP+m2Asz^Sq}cSvQUcL6Xwj9AK#uTa{!31 z%=mEvtc;u%kcu>j6|qzR8i^NiBxop&H31$$mdBBXfoW zeD(`p%7b{F!6f-~+|C`~NeeiY3}fc~{tUY~2`I3c*8$e!%Ecf3^e5)zXMT26 zrhK1rb}G*L?{9p|Jnkt^Gk@_{PlEj$a%;#jP}tVjhih1ILq*#b9G`+dmewsDqA<1& zM=&qA;PrCtRyq7D9sZan{FQm*t6y#IvaQ$5;Vkitn*`vu01v+jek0^_EFn7);!B4T zv^+!vVYY)rRbVOvuK=?Jm5k+2MZhhuc_HwwU3W3v*os}5!Io|w_X8eI01&4uD|y31 z->CJ3!6QQu1N;F8+kIOQb77F7-qVj^HK$ED}i+ z?KGP$==<=SnjR~GrV5WN%uZSrWz-cWL;uAXnbbgDC!ptMU=T;#(BBPtWzdhtPFh?i zF1MsGA(xc5-m(+L{8I=D zI2X?mr_2bUJDnmBX%!THk!?JA=uJV*Xc^w+F*>^R&7lCdDRI@h8?$yiv#u2x30Tm< zAysmsIx(2A&2b1~^Ocpl63eqyOs9Z>M+ff-U8L#9)qI1)qu9n=l-11i;knDNODyh& zKj@NN;fHh;fsaMXnOtrI9Kh=T)Qs$t04(EsPk=74Xy*atjK06=!$b#f7g&t>?Qef= zPCxB5^Zxg~FNS7ucw@+x#rbECIKw>exzDw?0!&~M04jH=OcA8ZBdMxtFray!zFzF=svIvE~)8c$K;On%_g|YB!X^_CfbbuW4*s zVItSZWJ@eqsI8pkrMOE`OgIah0aiNHM#=-^#5p6KxOZ;wJZZ;~=X6I<^Sh0|BF-P5 zbn3}C8kn0xNsYSzkD~pX$D0$R(ohOU*pLvhxzD+47u+O31Nsr0ruI1y%8M@>&`UL2 zZ(1j&%`CoyxCCD6tmY<+A?_d=YB4%7BTyPSjGL&W&nRIFvii*lOxD|QeK3ZnOr0*x z$$*GdFou?fNR5{6>Bx3S@n-wQHjP&R9>!IG;p)Evv;FSs=?avKS4~UR5!16Gz96wA zS_JZ+hX70+u1mr=ufKn=fN|311V9P4uJ<6y4BLDWh=WIta`2BO9$W|vVJRn5fT_ZS zV(RK(ek5VsFisQ;UFw5he7v@A-)34cYp2;nrZ51e8N>?stZZitpv9jJ0FA?=q2d+z zsS7Y@DEV*_ptY^VbmDA$^+wY@eDHvo#1>nw#_1|M{h1eHw$vynTy$H%$Xn^+nL+L% z=3B|3~@<^sZCE;&RP2oKd{;iyt=lv^S$8+l!+PGF$3EbH5em^z5X0 z(chhCe)s!7n%BMVE#|UIzw2SJne%*V(fWqByx!dB9;ceIVJM857?)cCC=0@5d6%YI zIgx~k_R`HguIy$N5>~wSdjg1#n>hLG#7` z_;N|y8zGQ-5l+~7oU|)!;MHg=;#}q4++9(RHf08F%eTJsJy9lFE>3NStISkNbc6iISH5PTL&tRX^qTIzts-~zoOI=uYwQlAUJBWrf_9kgF9-ik zIF7;Vr3%=c^)H|NjJe^)-IbEDBJh9q=VzIBzw>S8p16B{8oE!WdGrb8KgSg^j1MV6 zp%ihbDy^V=n!;LI9jT0;z~K^b?>wjpTYeX$eq2?i$qo0Nn-L2C-?@DhDV)^QIXJ?L?g)gvl-3 zXjwxNL;#RKD{NL2%ow)8^B4d}0X#NJ|03G3E?gz6eLn}8#+ziAOC}AvlwUw+DWEc8 z@wC(2;zzP!VL$Hhkt0y@%*IXg!e1Vp*}AnCHnn7WjcD~2rhe>rCsO!G)Id`Xidfq`$B7y{Hlp|;>-NY%KxzL0KgIt)_L6s&r09o zh0v$71h!D_{RoxWA?o~SQgiY4V~>G&GkY{lmkBIj1spm6Fe4r`fDHCcNjfwzU=bTx z%ntH;yv3P$b0@AZo56}Q&oI-3^jV&{sd~vvJT--pHj6)qjPANs0LXzq=$8Kla|AP- z{LIT>DVh*mHZ7MV>99#PHx^|plnt`Jd0&dM?%B6q0I`m3M%Cu-#8As3K)bH>Ko0!K^MR{Ce}8NN%Fsd^ZO^A zcsyDJ-5n&?LOF&AD;bB69RN5d+X)*YV+j0U9>84lhilB<-M2anYv}FEO@6ZHp%MSr zKmVzD#d$9?XP$YcdDGk8g?4O@>FCBvf8VjDy{i|4d93VnmxRb3We^YC9Q3R5EN~dS zbCUqv9ng-+<0&X;`^O6|fadM$Xv?3A>L4HRXJ?pqz3c7fp1XFMi6JP^z}uk0j~ zWO?w-)rkP+!98lLF4ZvBl5oWdafo(lYxgmVETFlroM>!6XHiwvinde9q5fZ0l*Sqp)>z7Bh)`UD1w3I3HnLBl@Iz- z$s&+0i?N~`ArlprEAv!*6_;&R<^inM@a;5uI_Y=s@^k_c2}*;3bhxFny&Vctv}G(G zJp}^YDRc0^0nuH+BLyx$`l(Kd5c48DvczA_8|+1_)^*{`DQ7QfSg5?i{s^=JlXm)s z=?IAUsi%eu1maT`+RzEvf+Gd_`J_1g-z$Fn6Vr@=SsxS{_4f9m(@|%h^wejX8GLk- zW-%!xu`Q*~pFa2ubM6aYY-T4$%;fkezO|T8VgXo&YFRMFFb|H`^!4o_g!(hg50Ass zoy+)$OGL;CRssWp;sks7)`$s!()dzB9i4GZ>d#-+7W`q5D!Kr$!b)Xpx81(ieEnNc zT(^-8Ixr`lc#_$IbDX@wlB+|~zB`@>QypjB8$887UF`Ib!`zwFfQi5%XiEO^kALQ3 zuc0?{k>8At44H5J_kWqEKJiKBNl$sY`OmL^(@fy_)YfB;Gd+FBm`3oEEBeAG@P?C5 z9N=^13>NVZQnS2dr%V*G5f+oZ?a&Eu)vvEHpa1L^0?=Bs-1mVGHXnHZ#peEZJ4to| z;4W#15O)GVKG4|7pS(f2l@;y$N2ILkY;jkdJXK?`5px!QPOu88Nyn6p#!tlVovqt=qfupMYGM)!f3cd2zQ6+Hl4TmP4g4>RU`@~0HzIZFyREBw)>PB4uHEbp~<$*#a^$lw_a^@*2d}QE1IzDQT06%R-H=wx}XSArW0%{&ud-|A03pz zs+$-fwrKO~TzmsFUOYp{Nev)+YqomLL{0us}zcQ^IEjUwFG_BYExjM*a={#b z{PCuj6M|S3;tnXC1au{VmeW}i1ZX*O6QJB`O8P2V_x~n3(Rl+aGh!C>NCB^>@=O2yllWVt<=@R)qZrU>=u8We3I`9c}0HO zwjkgM(m)rl@CMxe7(RATqdU)uo*8x&Saqb(tUN+5I z;((TJKrx)noWwy}9h5U{QqBQTXmPPQ2&SGW)@u<(${@ga(-m*Lf*8S{&@M)ZV==a+ z!>14NQU?M%sge|bT$-W9LySP_ z$BNtzw{OJh0lI217R}18GtM5 z-GFV?7r*xsnaCJDifxwoXmH0JC5k&6WXUSlz6Pw0QCgchj1gJRnVLC2XHkHts~mC- zci7@_oj;*iWjMLT1z6DqfOFP2-@M!W;D=Y_OJv`#p6dG|1;Q!1YuDvcqu*Pj8gG1)apZm0V;yI5ofAzR0noocJ-_3#k zA=8Qhe+O3ko1jmo9^3lo*aPP3J#`|WLx@gQ*q%BbcuaJB^%`1+;}ICWQE&V)C?YJdYco@yt0bt2L)agxW-^@CL2^!9hI-4kKc@h25kN5B1Nk!MvsAA%sZknN$+b}@ z0LX3@xSar)FW`)<>;S;PnZmM4#bK!mQYRg8T3pi*00kD(P3T#iscgo!{7%eR#tqNd z503DBlfMJNO9R%@`PVAO7=+{8XlGZKoHvfARf&5RA8BcH(a=bTQj)QB$;;YHA$1^Z zp%K5erO9++)t-gS8B8{N6WF>rJc=#rw(;_liK5ngvgsf$Hh^tVC|JZ`Dp@Acm|3o0 zT=g5>R%W-%HeieP0#2Ope)3i*DSOQu-)3&S{+6uhHR*r%d)>pl{ta&unHz^(%}vi* zeE=jE9Jg!?iPw*eb2HdI&M)$g#g927po?`l@mVaMWc;PV?B_m;Q$yefd6k)awqjiM zf~;|+b!KYDeD2>jsxmG<;W&L4BvP-IAGl! zgMz8czxU%58Mp9j;Ke(#uf6)0=5^=)ojL2F55~28uQ9*X65P6c%%Jl|1EBfP6M$@>mosLS8S0Qq(Vf z`mOOZ;n6tZswmp^nUZF_qm4|hfaWTXboS$nORP*^Si=HY)-HTZJ$Rrxlyr6 zi9)Xw@BFA;VU&qMu%$6cOvyZiITG^VBTsa26dg=RA-8y+`$VmNU5@jrIA^x( zBPPDNz)096wbsYs^pXs$$w)y^mL+$T3_y~j!+%Uyi$-u$n4BCdpuP4d0J4%_*Z!Lm z09ebwoL^ydRF42O@S!cIG9`F&31t$gh&NM*~yM)SnX_mNDgpLvi zd6)m-`#4gC9Yt%%Fk5^M?B8o%dG5Jb@qZi!-QP5aMyJe{t;d;?oO6EIl+$M{ahd7N4$TPTPS75(KftEBtfnv zh_1kgtrT2x3j*@ji2-|SbF;YF>2eMGH12a|I)Mogc(@Qq2=bE+K05@QRH=5NV*-up z4S&22?8cxU0}-ag29|gWjm_u)x5=)JU;pMB^QPCoF(7TNST275yG-{Mtcv1lK;8<# z?VB86k+sqbd^KWd2js)y-(qj(1KO1~wer51au1V{h zkxM6$eB<#}`pGwED4W7aVk18(qjDUe2@|$0=4k&>^G583s&%t{=ZWT@Kk`9y#slwf zCWeR27$%oF37}_(`MoK%6M(sM{90U27lp+um&H+0LF|%gD&KT)j)6Ps?a6mL6*`70 zSYY66ins<_cpab`4d+#|t=L(c?9W66I!T43U~ze` z6hIk-dmzJMKQOujfCK-+%pzbKuGMV(Cm`jQNBVW+%nEi3hD2w^wu9 zb|cQNU-6SG%ubr(V50TiZ+V;fv$=4 z{Ln2sRj@E43I(AQumv|)eCyK7%{SM;%@tdBoM1loi4U8zANCM4H8x_#v2w;8cysuO z=>({*mXS-yYHTL}Jtc&e@}>l6iJUDUNQi=MBZzh#UPh+xtOt!%@yXZf{zlce6DuITYaBar|!EhG$PG?bCdg+kTRAdFE z_)Er8{BwB4h+_Lr9S9>b0buikymOM4`dT`=um}csXiDcfgj;1BXy)+W>Ufi}-t=_j z2mmuB8I!RA8-xvDHtfuYEnDP&wjP&WHegIFvq4zhaXSD?7r!qbzP)rT39S&kG{#6> zWT%!Z;8tTP=Q3huaClVCp>s&gjEb>uN0+>v!^Wpm>jZ3wrcC1fpwIyT&Dx7KL)bn( z4LtzVw9SNcmX%F!g@)0wMTt#xC$`n=grDN!w_t2+0{Q3FFzJ|+zVMNi>;-IrVm`@J z%eM+hyYYq_QQFo=s`0xiCrepqRae|)#PiEw^5p1cuDR$plH;ns}xL0SSRi)QfXQS+6rWu383$GM(-ySjUD_dNB2xJg0e zF2;Q2VLaCXL%~z<6CENd0;IClU;Ofy=HR}A@d!2R=Ji%XxV@mKXPYWV!{D2`k!0>0 z?YYVh>TQq%b_>HcS-lw=LIj{!mtd2Caw=Ml@9<7CWx>7A}T zVwjVZIg0#uEK(_Fkd7~38i6wAf@J~#Hw<;)hN1i2|GuR#ri3XLE-LT})JE(Alx_t( z0Fog~yR-8RFQ(320NfdT?8vye+Z4(%1^cGgTH(Ng*@CvNZl2{T1#Aho<)d0}-Bi%cUkZNNJ5tAxl@N^^sI29y7QaY4ii8RI2 z^M%z80Ys0Uj_^Zuz+X&&LOx{%gdodj z6Vg)AyG|NAs9n%l8Z{((!5qZ^07C*9Xv<(b3ZyREnEBZxGpM|YRRsTN_w;kgMv;xe zV$G5Melvp)G*{N#;GZ~|+f0KXmY^DI0g0rWt4TZt!J?#_zH?9r!-j1R4V?N(fJU&^ zs6&x)M+Hq=J9+Bb3GpiZ<0Wz}G=|S&RlI;f0|#X+BjqGsfKxwC0yLSPW44>2=}Gh4 zr~fUknp#`ej9qle#bz6n#S9-lZ04XOjRiz`Q>k@j;n*e;>m2g-SQhb*I0#e^7!?t5$4b?CS0YQ*?W;Rz4>h z5zaYJd8ZX+J=z#}UZGPJhTla1?9|=kKwy)5xp*&>nGR3oq>q zBF8D;fjs?OD{@Zsg17P{6Tk=F(0_(gSE`cfVR%w?dBu@ z@*(qtM?b<$;~O&qE#kA-(I#`Cb^w_>TCl)_iX!nrn z*PR~mCj<{|4kq*oY~cqw@Jh2dy1Du)I%L2&=vz1UIOP?dyHh_tHsK}!u&~V?0NMqTvWiram6CFsXe8}) z$#EcleWdp9=%Ac*>(h@}aT>0SGp-KI#H;*|pimyT9RS=`+>WzBi#5YIJ3fsY3P?L? z)l||NJ*MPx9j5Bwwf738QrJq`j%%y9k|5X+g{v@Ba56muC3}eA+O9B_0E;4qlPU2? z0apfi2CO1u$s`Te-+=S~$Q#f9Yr0_`Xye=pd0N0gtqmWUt;ZZ=ZrOjpJpXy;qCw75 z3K*leBI1@BSZUb?x-b6VheSE#7`DUC;%dP8S@4gW0i-i1xn+(60^+%dy92-!OC_*D z^f2kbE6tJFSHSaT?qL@Z-o=zT;=8V_d5^lH{ZYf$1yDH z(dqMA^GA*Iy?s4;#TXj_8jo#}7;v~LEA_X(d@&4_mdf$w&^CPYvdhtaxk&}VTDw1( zw0+i#o@*Mn6wT~B^mWuDA0i`^6G#l&A&(`@YA$2hk_$EfvdBeFkuK$widLx@kSk7~ z|H8lH*veG_c+}!k84Xzt*Oty+^MMb2z&!4(GtCUXGq_sV%tMiw?gZdb0R0BZd@}6e z_s5A~GC$5IQU;7$B7>#EN<<5S?9d}{XIdge5h~#kjcSAad`lGZw*zqI4c|*#*JYZp zTkaueJfa-dXd0gD)|+mCe1j^;`+^&>1zkHP8mE_g2kY_R+QF$oBg-0~XQOtKJyxz! zD~^Gp>r}`7n=Je?8?M}K*;^;uANiM81NtqqW|)<Z5Kfwu$UBx8tZLy=?5Vd;#bp zjEzm2@+MeqYQ&F+DRO~K^dBTDY61GbFnLaX$dfOzR34;y=EGMp6!Im5I(*!4#@kH* zG|bFSW3Z9B17NwiU2ztcE5b%@+fc}?R`m^2Q{D~$)^vs}7sr6|&zKY+~XU_h78Srfh5=vl+~w8a8!BhcK8E9~%8XB^IXGl&%vcA&X34%EyD zeq7%2W9ML>vAEjFBLLD^!H51F$WsRbXE;lDa)ba$R8|GpV}n>!JON*+p><2$GZ+_idET;IYa{k0IES)=~ib?YhF{VVImyMJbHzP*H-eqa17-Kxb;;47@C9xocfqdDA zdH}e?W*#&9U-;5jP!Hm?tbaErLHf3B6`lkF6lB|Sk$kg8efTS%1^demg+5#r;-LBW z*Z#rWw)=J$t>)i%J@KyQ8PEJ1OoU8}Jn)-X~DZfs5_mQ!`WM4X=B%nZ)F9(gaA7L967`+R<%3^zjdwXFTDt zIthT=3}&#JC`SNL??gY1HvU!yLqe6;agK-KmQCuB4JP5Ph{}M4$UWQAaWxwgzsI5B zw;A$=9}fPh`M(}d2X@>&*8@ZEQ5zAKzxRjK2&Q6AfQ1ttmzRp0OXqW!4m?C zoqLwIguO&7O31}?AJqn|O10o_`OuJlZ2|`{&}I%fo4JSzB`Zb?IHT8sfp!aT8(0i7 ztf37KKnDOCUF`^va+j+x7fG8Bqn!v8pMdeKJ$ELMC+t|zmyJ0aHY#W$ex?;MKDr=) z@s)SHO062R4+;PWk#1c*wQAc$_=-GiROyEt07kavLV;mdSGW1}7rtmd`^nE%lk}>^ zdGp&}XO7$6C07G3U@PoAb}?{!r7K^SOWY)V;t<*HisRa1$i}!tA*qCAC!PwPo0KSC zb@7+=orj(9U}d$4_ThK`_gm~rSz9_LazR@t3!2HY>?n!v z1$oK#kZ&GW;Z7({n0DeQ+l@R5@PQAfV5?29=GHCP1@&I@%)fe^nZ@9L5R>k_fkSno z@Ou*72@tRx*>T+Wa~Air;wPUEA1=!tj~{_!paXF42*7eUyo7@EA!T3;4~y~PaO0$t zPr+UfQ4pP`;;-8CMU(_{(F9 zkQs3-U3wu}>JYoj+H$2VM|mI_@svU zfVu5Jgo%WKL2Q@gd2#Ll2pVc25q9L|i4Y}pakf_PECmXbr-m$;A<=FMvCk^wSW*(t z&H_t5bLT4ryy3>{%_z2n7905?0Y;>emAr(Z6T)2_yo##{<=BX$2XDUcB6H;xSB6My z+}72-)m-$R_n5-M6js0{a0~#77g?^9)8OqgK`ti^ePhKTKn~iylQDDpq#;j5)9 zvR6Ztf6gvl3V6wubsVdpmhnq50WiZGR@R8wx^=6$|LONP(>$w=V^LgvB|}1AO+KocwilaQ&HFy|e)Ga- zKf}!77{I{cBWMRFvFip5lbrx~lhH8oZvorKxJmi|BGoeZw+qd`b*)LZBa=Q*Ewlr<+3j~o# zCJJBy;{dMy&o>lJ_vn!6vV>;R8z9)U)G`NpFtJ%c|BZ_{EMINU;=-%`@+<8RWB~=w z8>40+hwPKecau8+u&9J>|~?4u21_k(Mn z6oMG)O?P*POw@69Ky#gjxiWrKlpCVXi(cTAK7OzHU9q_O`6gbYis1Tqx2O$xIAeEdNm4mpXYMs#9!A@y&Fah8)z)#@3IfN~Va>f!B zT}_$VLZ5lwM&Z5<_`r5KMo=N$9I$3Gso0^DhD1>k^-A7c`P{Nf#!Ksu3G z;c5P9e(6{}-)JFt0a0sCuxKRbs*te!;J(bb#Pl7|3yZ2=P?@I;7qAnhh&JN#%fBDf zbR5O8E?t#Uj|L{@l2;eO=_!hez7ibJ+Lt2d0{N}_>(uZXhhbr%8 z6nQe@W_XuJTnfU8Oa^k)B*ex59K4yw4GPWB|M2ifKCBeRl41RraFOGV>{H<5NgngN zqsc2V@+*FT!EMm^*qT}B4!R2#Nf^c{>rJzkIe_c1QTENe_8DzG1t2JY6@g^t=x@U~ z2&P7S#^DZt0=}7|1P^%FR;f{plq_A`i26FHC40N%7LCwDaaRBYx<84 z$~8;OpL`KLh|IkDN!v!0jU74Ei5XE&I8<-Y;Gl6n3c3pT_|iCv1Dt;PfoF!rX1NKg zoP1c2ZW3Sy9gATorsKKYEE7*k^9g9+;mmKP23L)k-M8EZ>Ec;oE@$u~6y{LMAxJc^ zi12Zef_Ja-YAh-*+q?guc@-uBCfAS#_qV<4U1rzLoo4vZowyZX0v%RKcHo!;11d-}`R!4=?*W8Q2aSJZz>W>=mgPoVZ;VX{5&MGht%fl4zU+Aluv{o_T%{ zf%xK2Bx)K``ehm$=o2a*xiErG(<_{7?`7LH&K%EC*8}*nW5+S(l)Il2Myk-Z=hod& z_&bRWzWnB*kKxnf;nFUXUj0{BR|GHW&7*K55e??tQ~51{n?JS90f-kDygIPUs{@lt zEKUAuEfEWTe4(H`+IG)=Cr3xeafAem7W7B|jr~ZjfZG}M2reH}6mQ(6Y1>+&j$FUd z6mmtW07!C={PBuI_IXEeUABA=VmP+&8bIu#=WQKIk>-Tnk(q~$w?}MK+7*10CW>Tg}7<} z+xCViw@$X62`U&QY~$E?(V zx;$eR%nUTeQ{f6H=OUdRR|OMf_#Uj-H1PAhTX8gj9Y@w<@kWEi36QC&Sy{1@9RL=; zOUAtgo%h4|0=SJI<=7t$mZrpuzRlt%ny>lt=)6!7r6t{?sQ zO7mXak6rsl3!?*P#y@a1?fT%?I@p{Ra6Xi}6@L7)E6ssBP~U6N*z89r>w}`vGavd;boyo_ zI}FrGPcVgi!4u}$MJ>ZH2Ug;(7&7pXwUkxv0>H!F6-DT-U|aYO9EIAmFN?n!_)5^R zGC}p`HE(&N`N#8LY8EgF-G2zj0Vbe}1_NHcma+_3Oq~HqO zA#!l;?fg^UK9Fzsfaz_6$OArHRYa-Fu7)!o`RAxp8F4){5v#|T+i$(ijN^M>&wdd7 z@$eQJxrs*L=wbc%J(|M;$z0K#e&`nS*wMrCdD2c{B!x}Kn&AM4|G2sti%Y#&gqekF z0W)P%^zl5vGZaUKVB0f>tMI0#&|j8?AAURQO=s5@`Gy9<8^;y6Q&U1H{oNA)=d(l_ z@GSiKPzh87h19&Dk2^X}U0KQFY;BkTD55=K>DvXMfcnwrwWcQw7nIFc=qVTjn+K;UV@KvA}hW6-N@ zjaCviXwzuW-1ft0HcM3`glPgquazDac6xcvUUt<)kyYTrKm^;Y#>d8`VU-RMVsoIP zEsG@R;VVTaN#hO(y-JS*b~gGnTw$0*-p9cM=7qjYLei1E$qzx(4FIc|0NA&8ucTQ- zMALh>{_qxp^hF}1Nd{a4+N`G5X48tnbR(25e(d9)Fqi(7J~LWab<&r0Z1!bVh{i8;NH64o>#QowV9rNpevtMWYPx>`J5 zNH04ucpyu<0SN%onz4S?J1^;;2NCyPsz7c~?v&oiHuQ6re?;o)PL z@D=jP{&xZNwRg0egGUaUcf9T08HsoZG9Xsd`!#QVgL%svUMpJe2M!*QTT9bt-6o2xBMiI+RCKJR^d96{CWRpQv>$H`>gqz;k?m=LMMprZvhK>Yb*&W@*E z-W{BWz_GiVZZZ={tiA!?UHj({kcN0RXE6ga20b1x9^8Xl01yr!?j6`PYnua&kgGo2 z1i&L(+Ojb}(M5+qtUcjmUynOT#;|C{n{#An0303Y?-sD*izcj>UPKAW_Ygj&k>o$5icg_+){#uoNM{nYfd2n0v6G}2REL9UdNI2Y#L39=C2 z!rb7w3Fqe;@yIzH&V1sJ%@yzVq$Ye9136VAZ+<4r7#o53h=mgX?L23XPFi(F<4r?J z(iHApC5=k6#pckux1^fvM1*N;&1VsEg^zEU$_D^*$Jt!;F{ES3APEm6hqfR+dZ~l3 zxhFc548gZ|?;U0m`P1$HN{8cgB$*&3-=e9@q$LIzO~_LVI>ECT!Ci2{Ys`%|WGTh+ z2oNe(|K}C2dYL)>w0oP;;Xzyth^?7C>nST>C?b%XMzKm+PTJED@+LpIRlNx^J#W4PWq|WFsSJ=pkTj>Ac3*Syu9Luz8Et>Lz0=F8XAL5!`2~SF zEv_9Da3b`OYTFD5PFTt03XiQ6Obg2Hf56{wU-rYCsnizOd9V6=^VZkD#?;|f6z&9= z!b%$_z~#G#Z?g^=@}{7%F&cVw2+|<`(GVnBias^-_hIVfK-v6;^!E5X?Zvnj7ZKgU}v2vynT; zjt6V?9rRIA=DoNPVh&~Oirq?{WKE?&CBx4v2mD+x8bzNOoj1hQuLQ4xZi3QR)gQwc zJ~khN3-iN$y9+9k`fDb^%Nh{R;&`c2>_|6@Z;aa_&BsZbE_xgY6RLGCWj)Lmi6V!SWQnZ+!0BL)Wnr$x4+={T2ri~Ly!Uz~j4amFTG zx!BR$V-AlDo0mTSr8v^TMmlZ+Eb)LDsD__vAiw}$zgq@dXp_DPq zRmq%{M^QS3lE}lTIl;mRD`vCxM$(HiBXCjn@XrA&%Y+@B?_74dw>q+7#I8a@T5=qy z_xALF4crp!&N7l5$0hg9Ln7M-z@dI}t2%Xc{Q8=!&Gk3loF{V4z&zv0PsaCQSUR@6 zj!lj7i+1N@1LT$F=Z9Bi50kW?V4YlPfClaYI^InvJL(a6<6AE@M+TtKbq$*HUior! z(YxMhxQ;k@=&kvL8hAgGw;{zC`N{sRY1T@kVp`__^UIekX-cw8#;=*zTE95q`;rz=ix zGeQk=z!SJWwDCM#BE3QT{R=1toDS{j1BbZb2RNSpAAuf#dS2g*s{na?ghv@wDKit` zxvCH{|NF22S@n!2mj2HpRJW6aFtTp=o0{7`+PkHh9U^OBa#B* z%3?LeHz2|hlkpDV*sy_M&P;OkyNC`xXVj^zLlk!4J%Ov}xb>QYNv#k-8X&L<#N8?B zx(_&Y4YXowI+&IMmEQ|TF!=rFK7IV?|PSl#j97 zUB5e?^!jz9F81~IVjv0b;y;apX@1HO#<49RcqwrBsly7BtW;wHU~F;3ko zNH}1F@IjNnuXKQM)gC%x+yM}208>aS=Oy=0%VdSpFYIUMJYwSRNT79aU|)yT_JZt! zAoWT!-r&JBRu^Wu?cDh@mY-+aVe!2L#Ck|4f*xg!(t7mBA+raX)^X-YIv|NGN#gO; z(vTxpppDWgXS6Uw%hj;<&Q9~yuYc2g_=6u!iv+(sxHFn;y zU-5y#tU6cp+_Fr(l+_*gEs-;VTJU77oS9l~wpC477X2jHCpsem2V%jws#=c@%`heg zzy0m+Wg%aO{&l9Oy9;e3)?isFBkT$tQLZ}XDLZ_Z`&yvigh>EQ^a13u%P%*>qtG`} zgC^My8PkkO<7YqfS!Nzr0?uKgRM~=DN=BI@)A*O7~BY50L_>H zV_8O5SPJYS^4vc554T-5CYj}oWNwR z_9}r5g2bbotvH56+08bkLnHA5(7+N2UKs~-kM#LM<&>KMkoU21UJn~`#?jK%)gg5_ z5we0BZBp#%xk5x1MEZ3=d&o4J^t` z55L3!bNI)`lxc7_ba4g)b8P*SWNZV?75czPo&s0W;y^(htt>kpe`t~$05q~R{JQ z#teVemH-PpQrH)SEu|&3?D8g1E)yP}3P8PB*;dLiHUZ#)Oyi9YqWQs1x8wIQ{XRqo!TBGi^<+}V!v;;(+qYt0B1yRK35 z^k+ZQeE6R~V49KPLEJw-IW{WaDbbH20~ckppFLL&QNI|RS12I|{TM4}w_*}WP$Y

S1&+=z)h4p<$LxiiH&o%7kw)HV}AijM$Ges`y&kBshpKRyY0lKz5s;3``XXQ40l@PTyagd@6s8NeQBhf%GR?zHPtjh=zaA}mr z>mo#<7?+bDoCIK7#I~6$%$(VQvPbb_)-?_PH-<;}qu~q*&wNGUvRn%|Pq+`;|0i(l zAh!>pSA{{IFHGFFSFu1cG`G-r+k)g+EjX zIF9YdSNy1!&#frrTDI3b6@KX;Bvh147fkAf8=u+Pa7ct|OWNm}*UcE~)0EF;!-zU#3IpaHwQ zs8^+_vBiAslbcEl0MVKQ851PIE z_M5r_6#ycQSo;2SfXI`irE=9dwRr?!5v;G#sMsZmI#fSr<<7NPpjO&5OSR~5q1}ZG zIChQ7?o=m4S`S;Xz$WdHg;5j^CX~6rC$QMWGG3WrnE@Gy@_MJ>l`kRsmedtCO-0S| zRvk?1z{D%RZxSXrJVdm5l`dM}NSFs3u`_IO#xKt}CKRg#que+HKzrT9L}CIU9&O#b zH}V7k3s(dIm0aE;9Q1UJ03m#Nr90K+0!oc3QkfbO^DF_VsjCKk$~Ld*;0zrHReV?> z`LUHP@W3PR5t2(FCmG2If|EM{s1ely#0bvsoE9a3I{3@tx2?a5)HN`mE0PTf#a2RW z!EMBy+zev}R-5fQInTw(kVyFkp8>}My#4aG*$_M~UF%osMZE^M-L}_Ep>x9t0HxEV zVPpEvlr$swEvzGf;Ap2!6O=GEZD}`6(8X}+cP=v@x+F^LP28&cA@^~1KWgqoP(f?-&1 zq$drYbl})2j}1x)eq@r^OC_2flX5=posxV2lNPo#e#n{(@!Sr8vGECJq13Z93D+=| zjXVKh3x1()#vTEnFwj_`Sfyg~Dv4+HiIEQ|Jot7-{Lq=@;I|o9mC&%$AUXi}p+(p% zmE5>E$R#T|GwU1Q?HC~(iNSJ3oL62^E9zp+6t)0rBVubKYpRFTw+HmweE4DH0o_#Y zMLht7m4Un60l?b^7|j*2#Ptz)X-2Gokq(Q`@CZQCF$KkH`}Q9QR^%a+h54Uh|{xU*Ut*tbNDM6U?h#{SRhl3};_? z1sOEY@`#4pZK3Rr@+j)6ENfN8Ema;~@;Sl07%OFsQwBGMjCD*_t9cs$c9Wpa+;HRd z=B69h_DX7AuC|~PSL7}swg_ZLARHdQJgeeOc!)RK6A59T5EkhhVrK;xoxb&*@8deD z0C`^WRs=4sIqT@|HfKHTVPFeaWp%4G+dgSag{sndN#{_h>}2ChgpjxLaOVnSmbU`n zd&hFT>gQLPPkr)p83`pYWx%bz_t}p<+kEbGpD~^Ih7b1-pj{j@Gn@qAK!iIoAZdQN zY0i@QOZX%_a^%&I@z&=YXzfOeX-U&T5r3Z$?{A4&bON+OI}T z0?c#aSK4Y^6r?vq<)>MOSP7P{5i+3433`rQfELq%bIlX;Gv;M4Io}+@J;-apJPn%1&wAupW@;GQUPnhzVCa&8HI7~G z9p*(oJE5~eTaxQJ5P1A91)USD6pE5z)zF?D$5Jcu(||3r1v8An#t+xXW&u%g!pWq_@1nDB=E;!8eg_TG6Yg@g!_lbU!u;zOK5x3v(H=Q^6gwQS6JT;0yD|WbKy$zBP5=n4d@C*4%Ecrv6F#0tS&n$H z)p94?w*r= zEtyFE_#h=I@&Z!}zze6f8oV(SzEjFwG8mZv@If||eWOnRps=|Ez#RcVze0Kzc_ee0 zn02o9EIg|1Ft|qIOY{SC=nosV`l2AoGwReSj7A`nm>l<w* zjzWzL51M~`^&7l2)~@y8XP#lc_@#d{z0kNVIsx2H0G>(ajtm&rK8js^`7K&!u@R+&J944S);<<1*Hs5QL16)uj$^-zvd0KZ8Iq~Fz zg5d`J^VEgn$q{9$g*vt{Y2*YYdyY6n8rl*mSLC5JsG=w-z@CKD>NyGQyaJ`+9tMt& zT3cIWp-}+Ak63htUaAaxX(z3fCxRI4>`NAL(}Bxs{GCi{o%(Fl34lm}W1Ek4v-_rf zV!m1C@XDvKsAb_S?~iL3gEp49%JbaEsoY`q1qm#io#nPrnfxHV_TdUjDldZsUE0nAZdi$1 zk5ZHw7h>&c0rA)z!Z%ce zyr9{%s9aNSDBrvekRxI8|CZf1o4vQ~Nz%8beUhA@*6%iS(fi*Hh(nioSL1K8y05dqTB;Jx%2J`u? zkZVyf3BVgsTHD*r_rL!m^PgY)){>;X+DIPskO!I1|Hl{2He6Xb+K-(8m{6X9BHKAk z&~cJfR^a$?ikP@tr8@xvFqQpCi08;)x(6XvmmKBZGwSX8X`9Xd2p`cg!5w*c9{uQZ zOi#xaFLrg-8~*S2rhf#h_>CN(x%FAa#nkoVueo+#lm*D~)O^w0V_>g218fsyD+#0K* zUr7R}h%LmZC_;ByXqZe07y9|OWH?jRN7_{IiSKX+fFIk)4O{GATXuwW3Sz~cOe2b; z5|>M-k~mqgelvyRM`6Z2k;sa>9{o4!1VE%L?bdk8Mj#)hgl;K?J3>g+j*waoet9;Z z&4(L!>PMGucIG6gbV?z*050$n4#|3uoEvVC34j2sD$5kK9Xrmm;ZftbET8VMk@0MP zVlEc_$OAUel2tqSCNJXWx%~LF3l9ERWJ(H~d2R*M?U(U5QMaSnm^=61VaBimuR8#u zP>XYUIfXSK7sQHIcQni_|IW^C^QHg#y7|<{KeM>xRw(u(&pOjQ|M|~1Q`m+%g^sQ$ zzM?(=ez*g+dU*$3Dj6$8Uf(vMwbW1^jVj@OfMdor@2{3+cUD%WCp z7Ef0Ye0wJ5ln;#;T-It6WH~V|Y2-JU1pj~b-UHCKqPiQO+xu%Y!3MUdQBe~T8zNwj ze@kLaK8;4rr$!<6C}6=BL{x~q#Tb*IF`|ef8Wf}iR0I_NQlyvH-+k}h-plv6#F?9N&luhAa3!elmySBY>uRM+Kr^BabTF`GAJ{8RXLXl^ z7kHfdTfAG#V!Y##wCEBVJV%FrJQ$Qv6;u8j}R}0k~hA|YxJQ@AeBFm zq6kKY49v2D=%6u+^NKU*uvAo@9HtuFefQpn^#mPM(NA)lSP*Unf_YeJ7^BROaGd~X zam}cl+fhlkcXhiDf9w_!WG`^Z<;87CSVp`0z z$f3xY-n|Np2CA;7g?%>&9kmM;^frr}HnkD9=knz$}^}GY21f zKlf)?nVp-##i`sm1*9Bto8){PEr;{yw6`<9%A^-Ax?!EWi?LKkl z*(Hdr43wjvaI`!3yl=VtA99Er+s_MA$7Lq~7cTh9k4VFdR5fo&MK23QiV_$(Ffr`& zo>SHYN%Loor8yeh?4kpRe>sW>o@D|9?demV`V{xbM?WHgS&wCGc)j0Z|4RI!tk`Xufhu^7cD zxOav9OCGa9h~hmG84O8sv9|^=QLZ_%HE@no12DbBEUfjqP7+;a`6P4b+;DC-S>lAK zNWMbWlv#D*ofL3^8dmW*Luqguu&bTjwxEOt#){zwc*=?bIMC)Kz`_*n0KhHBRHQ8_ zAU6F8f-X_H5L-Lm+zu~s2Z%GddK@60{gs+rf(V4`jjG{SIyTfDRx5ZzJtitDD)q^& zQ+M5SFAk`3n-pMVOK1tH6oo_#Q*K#nBK=5PgH-wmgAG$l8!vUkP7pLyjQ2mj^Hg{5 z-P^Ln8Vu6W(dj<=i8EZAo5aOeL-IB>cLK-`7KlmPs-(P<@~ACBNlGRCn!Kx^mqe>V zWvQK+fAnicr&50 zl*uc8ewEv|4_oRRP$efPV+Q~FWiNBRU6@Sf{4sB!#{_CvW zqZ5(NUPu0wiBHIw&snzOH~?3t{_{W2bw9iE=NUQ*h^ZAIG#vQo$3ND6`+MiP2Vr7n z5IX_Jc_#oTNqHwgv=adFVp}KN=f{!IT$eO3ZBI+Y+$AlNj^;#N&E$!HoUFioMANwR z7nA;-Xro^H`v0pd?@1Ch+it(<7I)`;TU`?_0OX{ewiRH`Geb75v#UeFRyU2dajvD! zZ5bbN50E#{BlMcXU-xj}C1Ck3j}sa6!+{e4rdaPKvadKvk4;dRW91pZ*bDMjlkR-Q zB$N6L9F1_KC#Xlzw4oeU+Lz_kO5tby0bo(lfiJ2u zb_{~b&thbHSssiID^Cvo8Q#KFlMJr4NOD%*I49K_xC`ROl{bFIE6M0i2<0@STCENv zI~*Lb13V$`qE?UX!2SWZXU|^nV9$)jjY@UjUW_c2#UpDIdXwrEbW-E=-beX&g+Cb?cMLilW@VLZxnondgRi`hDSc? z58OA;|Bide{f}^C19&136V21(xJVC^0J0MR+17Hd>SoKvDt6I+gJ2M7U)D=L#PE%v z*UcJvV*26Zgarrx(>V6`hmSkj9rL6=jZhY1xaP_$+#nVb`23&l>XBT5jNx?>NBEkJ znlTN5oBvz8+&CtSp1R{cnS|i}z6{?BVQSrP14w`=%9n*SpG$54NNWH~ZPN)?Gu_$O z2wwpZzxd%jTe8bUQ0apP{nYouqpkd<=m-NTSiM%3i!?pYJnK@nUlXWXEM6Ln#*r_7 zg+~DL;+)HWy}Z`ouS~ecuW$BD3ZY&@h$|a)eF6Zv0Z+wQzJYGWvI$!&ep$LPo`hhz zHUbl%z<5x^g3DWxc|Hej;><=*zl7Jwl5YT|d^mINSlz*4ZiBx?8a$;PT6_bZ36#V+k6K zYI*Cs-sO&Y!XM#6z{A43`9#Qt4iUzp8kq7mtBMh*nVR>I zEYUGvgyYY*hOOujPoRT-6IRq#vto34mXVWtEH7DAPevgj`)I_D9F#+L76Y zd_uZz*to&na@(!$?6W^(URH(v2akN1JMRbIbq_h>0oVz!7j4p*n?l~2newj#NZo-S zk*pIn#XGu z*I#>$8^r-~xJV=wEbRUiST zD_8%~i|}~^86j82Ie?2sGc(-vNnFF%%a(QGIK~xHhLTG+AIP>>b6tv-hep0aXKxNc z#7o;FzjYYc9~g_C0SqbY4v6Ryz(_Q4vrGIdXvx4q1Nm-<%pkPh`cokO^e756Tk<33MG87{YG_<>~F(y~~ZF^5ndMOdb%PCNE@+sgPzCmh07fp_{o|bl9lS z+=3Ocj^0k!g%vTLkNxtOzv{mGtsjJeHKds``{3t@;DI_$2Vl2 zxw^C>Y7OYQbWP~Fj*SP*V^{JCSe5L_o-y`%b0TRL!v6IQH=?6n?Z`{x#j_TUI00Z2 zAM}^a5?M%=zT!~6xiSWj698@41#sonSIcgkERDv6oOt|+*eTH=12@c~LP+?mqA|8p zVoF&Jt7bhJ51npsUw?_6uKizrFFcn!|2n4@Vt!r2Fp+FK~Zw zEmIktu?UOq%c^zFwwvik19-&S9}k8zysa$Uhw=y zVQ()k-2MG+x4C9acsI4;_#>%CjA#&KMcF}{P66Q+g{hf&_m17Sxcl**H#o90>{(V% z>%Xf+0=ff0GlixwfAiB?GY+db_8avG0Ab!AG%l{Nu8CACuSM{xhJZ!^u3)6lXib3% zeAfpiZSHW;XQac((M6Bgw#x2SqhC|+%RJSohy2t=0zO4_MnNwC78)i_aj%W)%En&H z69Agn$PIeY2XcpHgLIl&j!DCp+wS>DvWdcH{K0`06IXmy!GZt&<4UTYfie%3BftSd zq{K_o&N~2j=fJ|MBi_!B0UiS)`6v-mJX$0PwvT=UO>vbwJ$EY;6AC7lROUJvBGwvy zAT!wkFz5E*DW)=N=gyrt)-WYUB*bsRQ@~UNC(OPGm@ovAPrx%4LDVqFb{iV2&YmvU z-Phx0kvC3x_o?p28;e$V^LWdIuq=Q_KK3#0UGF{xD_fIpYK&)HvHHjlJvF3==IInC zwKPML)qHg8j&;dIDT$Vf4XjldtKzKmN+1aen6#qTj>erG>b?E_S)LjWQX+ymJagdB z_v716eRz-l~O*QOK)}i_w9E-yY%W}QU}~6E6WEx?BVX1qaTNk zFrIAVg;)@d?E=VRFDwEPy+EXaQHV!4J}7gS$zuqeojTru!V4{VR&@feTAcEa@8NiF zRjDH%`~dgeAAZk0;=xC`!9D%xtn-P$anzTXw3BqQHjmXvnaC@S$+J*c1*8m2-9FM_ z*y@IM9&O^xd7_nKWu@n-J$sMGz%yT0E%jc?Rf5`*$PJ9P|=3;te zozBHJIC~YE4HoK3KZMj`rtkPm(@NWGq3m%JBs5a2M|tm+Dnp~g5fiiRSyNw%(!DScBTAl!igdN=h5SO#at#J%N2@A6kfEpm$`tnrKy-WemYV?BhBMT>SdwR@ zj3*6Ue7O<^R0=mFVy_Nj_n`Fj z^th{k@pJd@U;8FeSCu;Ah{N4^Km5LX>>oVBjSmjEDGdI(y3SX1cy^f;oNwh3?@F1w z2C?L&iL_;T4%W-iv;Q3YPhmp$up^FefBDycRYRbaQLn!8DmRMgwRUy`kr{yajIR&h zgLtu-3N z@nEh7BYKGAy%%W`5b*6di!|Cb_%EVc!~;pAd`#ozD;qn6B-kHr#ln&o0I0-*jJS#n zq0a+pRvxuRP&1F`c_lG6%UaSS39O|^r{fy03}3z3DBG2`7Z|R75q;aT#Gv?jvV?7;i=6{)G|TppNr+^V}`s z^Kmjwjjj55r_Y*whk`a1a`cjDIX;&OL7Rog*0J`C~zXPyli7XSt3lBzi z03gnbqcrzKHJHd#>tps8IVfev@s{7;=x)8~=EVte(W&Ii;>k~Y8n$dd0B5T)0f$Z@ zF9YO6T)Mo3IVChNh)uARPNKNI24y;$n5;0}N`+zG%HtAG0FneL9e?=4_u z(YP(3zjWXu4nNd==fd;dqaXS(*$FU-+w7-McDWOPTkbWHto^GNPfgRR+(N#TdGsa7 z!hvqSa;9+(6a3RtQ}WsmUjX=vm%P{=ddL>IEL@F_47=;D|Fvss??Sy5Z~tc=(GOt= zf7}6}E4R`rV-RyNj+T)>szekNwEnv)B%mqFL!9;kfTg+UnS^I9f%26OpMMUO)Gaar zK#!qkqZ(qT&BU=U?N+PZ!ZtqCUYQH?6NY6zOjkGd8M!kzPq9Abv9`D#_A(=Kl1#!% zr<8LZ`K5<>F)VcKvh<8DeF*Cl0FW^W6@&IpTv}3<63mHq=GJEO~xzm zA^Zk*7&VP_P{#?0HmrK~ZrbQB_~DP;CqMSjN#w{@7(#@xT!x1|;?a1<@SSdUV#G~h z7XSw!){wKiDxJI(GYb%{P+F+}6g!c%E~%24rDDi917pl5`r$c1yd1%e7Qgw;Z{rI( z4R@936e*vLlWl-?%C~ZQ(v^axY`I$s6SF9Lqd3m;KNnxxz*5W#qaA0fpZ}a^%hQ~k zfa7I=zJ~Pi$og6A2vVA%N)J^jV-YrgVQI$wC;U2af5C=LeQxi-K6mMArU~N$K%A}UGRJnydl9EI!p(5eU|5Lc3 zpXdJ%J7|l0^=n_fXwtmvw%go2TerIT&R!HU-|z8tnFYeULcaKMAlTf5g$q3Y*MYKV zD#^D&*Y(vR0U78>M%9$o-{PEW`GLC*O%cZaHT##$Q@&eg&MDm}q`-?}4fzfUDqQ&t z;i0+6Z-~P>6i;8LCO-^AcL4B}ErZ128f0m8h8G21Iw+N4 zLoy#}#%QRd4q1xo6a`uYqM0}VSa35LTM&5@J_p*cpyXO1(73W%(+0AZxZ9wi7C#wW z@FF`Cz!B+@k#xl(JVzcGnPSCw#4*u$5h66zk=9tH$R*`MG(h051>5=e;tVgJUaF*c z8}n|wp-!?4UE`AEivClV_?_^fL9lo9o)pw!7h)|>LAJ2hd(WFYJ0mS$X< ziU5Ch`4w(_B<~1LmeTS=l=pPF&;PR*;Ca0sjQw%Z9G(S~HiU)HGE{!%N|Q<2&2JJ~ zXp1=WiI(u}b=_50x#7tv*Tm=lNHnuOS_l8+gY9t>+=<6s00)hY(#N`5BNEX3Q6leaPLj5P zmukk99CAB|a56XQbEjk{VWeP-Z(T?z`F;f&IcZt3yU}9)DLa%O2!T)A7ReySrztD% zD3}CdJ~V-{P>W|p>p-UCS|0wYFxMr(3@ROp1>~-U`mZVIut0_8I2z2@>Ea}qmh%~& z0%{7`kged`fZgEr>Uy~p`IjadWYXMw%0YCq1NGRKp-Hk>+OPvaGd^=q^P7QtdC^;I zJ0<}3?{`Buk6uX)4h*{C;Zbz>{dPpjUlMFK5|s#<8%0qdjwCqAecFw2=ZaV>o`_^z z+B-Wyx5d5fou|0_w&KV_BP&Ga6K8+YZNbxCLwokPIXrvCSIO9!(+(e)Pevcsyd1KG zkxUA_FrboXS^uf{Wf2M>r2-_9FMG%*Exh0dd1Y7s9{0Q7-dI3*C4;pt;5J54k!&Nw zB1R6fWd(wNxt$(i-g@_4?wViydJ)4|Y*JY{JPels9{=>?FadzAd%Bvz4W6Lxle z>-*nzfBMA7I2hb5?lj5Ae(K)2HZeGz6 zNZ+@_%uz`N*_?gd$m$K8#Ff`JxJ@pokkmQCmKL>AFy-B1nz0|k(r~pidex#B(@|u> zPkGYkmqSb+9(S{GmvIU|#8h)tgshhUl8#;ARHqTC$prO^cZGRHSOhoVTF}=g0Fdy= z&o!xH^kSEq0?0$okVbx$Y)jqpuFlT!yeU;U1d8%~gqs(LRw?wI2`&#ED_a~kEL26j zRitHc3`5Jp$d8>7&&DF;m2x93{$%Nx3w1iTwYB3Jz(MQ+sC*IN-u;8P=m@JWoP_a> zLl|n7Ck@-UNN^H*q&c(;V=+klg`V?p!H;a;!ircEZY6KWWWbK?JKS4wAHdX<^yod+ zM$$A=4|vc6-N(=Qv}=W2M}}}IAY?p;tG$_>wTb4$jtR*GVS1JL5G{q8vQ{DBopWTK z=dmPOp4VJhRbG97`QZjo7+H77d36k&rm&mk)?07I4eC`x$mOeXfm&qQq|5m(!_2D-%fMalht{_5qf6D#U?H`nh3&^m*$)fmU7 zEQ^$eVrAV*Lb8G`q>?zBCjbCI07*naRH#+2P6BYkvju$f&W9b?F8=;gPcIqZ%0t_- zX@mRL`Ty;XdHmzu5MJ?+oA>cLz%&NnwiAHk7OgimJuFp3mlgD~@g|`(E&&SU*?&Iq z%l2>zyAl5CrGM@YKlI?Vhq~=MZokvreEUsqwz~&|`&QlIh{2xx85;qAY#(OPXQAE< z{0B|walS@v`RW$Ty2q*#2(m>kuJ+x~PDq`j6%T*q1CTFGSW!{eTrvv)YG}->VOGwX z;SNUhvyxXS*lID4Y53>SeXNZp_(J@7Pf&J;fZ;5XUL@G+QK-m(O#=8!02fHDVo(k5 zxIO`ZR?FopWxs}y1ikc5{Gmi4H43Av7H?VT~Uu=^_tjV zi_+RQB2&~6jS$3;j0Ab&%q1%)lCwd$gb~=4M$&I03kWW;AW4|Ht6~DHDSLMBR<8xW z+i_0-Cm@>JI>aB*G+H2{DUj=vuBL>m5MU0}bKr%P;XqnpEXhj?&&GClxpv%;-HGpt zE3R}O{m{q4P#VY_NBGZP@ErH@m%m&txtc|%mSbMnMLdZK&iLmr0CChAvQ$SK=!(nu*W zhSEr)dc+)r98kh*90W}vHp~D;?lkj*%sJWFiAzwsy1U(XzjuMV^pgKg&|FQHzP>(p z?svZBp7JNh;B|mOX%Adu*aj^9xN-;)glMB{oS)+!ep+)4gXi10#T_PCiQw$c4c_9begEANbSlUb+a+3^Siz?Zc zLJ33<3B3qRI(C8Qz?i_TWC>%b9mNn*$!C!p{VR>Iey)YLG%jBi&2>El_hQGzaGhK=s5c+Q=q=~8Q0ij43K2N7zLaA)*nP8m*G|Xh%T|(V!_J}O zp7vxnKRq6lI|$RuQ{z%e4r#Zvy}UF+pJClCIUJ55OT3R&Uj=A$U7c+hXpEpX3fd{C=VD1(`|vf(tKnx>AoON);+HT2{4!Q2nZ**B!76OHNSd`&j_JA?{3k4 zZtNlIYk;1((O+REDs&Kn7QX70aXklt7UI*K{sL4Q!-#n&3qS45I4bpY7{CNb`dXd_ z%UE&zJyKc=$)??wz@CV@TIO*CAOQ}7ErJIqtHZd~Cjg`o#8wa{IBE!16Gf`nrO3zP z!7GHFa$>oPZq+viSVDm(TV7yPYs#bH2D+3{`O)(2jS)uL9y!!tpv0vD5kETfS>(MU z$HokF#@{9YlolQFfj}Hq5Q_QD1Hp~XJoCiP18?}Bp3Q#=fLo@vZoN+e>FDT`FzGi< zUhNfQ6L3<(XaN)j7P-^FaM^G%KRDOf+vPSKe2|-Jnssmghqt?}_iif@;DJNguyM0H z^W$f^&SpHg$X5YyzEpd%QhX7+tX#8@Y?>6sD9>*ZZUi@ZM}T}oNwlQrx){omAekg7 z&kZyyBZTz#?{>R(HuxP8$>^20A%e^=?CdO#@-j>j6Y_Mz$E}mC?HI6Nh4cqM{GSwN zqgk_pdDaOh;y4uQXlP{da=)+wi zx8HS_`_ws~t&OP_hTVhRU0?s^f4XCz{A4$Y2~!Tr=P)3j;++7PIF)=4L7N0w?#&#@ zKQaW72ducv&=GAg>g8!%{5OuR{|`FyNcT6dd38Ww;l}^}x@+96ciidbI(p>7Qsz0~ z9g(0e%9xah0g8WKgp9)w?j`&1Qjb^BNniu4>#I%z+yNkif1f9^**Xo;TqF|!agKsq z*j}I z0NyZ;+~m#ue9+_iO)0$d(4k9zFm+@l`;2)q&P-Wo z*a)V(q%BNx^a8J+@IHVx+;7p@-s!&bZ~y6j{oCJ16jr057rVT^{*8Zk$3ErBZe(yj zb^=Vf84Sv&Copl!*8w18tuJYy1Y)TZt}2jpghPY>;LNs8YhSj%c!m(WVg#Z;I6_{VMnhxc1r+xgh8D4v6+l5`LBd0_C4fN2g|&^B+H9ClB_vyh?? zg&=kcTmM}}642C@!9VYRfYtJ;WaTiX89?w-j4v0==Q4SQI#2qRtXqUe26g+0VsJw@x2*1aLpgpzDrtIfLmaLDY@wv}Io`s&z>-6WRhF6$HI0`s79haTd7bIYynZ709IfSh?Oa9&36X{Ud{J^aWc-7t?_phMd< zhf5c+wNqXOARj5gS)*?(rko9|Ivpz_B+-}MwjkcZG>pwrlm)~|0G$AsotbyHVWn+V zDT_90d9LOxNSsB2NfEVrm}QUka0}{)fss*n{`W2_qR^0V?qGP?%l^u>phwI1-Q_YY z;WQQDfcs(>B3WVt9%?X}@7yq0Z&Oi&xrq}md={`1cL8*DV{kg|PCf1YZi;=`Rjj%( z`2Lr#f6YAuPpM9gj>t{`-k~yyivi`CGwBaQNG43kA~2>Tla{pPY~Zjg;X}JSi^2aG z2L6+nJbc3A9^+p2*Dp=cSeTVN@P2;T<^{|Dd@07N5%b3h0Df6z zzUUH~4O8=JzMJ8MddQK%L%N~6aTn{_>-YW=g~Dj1{KG1~r5w-WUJVWpjh^`-NGCxA zLgAQzLe>KGYVi%d(HzuLul0DR@-3#VxC&(){WrOW8TUr~Xyf>gv>S-9;?VAUMa zOvPEPDPSf5a0(R0pIdC%HK9y+);GHF%lnbaQPDG0Vt{ZY;wQCcGzR_q_PhP^5xLXIx0=>TKLRYzUq2$1N6wifV?x!i>GuINaLE8d!AioP{1NRq&*d&X28lq2Al_| z0X5HiZLmu55k~NX@*?E}lUSTgyc_ob%+6uOaJ49rf#1f6IF**aA(~!69z>B<9~dZ2 z`KN}DM{i`}4ZsVqk~us)R)NxibON(E1>nRJpXu&**kQV*9IL(Dfx}KA>u&y;icJPt z8l7BH<%Z`SrOpEmCotOls{qumz2;i?xzBt-{IBMp9!vs!_3QuYPB`xAZhQpwCC=qf z<8r&OFn#VJFdI zs@t!<*4@4BJ~z|7!C^K>^BmX+)1O?ibIUTwHenVM#Pjp5?(m@!&J zu>P~kB!J5w5u>?i2Y^N|%r9aNGK0Bs*Qaj{#S_cQ3@;z{WkfPi;IEqCNY`i=;~EAD zD{(R5hs904glFLM-@JK?*aQUtvc=#xJE8QPmo{5mbE?sAVngY36iYg zvxiUg;;eOGaz!$SDt94|jL@_cPVa`=f`6W3>%W>Wz*J zOL$V>7-J-qAQtkD+?Ibgcyp8MQT)eF;-v5oZh1{xrinIrvwT;rln!tIa`|rQR}gIzC!Qa|eLZATn{MaKFVIFCM0cBF|}Jwc0^fLZxnD zK-YW@VsMnYEMkC2f)MdU|GuZLZXjgw7jNDR9KC|84}&^)KSaa~g*qIkrrG~xeJa0V zrW0+;qR4wm6-JZt!zUo@)D4|VQd?bUYq<`T#77~Tx0xW#{&4~?Q&x@xtieR)Svaj3 z7AqL$*&WVMQb`3NC6on~9eT!yolkKt^`oe<2Mw2G;uW+~geo9$ORUu!zHC;&hEvQ= z29Nylc{tt!pz#N~KYL3%mB>sY62(T~;g437u{$NrZxRgI>YP`x67N^o$i#=KHzS-@zyM<27Q>b4WDdaNsOaheGnS z#VN_CloezQt^(}c+wb;Zcgt#1a*-1v71s;ZQ#UE4$`XmFi6{I(o&uv>rL48$LbfS) z;SVpaOz42R=EwNC&wVa-455R_88?(SD{v8Z6!|psg$`-Nn(R@y$kQ2Ra`z6L3vR`I z03A3A(A(4D#xQyK<~P5McfzlF#p3{9`0^Lr^Pc@IJf}K>=NzZp7}1k4{vPWr>dI|c<4I9f05y=h1?55Od<~B5v4h)!^rvYPF1meszkm7iFGM;>hJg&mmo0RMsR4-zR-Hig5W*u=T7x3S5SuVOP+loPq{XKvOCp&Vr_SsX!vniS!_U zr3=1TE!D%H#Gg?EKir-qFClRx`KA;;FKIgTwBvZ-2KN z94NXJu+H!o(OXAnxBJ*9&T^Z&T5;xd6f01889?%-HhRpS`1dL{8H9R1G{jJ|;hCPr zSQsQujgoX0#7KTG~o%vz%Y7f1jCRZuk8zKE^kbx~@f^99vt&n=1&i?NC2Voz zZd9_|Qsl8K%{+q9+}7+`@dRq`hK=r**Ie)Z<;!0qv#VZpc6Ymf{_LmR3!e8}H#aqb zG{Td8m;{)_%KaQB0hnkaEiW(Q6zRPand=u0Q-xl9&K!Z?`BH z=_MIkxoGPX08P!d0|42AO3FYc?^I3V+I>_dSs5W4Aj=0Eb8f$5S=7!v3MpDbHij}| z$Go2?NTidcl@43H;Kwj2D4w;az90pfy45J2iHtc)Fp6N(c7~jUksnr2oCMK$>m-0| zXXd&5Qtaj+F&KP2@49vCPOO;VePfLF+4134V!jM;-+lf9K%mAHlKdIFDJUZ%P6nrL zW^pY2Q;Sc}rKBM{eUKGo)P_!RYkNBeC@t=~>wn|k|K8I}hkhUsANRyRa<6^e-?@pQ zesp5Fs)a3jOc%?Wh-RD~%3(D-v4|sh_$h<0lyiRTOUN8T)j?@KtNX~uVA$Ub-ZWmlCAe%bM?7=;11 zcXhg#zW61s6|1UEs0X?0hATtGN*mT6_n6FvWLFX&2{{xy6EqIW_kx*OC$It}VCVmt@-i(XJTpJo7e*qkr8IAaI{>r>acB>* z$RJrveym_98L3}Fih7Gw54SVWNS314hA@!X%Mg8{ji0Pfh@Y+a#8QrG^M& zac^g7h$z*>gyi+;EQpj3WDhANHPKmqWaE(q5T977W8gE1!byO2Y`K+4&3?Ye{FddK z<<}?js*wDxG~ag+quIHV0FB2(?OoXoEyO^>CWBzB#bmw@lVo`Lj=hUFha|9 ztpu#%GmLV?0)WzJX#&&O(%R%YdOC$&&Nlz|x6X4%KlW(%+BdwiQs4*F?c}$=(_MDS zW$qW(Uymo&@XQxh^_meSiIv4w#3Mh#E8SvSG5ac+tb1Ai;?sD@l1V^Hq9y#4vXmn1 zkt9E8w%}!!oao!OZ6{=@lSHM$Yjh+;bsF`=EVoYj_(qw5VkoVOaz_CHghle0cgArW zUb5IM9kcU)@MHI)7d=Ow8t}{q*of{&S&nq4`QNM&lkoS2{iw$tiX$<*@gLA84 zl%CPeFd%>!H|=gzjTz)xmMbh`jq*WfcGnHOtFFJn{p8ZiT}yk1>%qr`5#<*en3A}MED(nNGRt^R zPY(kN7*;bE7$>mHfOPmLG>8=$0xykXBySwrarvKv1lT${J4A=s2LEPn6t0(?kF`7j z;1j!vXF((b$Se_|$SM3^If?~S3nvHWG64W9x9D&?IxlIOLqX;a0Ev~NmWy5h2LE~F$U`1HCt%Xh9RQf2gg@!DAl_DKYGs}!mLCM6RH&30XPCGu zzJLFIci+}s?*0!xO3o2d&cm369mGXxkhu8Z5B&%QdNfi=Bhg|N;KaasOxgGel{9_Y z1y%7QXBb*r5ffaJMOltuJK0A*{7>#NM?cE_$&>!L9)_0O5A)Dx{^eiY^KrIyFIH6B z(Ac$Ll?$0(qKKP^0&sv47$bQHupDDaH1%0C133YvKn!2Xj_6oH zL7lR5=Pq=5=5V9;Dmgg|xlM8#BXb2K3zM%M18ESRD7t|gjTC+rWhm17B(4G&B*vd^ z7ON;NsGoj&;|=bvJMMN5f5cH$2sV!MmO~GAfAU99a2H&75hgOQBFm#Utn);k`Ft|= zzE(^jP}Xo&gp^zD)+`xKOACPYfoxz!enwS+U31fDhuYl5|ML@f?tgv9{mozhMNXhu zsyRq23dAQ7|LKg6xK2z4ocoP$puV4VqXV<9YvU%@go_e60m$^?gcUR$L{XHe6WI0> ze*y#l32ghH8Xb0TI{74b{L`PhBr^ErB|mcaU`2EKkfUVLKx=egqQHxFMWm?lhk22y zj5uJL%{>q+q*T1WJZ_~0wEnY-B`}M8FpbKVmqE(lHi)a$3K57!oSB1p9U70PO8bBj z0wN|&!8NU&cw81b*T$P$M5V|Ucwm@9K8X6cuoiyW~cHEQQc>+nU|X8smK4~D)+OWUW)SS?+8F&)yheP z@7W@pM(VSOU>EyOnYw&lju`NsEjsa2dW?Z56cMgu&=^<3pc^Tc!>mS$v_K6%=+BVs z9O^vLeK0ihTT^N+PXJg#&!OR$#7BdMyv#305x7`6F^7D4P%d}M8=SbJ#qBxF%Ch?H zOR03qtT0C;Xs}3y=3;J?94jhJ0H6}!;9y~jvzh2$^c@tTsOQ1OL;9yCjH4*z zf~f9rZpW_OSmebZX|+rMAdXX5$>3cfiNQeW%;3f3wa{B6!T^P3N;)8t+c=@fV#oSP zcR+Ch@u!zw*}xM3`I-Nj$3MfJf5G>Vm#|WXdXTZAEc90eknb}PP6=b}G*sSN69=gy zf~Vv?mq>ix2hf34GdJJk1_p-Rhd%gG_w47K=nmbSuIBQiA_%Ls53-(9W{G9UU8XM;-MLcj|jiU9xDO z{i%O;GpN%$aQ>h9L4PzjEnDfjoX`K^#Rt?`ZJ4Myc09gkgZ@`%YN|Zee@z0o>jfEj zk`t}i<-^*ZYx?UO0O;>f|oj9BTz=}M56lEkJ`)(&mCzON} zK(spwC<_`C2mah<|H40iu{-OG_qx80t^m6><3SrYyA9YCFo$x$BLGtUMI`3yk9{1CNZUB{1dX6id^{+N|FuZ5qJTZgMZ8b5Mdri9JImG zR4nim_+N!aOW9QzwNg=%jL^~E&WymlA*>OkR#*|a0i~Ipbm^%3=+0ux5DO^tG@riH zv+rE}$FLN*(r}|Ru_6flm6Dc6lEFHPB%k}yvQ&%jEG|19#tZ4i6-yR3hG?}M*(D`u zLxqoti3tqGB@G!*zXL#SHD$yVEzsiBrfZTi+&n{+9k)C3n8odP-J=sN#78B$qrV@! zwI-#aQgj~Vvr+}2q$eYKWTWby(Hn1 zXt_b;18bvb^#jXgD>~4-@Djkx^khEil?X+K;Fe`h-c^!J=$18ykbEJ8dBf^BVoGb;CAK?o>ft$_f8Dq&4q+;L#u+5gW(Ft9<_N4D2M>gcZuAP`|wFXP9)q z&CR!PSAwluS@}cGh%$!`a+tQvf}8-D#vKJm@4nqVeR>#|0cy)@K9_>mb()POff?*x znZ<>lP3#L`FsoT5nFE6Cqwbg?XFXxZQ|WFvL)b~8gK47yoycaD=qD^W0twtO~kzkk(VVerp$`1Pw!lx3W9LfefeTRA}+bcERRu{aqA5)y#y19#w_mw51R zJm9iV?3`oS*ElD8%SjoSpZfHew;KIZw1IdTHyPT=;D_UXWCQ@a;vE2DCoK42T>WaT zP5^`nJ>TA;w?QI*$ncZd1xasRm^c^Iog-rs3pH`&E?;S9hg|KR1065j0g$Yce5lY;m5e7Aio&8s3Y-8KmorN9 zaxPW2F(TUB0lf= zMFk72$jzfHg(mSx5l*o|i8CHt9c#gH0G-c zdDSRvWt2%mMldX$mN@x!PMPQ%1;b5aCX|Orhs4KO+NdVSuMhs?50C1O3 zYdg-0V<6m(6|?id_d|E_C07{In$S7e`Nwy>&7FMGo3Q$afi?#ElXxmncFM?LpEr%; z!{A?D+Cg7q3>T7(3=X*yu@mG4&wbXC$?1Fl{Y^KHetSz7jwYbZwJgL^8uy80S`BH- zK^_tq|LoC?A+cYzZ>#XeWM8YD-X%+%h9-;!Atd-L`Y=t%x0)%CFY@Xb02Qp21M?Pc ziud`8IZEL}xbLZ05l8V%3vn%2>hdv6AFhl(z~T%h)b4lq{oDf|cz+;~miY=Pl{9m* zjFIK!hlG=*6+_62g@%a(nS5Yr@$vCN@(uv*u$4<+Ni(iYOEQ&3i+RYpm4y`1@KK^B z+yC*Z0#SJcAaCGbM5ZhoD++Hd*M&@Id~)bq;j7ITQty zWzsq}x(h&)n*OEHOcm;=x`L=oE9=^c2>^Xl6bZs9m~@3F zkroCLVOfQ&6t(hV#CZ&23>T=dGNY0~Kj0m_*hqL+c>L=TV}O|0btHNeGR*+6BHgil ztJ{H#(Zmt3{yqJ2KY(;j;9G++pD~S85V2J~#P*s^`Ekf46SI};Y(SX+Kxc~+0Bs$u zm_TW9eSLlIOJDg{_k;5&q(GqKwG^_A_|b(BQzJ+l({GpZxg8mP|-{ z@C51QS6$&|J2$xIUR=EEvxwy&(`~52`;PETfScwn1x&1Mo*Hw<;$9e0=;^KHPVbT> zQUem!x&n7%p`l8qTR+jrbB2hMd`uGx zQ>pA6QfU`4##NKGCuw+KU!*LM&oRS6D#Ou&kv0t#cq{hHa<@}=Pq(|}mRsCsKmEmk zskLEj#<}M=z5ey?q`!Y7?ghXr3H}K(4*I#f4h<_-OJ?1`&;YjnPq=ry`OWTO$OB8I ze)5Cwy4?c2*)5}GHqKv`hD=Ox!#@96d@ZeAZUSxB^Y6XM^>D> z8HOjJaA1vc){TBvD<-iPs%Gc7QtxRqo~ZQD6DlNYabkVQow+()7UPo=C#t7V50EZ} zK)x5E(*D!-h0u;tR9Mv=0Gt5;&AINb?)2cFK`(Cb3uAuCeXr#S088xoZodNnr5kw~ zCD2NNh+hk?&#X%+G!A~lb93Fi9j5OmXO0=XSJ1V0%r^Teyv}C0>@49iO zi8~FF*rcN2B}rwmM9lCnl_)N~y84&bh~IneyT{#q*WEZ@$gPJud#VYdG~(FACgI(F za)L6a`g9VsuYiI%r&-G3g&;mT!_~1S+_v9=x6AkM8*p!V<6H3NY~!wwo%NYdxuYKZ z5H~ikUk0$6ACP{zx8GSVPo31j|lQJ2;hYOG}`@9*vIhd&50)*=T3g>NlV7+BzE$B^P9Msu)WK*;wE%fx~#t0 z&SJlb4sN7evDVU>i}mu{9NXe%chulE_paS{3xC|JxUSZY1hjO{xj{@~aZggX06-~Z z>jZ_^V4Rh6FD3$cxv35bFp+?rK_hquP^t~~1wEZwrRr-P@kTT0u`c57Rq3mu4$`}} zYNedSpF&yHK|kQ&R}?grjf4!4htrX6j4X8CiIP^t`=DVuhID$L|2Z5dc~EWsu=lBiWUWzLqBdm~u$?a0kGwWDo+GBWhAo0BZ8FK;(EJ zYke3J)Z$4`(g9da5@|BGe3ToYe7Rbwr`)@Sk1w|mK;XhzL@ne&Y?L^ zO!UPzP(GML{Fow<61Lb<1dlPqR0^#y@D#DCRt-&lQC4&U5gjOu?RIS6fwIXvKNc;p zD%>nxIGE^_O8_}XSTQ;kgkQm_FkbYN7<6i7>d&Be$Wx0y{LxPe{Wpd~HeH@|{4?DV zM;swX0Jv3Lk8&Zl0c!1yYJO}3lKBV~pt*jf-H?=#$(SbGd(w_G@O`+5YYb=L-}TPZ z+)Td53L&k0rzY$KIQ`U9+-qO;w{8Zz{YJ6tZ5-baR^x~E?R5`-_(R>-zWPNcm+LN3 zbg#Yo3U}K*_qryY{m0HTF4?hnre!Zq6t%Oe)!Kc27PpnpG`UlE-sF05uO{2=yq?bz zvAs^V0VJTgqsa|)U;-Nzy_Rt1AOCqC6))_e0fIcn3BZjTdO2c@Jn`Nu_AR9oNRPhH z=}BcYrR~89OA?iJBm2GVOU&blI&a|DoSej~)i#Fv1cZC=$?gAoUO$pVmR!JN2m0k{ zMdwjGgE)m3foK+~iJ!n@+EanOb{_?V65xQURSMz`I0P(%f4LOOVc}Qq0HAp63p^)? zu3F1luM?5rO>;fnJ|kdS3weZU(Gr}L7LQtGwj?aSvPDd8Q{upyTbZ~6pdG8XG8rJ9 zP9o@+$6qDBNuvdoBUk^&Q5h^$$;!M`P@t3(a8z9-3N%B_0Hou|fj{4B|XS|KSKu=qb*yIR9Rk2k@8mW zuQF1;*Boe)izI@%iH`K(#iy;flL40lyFT1)aKrC@=RW<}FC{gP~H?>prb_o5fR z*bVO4>&9^L?F62d z(~id-g`+07X?DULvrmuHaA1Y`>vgr}Brxyx_&YtU(YNv*io$XcVN!1ai93OM(AVK) zvE(zrrg87ZDB3{cGrx(-Bd9L^H>9p0LJS(3)fGn%klzu2_g}xpqpA#vZFtNt=#sX5 zilO`fY~vq20+0=R1a$xx*<>w^&ULV9anDj;8VK4fpR6#+*#$R;NfxpZ4*s)Au{7*7 z3{@KqH4H<`#E2y{^m$$e!QEl^?BS@!rVWf^7;etEk!SGG9t z=Olnl1hlo`tz++lyof&g{rU==9GZ zKb-Q;Q{5f6-dPhz%ZYs2>8H8JJ?atitkpCo0%V(=q&*^2B@d9#qLj*m=fNt*HIQ*7 zypm`_qGle#fEkc{K13wAP9nC*MVI^b4}{n&*35U7Ihi5PLdoh-TEacss3VyQ>%}j6iQ9q!A?shebkj;ta$1cZ6Vgag zT3V}yoC==_ENuuhw3$o`?wD)A0Jsxd&(FqP05{%p$I>#^kksd~+pF7s{G(^M$3FT| zu5E6@Z9J&QefjgBc29l6A1^7#JMX>EU3%63x)!|3(9(em(Wuw}c_k_6e+cF=Lgq~! zm^8U9gFD@psZ=_f=XHJ6OF$=lTHSUmRv~vte9;?D#oODFO_)=!t@L#df_wlt27ON5tuhpd3p}{9FX^ETyh5h zBdGiSdOAC8@NYU1!!Ix7RRvqC697y^Bs4Qa(_C+Fk7WSn4^azAW#*tf73M56=i$`^ zV=W;$-=9Z9=Zh=T98B}}ZaoITPCT|X@?4Rhw$s6KPDny4=1$|ng03d>!5syYxM**9 zSZ5dwUXR|>(<6Mb^0DxQPE?|kVdsV6MF3ee$#$BFgbxRij zCc7vcqf9?Ai(iAqI%$em_#?+{Xkd8akT8)H{fQDHvB!!gV}g2UuIQ--$B=G$Zzdw~ zA@RbH33;d$7wdH6_UBE99OQ;Dkb3ps{4H*TW?slsCXYO~rH6HP^|}v#{3EVqcGOLb zU;vB8i*H-AjA$}v)2WDKOPNYs7x}H^r;J8Le&~_zF^_tr!1P@1z4~h{w^9KIL8SmF1!Udkq-%NZy7`eeS#8JKw$dg)ebmI_GTn!sjjB zlYbN{)At+y@)@^xa2OK+xcMKGF|shBg;f#!N`<5=+9r15Sat*vzz5=39y_>%*OOo1 zZC$N&2{gC49Vov%|F3rdNCuHwhq*#+Vg6u0nt6T>cSf~i5};lCcF0BSI&;#PcVAKI zL@0okCrwtC*-SThMo%FKkDSQDlhrb5k?~M7K%OiFf1J>ivJsSoROm5nIB)`>y*+ye z0425`M}R1wINo5`OEf03)Fy^mivs8PhcCpAThyXw0L4tZ|0tG1(ZFq0Db|l`Sg}aV z&252Rf zVv*0KGq`KJ`^I_S#@YWK*Nl(7^RSYP%dDoHmLHmV+!4F{a8&IPL%KV0Wlf_=MAv^7 zECJ0IbIl!Y8!|@=KhTt-Ewod00Q$4`MKdt*xC~_UjBG;H|%ZH%7gD5u-g-6&Dky9QYClpXW|14*aumivF8<)^H->4bL|BpV@(ZiIaC{1<45j-Uu3qD?_n50MGMb0-zha0HUqgQoGn7KW0Kru|1}(v0_JsACqtz z6CmTftc4K*J^E@zWzKU=oMBa-ePkp&rRE_qX_^QOa3V+6B~ow5oUsQBa)gp^$csZh zCet)e6TSNu%t1L3(gIZd;q)rSsu4&Tx0#yDdgcm}=!F;dw<^njnwmQ0M>Gxo%)| z0%d_)p*h*0^|Y>HdJZUV03_L9o>M)~ z@eO(ER@qjE%GCAr^|^EKBEtFSeXk}?mJ6AU;Kx7tak&d(q<@c_$Av!3ES%D4lFea^ zm3_(;lQC3jd+3&Dt`$Fz#({xRDqb3>KDY~D3i9Ifk36SXU0DIBMFO?pR_CA+#_;e6 zZV5*`nlZRQ1*SC`d`QNwV37k1G~<)wHCT0S#SWW`e{uN!0HuLKJ z``!P6?zpEt-Ob>(?0FuuVed1l&n@`T{`R#B2mz#s^Tl;XyAO>&=*)qu_ z+0Y6@XFBnAb4fbbCvaTp#G!5ONn^MG5X-)6y^~u?=&KHi<+#}mwspCEsN-8u56Ll2 zi6=8n9AU{yu=-xDxN)I)1Nt*)ZBSBcwQlqY&E}&S zGeOY=0PZi6@qa2Kz-qa@a-LgIGBeQfij1V0fgP>j_~f{}*UN@cmFnHt{?4uHx_vGc z#hieZAOGv)FLg-ZbsEosEKKn{Ic{6+#civMkMPXKoYhmzznO}{GIx!0lS}~cxfVI^ zFHCcFt;O}qr7jG{d>F+bQU;22;->NGn1uWJm&lhGRD8aVs;dA1KmbWZK~zatUAAoa z3n!iAKcB5YF2;g*V;VsM(n03sW!SdXfrbmaIZk`uhum*}{kwc}2N?PXxX9`~?|HwR zQ63*2Mk6`n?*kxXXvi}{4F-pv_Y6uHr;3gt;=;_96jiAhKc)vBs?v}I_E^xkv@-BhOp^3vM5D=xgi-FEL*H`{YC z&i`*h0nt7>b3ydMpq*FN&uoiY+$b8Kea2<4dSnYALgA)K;EK4iOpnpGK z)uk*}HF_ ztm5#zu$=k|Izhr(VKh?06aiI_Od&aV)jAItsJmE&@qUdaOjb@{a`n3xTqM1g>LDBe z3^JRej(+^n?ja9(2+9;DSUAbTqX4Y@fX|1F;2b}VtVio zX~7AZHng1`9i8&r>w7)j!)$YB=L}ZIc~=a^0y+U8xw}$+TcjMpAQlu1J7Jc~$rJ1oQxm%V-FMJzo~{RH zn&f6rcHk4Gw_0RaX~tY?0cpkx+AJ2(TqC)a6#b+?kB*^ubn|{L8ZPCD>y`xj-6*D6knP*wTF#H7NffVlpi0h z6tphuB9cP**_fp1#~5&dAf+RQe8w%}tvD;oNq~0j2DtI2o84*eJ`HzKq$$)D%|-Y0 zv9r%`n>Tb|5&)AFcol$`0!m(yXp!eM-)VcEv;sD>=z-&M8ROKdJ4BMer=4);hH*As zJK=d07a_bnvP%Gy9RSRm=xj-aP!vX`bPS{G<&;x|Qj|kaK^_8Fs&eQkO02?U_34^x zf91Aq-JYV+*jDVQbH^Wl9Cq1Y2NqXv@kw)2L6#Mq;>ealiqXrNEIm@h4a{@_Av@~S zg5wz-sOPrhiMkKH|DVbQvEs2z7)u|K|NWnf++FwG&3Djn^n#ZbTbLaD^VJsWglT$7 z`-2pko59!A?hcyT=RSJx4dSWNG^J5p*H^v-QYF_%h#99zCIH+V7AK^u?=urKO-2DG zUbujS7qhUCv1!8wxhIGVU0gWkZYJKU&u32Kd>LiXm|_k{9E;#|vnksohw3WE zQ9?R!)_VoaP45_}M$0pjKr;%?9dz)aGMHpN$ZfDuWhLG`UPL1Hmmmj=@CRA2&K|^# ztuu1bo649s!EQqQIT1i{#S$=j9MHmye+w!x@sSR!f?fUd*C?fsEyxtOy0w5_b&n-6 zvOFhP4TFc-+$c}J3eet-rxr2M)QO`27hZUg`|LSiC>ieZq8;&o2fGh`;DgRhjkzft znc+&MrnSb1*`Sg#=TICOd_&4OEFK^){5;q~LbS5TPRa4J;z_=m%?{Ar(!w?UX0Po&* zgL}yE42}bo3T_=~)k=V6xy|jyGl0#ge=yCPsAr{MM|mXfZ?Y50e8nR_8#i*Xhy#C4 zJYk@Kx@b8(n- ziCxuXcCSNYg*A-=%9j8lCC(U}@eCsdz}yP2%KX!xG_SrQhvb_CXwJSDEtX=BLXQynz58KtsxmBYr3Ha_LyR9N|P~fi={K9WN1l1SpU=9 z$_uk(5&$#UXh6>XYxESN$L83Sp&tOsV1+oNV+()wGYGzlF$}p1YH4St+Kk(~7ZU)| zk>OTOOG(p96RRIF6BOf$Pc+?8!%Hh?F)?-VPkttMRn#BY^80+?kw>}b9e*Oq5t>y$ z0l<~CNYutsLJkRwVR+r=g-R$XA7!jcRezN;)D9PI7y ze3u)*q}aMzxCHLF`DXWv>wo1Y+WK5m_huPnFdd|0PMvAu6-_!Z$*bLj*}C2 z?-7g^0JFmvaa#XbD-yt9egKnp&9hwg)x4ucT5^m^fq*z$DGZeq&*Ass5|TmhMji zD1t_*eYUx|mE%8tl~}KVrT*?dtYpHy+q zu$saz%Fl)Hz(E^B9lUUh(j=bjiU-e%$csHTU;wN(4LreuSQ3zcWslEF3Li@!1;@z* z%(n-4^%>im=WuUA6fDu$y(_DdqJV=MDYMVA~0MD3U zh&;#qhH}xgk7YqqY@?r~Ev7<~QcA)KY+1sJn1)vh4nC?l7X-cla1g~?n71 zcevio8{OFKlza2ZZ*x1h?_5A&OXu~BkDuiZ#qpCNyb3Ujdn4G9M3;fNMgEc@vqU8& zZMHxhX)lSyG5jP_j*SHE%W+PFYCuhUt!Sqdw5+ghnU|*k*{N8`Y7R#}cJJ;-Qv01u zj8DZlS$s_|5ql;`jv`n)St&<2AnQTl$5&*n7;Ls;QuNvze(i3#_0E)s#<%{>D_`S! z@VW~J{xT8EzE*1cZ79*uTyN~^p&?@T0cQNim7A~IP&?p`2HpqIhqm9I1FuwP0VTPXu3#kBt8hW!3qKBKDhf2?{NNG$Er0`zJuCWavItJ_HVf}YyD7Z-qw#&q7h?Eyg8o-2@e8d|GBpZcb!mXDaB`i z*?;B^0LoL8==?E&hcByUH3S3od;~0E@t>oAZ1C0{0L@+9vn|a{tSsm;n=MFY4XCv` z0bq&Fa?MOIv$3z2Iu5nZaHoj5FKre;24$zua-ZqhKz)YJIs_0a+)f6)ya7MispZ)s zoNL;&QTkv~88GDBKn-ZR@@dEz3P?r5%aO)0XrIM-d#>QuJ204 zcm=-e18bOv9(I^J>zq%^6Op56+-LCA-5j={a*m6WNX$2()EQQq+qAtJKegfqiC&Gg zcMlRK@KJNP^lPNN7$%X1I-wR2uGv_`i3Ju0Tr=?s9!MXTLLrU!>_tZg^1AG&D_ELw0WuvWXQe|>#xX%6`_rI>%TO-EYbZypzxJsj;l z6|W^`$8KG%H3_83Fb4i3sBpOOGmk;5wB{=LpX!eW{gbxV>>B<~{L}YdY z?tH^|g=0RAm_~is*+0+$uGI+u%H49cOaS!t^~ezb9vVkJpsTq5=+;4?;-HV)vj4rzedvs{+$C53()D3*&JHgtAYmDJq^P_q4;GU& z^(bT@=n)6(06@oioU6LN0_0@NMr`ZqL}!6<7G=7GL|9Tb>2S*0BYw(yD2oTevWOwsYk#H7HJ4=E5FMb^2aGLQv(wN)7e*k%u%^xBql}$QD()O4_WGaCY;U)}4 z@dF&ZNau=1zFWnK0L(`H=qEqH>Qiv6D?zUjEIGnJ98Ngy>9{p|P~w1w3F6Md3)rvh z8X#6M)OD53Z}aB=8?!{el}9 z$9d)6L$D6ejscqvh$O!t&q#ailZ2@x>3|(PvvaWFmUq|Q;+{FQyRNX;J*`#=G`TV6 z6&e4s@cMXqomvDk1M=Doc8IZ`h)d#_(Zmxcz1g1}#m-vhJ*mdbk0&cy0E>)~L2Hym z%@VmGkMr~~+E#A=@99~<9ROg27ZwiV!a`Y;3+h}Mv$i;?b)itBX7$>v z>&NRk+yTLh3T4#bom4YC^|iFWk>E5D18X*1sXrjv_zr;Hp6*#BBL_$}0g&~!1|Z=Q z$Qp=lCW=gaW&-R0&?5jmuo#z@in%I7Z0P_Tlm^4=6kDvL2vV#j3}9xYvf`E^lv60W zYG?N#ylsk^0of77$cHLZdCb#RW(;u)JOXUO z^H0+lC``e9WOT&w$t~VUx_!qk4ElGuZ98_j9Xq$$qP6t+uIkU=- z4L-T%SEMrh0#WeABPxGX)_k%=S^%lE=VfNK5BDH!@5dHkV5iS9X?SVM#zh)#c5}~k z83WddWB38MPGg!{%t4(56W-F)y(7R zKk*6fm9KtzUQGkoId1X=~wdVAe-q&IS8XnzL}Z^3(W^EPR30> z2bEom{YbsyJx}9Op@r`N$Pa7jp-d&bWPpwlFEe7|adk-Q0INh7lL^X9gG^8@0LM7O z7qT9o14Qe@-86UIeY?Bi`d_`uxvHpVN zrMCnHjxDSuS0Q<(4Wh~WYMRgnPEX-^KuiW)|Ep`==Rf-;_qMnEeO3sGnj*(}jf3vJ z`)>Es%P)0Pt-Y?J_fX&dYkp#}j3(Ep9+rwVYctkGla!Ml~vNLKuoz^#fxLL`{4Fu{VgQ8@xI)7{mj z3%->+2vVz+*)|jtKy6*&L|7Ri`ybyLWc zF-d)CRbB`YC#h)U;nu_5|6}hx04zDGbKx^{C+ECbyQ?e&m}IcQ7#n0T{LX$h{-6DW z{lGS0Y$TE-g9ONcgb=|G3^o{?csBSMj1dS4gg_t(VIhK0)@paPdBUdIot<#+%>3VX zPFHtz-|ib`?(EL)OwHWx3a3t;I(4eL!l^2kyO><0AwmIgaK)7}!Zu@8n;+Jz-uWJR z@=rZQp8oWwq{TXAem#AI^4{yNlRx>>*We>RDUDQrgQ~A~GAT@`E398;63k8bhGhW% z7-Y&lN7aiom;>^OZ#LFZ`fd)n4`mIG9(bS6lx^r!E zboRJ>=)->_zwukYCR;bHk43FTei8`JDCa+Z?1OUP;8AHFIuBRL2DftPge&;Xb{Dx- z5eWrFmvmK2fExgC%8&fn{>POkJp7OFt7er0Y)hxus8EblDrhkQaWdlGDqmn3wP)ED z_d#osi&UM0d22t^wC=vxdnjq9X~O} z^H)JG({(wGD5Z~4#HUSiXnjNbtP=nVYGDRBE2HSw^M(Y~06>spb>FSxB%;JBwk;M( z6kjK3oD- zpc&c8RXW7Nd=00=TuLT=3JC8SDK<*=I+J(=ja4`x4ym2T%w}h>sA=&?X=L;9X5>LT zs1~DuMy3e^yZwg_%8lQ=T?)8PZ4N8{WGZLnp%S21S)msTM-#43YLPZHsyPm>7_5ym z(!_r>xJs{$A&sF$QDi>Ysb?pj_m=Z~0%8TZAf-Y2`+DJIUYRi8cm+RoBpyaBJz*m_ z;#jN%ph0AYSt?mEi*Z%vQ-UE%t6}l1m7&doLUPIvBJ`2Q$gltwFV(F1!KlZt8K-AY z96KRzc-^J)Utjs6^!8zvc^R2sdfCh5*MIF-<+K0(dFdKlW0Mz#L5Ua(i^$AMiR#a+TWrJXA#^#kpYwHjx$~|&<;cM!($fzuu4QPl^7C82@f-5J z@85$jEg;YtrK)SRf>fN5EIbipz|b;)Wr8DgFK+#uScMfRuEn8J}eP z=$KCzG&bV~lOCvHqqvr~0pCq70J?s))l4M7u|XVR97UR|TKF^^rTi@4m=ECr0HE0R zMMDFit$?u-n+zKqgsn^|WM^L33T-P@Dp>IRQ3&#bgM)dzISIfC0Q$#FRDU8{3}BIt z;(Ac*la&M^*>JECVa4Pp^3c&^I-42AF?>BxZDOaFGn>o_eT#EMALk1QwOV`&3F0C> zrkTU_f;pUA+SJ}59sPsSgSZIwqJc}y;U%iBCIFCP|N^PX#E^M>_u zbYvLk0Zd}92X%^FN-ftQgP!DAO^DM^P{)CI$Rb@tnu$0wV3COiX$EPtLxJ{j@i zIbfvkgM-99Qe$q|4zoI3XVFf|p7C8)qiu{Yd--q;0btY05svY ztigdk2ZnG2z#KYq6mo|{^&%AN@3jo-wui;>JhH6{8nh>XTDJ}wL^u$8F$M!&EP&0h zfeOB9E*D9x9-ZtvqUiV^i5Kd!KpCbDyUQrcM)_@=ACL?*S zf~ZO-?O0u(L*p@z!8fniLJu45kKf65De|xr+$D;RAag)GdgxEUglXV*Y7Q z@`6_Y^3+l`>N=-G+5p_&*Qfh_K_wEV52poa$VGccPfOSQBUrlCk6vn=1#h)Fw+nY~ z!{l5`{`Sc5tQ901<}e-B1t*aD-fkd8`QT(vGt}A|vGpSdO@J?c{)_VIzx#AL?o;Ax z8R6AGAN|Cq^c3K6++Hw?6XWOgW>8md?oA>sj>~67WGDC)VW}v#0FFR$zXACqs85kc zIH zo313H{>)(u%@=OG8SUEX#s9v60r{C{KU3!NJeU@1a@zL0{JM9XDUmO2kt+^WI*70( zViuut9wzC|<=$~jC>s4*u#EIp0JLdM&~j=(hxelXM4lAe30MdHGdHQ`Z)|ja8S!-dww84f9PmN3aj2Lmje_U9O#F> z#~d0^)v6*hv>n!m#~SPRvD?puu_jFD^+OGxOKB>6tn9EKLVtkaddM`r;2&ZNEr!+$ zF^{Y#lCUl)WgFijxX(Ika~5>P{yhKd7!Lwx3^&{Xjag$EBcJ)@&PQwkz&QkHsMQ@X z4QC4A5X9gX1Q6@I1DNbrf;HKI5jRV~FsO2j4iVYR;lJ)f#dxoyt-XM=b$kr~2fCu( zHWFM>S!QNcuqo{n03G-b4h~QQKxc>05Liushi|N+`aZR>S0~PdPR1~|br|dQP$I5; zPfuY6FlOrXUgjq5vm*~ua!IeqJMK!R8&bgr5{;Lxropc9>4k7}e&8s8Y8F7FjtIi{NH4@IQ=+5bbm_Ei`DH2i#}N;!rORccJkFX^luVj#&(EdDa_82MmX22Gz!r>o z+z{~2tFMv!?)^!5qE8A$MD$NT`|0w!*S;R-Sxie|@`P!SXn994Ns@fIb77g#jwuds zQSZ!D7KJs84D{^GXexLFVjl*dZkEE_j11#2Ql5XpYf*LbvP5|VIj>DN{h|sOnoQd9 znF+GaU=9_1EwnRetT_1j@qIs*9Xoc_hMtp%f!qMlmYbE|KXAZ!$8uPaTwKj`CI=ud1x(06*DLsN zQ9>d?3EOtan)X2Q>ghljnN!n{ zlD%o;rgWGpe7XA1g)eTyMW#ZUS+T6I!V0D}EySukK>OT=o11!|0T52~2Mse1L7c(_ z0R7p9yZofJ+X0J0+>5aUO~HauAiwDLqh#6n=N?9AeD`&C=ApUePXH`7nFZ5#DkHAY z2>=yO7C2`DR3N&0dP5BWhzm^VVo3{5^DB+T3tdR$qBv;M&^mFS`xN%$pEz+02~ftJ z!QJe*wx|_*7Ma1K=mdjjHI+9U3=O3B)wYDE*3GR8&0BH^Nbl_FTF59L#|I^ z(@9s)Pp#o2N-y+UN=qVB3Fnfe%;F4lsXPYX(ArRatQA{Ax}josB43a{dc_~g$jDd* z4JU)r^rSC+^XuiAPkE9|j6(|mR|7(S3_ohE1R|r+!W^p0B;N3qr91(UaHbQrM85G{ zAQ6w4Ff6Q}R+meY62>YGb_zmgA#(@zbI`yEfZ+2NkgIK(pLvfDH6xX2h3rX7z`CXG zsjwXUQ?m;r=y6PF-tyI3Yevq}AS%w+v!4C4^0X&igf=&r0MN4lAWIH3d^?MyEM%7R z2;N!#39v;tT?$ExSSIR60MrXzm4>EU6Su;leQU(~|Ki4zuipBtjMR%TX26}f-VfgU zJ=_ZO8JXjmKy97j-hb;8SWD_;g9`~16yqAsAyIq14B>joUK~P9Ls(Z{XD-)Pb5@!I ziF$>)IfJ%S`(hL!{d08}(n5hze`2-PpZ&xx^iQ|~P9NqpN?Yip3pn=G5nvOg-WN6_j)_p3%Q0m+Bs+`&6uU5?380( zp%VaF;4JiT0-&wEEj$B&djJ+B6iZSEQjo@|i^7BYh>mSeX7gD6K3uFC*NB;RT!Ens zuuf){3=*%0J9L>p_eNWMiV*ymxFdi*&W*P<6cH{2n{oMYZ|7g~P2kc-Q$o6gJc?OM zp4`gGKdt8oWaHkB^9rKuMV$-JGf0GZ3>Rlo>+$13L#iHzH`55SO~-OI3EC3Q5>%&@ zTM6h`kc-N9V5gRwY64)Ey?4!!?8FMrYhU#md~DJvatLBrPl}_dxkWzk;SWlGM~h5O z;QF6wOaic3P&vY@!X~0*4iZ^TI7{cVB;wLhONTnd$+wH;{GI#sdQU+ua`fO~^txDs znHO^1I3MGG%&|0i%;qH88wTpn+E1!SH{hHVZdm8GGn7Feyrn81XN_$y!W zDs*-+k%lb{>|CWJo={H(qDr^UJv7n?K~DOw2#hY zG(96J{mHhNR}mI;0;CO#gsh7iHR!w@I(QI$=_#GCh~YD!M8?xoDeLQSl?C(xA|}V9 z(`7!o{I7$6*#cUS{F?rLh{xq{t(5S#rGT4pcuh3%mLh6hV<=%b0C7 zbyRyQOk>Z|L6FJpMH%2K5+*ym%FUd_a!Zex39A>-(5{V&_ITiOZ}>A6{oxU`r|Ln! z&^Vyb!&zKF+S|dnw}Wx+#5*LIcYZ@{3A4A(FlHO}qVozv8_dy%#*3smjNFzQ_OWC1 z+5krmnX`!iI8;&`EZz>CAuaKAaY}$x8B}r70p7~tN$bMswV0F``%~$G%rrFR^sd|9 z{sFo3o_pnOS6o#hx0O>`0!h_D7e4vP^4iz`nM~ta%bBSOXaL{@d~_ysRRGvj#*45< zv^rZgoMRjpuL!m85dwu4KXmEr%uK~eR+ICkBiYA%1e2n?AqC4Ay64!+(CAF3woGP& zV7|&g`ecOA>1i6M;X}PaePIWYS^&Ir{;}=b8S)bR$nWY1LM$s&$l*8GtV6X7rg4KJPGASE3ys1IYt~B8qh^DyAlwf_(&W+JT9C( zwuyoYS(;3+C>kix1qDbpXyBx7Ya0gMyfh0GbBZ*}FvR_Q7}t)Dj*ZB1+GDVP_!N4KCW71AM?bpM;p*rRv_Xz@)<^l=P3e zE5Q^;gdC_rc~a%KfQ^3zT!)^Ab_B@ZWwh+SORW5-cEimCnTKr!E zLs_Sy3mY>c!)zwp6HtZX!6nwvah8)SX^nanpb5Vtgm3b|-n6R;_{0Xf6&<`m3|!aX zFo0Gv5Zx6vgc`#!RQ)ZgLlL-AUc?cZAjQa|LYt$yEcikb2obvy3O&kOyeu+#b%ICi z^wncEvkBTLojtws$$$D6`Sd3~oq^9ok^!+)-hceM*U8WQ%+JaRTy;5(4uERDa5aHH zk6J730YpZJnu+`n(1%;2ih`UYIH z#B5qICfFIzb2S827PB%a5~r+II=N*5JD)LudjeM&{>OiQxf-%g0c>#|8!_?v+~@o> zCezR!bAKwg10*X@NU6my#u5Sq7GGAAVR3hPTyc0OAbtv|I9z=QCTZ;2(?(1*cgWv= z>R;sh%XqRTnd92Jea=OgKXSu+<;aN{Y3&|TtriWT90cHOW3BOnpZYTZUu*IiVlxBp z*Wl#;3veje90p6VNrNbvRoe>WfL}kiwbsilv`ZS$&S+oH5A=A$Hpu-riMM8OK(7EM zIOnD~DKLg>Cb^Kr{+^l)TtTRZJ!y3j-8&F#47_;-CJ%U>nZiN4r~|=S`$+`qW;*WY z0~md?vSaryoZ8Qc1?HS-@mCx}sbK#3hc~y#_d)}r37W!c3_#-_Ja|a!qcwLLU@&}^ z&v*puY_T{ohl8l<9j7SaLLp-O&%u5H_m$-b2Kw?%xtu%qEe>2&s;4)w){E0S3k#y2 z10)X8?qAu)bYGok!Z+q8B|4l+V&83xfG$)=prL;4$0;pem<20f>F*P^Dcn zH0Wg^o)^uUOBP6GvFcj~)v$h?CGb;6#^m|qN95^P5$_(!Hb7@rkG%W356Inj ze6P9$mkio+!ikCxeeB~h*w-(|kKjB248Cj^05ZXjYKpO#(KD4Ne!LO@VGu3u=f|za z9wMteQZfqpk)G?yah`NYFB5w)0WgX4QP{I%8(|%$v>Yv#*^9}g5$vea`T|EiCLX!R zyaBY|e%Bq)@Pa18vNewSIDs#|Ss3b4T@rT1s8dy+o}LfM^LSAkf6(E zAWNKl=e2+hxD|lc-p=6rbH!V)l9|FuC$QQKofP=HZoNgm{;gYaGCRJO7@Tp?!!Mup zN6^H9P2?2W%I_)-2Sf9?7LDKGIlS8d@xS)$(Jcy0wRI zvc$eD`*9R73Ks1OsQnST(It@t*|4nYa`6jvd`~NLFw>>I59)DbTW5)1-{c`;15ojVD$DxEc zpjdCT^YP7MyW(Nl{`e!%s{D~OH@D-P;ZqT0m{F1$ft%;EkYNMR%522q48J>S5RDTA zoCKj?gp&aF1q~o8e>vQh$0!Nu<*XRa<~DGIlQOJ;$$wn?-VY6c9LIbV7e+6Mq8RC! z;j_5QCQol3u)taP6oNcH)_Kf%6|kQShg@R$oE3t-69}rxs&)2F+LyDaXk!8;7fb@| z-?!gZTDWN)Mz_E~&IfP`yH#D65%*TG;GtRfJu>VrZWRrIivo<*wgZHB(c^L9>pWm3 z4BI$P0E}ZB0P{YFYlAo;+K5%OHERYWhpiF?`1_Reqd>PqMMODx@+{f2;Jabsxb#65 z*0vO63Z1?C(8wNx6tQ8{!%>(p4HG94xMFVH&2N@z zOeWPsW1+6DQTP1D3pXkcIo|P5|ht zf*-cb?H)En(^A>r0*FI@R`>O}Mv6JfBO90Z!LNV4B z<8Zj5Hz5hPbS5i>q`!9Pksof=m}_p*t!DTB;9mLkC;wSq^V(M~9f79_=8t^veOTob zX&*XU>adR=0+3BPf^?9E@9Yeg`Wv9NGYzSD^`m!5_vD1dTiRmJrfoH3nRB33?&X!k z`B}&i)YkFMvf+HtQdoaJ%mW9wY=&?>90tIB>d(nMO6LsDkQyEtm1zhruej#Ke<`tL zIoe89!;#{E{#y(;eh*4h1{we@&3R}!VD&#d1AzW3 z(yUGZ1R4Oi#;34uu&40E=n=>|ltuKe3l(+MNUKPwDQOh})>hUuSub54sKbu>0i3R0 zfVyi#tEuzYYKuVqy zSms4Lj`U{&aB#~{9)}^0LUl>=2tT%|TtOQe?AN`TY;axU$f(vF0tIMh9OVfn4rkH8 z<}feRhNhwscw2F})5UU3hB0eu3*QzaxID3Mv0qlC95eX^b;V$lTn2wbi# z1FkHT7ioDSf5K{LW@>xF3)9?QN%jKpo-zeX> z{SJI^TX1hR1}~Ir-LFS}#-)ha_SqcgP#ROR{WPx6?f^9O50IyTWFyTA0WOvMjPM5R zDb#^;MtJ016KW!KHLy)!@^0JX+pvnr)fQ_Li>Oo&JhTp%NVF(o3D^pQSd0-`+RSI3 zjS!unn>QCUU;=aU#FTvHrmrpQ34oxQ$8z6@TWEg$WiOS#{<}}$9(T5{?0DL~(iouU zh|6QdArHi?aRwFz)1v~Alz1*p#xBOec23150Au}NhrJ0C00m5Ha>C~QANZiW@Rwd7 z=byVpal{`BqbiPL`MYkr@r&}^AN@dbJsYL5Yd{A$uGMrrBqyBQuzl6G25;`x#rgm8 z%SU#}rNg^)<%YfX<@1GD{`_AV6hc-|%MZ1;%l$a$nF~Bk5Gqzp?f+^E00--=GQ^%5 zj&`WMy;Zuqdk}^#iaODan*>mYN3r^CN<8efB#p*SdX{lp94G&zhG(MW(C3-QVE~QT zhtGqD*?ttzhn<_s%lHH|5U{Pcu6{p11?W5BheK7H(1&9GbRG*_bM@$(Am4LX0Aw4) zSq=W_iT;SBsalrf`9P&3Of$4&2l{bMGu+HR7tS~lQh?^;!NZ5KXol^@$(#{sHKqi- z(pQA5M1&&PR1ao{`x{i-yRoS`-_zHN34l=Rn;Z$ak{bVv_2hVHB^p=Ylw}o)hQ{Xn z#`Wu{4N$;L7_`mMErHn3m}nHsN3qXRu(C`ls2gGR04Slk65W7->B#69REmznGk`#4 zrkc;t09L=*NrAUvPe~(0nwBR9mIO$Pq<#yFm5DApfGs2p)dq(#5t66sHnORdL+bm{ z$hEe%p&?}zux2<;8~g&cP)Mc_`CwfhhX7*mP$)>t)T~@EIU`TO`2r2(C2`|tQ8X3A z^51ZJCP3}^&>E*85t!nv7T!aVARyuN04uQ;(?Rx=-=R?udf=yJ!?#(f{?^pqBF&i9 zZo%*Cx85cny#9ZOQJ0J5J=ecSHVpRItpJm}4;M{`%cP&zlTC2I{)LiIHeN8f!Opnu zL)SWUvNO4u^el%XBl~4HR4MBln{|TJb>;M}AOR!GB~<}fS)OL35s$|TLO?zOJ6;VK zP&Q$*?Av$VDSP*Go7eI*SrbJsec7*|liiJuq^&wx9g62XJ4ab?%BbvUH3;!jD5Am( z)CmB5TqM`5`#UnI@FHhGobKLpGn4Y(Yu|-|O1-pnLd6g9*HN^cjgvSZuM z2?2C`O|B3euWfYU2M|s~_F#3r6K7;`<(z#(%IM_eq#VQ+0ETPdMdgyY@G<#zhQ&=3 zYBr#Sp`EtCg~2s_IAPd;$-|b84r#}B-F8e!bYju2tKWW|y?yZSm9}p5VY>%0S+GXh zdNxQ$|2k>!h59};1Ddg|vH@)n2k@%-t4SdpODdvV$-xi@{TTk*&?oNe?bV59kOIR9 zvvTa{QEa8fA&7`cI2w(rw&>ye`Su7R&1R4xd_ZFwWavkjO5g})dE*2CyYl(A&aQlC zFDC$0`#_=%0g%1W6(dFudkcWpLfnyJrrWDfZa8m1HdEzs|8@<0uoAU zTpBXG<3I`5LgFM)gf)>XMXqh)UBpMBt~_=K2LbYH31ex^V*+3Zd!3qeGQhHD7^6%E zwgho>CVviZ95fBerEh61<{3|mn2PBo41GCsNghl?HHtGi^y9?GKwpn^v7raU><75A@& zQI~^d!=}x0*<0Qy`H4d~5D@1|;Hoc9D(eRp1P0&57^$`9X$*PHVJRikquc#r;2939 zAx>nAv9VpTNwW+D%hKBf?}?o|sZ>ohJ!I>1_{KlP8w9~}4<(zZmsTV&9D_v7Ei^)_@N^7UG1jFJ%aaSzc zC4wAW1t1PI@s3I)5fHXx)}kSlWkqcboTlA`RW)8ad+Y7rmVf{JjmfAh!u<~){~LLD z+fH0f2JS|g2{(^(49j)sYgFOhxUVS=7opX@*voH zsOr;RpW{|U(8|?*hGVvjeOlY{fCPa%-nKF^m!Q$3eO1KaUmG;E`mhDUCf}GV)Kt{J zBRFp+KQ)d1EvEq38>RuRE<@@r{ZP;>_=u#^NU8{pa>`L02)9EM3O&93GtHgdyggLM zdw4HF>QxYW3PD$L8_GftCIIFOP;)7qckTtM0g#`W(g^^}_?Z$w74nfK-xZZ{naIV# zk%$vN#-?htLZgE_iBn6tcgfXKt>S3tdU`RVi2XsV*lgf58s0PkiXyvuq&$jgO7|)D zO+i)*FHH!!z?&WAS>)>w8gIP`Ai&SIpu4k8dUzNj{4DFVkwY~0R2u^6$^q9X8rp+6 z+^Gd8r8YuMD~FEj8tf1I*%@57!cHtZH`vP_HIT$m-uOIsY11$6Umi~_AQqlx#IqzF zF`NVoK>58>J3l_K^BdNJYj8Sn3hfjI*Kc~mTV&4@dy?o*sr%)B@Cv!)7cY@1tmsW1 zKc;HTT%j@m6C{3Z_9 znnb;!77bUaKs55`v)-6R@crN7859B_0NEU^PKN#0n zrW~ovp|RmL&s_a(!V0?X|5tcH|M4)cjy>w4s?3HTe8rai@F{ap);cgiL1$+> zE1!o0+OK0{GCegDEO7a_2xpjle7-#^%`E@4xPFR$JleCu93HX-W-gcj$gf+!0jj*s zP6L3=3CDq1XqdzqYgXz601KNx7CZz1Bf9$e{F5)d(1c=o3g-Y6R0TihTd;PTufKR zBX^~?#qpxjR17Xk%NkRq8lJrO`^XXA{f&PDfk!Pk1Z;2+hrO_&WBy}2q>}@G^R(>} z^>P9mzIXFl8SpkYUyvqDAl75`{~0)$b@Lo%M{$)4zA|_-9utCbxC8v=S^Ufj&hmE{ zTuz<47VmOtR+~n^iOgsg4t}FGYQQPyE!Ymw4GoR4@uTubuY7fE@+OX?)|AGvXSx3O zC;nD8tlKEZ4^_*X2^C%CG%2_BPn@c6#SBy2)O`kK`a*&SUton2lNNgF+^QK z+x5`H_sb&>KE$M!v2l5cNc@EtULqHsb2hX9@~A)f1f$JlCzK3RXcxu3qp0O=3yD)6 zYY5>LN9gBDIt+t_cGUuawnh$`0L_>@9@)EBuDUhYrI`pejth+Mk&eq|OrZL4OL1LS~Jb-%*Hj<)f^rem-De1Y%4Y zC&Nkq$V~`?^5x%h!~ut??&~3`noCN3@sIsf-AaWg|MZ)dk~p*KxRnyD-*=ub(}@Xy zHk_XTZfJjo`6YkGMvVu$4Z`tHaoQuoNn7OdKr*e>K?99tCKF#2;BrlVvSq>Q93E$# zwbiZubLO0W6=RHWpBXmRjFoCw=q!BpK5z+VV}8@7O`HJGA5_N+dK7?44u!{rBrYi_ zq8k8Hm(D2w06+jqL_t)fgaAb~CZ19yFFMV8HuAG_-ND`FxQ?@cr+K&V{lYv)*UCsk?gLJ3?zybcDBS&x$ASObP zPX@6q;GllpP`}NlqLJ6sV@Y^ifPWqKE^>#~Zm3*MVvm;0;Vy9=3<&%iFf)1yt_IZg z3Up%mVPK<7bY!_RG4#$e_3cGU4PT@u(Ys#;s>*52K#=rKB21)jqKVHA_{m9tM(myK z!X&^C?|(opf8&)&Y=L`;`IM1rPfwRz|A8C8!g)D?)qy#l4vdzBA59jz>()lmDGNyk zM4>TTgM{_p*N(3--k z66+5;yg?Ry*eGfxHOO($$B=E0aA(70g}9W8a4-)p67Bq*oKGOK&wUcf-|%zi9qwye-_0rN1`uHVwP z@BX2zKZZdVwVealGn#R$nJdcyCo^$C^uo|bJB0HhxcblQBVCQ8s0HGtZ<93hgk|T>~q600Z&iXN^VBd(g!?qu3Vi!eC23 zh(ntAQ=0hJG##$;1n9UpU<~mL;Bf3#Yy+TSyUn(19(mt~>vQvXD~%4VCFWds7kPML zk+lfQ6-oqBTN^S-Wo``ZFxHd&S(`V9<2<H zMB%{r9}@t)4^Vq16p+P9DTTgB@G~VOEiK>Aw?{~PU+pfzV{GK0Ra}ChMdRL}1~zoK z>O-3gjf4mOmA%o*O0d%Ru`&oOHqp=)j#IxIYM60QpwMYTgvL_DFsuz zFe98F9BQwy!_1lZPH3zA#~1%gKKZwwu3b_!!1TgPenDRKzg{I%I1F$ahXipIlRtNM zgz+vc8BI~^WF={*i$`|MSV6@c*>O@r(qfWk6Y~%=bMhtK`s3|p1z|hOgE8V4kU7z)a0!IEiJaXc=?ro zDWg1}=L|R3UwbVyxMpxNIkp^PP)6BR6R?oHv(IAW!NEVyLDxxld}U=TZtr;M{$Y!T zZ!m{+t7er0=^U7san!XLT$k63OTFiyJ>qJstEcX*@ioxCaqU=i;0gFR>d6f1G5dM@ zajhPWs@%IU^-yoa@B5o~l~3)l0v8eaKZnNoCEd>7e|s?Wh6!Hg2;IgB4sp2fLM^O* z91z}uiArui(7`?`CvVd_fN=!({}YqdS6Dqp`cu_mA{AJYbX5=~22wKJ06EE=V>R4$37i$Za!JA`3i{igg-XOGSfq2k*~fSfv_ z(RA&p)&)wgAI-Nb&aw|L{Uj`cLVUCY6!Qwae4esSg8)tdU?13lHhS<$V$E7dgei=trC2C8T|95%V_-e@ z@>6cXANxmUqsc|)Tsf+;okLrSmE1ZU2wOiZS3hwNuHD5IXlE9CR&!HB4p25sKIUax zYop{_Tcwc$Tl#w)+(?E3TNpNvz3&}3Ae#r^sbnJNoZbMiZ$Bmg;AaQx!9qhI&%|9` z0IcyaM`=V!96IS@Y#)QUeV=5E(Cv~6J&G;l4g172A22l-=H+l=sad+j)l&`4+W#Xqbq;mdFQEcPy+y~64U~~1OS(F!BtO0G8k zMRkL@2n?+*oc0`M@eUq7g1bb~!E%#aREff&IvX}@&;cq}$SoFQx*x2*aY%i?1=>Aw zfg$e7Mww1mj4Yrsckm248(M4#JAejk8VB}qm5GBHrab^Hhz_jgYXitekd0wLL}ZJG z19NN)q4xg>)HqeE0SDF90bc`lkU&!8*^|fQLYxJFX$H{ZRL|Iuf+j}L9}^&rMrV** z$}20}QJ9F51)cz_)|ru7wP>O&ry(q&ogHSI1mHwK6Mi_7LoR#$8)anpQ1xjqQH*9B zOm*E2@5Nfx6b=L&S6NUk08BPh#F8xr$%y2thC~(dFOA97re`=TAEX*(f=*8dWS`d* zoo;5^wr$V^n$T5qvP)T(=)~26Ym4QJUzE-sy*Ypa?fhx`#+Bt9)YS^|m0P|UoISEw z3QlhPm;TZ(Au|~GqYcG=0DPt(YbtanmyU&|=E^3DPbspWl@$vMUMMeW0W@%86O%Tr zEp778|Mu^4_xFAnMLmOT(`XO>{B^HZ?YoY?wNj7#@;k_OM8Aic*JAD|4A_^$mb*Hf zt#a9}@5}SC`k1VnN%tAVy49>KfCH2ylZi(8aYvUO$V@eQx8S4UfB+!BW`s3(cyMtC zuCA_j>BW_uxvw6WBgCGB%3uj`homP$fJ{CL6#yiW5<5 z=nTpj9}|w@(7Sizy!?iBYyC3-r~$ChLZ~6Vrv}wZo&X@nS@2FUR14r-y9yB7U1sy> zSQT(J2z&Dj5)fAr+}jm#((U|f_qG&R1vmjP1r=p8QOax9t<&p?sMZu^4$Xjq@Daf> zZro+qNi1Kh z_Dp%h>;GJ4C&zUw00%I2UUQMwbl9iKD0lg;fNQ0*T7)FO5O)!Wd|A#W%8{e;*+2a=vTn^9T*QlMQ%s`j7A+^N0g#HJ zi%`)!U{rB(6~(R=sC=o~7m2q5@LIq|o&|sv$^y2eUjCQwKnI&^4riA6mrs679@@TB zTKfj1zM}`7EI0T^p^#x=*?8YA6>Mx%M15_*XSxeoy06^7Ul|ir*^o))c^-nUT2>SX zTIBm3P+>=VLLoD`p|F|U>9;o&9_J5V^y~Ve{@(>HfB+O?IxC|iBQiOGexu4S85s&L z9iEB&L-bx~X=^}7ChoM@<>M$FNn+rRdBNc*Z^priYp?~tCPWCuQ?2Gzb50M@cB}tN zj!T~oa_1x&M{LRDf=qGnj~_HM=b^b`d5UtWRj<8;7XnVK%t|z zZUPVphZNRt+C-tqW5qF#v46p22m%sx>LWp4BAJO2$PlSA=>}g)ZLk1(9Cw%=#X)7J z{Sw6;fU1u+b_lWVNpW&9Qeut-Y=kxuknSY7HpJ<{#lEG|E`~PTh{Hj>c94<7qqqs+ zF!x--pMx@nQN4T(&H>;Wy#^>QmzYt>$pUTQ@Wuj;G~oo}F{Hpl0b3wGjredhV*;QB z2QAKHCGOV_?USb+!<8w($!SLj5L6j;ioqxi(3&IrX%snHl5j+F@=Q%r^I7d>ArM<| zmd2heJDyD2jER~?99-Fea{+Gt>Mioo5B#+k^VD14@Yc7=v!4D;90)k3+d)|FoRG05 z!%Zw@)5+Xs@;Eaf1z(Z4q(x5iOG#hB454Yra6+aY*JpO2Oo|dpA!hk_jUOAAM<06( zRh)b0p_!wTWnLZvD_@@P5@n#;6w=F!CB|ljzXs&#;OMB_ar+%hM00J>2A|LvvM!vu z{fp0g9@=A68l5~t&a?u>z?RgJ#o=laC5}>f^jicNUf;#DO>)@2LCvQ7fBX~q&<)G0 zJ*y_)X|Til_R7aU`AM10wMlEwIz4wo;rQKU4GRSUE;z{>a*v^NSb5~c`9yuQTy$hi z+M)W-Gw&tYKw(57LasU&d$lBr7cDWB)Y6Wb~&%KX`Y~2PzSPwm3V;g8mUFkNa6n=p^ST9q2LURa$tJC} zF}1@4dXg0f`D3Q0a4_AdUInOY(IMeIxNE(us{;ef8_w<~uyg?X7H2A?i1O`*(2lR4c~++G7KRUlJ;PUaYEW zj@274c5SUJDJ!kCGSjqJR|;(QnRVGN%Ne;q~^!|?(gNnhaaykX{Qj|N3Oq4 zb`Kwvj-DZD#N=)G(IZVL<6ur*nL^%#Kg?!v@ITkkiap$o@<#_B)m)r@8;+Fh)yGNY zfJxE)o!#;T4%)&g7^5)tT?Z*2=vAwZ2rwJ2sn>MW1Np61MSWlq?EW7sXm1*pO z=b?CPcWHu`IVyf8+p&rxrU1rqkskLWa1nieVAzp}+EIQ>$cJ7G{(EsgNzhm+57@UE z#pLn~t`uhs!iD%rMx2y150YdijOaWPZ!g@l#(`o1F-Hhzbdoesiz$Fv-sgrH000!8 z@sx}5O^tQQ34o$pgvict%Zi);h|2UInl{Xm4h{|CgG=>w`mth1@eST!D|!Y| zQ(?m}SZH^@RN!)+A+l^hmJ4|R2&$1YXVXy6-?JB|fNNvN2HsW!*Q{NGl^9-a20Z}$ zBI6OM`~)t#?TLh$_gGsk5bVfD}y&|L?B z<)34*=E6%NO4n=}p7c~Vb5+`CI;BAt6q-#_5?y&3lQEo|A6~{b{uqN;hK*3aXzgf| zPMq5?g_XiTef6Kn-aV!s>f%Q4$|;TFEY5b(Q=cMldCOZRKZS`OOuEhK!kr@^W&fVSRQ+5IS(Y$^Y@ge{*;`5 z_Bog?o70s$USCUj!^0JfG9;as^i2;5xmYHca20@r=t@~q@g%a%&kWB4;FX#kZOt+@ zJt>#H@y$42CN*}cVI49Td3^_^2}UwfQ57a z@NSX`Od4Fe=X>&t`wu`P0DcT#b~#-o=u~kay_Qfy8s)*3S((h?#tE$F@z7dIo%>#; z$q=QF+dA8D7S!4`Lpljr&%r+arg-=rZUC?YAKh{}zn7fFB8osNl6 zQJi#`cx5uYq3iA%c)!E|O7k0xlTM)6LaO}UQ;$`6WU z`oriS138@O2C53rxafS<@P?h^JlZ+#V>FG5_lh!ptGuEz&dk=;J%pbMFAHBM0M>8V zU=si`I|Gx)O@OKapfXj7oO>`%qTGAQ8JtL)1KK?H8R|7AUgv@tWyf*j2B_z;Bk9Gf zIgMfFLrdBAsXPrHF0bCIub&sqn{||V2I$V+dxI+x>6g>f=KHa#+=Bg>>_kU|84}{N z?uk8_6hA3*7}Vd@*$&MC4*sEL2KA{HXazJPktS$V# z6DjO;I-~X=4d;iqf%qBn0<)|EUN+rJVSKVn<6lJjE~ z-K+i-18quk(s&V*K}+a<&7Z$kp7)%emJ?8!D@;x46+*UuK0DFkQqP91C?qrM*(K+P zn4MG^WiE`9Sgjba^h0HvwcvCz+aBA7&fGNi|2J4$7fcXZ4Py>nE}1Le?&UJc*QDD6 zU6?f;p-)W&L0jO$s~(ysbvWDL=!p~Zr7zvQu;fo#EI&t^aL9Gtto6%uu!<0?f&SZiNA!2+d-wPBNFQ$i z2>OA#XoU83KQza9eua$_it92{He2f4d=YNy% zKle2A)YmSoYDUJ047w}uHk@oExf;QbK~ug??j9JFk*02Gp2UD3gHUc^&Ecol0^(4o za}SNnpB;N#aPb&;jfohDkgaeKKTVug=S=vjchb+|mw&PWUkXz>Jqu5tG4+F}C;}H= z4UIWz#T~!>gM;!t?4P~-Emr_Ci=r(5rQiy2*2%lCzXn?Y^0*b?sMKNB9QtRRi^dQ2 z@#K?}$Y^N-ti^~vqn0Bu9d(7iT(P8@AoF>;m^|)+-uBqzTK}jnuSVNA#|xKV=dMl8 z7KSZ~Rf@w5V-`4xi+}vkT5+Kj(Qp0s?Q-n6JM^_Sd8sWHQXl2h;ePQ;epxsf%TKMI z+06Q)lcoU#qqv`oq;KM@jVY$8TD;ccrc-2J6!$%10dnHQZUw-(01ZuY!v{YqJNGX4 zgB-JY@~IDhQ11E31JX9INg6wG_#X!n_;6A#xn`K;3wdFG5MF#7bGV(TEq`3DdE}e= zJ!K~>MO3m0SPfa(96)2Z3)|Fk^Ehaevc**cyscil+D(73De$MAZ4M6r>_$J3x2otC z0D$n?*ikGrSbx!0y)~{wRhHWXjDVB!??;bA`XR;kbUaZy#W6B`fJpnn$P~3DHlDQ! z7ldKIB^RxTPI)vZ2lfxcf%c0DtA7T*q8gqBm+rrS926l6OIwHkoB^2Uh3>ek8|oZb z^*{fDv)Qb;NdOLtIPR-O(m-?i8IA*2jR^pSV78&Ld1lMT4HS}T>Wy&z2ptkmCm}qg z>Zw>^XFL_wL&-6BALVkXhl3-{y@Q?Nscb4kT-V>l!mF$*{_^7qG1s zEQ;YEMLt!&oq!QA@0I7Q=J3dv96mA*rf>oP4Lv>(omf%s#i}M7LM`G1R_2p@U>I8p zwt@{9(Cx*C;=vxQa3EQ#c{@@#RYZ*dDKy9{NB7EkCr0HEGy`Tadq6D!9!nFFrmZZI zI%9e8a}p16t}<^AfpxITz%OZVye!Hm6_i_8e4#~oUx(Q$mS+>RHoAKI@+T~+ptky@}f(i(SuJfx6crWc}vjZ$Mx+AFh(x+s=%i- z&I%A@rVDx;NwhL=XvcUUAXnXZE5JbvHm-T+dn!p}*}?4H@tC~l{nueqqgk4}1~H!J z_cS7t?MURb$q-Hp;A0G4)iIvel`uN1RLrf7;OXiIv5 zkOVEx$(9D~4MzW_0D$p72K+NK-MP-0t>@brZ$Qn}e@+09c=v;KWtpIuS5~Goa?9~u zp}nnv#$R%$ecZja~)Iu_%&4ZzhH zyQXYT)()Z3;~_oJQMW9nQIqCZ5kVf%VsN;)?(Lziz{-vmTeBS0e~*qImwo$3(BZ%Y z0Qkdd-`dhDLxcT_Nq3Be$#~4CpZmeeQ~i0%%)J9RFhzqP9zl3mp)kSH0V%p-d>;;z znSyoz1|K*OkOy7q%(P3#y@7{g5JTjibki6uK@#Rw?&;yR_+Ea>#jPMbhQ0#$h=y`> zc$foFEkeq4@Z$u4t)AsDAnld+fA}ME>&@S&h>DW|^x8MPL7w}wKP$%$LUj&X0dS6p ziU%Leopw4FN{Gjvg`%h?p4J}_%zfGYxH*KKBqxv6;5k*WyPw#lhZxmkg;me+FkuSw zkj_pmrSc>vMLFpXFr=rEhCYtKx%~Lf50xRTQY<5`T-9Us>%@skx#^~_r6FH7e%8(v zlk1pxk573}W=xw_l$+fO(1`5-7y{!P zb<>SE$>%=*r3~tPTp1vz$orjFULl7MpTL!^T~dd`HjUDV|JvrMC;KVpZ!p|a2Ms!W zQ(GXzuin2)bC5%u=}!PG-<+Q!hOZ_I4){_*`2P}tem|SSlZx=+3a{(~}Q1xP8 zi~(?OK5L^i;XqEdtJ*)rdmjeACvbKYfwVtOV2u#NR++=aC9VKX);o@n`Qk4!aaZJ@ zg!4ma_v5J@1n9&Z+F5=-)?-U>M4iQ+#uehDV>s9tf221=;)pk}FhF5Kzsf8=U0E?= zc)he^{)`L{{B!(|d7U{N#f0PkIE#T*|BYL>%yjp3adDS!4*of=~;h$y6k8wkDgQ}}hg?1h*NN>9GD%rMeXZfU01_X$F|A#&(JvdQ!d<=&OO;2g- zVyhBCzGKYOIqPh3P?{IQvt(woc^)Gt84Z+i(r|FiT3pH4iqLF4MNvYI)ho z^Az)>CtoZVo_{_W@QQG%}cAjn0 zjBiON&Xt+PUb1&w^==tCoK}A6d|NPqHHvuW*S{+NdGpt$1(Sn4Yd6Xq7ay20`!7x< zD1X*Ro{@vEuuS3bze}EYK>B&*T5*i!QVmlmTlG5G9LTB#a~Q-6!iBJE zF&E{FKTa@Wc~4rCeZfI@tFhMGovmY5?%b39o(^4#nhf6dj z+%ZQY0-b)iZ%+ZO*2^ardF}4%#5M>ng!us?N6-!(J9=FGZERq4SnHKcw8k_h(O2ju z6&SP8P=OokxVWAR3%ciAaKTJnOFIYuZuNgf5B|x{V8}!3iZ_&-*@lMtnWtU+B%1)3 zEkFYxU%(Ck2uiVV7yA_Rkk!dmsZ#KnVJ>n4b-H)dsbFKJKa6UR9Xl=uMn*zP*p4x* zqq9SX22knI5Za*FPXrW&M42UY`d~G(FBNWUvo*s?DYrxLOc3-g$pkd+IoX7(0Nc3t zNsXh##UURpgfCth*oY56J36U*gMc8|fPyEd7uBG-dDpIor2{vQq{Z>F@e@n{U>_G&cUoMPipU7z zLQ52;wh9>SqPlkiLv_yL^nA3%q1IkFO)qN3P^EnI;HZ3O8C{cEDGzv%;V(S@c`^r$ zDg7pK0^k&M(bD)_X&wyDN{vs$mBbj8D!&0bp@}w(TLC(8-RZ$YV{-ZBZ?7)NQv`iv zbVOeJr?0{m+i48`H%UVqGysD3&AlU@FyEH)hH3_qfv?42h$ zW;j-#s~m`Pz?YMe#yXk6b)wB^H*~5OqNd+i6Swijc~4~_63Y8ms|Hv#wzb79`{Rw_A&c*gN=NQo&*Li|HEMKHg0mn`u{Xm{;;5X z!9`EPL_p3B{+$LuDKZj>&phYq1b~$(g@9cRoamOD*}Q49O#oolh=)jWHVM+2Dr*`& zl1Ia~q|Yoe>~qVcX(LIlb1?%An0%Kj=y0rEyB4ak5CcR&oq;b*n1r*O z)C?L86+Sa5(*Vxek`E|6Rs1W z#4m?(Dc`^P>o1j;zx?Gg4YlmKsY#r2%>fNEpgM^*{7)N-yS%$}s zO6%YT$#o9|d;cx(%(49}v*rsw2&)eMnRT3vQ-J)wdEb4qabjBLFrjPE%PXI&)GSX9 z__gXAm^_HV1EgUG@tLvSIxi>BHf+_HCx$oQ&b>?A_27Wyjwp z!A+8jxxnwZ!&)3#oljO8Ni0EnsTC!g7RL*|*&Eijm7fTFFNUxkxDgYuW*m+=*|TR4 zz9ZDcLM-rgB|4mJs2EciG1Avyg<$Jpp0866qb29S*)J0soQJ+fx7&psM{{v9*mr?3i-rjQ*r%pBuB0Xggx z2NJpRqH4%r?A#&GJhD$FG5D*S!=1+{6P_jD7*0C1ktMVeZIo-|@R7tC2B;-1MjXO- z5m#w^K6E*Ib!nHwmI(LBgk3*g0AZQ(I<2O*7W`UuOTa(<)4#~){?C6T5S%*a)z|!` zY~3&@CytF{m23tp4w(Ak$|e}5oseiW6A)R7lP-)5QAPx^vXJ}{hr^eQZ~lBLjgVDp zT&%%l;pt>{ZhKrcpXP8d6xEHHpMZ0<6#W{qgw&NYS}F=e`FDnPWD!4VHHqB!z>j3- z<2zjNY4bkk!i(hT7hjAkG+9^lvm4ZtcoxWo%8kdU$|KbTGM;h@)d1jEm){gU2LNXv za61}yjLEzH>U}bX1M!xt`TlplEua0ue@U*rSK9kA0f2slt5_zRF29yP?yaWJ9KM0G zb?9NCDV)ZH_$Fu%yi89Thx}sFD&v#7T-mTn*D2%x2QVDTi8Ra2J!@nRi@o&2IrdRo z&)~2kI?X$JLf+7|KX>b9X+{5mYX7K)y4`KZ_T6#_>hI|3v39~UKoH`w__cewx{{4C z22n8`jOBVv9vhG+e%pzLZ4ItJ$M!K-^XSWX&~n|oPo|;0sN0F*<70@?qhTc;9=+AX z^P&aFi+!S0gD_+aL$-Nn|IcFa6RMZ!cuC#N1s9yh0g#&jAQ{z=5+!p6*;rRrjd1|I z+#DBXXEtm-XBuZ+Qwv~bW(Mmhl%`W9Dix+{GhaakKx0K4JA+Ph~jRv?)rYc5fv z@!udDH{rvL$pFrp`gzQ(W);$dgqFr+5|*Bvg1b1#yL)f91g)(mrz-2MA(+EA|5Dyb>n? z$ap1&4Rt^S-Cp~oPI|d;wHH4kxZ*J9lafsrw5WF_X)ibdk7y^;@|!AaFJVpn&cTzCEZq`9tuZ2-^&;DNL(SCqLGdokB21(g{x1;0wc zD?_SK>>TiiUJ)h`vE`+$UHba_s=#pSLI3;iyW}WTIk~c7GGl8O$}T)&Y>PQ+OS>n! z6(BB#F7`=5gA8id7ImQktH|Rx@buQ(zLNsD{Js9)|M>@0WC%27_`PDQm|Z|tUl^VZ z6SkYBKoYE}0yGgb?kvV*t-N2Zrp1BnWf=T7;t;(y)VCcwcF6nR_n|nlW#XEdJSJDX z<&82`sFTiKtRA+t>334g02$M^!>5%@a1A%^YM#+f*;_+;Nw*toSXz7$VqF5t02Ik^e`v9YVK6(QMiRXMm*CT{1>KkO}*3W*A2M| z0R5<}{gi)013Ha9Lr|*UP%~>oEJ-rVZV~tMzhai**f+6{ZO>abilg??j`#2R6C;0t@#64$~gNatJ%E-%YkL;=Tj^$fEB zE!h9p)z>3OPfWt>2R4hejo@R|7(^FeqET z{UW!-y^K~w4d6>}g{7M)iqxtK^78$BZo-Ss3rXWB1!4J@&qpqR-(>iTJ6J>#X z(O=L>Ol4xVWf7sLZE>;VxZ5;8l&(f55vk<=oC(uXShRHX!t99YPH;QXyCEXdsR^v^S?w1vBQOU9oYesvs znAm5fBnLOH+mJ|cUZ(TY^4N}@IA;mpPb?BzN5u%RFXm#1DIM3*nINh{C#D}^r~!~S zwdd&#>o-gf^!3Ij04hXwMuV+R09f%--fRLu=4UV>oL;|ay-ol?2LQ(apa%?rvZkht zJSMg4U0Y}{e6AXffaB)<^Zt78e(=GOM*KS z209P#T`J52J84!n;LJnt=0K6FncE)UDU(vnUa<@rW2K2R@Ys)>9ioE6YjO`alHmyYvs zZ|fj{EC@CCM)vpj;+zG@`DtQ?_wA7fAAJ<6#;9lP6wDfx4vB_Q_pj7krPQTvGcJ9e4Y*)vD z|I~)FHt`GqY($*HEdbP$sluEz(ef)_M4xt+f|f&9N70b6QQM0)vE-&RkGB>L`(AWGhE1^Tm~Z>z&l&V3U0^e&lGF10L+Re(eB01Vb1sV74>(BY?3Z zNP!M~IP?sEo}qw_+87Q#`tJH6#l`(%&Nw#4+-R8wAywCH{|N@GPzlGqvp8V_jWD+* zS?4KimK*|2Qy+@Tc{{xE4^reC+KMBDLB)J4_6dQju#`x$3M?LI5@C!?I`HEQ6FfWx zy%{TLUESSs%QwC$H(dW=57DWx8u9H=PyEOy{$AQ!+GOJB5t+pbH3!yqvPg1ZV{ww% zwoI1DLve%>z{w!3{gEf+oE-#qhJj2bS*9&%O9WoMYw2V?Fs?{VbsqQzAFtV zzb4eTZd@h$*yE4O4LAHXAy2tEj<)L+|MRzH^7wHX+;pzgcMRx&iwg?6UmDr6mL$lr zx&yMI>BjCiR}@>Pv4vp!9U8!1xyP6Qc=ff)fh-P0wMZcv>g31JC~2K(z!iY#OHr2{ z70GIt!=W`z{Z-iD zP{ipqB#E3RFe8+s-3pC^7iX21 zTxfmgNQ^==i??vS>T8 zhkWCPwc7DgR@g&Ruq1(x9~JzJujtF*<1mL+{5yKv6b?jdH;t#B44}Z8ztf`bx_35r#<7T@|xGYR`O8Go}D@o>~Ti=7+?p5AG{-) z;^i23MNrNbrR97oeLKw28*<2I4?nIZZ^BlgMyMq(YZH*iJbdT&Z=-%vBQ!eP)K)ME zgdgfLjXl*GJc@n%&{Vr)niseLEMDwlYcW1AQ3m42Eb)hGfCuUW+PB+o|E7!`%-RZ5 zoX)B7@$wQ7aM6WNlArq-TnPvbfO)Kb>*tsG^bMlfSJ~pnvBX_bG$O$-D>wqIp)E)= zz!*=O-;*F?DwAx-FqFn=_zgG@fVTp)wRgzJKlx8`--D0Ukfw#9y7ujFlE}e*_qDc#NetztTF6Jp|oR1A>c)m|w)?xpD53WMgJciNh<*`Q} z!*z(0x-Cg(2lNBOY&a}Q>>2JV!5x;66z5Ul8%4}r+tdJnKuqd*?r9faFpbWDn*bn1 z3#6(-B2E*q)d>JCC~Af1=q;Q8IPbzInFfHJ3lNU%N=wZ`MK(iP!o;%hI)LOmz^5Gw zwuw2MsyvI8gq_Hx<`n>lgPOwg0oJd>>8bE~!+9dhIwv6QBti7EPGFe)DCQ%g!C~ z$*-bxPLju;U-nwL`24LhF@9LL0&s=X_BbPZ$}~RW8H@8W?kXlb9ybav_IH@<9d#F! z4m`AzfWRgkqIcHDjS;G4ZX3lFPWS)hKGoEz2kqpi%c~>Z@)+Ev2ikj#Y1BeTw-)~qz*fVMix<6E}{gNj$~-mY;Q z(EjBrV2=W?K2|x9!hr{|U1kcCWUV+egl9~swkI{%VzB(a(Z{}PT>NQ8|6t2ToPMve ziZF=m9Q+^MwjKB0V{wKeOi%A+4#Q*irF#Y`SCBv&WIXqTh@kkLupTMf)-7ANV9OD< z_pv{0Yyk9}JoMnhT94TlN7#&uMpT>+5q=9zdsYII7BA@+(Zi599qK|sVg~@mbMvav zecrj7Ii91PW4v%Y2m6XP$#KaFWrk`1P$)P$<}uyV7d`o63X2L1CX;X#ps4|{{m?&? zI)Ko`#WBlE{cH9}rWv@CHH;kz4Dxg62tT?Fd*6>w1{9!Dqmkple=GJT^9m3ijG~o? z5bk3Rk7SjvSr)26l+11-ZZg}6U~Bx4CuVCF9g>G0c~s`|Gl2%cobLTUck>2m=9OVU zW{tnYLhReIs zrc=k(0wE04AMI$LknaYpjNu9!`SML)Uox6%fjBCgIJW0J=Q*-v^H!5X-kyODzA>M0 zuKeP0s&Z9{qDt@tDe>4z7mH5I&%N=1AzvCAb9zW^dq=z6_1(MWGoSfF0`JLjjvqQ8 zZ+z>UWoot&x2>#^94Ecm*t*J~?H65|6C(=Y{GJI0{j)grzfj+dcV6DT{Z8q|w^vP^ z5$3o0T;)I+4!F)bPCSvDJ1}@{Xi{;+CKDA&Cxz_0y1xjeslNB)0Kg4HgXjYygx_F{ z3y)#j*rSha*OTw5F?l;R-I)b2a~ z!V94)7o@-!>g)*|R{Y?Dk5YvW{@3JdLd+}%CP4J9;I4Jcv=fhj3P zB?B#ft)5IOOpYJspq6Rp3TjcjT4>+?{kRu<6p+E}A7aq939EZ8P0fhH-0^rjVDf9J zA`Y?FisX}shdI(*+0-AhvI~d2jAK8es+cm*D9}yNgy_M?gogs!)&P(g@FC1S-TY$U z&6(x~XjUA@V4l;9l^Ei&-z-)_MBZ>1hpgZp33F;}4xf`Xbu)5cEJx1e zzl~@QGzv`hjDq8~AoFq0s$7*PS0*h6r-8)$EH%nJPeO0P6^%J;amit&>W=T+CD;Ge zwN=r+RKQRF>1WF;|L~P^;>ajYX+ME+O+gRip$swyEbk=W;_kR>F?9Nv;_}4SfOO1D zcUPAT4W2$%0N#D)9ddMfMm0gWEu?sW8Ra@AKIIr0MJornWZ|SYiwyM#fUf>S{&RX5 zV7>hJz2BF8dk!oaO-qHi1$T4*+;g9Uc5zbc2({O=E%X_XC_o#4COk_K=;JB{SEDfP zOb56uX-5Db(hN-$X%jZ1H!kzlhiS>C5sQwD4{y%H4#8qcl{Jg|REhKJcl zwgkeCzWD@CC#~)s3#`oH(UpnC3ka>DrBpKMK+cye;{Jc~pb7n>3op1JLdHOtk^Lja z2i;O(^*j8I2K7FQ*i1S|9x=IGJ2#)t>jXgWVBb`CUk|(J9M3ro z0GB{z_|H(N)d>JC*aixNn^eRwOs3mAx~8|T>Cp**e0~NR+PG7~rU0-Zu?lIZ2DS>c zm4Y&Pb_naB4tjL_m~7wi1bl)C05*ge%&r+4lEMB#WDC#%z19xU5qS6sW_T+|3b-G& z)^@+i@$F$c!C+k*FE(5R0*xbMhj0$SURW(Nb5I%U>c))ly7d^&8<W>@#HSX3 zbu=-!z@ElDe&6f00TQ=3mNF4;gL(O0Nn4&pg={-90`mONvXj; zvR^B z%P*H_JoTwEF*YJICr&`k8AZ-~xxBfz$J!JxFVwes45>I$0+1a95`*{Jwb&zw>lIH2 zGchqC_k92Rn8~%X3rf*!Qyirrys!j&ybxV zSbeN=pcDsKHxRzPy;~lFT-MFw!dG@$LqQ6K5>ROA;Zz^WEIzax^q;+TGtR)`!M5nf zak345k8OKgj$ylvUVEo)smW?cN-F*^G;RrDEmz)VA9^YjaT{&}hefbl=fOaWQ|+zP z^Gp^?<&%Hv>2kr@XQNHTVSu=dhufsEd=)f`QFfx3ku3^Mvw3i8b=>n{>FSHJ!Z`NEC2EP#gUqwLx7xctE%zC!AuO*^>Z z9I0!;VQUnspsx_I$Il<_XvYA@0|Iks>*fn5`;GRr&<*YjwNo7!(5_L+j|G!DjJ7XbNh%(M-K|SQ# ztS61r%!T_^l_=;`_K;r7oYAf;48#Pi{6~9w!TIM)C$7Ja8(x*Y!^Y2yuf}mA4oR1T zC=RKGPce4#B-^n6#H=B6d7ONX#oTkwzi0}b`{V>bX*AghoKYc=@>K&4P#8EWwh4fF zJ2~>jFMP310L)BJ<_j}o*!)JAa0#EqBbIO@mu?MZBLw?E;>!B#Ws!j>(Yu?iVUY&^#D za@}$J?YKL=fQ}u{^5D?dbzCAIl;fP{C<3<(Cfy6kHWRKW@oJEJNIe?aWYuE=fCrs& zd*>sV7|EXaswTZ9giYZMx32w<-~L@pV$9*Z3`_#p$G0RA5gIZia+QpvfgS_P11}iD zNu-a!E5*n=`0m)jrr5@Zn@yTAu~iSb{>yi~TMjJo?GD@y_=Z3JBdi2YOaEEtNv^9O z9VCA9Jg$c9jcOVP{&Og?0>7*5hvS@_-#N5hesvtzpJF=}x>_N-_&)#D{~?4B2q8d#Kum%Y zLJY>3;y}RI7!wE~gyK*G24iq9vMt-PRjgjDF72+iz4x~K-*@KDy>r{!_qJ(`-|XIZ zr<^%+=G-%V=FHGW;fDqrZwR*UT>a;62`WEax?~BIB^za)hG*#4;wNuw2QV?lvS1CA z|CLwCQk(;-hSDf=7pe_2@6!^w#YSKj=%5gByTHRydvCpE_!kD!6+vC=~vtf|(Nhw!q^ zl*K{*QJGd9whB(3Hcg)2wnP5vFW!Ui?oQRXNDHUbjWrzm|N4LbORl>7a>-!pI*$VA zDknNK`n^F$8YOA|N`now?-*ewATb!8+QA0_oMfuO0Bq^fB{2*W(IW?4t`9tb>;FJp z8I(eq?{6rzl56uk7|fu>vtogxA!+_mxFoktm|>3Hu_ncWUwWe9xE2m?TJ=HQgpMsc zxvb}Y_S3sl(wHFL+E|5iMar(a`f6NNJB_ohPy@h8GJIjkC+lDSSrgc-kA0_yScqcY zqk+wSEjL~J*{mOjAgsIi$F{qwx+zlU-Up@4gG*3TS z8m7#FOxZz^CzZ;7iZdwmGQ_&0A0w(9cEw>9TQX4oHv{GJ1KT&?d^V0{VRDXI|C91R zCYdN9{T%8ID&k;59<@=dC`J}5z+^w zv%3qsW4EjBBvph+R!5+W4}IeyG0>RDaN<}b;kf+PzR@fi1%N!R4S(GMaQ2$BIpB%z z0Pv%|oGqmZ0Q2=JM-&)OXlf`pxdkg%Wfw16l);4(nQVp=0DaH}0IhGFLNmP&6MHOw z!}30KN?wTwI!5$hX9;y93hOQ?0z8hHcvCS1=oT!PulEfzIl?9vYy=cTkK!S&!c39i zg&|df!$k8cKn}9^Krz<-{Rg$d;CO(Yt!b?d&;tO?u$UkScnF?Y6nI-Y36E3zJZz3| zY)5OxbWxE;2Pqm`w_#r8l z0AC8gD0>%~9>FyVer#0mkPRM{Xi@1+)8xtWgP;6dZu;bBQ)smjQ-Vj?`xBqKLFPi{ zx;r~02c>{K@9Y&qeYG$utOYXV%K^N($*`IjmH}dIQZjrjC zR;h*7|9-ZaG}<{J_bs&`&(exEki87tse>aM=zUk#$p9uRz5uQNQ#0Kf-HB%0rQZ?< zhQ$F09d>!`sH&4nObW6urUeDR(aBw3QX#wC)y7>Zixz`=X@{kxZex81&<4KxHkbY^#*8jP4FtJAic-{&|9`XjALr@mZ8v;0| zVt8x0OW+`bfsOttCIKj!yr^$9V$k<>Q?jv;FE!NGmd)sSH|(vI%#vjb?ckrMkJtb* zqc$3>FJDud00@733I@ky6cVTvt16rfM?NeAJ?+xleH=9qZUsOO8m*bi1%He!%)PM7 zV$1(di(~w~U#5$P=41^1r$9%@OTpZG_b=o)wEt7_T{)DVYJqVG_p%i6b~)nOorqDZ z3RVn#6e_29TJv0N#6h7_foOC)HF@e>r^?YB1^O`wHR2pLcUP5A`C|Z;6I4n zgq)tKsHm2H+|B-Focljx?>-yDf4l7UJxi7n2ZrE4AUA_@1ogqO#%8JM%j(fd?&9=| zjE{#gD4l%q(56T=`T{FgEYaO<>VtSZI9;_`LG#5l9_*Gp0@+ zu04|BJV6o2h;!(OCP#)KWKsX|qZhVuw>A*ws%mCs=FFbSF&rOG0C0S#RTbVZdsEC< z<#(M~kfdHp6NQ7qqXKl=8LM>y0B44=2Oyh;boFtX3E^I?0-H5M%dCYQ#dm>;vNl6w z-PN6uO{xTq*$hkzu0vS}1$f-|ON?fRtcN?4gvq*&K|}rlu2HuQBOOv1H->h8xv2AMYb34lcfS73e<-iM=32=f zZ^J4?r=HPMt;WhM5TC-d6z`M_1U$6y__lV9#AXzePE5NR{93+v5p;wYxlTbkA-r?g zn05EvKgW6F0dMu6A3UC4Qw%~m4(NB_1J4oKQj$vYPV$SV&O9Y`bOT5YMP5|qQ8!g! z&{&4moD9y;f9Lx@3;-vrEzXJa&N*LBS+?990pO&lPQjxFwxE3DF;44QL#viiH*S#z zOp?}PfG|k;LAqLYd~#{QLxQ#3fI29|&0zwdqM}y1Yn$Zv_iU6`Z{KX_@#7^rjc4h% z#DM}hV9Uslpw}l;*91kinAAr+PDL5xy*=?Xex@JABSbAt4YGX6A}D*tgrZg;gSZm9 zU7maX1=UwkNm++r^Vctj2FXk6*YF9yAwFoPFbwXOdGqG!E(kB9UO<`b*|iG_0DE;| zOmS#K;%1$`2D|Zf?JiuBJ$V&B39@iMrH8{Z?9Cn+;NTw$9l83Sz4U?$vrUavNjm@v zNB=tvS(*R{ecU@KNgNs5Nr2p{RjYIYpuacM%c}q|{#QkSjK&IL;v0ub3?kVHr1Cqx zblxk(9AHw}$C8Ef&8oIW(Ly!xvzLd$Xb#W>_v1j<&8L(jNQZoB#C-GVkPh(sk^p z3}7XjqDP`FulTn1fErI00*@4%&ya+0V5JP{!xwkAlt~>{(oa2QWdi)f^Ll38I@!E! z8&-*HF&;4`MROcw7~OcRNKCYxM9D3jJ{uFc){OH9-B)zps0@%-VpidK__0T1_pZH# zqdftTUUe2qxwfHB-t@ZHLxCJBNbdw5SFvKovH<~sRT&Zk^%xcSGlD)T@HX6w5#SF{ z6l+UL$>Q|~9}0vYd`o^$?qaIK1ZFkrvAUW%x$_(UA#0!6P;AnP@OORt8*zoqih zJpCkS{jbN4Ks&qw4f=6N8Ec4udS08>k8)Rm^Zq#~Dm?edX8F{X2Q&??q&OcVG2N19 zi35djfD&iQN1c4PWg7ZMxM-4P0-n?{;Y+0_nt_C}_^@3lmy>7Dl3BRgmdZ4B7bk%B z?mZ|wckNdFH!NprqHl*l7q9P~=;?Zm{`1XA z0d}=bX5vU+c0SJ6f7#{T&#-Q5L%}F3dlw#+DE2HUD7huem-oV_R|c}Z+yy`#0Gwx{ z#Q6wfmITRg6II3DJpXOc!U*CUjo@gGAf7?aHo19!3d+cinQ<0G1>Fti#Tf!K7vOBs2rPd zHQSO!^Kp{VMu}H@K^9=+nQic|6T1h7YnYZXIjO|<|Mx>_(t^ID(!)x)ALlAD!C<<( z>~`VLZo#m8^u*CRiOaQn$nlCW!3#)+4PvF+lJTX(2O+~xlQ@`eacNsgB7PPF9g*Ihxx@%ej-`WT!Avk4xt)m(6ifbPwu&PpKu$;_c(Eu z=y!=z5{3xAzMD71LPnV_eNc~7VnV0wSiAh{p8Mh`Cy2jS$6*~wHXHFfpUfNl2K-d?%xBiHNx z)?pH$(RN|A{yiUfzuIRlJQa7PwO~XAE$e7&eClkw;|Ul2DRup5E&H%rO|bgk)OB2L z-f+Lvc0-XZCY{_)=E!+9k{p)ezBD=DOCRLXOg%l`GFjF`lYb2)uMD?UQ1e7k8YyKa zjxTSpupZwrRNOk{DQz~5=fND&jk90?sGXPk`0 zipS>HzF9e0JKt(YJ6Q$ghs%~n+IHkM+n3Q}vE;LsdX3Lm9s&ANEM7)kd;YxVhs+=9 zr=OGr+5w)*#^OJg{yF?d9+iKE7tYGR8Y>v9RxOtrtR|waG=Pyr4C(B8!v;j<(0JU94$!EqBcnl^ z35mOD+jH2?+$ULV_r$alc5OieHtFzaHFHqRh)0~$A@;o@bQ@>#H??u1QIerusG%WT zXxE;WrVEp9A0l>yERQ|PJ}LyPfg-J{I^2ALodDnd_IKnPcYZ59sc{N<#TA#y|NW!? zExkAfknM&-BFGhwsE~F`e=;d%7?{kFFeAb+#-+aiU;Z^V$!jZ{9MK&>(}$Lq}r@e zr&VBgQY9uwIO+NGyYG>Hy&)+UbwYbgojG03IpcIGAIL&YhPe&E&chq&2lrk{-<>5{F)fgw0xg%4nAt&rbLsgs`CI@1?|Omg=ocLX_* z0EV=w2ipV4A<9ex+Wa%mT%n3lO+RW-px;ZLcxFB171E3e0JgJc64J=N%D+cpAiuN) zE-!d&UP;M>Gn$&NrIO&L4*?ta)Bs&q^B2sG#p`H4F1z>aBPW!%HW__~=unKY_#^kQ zDHeu_Bq=3m2%w$~%>qVgxU@|Mdo#@yQ!^`8FSk1YI3d8zNTnSB4r|@I+3_w}!2u5a z`f6+IGpDXRwFmYdZUMjq6ONhc4uA-g{4R*+A1W;o*zs^OeM5u(;)h#*s0W}8=bg6j z7=RWXmUC=`LMdv-nZJ`}&(_sHzhmW(I^qR0?vs!&LqbA;gYn7#b=$?il(yBx&DOlY$(;f&Kv4S|bgUnq>g{2S0ZG$7S8xXUCA3!cc$opFRrt zTOgh7ZMd?tTXjLG0D`}^Tv|;0UeRwBbrUuU7l%Rp1=*qaPyr#j^0H* zK<_8kJ}NIBJcy$mwR)6^VrV~mJf9PY@KGcs`hAJo7=b$IGvKK#DNiU zfDII6Yi$*F@b%-SButQVHHUp7Bc-0K`ia=`1Q^KYjA^a1{FEhtFUNJ2s5|g(Z)?Y@ zH}1WMow1`B3@Wz_`N+rCl1xaEX@=?M1TY)h5Ck@5O%X(X__ZgdTvb>HuId8Y|L93o(m`KKRiYO}zXCA1Bu&x5hY1<|-%$QTjzZF6j+2d6VjeHdHete+ z$+m5~aP7%q!z1`m(bDW0lV$G7GqG(J=fyRmM~FsYgQJKvehzldE>~AI%EuS45DH5L zsu`DbMk~ayK)SI17grVj`SvHJ8;Y39p;;M9DA3`u*IjA}KQ)7e^QC9-q1Sr=lEj<@zM0STH{gjG~v;Ei>4ZiYxaT6v1Iy1fU=Wl|Ju@#9jSJ1)JQZMg2ZUkByX)nQ2IWMdL7Nq#9=p?3tFVxyaz&`GGm zVDZc|SLca5af0sp!S|#ab()q{?CjtW#b%9fYp2Z-g|W>*km#7EM@WxDu4IUkA-j+;`s9CZj|r* z=%>;={bXGEH`A>AlZp5@Z66cGg9MF@TGPRL@Fpiv5Q^`R9jKI6=&JdL9UG;Fgk!Mf zb}s2vG9?a-oCA=+>I&I}$=(_)-mn7TARfIo?UNZfqm+7vRxX-Ao(FJSz>>vtrBw&M zkY&BL5?b$f@5R-3M}P^ju|Q^I-oz(9gVVz|oR>wCfXJa#G5hWWSU+h$%!l@aXbJZX zd|(8i&VZA86BaL6h7Bz|p0JSgLiL>Xmga-Q-g!f%TYjDcBy z76Y^l^tA!LclB9k^g{8C9sJwX|I!42!%Vl1cD!HqR+bvn2RY6f^XU8TcIxUPD@QfxDN-@o;>C+lW}wLzgCl@y zgKw#JQCYvD-}#V?3iHyCFk$-`s(C#Yz4jeAtj7SL{+!K-R+9BtSy{DozOI^SAz_C| zxo30CL568`*fHV>{c7-XM^lS@cQ$TQfZ=X};TrF&pnFv&@7;Gut~|I!+VPDl%4;!! z!y61h4YT7J%{6}f94fu~2`vaeXT&%NY={RGhqVBOyc6&a5p87YB_NL?bVxFTBJc`+ zob0K@nU?^gKwQ5X=mcn)(kk2b?vZ!@#rwjH^U*sg@q{OF14$#d=;u{ zY}n;700MG!-%4YD2hXhBLR>AyXIPd;V1xgnKZSu z$Pd2%Be~~3qkJ!!vas*Nzxk!y{Dps&8Vvj^>ZeGVD$yin)-vcVkdgZDQ3he+>Zxh_ zp!g4~|NWH>f*Wh)rtSC3wBBTy8Zu8|vP!NJ2SOYO%G01cQ(q??jjh-L(1*GVbv`;m z{EHeBhr(<6bd`9oIcv4lV)8)6nw>1j^3zbDNu6=!>glD#kun8epD5-c+#b3jVkG3G znAjH9p`H0cKDYydGk29pVc~o%^u>1f4ay6fH=#^ZDLD06xm=C%*THX-wC^aWrp~~G z02{}DYwyN)8H(tVr*3uKX7%vm7dzhnun{i<)!3c#$An}VkF`MnZnsb|fO2+kJcMu19GfXf!1BsJ(TR|1ADhT=fv03Wyt2iriGim3;h*L%W2TnCu`7;LI*} z0W8Br;FPJjBY1+Fd++%b^mue(_Y$`MqdaqsGU&F1a~*b#cE%j;JJ!)oB_BTTD+0tL zz{#*zy{oKK|HbazJLSp8pUj8ugokMzoj}F!zu^ti)YzccRjPgrbZTQPck&(!OTcMb zxx@f?I>^#M68xENVQI%BGsV2AdV%c7Qolo0O^wvE3?RYl{_bOPT>HRryspo|-P_~? zAH)qPSTCz-ngacOHHu5?M9X~OWADL~DbwC@^gFYcbx+@uo#eVJU2$|iW|1YxLKP_k?d zlZW-0=Lv7U!fkboY$Pw8`MSvXq`vcW7Xjz^nt*w)^@H2z9;n#Y={1jIe63KAP zaN`Y`mi=4=)U89P_$p-m<}K3I(E(O)e*{CyWWn5%WNLGh@#9Vjd}@8j0dM(T$z)>! zOC|i80k?U|w1A3Xelr$KM&-k;Y4iH3ER`@ip!GOU1-S7s}yrP;nMlGL1AfDyb$fV=3Z(R_u= zxy^*Bx2+3rul_eH#x+>+>XV=S;vNTJB3k2lC6$YoES8n4R;p6efgG>PL^I9G55_db zY8}Q;a(cr+H1<)DU6W(hUznt>f>Ki+1;7O6=8YTWi(mXoUK;wl+U4Evd!IbJ^F?Wy zagx+x=UN#CR1_0k^(S^sz)BL{WDf^h7##Fr<+Tz|cX^Fmxa)a&+n$}8p2Fl!xfEF9 z!0AnX}Aljylg=B z@7*U`wrbJ4VAztD z$&sUnWe0d+j=fqQuqSLxU<+c7j;**%MWIn&VFF-q0Nelja7r74{(+vE(@*MIuwX8a z0&on+43<{@6FydH0w94Y6c#)j8S4%Jw2a*=R-CF600Jq)m2B80gA(OSA}qc=86m@M zK}vxbv3Dwd1aQ2{nN6NmCoR-|zh&z-q^V^CZ4;82B5ThaEg2+CxX$Wg99#lT9`lW!O?VNfytaBUNY^w4u>X zCgU-Vjc*cTK`KY(TAS^|go4PXrdkcqC zyJlbcW6J~}@lT~{ybYrU9s4uSIy=UtiRSU#GtbM0ty?hjR-^hcOb5k&_z1Q1PBLl! zNw7ouxf~x7cmx3HM29H=9KNTouLSiuSHCJypJ8K@Jo4xxa_GR35zujBqH+Lw)m2xb zcze6o?1?5tyt?Y6Unv=`K(m@rj1l&kf8w%l+efx;#$d* z4s4Q}cRr{=>?~5&hYfnRE ziAW3garl-5NYbuZ&@h%>vuJ=f3ijN&^AdOWZGj@h_TydH0e~?A8whrKVQeo!3%B~X zKSL4h!Y0|n@zZh@h6?cLS3@A@0C)wU^io5t^qhUhS>2OcnmGZ$YKh(GG!zJW`5Km~ zl8JFZWrZVS3J-;-XXRO|sRsc2aQnCe0AZ$yBxFpoij`vEDA=UoPC($&#%Nk8piq}% z3s5fGFVAh<>J26c$)bb`72zvz_Ls1x{KGet!6FE7X|lwGaJc|O*oT>H8dg^B{(}ah zySGoCT)$DbN%4#$JDIh(XL-f4CD4gruFujh4f;c*xDK>FI%U#VCjUNTrY4QCR9rsN zh)VEBbTzzbnVQ-E-;2BD)txV5S~F`V0dRhSe>ltF(FKA`D;QvNchjR;8ko6OqtwPBd8ZP*P(F~==WfVNz?NW|s-=RZt&pr1= zqfbD0JV)1DakWgFK26&%HpkqMX9uq~1L1bd`u;AxO`FXeaTfreTAYubHf^fpaG&=_ zK6oA8IxG(|J(!cPeCc21#;<%$rp!D^TBo6%!sG$lLakHCYBEk)ZUlzI2lYT3Gk}LX z(8|l9{I8>3KDpxwnT6`H4|S()tC*PmVvJcbB@PUa14i;RE(D6btNU8(rLD4FC!loO zB_xf1wCmeT$QNM>B6{o9*dcc2nWtg58041l=uh?abjqVoJcUj^Y7U-xXD2{qeHc=k zFG{{Jj~Y^Bjcd1lBYFGyz?2Hqlgm$8A=P!{gEfZ0wU4fqo@~bK{IiuOgEZL3Vk0HL zhsbMkq+)$ZOF$3mEof!K4FIg{a=1Gg$F*N^{srAtHI*E@Q3pV20su@%GNlQCB(6w? z(&bJ7bf10h8nXic8*$Ou;0+>NB=(K58^H`j(+>_Tc>@Emyf(j3MxY#e{CZ6Nya*ih zG5`Zymg+4~1lWc1v&Id=Fq6uTz%mSadG1{Yvsw{^iH;@{p<+~>e#*4?jzW@fsSM!t zJ2@4->G+@ngB6YEH*J$1XvpSVGbaFeu7C9^%o;c0IzU|UryV3E!y|^X$RvtM;RU4; z7<-pX!(f&Cc-k~MP=Q?lT&zX#NDYONB3YL$myhn;E~{`9paUJgavWb6g!W}@fl``n zlj(job`wXu6M)GhBhkDhbD6+VL&(CRqv=Gk73{XjMiGWcF@cnYa-5H@;(!tp0hQPZ z@Z^*0;nss$sPrNDDbn4Cm zbnaKIJO!(WZU-TX@x;4d+`Cuq`R#8onytcMghLPeq2n@2YD1r!9;0R~BS#IPk7#Jj zHqTzY?!~lu4c-WcY^!{u>gSeNPMF~0Wclgc_h67{Y8OA@31-o{EJD*{402y{^)--P z3~)ICKp7@rglQ(O@r(pTFb?5VR(l+^j2$A18$6=4fhavbYjrcTTm>0B1m1skss1 zEE#gZt+PuU@j1{%VWH6Q{D^KTIrluh34p={C4i~}fHzMNE-IhJ;)@4N81saV&74_NWge~rJ3B{0;R_rjiVf%WS(R*03Yy=0eEfd?qEp3F= zuSw!u=Bck8<(E>q5Wu{rEutII#Eo4y8xU7i6Vp_y5qRbozV3|<&CBsGc&BIym~AxqU)c? zFBf1K_JG~Ph#ZeHd~}8%iqXrb@(Di}Cu+-2DR}O=^|Bq;piX4NiHjuj#y7rMCbdr1 z9Whw{@#;UrvElPADJA$>gvq3&L>gUhOF|s0DbGfG4dfsEs>bfM)+tlvw*UAqxf84X z{l^Z-4L95**L~*m(mZ3fOrOr{{xETdc1_!K2WgN+*pO&Bl!Ebo9PBxhLF^pNVz9rs zvt6z~vQ;WD!Hw3(iCwamI50*WfR2l2an*7q6yjE5(%01VEEV=)x=x*k zRV$Xtv>DSi*bMZ`|o~y{4nKSVPH?nUf>tlqBzIL&EY>jGsa|4 z0l>k2Po*?=uUzdN*)AOc$om;e699?q;Seq~0noc>$tj#o;*%*G7=RvtKJ3KBypyRh z#;Ponj&zey_7c1hKPbeyRS=DSIc8EjFax@I%l4q-q#dn6X`MV33R7llWyvlzGZ#z( z(C%i}OEEd&blTkz#vMs~7uS$>lbG#PYL=Nedc{ZEj>`rp1EhTr18c%GH8sj*l3?itv;_y9vNQH8|y;y{HQ}w(Fh$jl40_{bLz7p_8K|HsYTuP|xT>#Dm z5Pnj6Yks`el6~T;%5t3qsDn<70r2;_zr9YLdv=4H;JDlW=uiJtuDJYi={SfZAU!y% zr#%^V;8{kIi|9x3DGX#G4#M1H0u1uQXDY5jJbTTVe$0tz$!7ZGyZ`+IJ&RmbT@UiT z6$|Es*$EltjOP>2eF*d=>0qABW3%q&()t-G2Piz)25_~w9NS*o+uG%qKmXNBGUtny zE|wKbv0DvIbw95Jq%HtIC!Q@}fQAI&KHy8jVzZG-KkEQ5Z~*oJl!-bVWva#P0AKpI z@5pQ4{(iaR2fvVMv*yd>DH!}?07Y=F`kR%1Q%+1zfvK5iU;wLwcm}we4f<`MJ5nZP zvhwAf>t%YD{cS)xL^)OsEB%x>FcJ>9^7QM98fnM%f7DTfbFAoVark0oH6axg2trgt zK+ik(4D{j9Phy*?jeeOt@z|3PY1SFWPj?h(1Hx2o%J!Xre*|8`$7*YW0upB;09SQo z><=REOBXMe$&;tVB0snOc^q}@z_=jT`Nz5-TpyTdc_~QEn86ezoC+9#c(x=+{Y*X~ z+yQ{G7;b1ol-^lWXLnDY-pU=@r5yk$&=(bGZW` z)6?6NgC0Qqb0+|b6>E@)l{7~V1Pu!X?Bk%VK7BnTosV!5{8gUWB?h8IvNa^OhA zm}OE|*C0!lEk&m)rz^{T&m!Kt)>!7p#I-n_fc(~UV*PjpfcUW1Vx}#uEA7pXhKC8D zL!8AIU%PI-=3A>P_*bApJbl%2Y`?DacwtsM0-9h03i>J=iB zY&yht#qq4XXXkU0Q{H*aS9@L3!P_ox7m*ydDb{ zM)w6L>-ZU?a$-NN$&TZ4tmBo{C~bwNJDhky9K#nfN0k9GQP{Z)tP;B?`2P9bzmg1A zb|g@^X6geTjqH~x znABdmV!7#u;phO*WwswZDr=v328$-pt;B{N^#-H+sj4Mh(o8y$UoXvw@7OsDAju>S zK+?wkzLs<7%7yc^l`EkqL>D-H8i{hzChNFzI>F zL~+oK2U$cV*Q*l%Jxfnn)?HR#;~m*9DFMvGPE#iG0lZAZC|7JpSzBNNKo5rZy#6)U z>J9*0IGM?zMa1~OFMSt4R8))SWWg9Bm5_~R-!|nzPSxY#ACAoEZ0L^dyQR;Y$=AY8 zWpbzhPG7wmokM6(^*Y$5BCwe3JCd;V)_1zX1dT-*&V=odW53ZTXlyq|2+z~&o4*!~%`V>DYSn;BcRw1d(KgY^gg?fSg}3f_8vCB8 z;kybm+E}H*gbu$kD{=kWwo`0Azvj)S+qir(ZFYPQwV2$7{on#{6g~wq7S(uIrlkZA z%X@e@eY(BqhJTy@Dwq0(dYKHJ4lnH4EAM*8Ut%K2bV=gmMY$1&7`~6eJ!8&nx#_cC zfHEJEuJ#UW+warCJLi4uXO5;}sj_cV2M~(6L`8=!k{l-TTCZ2W>i442C!G5$U%eg5 zesNtPR&+RkWqqY)c%Oa*`SUqya73IVKwMzd{0Z7nQ(Ji`*-OSlWv`}wFPE)5w#rj$ z*A_ z?;5z-fkFL`GR^N?g^6r-?oCNSZ19TwGP{Uu>&`UHqv!o`)64)?U2*X-UiN^z(M^!tqxgB>*lD1!I{aKw!t{go5Lqs8-7ZrO)dS@WmZp z^|iHf>WY;y(s};>`3=uwQw0$&nCGG?wcZLsO(jfIS>B&EG=V@F1sLfyHVp zDl@YK;G#<|=HTDm0l?YqAc93zdVB3bi4jR0-~<2#2NM8w)B}*Y>Y8hNA%4A?uH%_Z zZ*Qir4>tmsT>$1JKuE-_poUQ=`-Wjg!xbV02<-|1aZ?Et4Nos@dvTw%9X11AQ}Y=L zmn~T$_4W0tK*dzSjKMHe0$TafJ(L+{Zav~nYD1+ljBtl6X2m?Tl;b+U4tX9|zj0%j zUYUqNfAgfta@wj>q=wt0KpXiMtt8#SHYW%w(79@>sh2(G6hMr~==X^M4Bc=|avWKxD60|70Vw_MHz&>3nCvqn)+c0jy#oNXnV2EZFd9E70n!f(TMCDC z$PJ^2xeCYWNA;sv3*Hb6Gf={rS~Q-`Q>MsIetD04{2xE*M+;kQ+_3NHxB}d-dd;ik zO>caIbhWife^0lb-#4F^FyW@#rXBFk+H`726~PTcU~q>HwExe=^?#?^C=>F%35v}g zTKkyZdCrv}%N1KCF?TkHu5ChQX2e9<+y{u_K9mS+U<0FUr%bR z*tb)K`83X%J6|rk;37<7^Mam!lm&c!PZ-=f&yO%1OTa)ccFdz7tbtq>1fBqdbXlnr z5gv=rPDe9>%OihFGY2{7*pn?f*s&2k$zU8G2anT0;q3qh|9zM|$e^Jq!|t-)`bxQR z_gXn;|MmpBN?s)nj3x)d<%<&l-TmJakC#4fN$fzC+AhgkKJL2PoC3Wy`Be}o@*bQX~fT;lbExr5XAFz&S?3=SHRG;I+*W-)y6}F}bg< zS$>E!YcyuFrCRmJr!DX*+Gdg;lxEE1UAJwctZv_@`<*HVv2_!xx||s`#-!#utv8wC z+e23Mc7hz*h$}*U7>y4bf%5%FVo9bc3F9zQM-;|Vd8b_;b6&avHv&}QX+a};+tKlG|8Z)h^uibLM+{RhL z8oCG*Pt=Q?8c+oC5rQHkeg|1IWm;)crn2vqcjnl*&G%=jUKL;f{epty0~q*ojdcJM z-_Ul9U2oNr#jOH2?08iEVC#w1figmFm7>37IN+2}TA{G|KI}x9GEjrQ6}saX#G}Kl z{Tm)(iyF!{CjuF@wYfnqxbSRG;U!82WWBqyQ`SE9G>%RJ0ux@QpJe;ET6&@=PrRF% z8PX8Q0NPrPxhh8H+A<_GWuI0RZXiQ!y+?zQ$P;S&TGESj^xC78Z0A`0n1UJhARM4`EIWUzTJHt5BAx}NC zL5?0dra817rG@im%iNP^DF3*~gl}%bJ(TQ-*~OWD@RpnK>(F`n%KU|L03y9j4FCW@ z07*naRHv_~rVLea8l^@1yTcuvbdv?SYWdrJTV*z`2kc|xHvmlpkY01ejYSWC%i|#P zp-<_0+gQo(fR`Fho+JQ9pb{YHRTz|woRp3Y3%`lCINYK5ObF50C{|JlB%W$0)#5dQ zAN}}^^5`RLL*V1-m^5XE-1zBFOGRIgWV$*q_l>p>ADnVYW27@ok;^w@N9F+E59MA} z=pee{U{?L@Q%JyVZ5lc6)uI#cG&pO3Z*lVH3J0B_2*0Z(Q| zvTw1OX8QIiHf}NSCZF*b@YjrZ;gL-78=J!6!P&Y2Z)4>I3F8vZto-vzKxhKPiU;ev zI_apXleg@7R^Gb#d6PUq<3p}EWhI{y2S$qn-O#_ZqYeYt3~s;C`rgPakF*+*_XHm= z?U#A-U5>uo(na%R{(^Z3z@!!?x>+gg+Wn&J+`daE1vrtw*yc?;U3{!@?bZ)(k}v^` zrOv*6k{l1VkmbnxQY`3IK!-=vtbb;`^x$TND)fKJKif7KK3E>xEIWv!i0F>-CFYS7 zOa1agy6J)@{l+|ijRTpu|F18r69AdHGgtJ?p5rS30i9*w%ijG5Wc{=3@R-VwD`d@04RXecMXG>?v#G3r zjP^&%1u9?SLw{@?K_{vPG#qVimQT%_;iXA28C^q+EW|hkCFizv$)6lOAa%I!xm=~z zGSzQ9C`+;Rg&ZGtpx{Q*lr7+2>Ad?f+l@>Q`j{{)14=*n*wwy#5a*(JQxw zN?C!zNCv%xjQ4H5%%w{^v&2E4zLaG=;!=USDwoa3FMjbW44y~7j$(3SX=>{m z41E~99+Nv^@Gz6UteeyWXJ8vWy7F_K=?o2#L`@`?I{6LFchqdgDqRA3@j~rjfXl@9 z;5hh4eW1J4IQR!rYP;#dB-xR{p6! zhHVJr4QUDj%8NzIP!dE^fK4tbED49>$0)i`g*SClRbv;$%9Se|2wI^i6#Al`UiY+= zfw#Uc9fa5<&6~gWVZ8A$HuK48q5npB6DXFQEGzY_i@22}bMfVuQn`CK4<|J9o&Gq zvG-7MBOfH>1c~L?7109)W52!sA-xR%pA~7KYlM#SS*uTx=DJ$#V6u_1`3`WHb$)iH zs0U!Myjogt7~rR~=LA%ZE*rc939QNoxMm}NxPQO=@3!4i+20KsKuwGRe-a?bg!;Qq*RFw~pLm9n;NwssB%Y%%C>t{=9UB&Z6L%axeL{F#5GRm*1c(M! z|Dh-c5AUDfx^0Ji=tF-G4cSx`a$?Ls-E@;IJo#ifws$YC2JFH2L>a|HxmCj#^MQvi z=9XTRX+8r`B1jzo7ekx+l*ug$F%f^-pzq+8+it}Uj20Zls5YIq5TVXjG(sH;9bJc$ zR)L6%k5M4v7?}CW*4w5Vs<|o5G+xKI5}m^;Ong54*kiJN$4f!Mo6VKU27ll5`)`o? z##&X-$@zG#w~;?T-;5HE79dEo4JS?luJBh016b1HZ)cG2N6ClD2U3eaFmk*#4KGhR zdBzogob?}IOTxi_8T8yh2S8g@y{taGLq7W)c7>qFGZ5)1dMF&sC2NTTqrrhdzKx94 z$@iL@v~R-!JXI>0G7UJd9<$Y%rq1Z>n_&{Lsj*JhU>7S5`$wz*dODBGLl3W&OfH9$ z1*UJr$pTF^NLu5J$4_dKsR41S+_K1MZd4xgD^p%H%F?AvW04Q;KZL8Vx0=q4GLqO> zb9lz~Bdj~$+m93ma_I|)xUQ7QE{JbO?GA(8*HB%QgAVgtb`VFh5#0NR*IvUN+V

W# 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�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 literal 0 HcmV?d00001 From bae2b344fad284b9beb09785696a00c2a6d697eb Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 1 Apr 2021 20:19:08 +0200 Subject: [PATCH 276/842] Stop all sound effects on a new message --- include/aosfxplayer.h | 1 + src/aosfxplayer.cpp | 6 ++++++ src/courtroom.cpp | 2 ++ 3 files changed, 9 insertions(+) diff --git a/include/aosfxplayer.h b/include/aosfxplayer.h index b390f9679..77932c2c9 100644 --- a/include/aosfxplayer.h +++ b/include/aosfxplayer.h @@ -10,4 +10,5 @@ class AOSfxPlayer : public AOObject AOSfxPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); void play(QString p_file); + void stop_all(); }; diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index b6f3a1400..ada9912b4 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -15,3 +15,9 @@ void AOSfxPlayer::play(QString p_name) const QString file = ao_app->find_asset_path({ao_app->get_sounds_path(p_name)}, audio_extensions()); DRAudioEngine::get_family(DRAudio::Family::FEffect)->play_stream(file); } + +void AOSfxPlayer::stop_all() +{ + for (DRAudioStream::ptr &i_stream : DRAudioEngine::get_family(DRAudio::Family::FEffect)->get_stream_list()) + i_stream->stop(); +} diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 315790041..823df2372 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -810,6 +810,8 @@ void Courtroom::handle_chatmessage(QStringList p_contents) previous_ic_message = f_message; */ + m_effects_player->stop_all(); + text_state = 0; anim_state = 0; ui_vp_objection->stop(); From 28847d294747c57438c4932a152939743f49b532 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 2 Apr 2021 00:26:13 -0400 Subject: [PATCH 277/842] Make recomputing vertical margins for DRTextEdit only occur if document height changes --- include/drtextedit.h | 5 +++++ src/drtextedit.cpp | 32 ++++++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/include/drtextedit.h b/include/drtextedit.h index f4ff02059..820e2b180 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -31,6 +31,11 @@ class DRTextEdit : public QTextEdit }; Status m_status = Status::Done; + int current_document_height = 0; + + void refresh_horizontal_alignment(); + void refresh_vertical_alignment(); + private slots: void on_text_changed(); }; diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index 81036ef43..5e8c7b8b7 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -73,11 +73,34 @@ void DRTextEdit::on_text_changed() return; m_status = Status::InProgress; + refresh_horizontal_alignment(); + refresh_vertical_alignment(); + + // we're done + m_status = Status::Done; +} + +void DRTextEdit::refresh_horizontal_alignment() +{ // Do computations to align text horizontally setAlignment(m_halign); +} +void DRTextEdit::refresh_vertical_alignment() +{ // This stores the total height of the totality of the text saved. - const int new_height = document()->size().height(); + int new_text_height = document()->size().height(); + if (document()->toPlainText().isEmpty()) + { + // Qt is very special and does not set this to 0 for empty documents. + new_text_height = 0; + } + // If we have not changed the document height since the last time the text was updated + // We do not need to update anything, so we exit early. + if (new_text_height == current_document_height) + return; + + current_document_height = new_text_height; // The way we will simulate vertical alignment is by adjusting the top margin to simulate // center alignment, or bottom alignment. @@ -85,16 +108,13 @@ void DRTextEdit::on_text_changed() switch (m_valign) { case Qt::AlignVCenter: - top_margin = (height() - new_height) / 2; + top_margin = (height() - new_text_height) / 2; break; case Qt::AlignBottom: - top_margin = (height() - new_height); + top_margin = (height() - new_text_height); break; default: break; } setViewportMargins(0, top_margin, 0, 0); - - // we're done - m_status = Status::Done; } From 78d574bcbcd481c02b9b664078328d94a7f83642 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 2 Apr 2021 23:13:51 -0400 Subject: [PATCH 278/842] Amortize horizontal text alignment changes --- include/drtextedit.h | 1 + src/drtextedit.cpp | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/include/drtextedit.h b/include/drtextedit.h index 820e2b180..46ba9885e 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -31,6 +31,7 @@ class DRTextEdit : public QTextEdit }; Status m_status = Status::Done; + int current_document_blocks = 0; int current_document_height = 0; void refresh_horizontal_alignment(); diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index 5e8c7b8b7..3583b632d 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -69,6 +69,8 @@ Qt::Alignment DRTextEdit::get_horizontal_alignment() void DRTextEdit::on_text_changed() { + // We need to "lock" access to on_text_changed. That is because the refresh methods trigger + // QT's textChanged signal as well. if (m_status == Status::InProgress) return; m_status = Status::InProgress; @@ -82,25 +84,44 @@ void DRTextEdit::on_text_changed() void DRTextEdit::refresh_horizontal_alignment() { - // Do computations to align text horizontally + // This stores the number of blocks. We use blocks here as Qt makes its horizontal + // alignment based on blocks (rather than apparent line breaks/places where words wrap). + int new_document_blocks = document()->blockCount(); + if (document()->toPlainText().isEmpty()) + { + // Qt is very special and does not set this to 0 for empty documents. + current_document_blocks = 0; + // We also don't need to do any adjusting for empty documents, so return immediately + return; + } + // If we have not changed the number of blocks in the document with this new incoming text change, + // We do not need to update anything, so we exit early. + if (new_document_blocks == current_document_blocks) + return; + + // Otherwise, we have changed the number of blocks. By induction only the current block needs to be + // updated, which is why we can get away with doing this setAlignment once, and right here. + // qDebug() << this << document()->toPlainText() << new_document_blocks; setAlignment(m_halign); } void DRTextEdit::refresh_vertical_alignment() { // This stores the total height of the totality of the text saved. - int new_text_height = document()->size().height(); + int new_document_height = document()->size().height(); if (document()->toPlainText().isEmpty()) { // Qt is very special and does not set this to 0 for empty documents. - new_text_height = 0; + current_document_height = 0; + // We also don't need to do any adjusting for empty documents, so return immediately + return; } - // If we have not changed the document height since the last time the text was updated + // If we have not changed the document height with this new incoming text change, // We do not need to update anything, so we exit early. - if (new_text_height == current_document_height) + if (new_document_height == current_document_height) return; - current_document_height = new_text_height; + current_document_height = new_document_height; // The way we will simulate vertical alignment is by adjusting the top margin to simulate // center alignment, or bottom alignment. @@ -108,10 +129,10 @@ void DRTextEdit::refresh_vertical_alignment() switch (m_valign) { case Qt::AlignVCenter: - top_margin = (height() - new_text_height) / 2; + top_margin = (height() - new_document_height) / 2; break; case Qt::AlignBottom: - top_margin = (height() - new_text_height); + top_margin = (height() - new_document_height); break; default: break; From e7cd62abe00ba3a73e14be2a38672ae1b5127761 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 3 Apr 2021 12:19:36 -0400 Subject: [PATCH 279/842] Make chat tick timer use precise timer to reduce lag --- src/courtroom_widgets.cpp | 1 + src/drtextedit.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index e15013b1e..20d6b2e34 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -26,6 +26,7 @@ void Courtroom::create_widgets() keepalive_timer->start(60000); chat_tick_timer = new QTimer(this); + chat_tick_timer->setTimerType(Qt::PreciseTimer); // Prevents drift text_delay_timer = new QTimer(this); text_delay_timer->setSingleShot(true); diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index 3583b632d..2ac6111cf 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -73,6 +73,7 @@ void DRTextEdit::on_text_changed() // QT's textChanged signal as well. if (m_status == Status::InProgress) return; + m_status = Status::InProgress; refresh_horizontal_alignment(); From 356bea04aa23659f1fc20740bb3f236103ddb380 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 3 Apr 2021 12:49:05 -0400 Subject: [PATCH 280/842] Remove unneeded text color setting --- src/courtroom.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 823df2372..1e8474478 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -911,8 +911,6 @@ void Courtroom::handle_chatmessage_2() // handles IC set_scene(); } - set_text_color(); - int emote_mod = m_chatmessage[CMEmoteModifier].toInt(); if (m_chatmessage[CMFlipState].toInt() == 1) From 77064d64abc652b7614b569b40253973d05343c2 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 3 Apr 2021 13:26:24 -0400 Subject: [PATCH 281/842] Cache file read values in chat_tick() to improve performance --- include/courtroom.h | 5 +++++ src/courtroom.cpp | 15 +++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 2aa733336..905da55a1 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -403,6 +403,11 @@ class Courtroom : public QMainWindow // if enabled, disable showing our own sprites when we talk in ic bool m_msg_is_first_person = false; + // Cached values for chat_tick + bool chatbox_message_outline = false; + bool chatbox_message_enable_highlighting = false; + QVector chatbox_message_highlight_colors; + // cid and this may differ in cases of ini-editing QString current_char; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 1e8474478..07188610a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1375,6 +1375,11 @@ void Courtroom::start_chat_ticking() tick_pos = 0; blip_pos = 0; + + // Cache these so chat_tick performs better + chatbox_message_outline = (ao_app->get_font_property("message_outline", fonts_ini) == 1); + chatbox_message_enable_highlighting = (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "true"); + chatbox_message_highlight_colors = ao_app->get_highlight_colors(); chat_tick_timer->start(ao_app->get_chat_tick_interval()); QString f_gender = ao_app->get_gender(m_chatmessage[CMChrName]); @@ -1391,7 +1396,7 @@ void Courtroom::chat_tick() // note: this is called fairly often(every 60 ms when char is talking) // do not perform heavy operations here QTextCharFormat vp_message_format = ui_vp_message->currentCharFormat(); - if (ao_app->get_font_property("message_outline", fonts_ini) == 1) + if (chatbox_message_outline) vp_message_format.setTextOutline(QPen(Qt::black, 1)); else vp_message_format.setTextOutline(Qt::NoPen); @@ -1450,18 +1455,17 @@ void Courtroom::chat_tick() ui_vp_message->textCursor().insertText(f_character, vp_message_format); } - else if (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "true") + else if (chatbox_message_enable_highlighting) { bool highlight_found = false; bool render_character = true; // render_character should only be false if the character is a highlight // character specifically marked as a character that should not be // rendered. - QVector f_vec = ao_app->get_highlight_colors(); if (m_color_stack.isEmpty()) m_color_stack.push(""); - for (const auto &col : f_vec) + for (const auto &col : chatbox_message_highlight_colors) { if (f_character == col[0][0] && m_string_color != col[1]) { @@ -1485,14 +1489,13 @@ void Courtroom::chat_tick() QString m_future_string_color = m_string_color; - for (const auto &col : f_vec) + for (const auto &col : chatbox_message_highlight_colors) { if (f_character == col[0][1] && !highlight_found) { if (m_color_stack.size() > 1) m_color_stack.pop(); m_future_string_color = m_color_stack.top(); - highlight_found = true; render_character = (col[2] != "0"); break; } From 97e5bc5a91b7e61303b53412b896c90bf09d11a7 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 3 Apr 2021 14:21:00 -0400 Subject: [PATCH 282/842] Allow turning off/on auto alignment to increase performance of batch operations --- include/drtextedit.h | 3 +++ src/courtroom.cpp | 10 +++++++++- src/drtextedit.cpp | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/include/drtextedit.h b/include/drtextedit.h index 46ba9885e..0a456a9fa 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -12,10 +12,12 @@ class DRTextEdit : public QTextEdit DRTextEdit(QWidget *p_parent); bool get_outline(); + bool get_auto_align(); Qt::Alignment get_vertical_alignment(); Qt::Alignment get_horizontal_alignment(); void set_outline(bool p_outline); + void set_auto_align(bool new_auto_align); void set_vertical_alignment(Qt::Alignment p_align); void set_horizontal_alignment(Qt::Alignment p_align); @@ -30,6 +32,7 @@ class DRTextEdit : public QTextEdit InProgress, }; Status m_status = Status::Done; + bool m_auto_align = true; int current_document_blocks = 0; int current_document_height = 0; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 07188610a..5d10b3bfa 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1115,6 +1115,9 @@ void Courtroom::update_ic_log(bool p_reset_log) if (p_reset_log) { + // Turn off auto align. That is because we are going to be performing a lot of text change operations + // but we don't necessarily care the intermediate states are not aligned + ui_ic_chatlog->set_auto_align(false); // we need all recordings QQueue new_queue; while (!m_ic_record_list.isEmpty()) @@ -1280,6 +1283,12 @@ void Courtroom::update_ic_log(bool p_reset_log) ui_ic_chatlog->moveCursor(move_type); vscrollbar->setValue(chatlog_scrolldown ? vscrollbar->maximum() : vscrollbar->minimum()); } + + if (p_reset_log) + { + // We are done updating the IC chat log, now do all alignment computations + ui_ic_chatlog->set_auto_align(true); + } } void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music) @@ -1522,7 +1531,6 @@ void Courtroom::chat_tick() blip_pos = 0; // play blip - // m_blip_player->play(); m_blips_player->blip_tick(); } diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index 2ac6111cf..832a908fa 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -25,6 +25,22 @@ bool DRTextEdit::get_outline() return this->m_outline; } +bool DRTextEdit::get_auto_align() +{ + return this->m_auto_align; +} + +void DRTextEdit::set_auto_align(bool new_auto_align) +{ + if (new_auto_align == m_auto_align) + return; + m_auto_align = new_auto_align; + + if (m_auto_align) + on_text_changed(); + return; +} + void DRTextEdit::set_vertical_alignment(Qt::Alignment p_align) { switch (p_align) @@ -69,6 +85,9 @@ Qt::Alignment DRTextEdit::get_horizontal_alignment() void DRTextEdit::on_text_changed() { + if (!m_auto_align) + return; + // We need to "lock" access to on_text_changed. That is because the refresh methods trigger // QT's textChanged signal as well. if (m_status == Status::InProgress) From 01943dc22ec8624fdb56c19c740a4735bde732b3 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 4 Apr 2021 00:05:28 -0400 Subject: [PATCH 283/842] If empty preanimation, return early --- src/courtroom.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 5d10b3bfa..690afb9fb 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1316,9 +1316,16 @@ void Courtroom::append_system_text(QString p_showname, QString p_line) void Courtroom::play_preanim() { - QString f_char = m_chatmessage[CMChrName]; QString f_preanim = m_chatmessage[CMPreAnim]; + if (f_preanim == "-") + { + // no animation, continue + preanim_done(); + return; + } + + QString f_char = m_chatmessage[CMChrName]; // all time values in char.inis are multiplied by a constant(time_mod) to get // the actual time int text_delay = ao_app->get_text_delay(f_char, f_preanim) * time_mod; From d351f064483bca28a91357a293e91c8cf006978d Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 4 Apr 2021 09:52:25 -0400 Subject: [PATCH 284/842] Consistent chat ticks --- src/courtroom.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 690afb9fb..6cde863a2 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -938,8 +938,6 @@ void Courtroom::handle_chatmessage_3() { qDebug() << "handle_chatmessage_3"; - start_chat_ticking(); - int f_evi_id = m_chatmessage[CMEvidenceId].toInt(); QString f_side = m_chatmessage[CMPosition]; @@ -1099,6 +1097,8 @@ void Courtroom::handle_chatmessage_3() break; } } + + start_chat_ticking(); } void Courtroom::on_chat_config_changed() @@ -1318,7 +1318,7 @@ void Courtroom::play_preanim() { QString f_preanim = m_chatmessage[CMPreAnim]; - if (f_preanim == "-") + if (f_preanim.trimmed() == "-") { // no animation, continue preanim_done(); @@ -1405,12 +1405,14 @@ void Courtroom::start_chat_ticking() // means text is currently ticking text_state = 1; + chat_tick(); } void Courtroom::chat_tick() { // note: this is called fairly often(every 60 ms when char is talking) // do not perform heavy operations here + qDebug() << QTime::currentTime(); QTextCharFormat vp_message_format = ui_vp_message->currentCharFormat(); if (chatbox_message_outline) vp_message_format.setTextOutline(QPen(Qt::black, 1)); @@ -1538,7 +1540,9 @@ void Courtroom::chat_tick() blip_pos = 0; // play blip + qDebug() << "START" << QTime::currentTime(); m_blips_player->blip_tick(); + qDebug() << "END" << QTime::currentTime(); } ++blip_pos; From e8fe61b9c0b4af53716be7ee085deaacdbdc0452 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 4 Apr 2021 12:35:53 -0400 Subject: [PATCH 285/842] Make chat ticks appear after sprite renders+Remove unused TextDelay field --- include/aoapplication.h | 3 --- include/courtroom.h | 5 +---- src/courtroom.cpp | 16 +++++----------- src/courtroom_widgets.cpp | 4 ---- src/text_file_functions.cpp | 10 ---------- 5 files changed, 6 insertions(+), 32 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index e8c1e78c5..6cefb198c 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -383,9 +383,6 @@ class AOApplication : public QApplication // Returns the value of chat from the specific p_char's ini file QString get_chat(QString p_char); - // Not in use - int get_text_delay(QString p_char, QString p_emote); - // Returns the name of p_char QString get_char_name(QString p_char); diff --git a/include/courtroom.h b/include/courtroom.h index 905da55a1..1d81b88c2 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -331,9 +331,6 @@ class Courtroom : public QMainWindow ////////////// QScrollArea *note_scroll_area = nullptr; - // delay before chat messages starts ticking - QTimer *text_delay_timer = nullptr; - // delay before sfx plays QTimer *sfx_delay_timer = nullptr; @@ -693,7 +690,7 @@ public slots: void mod_called(QString p_ip); private slots: - void start_chat_ticking(); + void setup_chat(); void play_sfx(); void chat_tick(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6cde863a2..3124726a4 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -938,6 +938,8 @@ void Courtroom::handle_chatmessage_3() { qDebug() << "handle_chatmessage_3"; + setup_chat(); + int f_evi_id = m_chatmessage[CMEvidenceId].toInt(); QString f_side = m_chatmessage[CMPosition]; @@ -1098,7 +1100,8 @@ void Courtroom::handle_chatmessage_3() } } - start_chat_ticking(); + chat_tick_timer->start(ao_app->get_chat_tick_interval()); + chat_tick(); } void Courtroom::on_chat_config_changed() @@ -1328,7 +1331,6 @@ void Courtroom::play_preanim() QString f_char = m_chatmessage[CMChrName]; // all time values in char.inis are multiplied by a constant(time_mod) to get // the actual time - int text_delay = ao_app->get_text_delay(f_char, f_preanim) * time_mod; int sfx_delay = m_chatmessage[CMSoundDelay].toInt() * 60; sfx_delay_timer->start(sfx_delay); @@ -1343,9 +1345,6 @@ void Courtroom::play_preanim() { qDebug() << "Playing" << f_anim_path; - if (text_delay >= 0) - text_delay_timer->start(text_delay); - // finished return; } @@ -1369,7 +1368,7 @@ void Courtroom::realization_done() ui_vp_effect->stop(); } -void Courtroom::start_chat_ticking() +void Courtroom::setup_chat() { ui_vp_message->clear(); @@ -1396,7 +1395,6 @@ void Courtroom::start_chat_ticking() chatbox_message_outline = (ao_app->get_font_property("message_outline", fonts_ini) == 1); chatbox_message_enable_highlighting = (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "true"); chatbox_message_highlight_colors = ao_app->get_highlight_colors(); - chat_tick_timer->start(ao_app->get_chat_tick_interval()); QString f_gender = ao_app->get_gender(m_chatmessage[CMChrName]); @@ -1405,14 +1403,12 @@ void Courtroom::start_chat_ticking() // means text is currently ticking text_state = 1; - chat_tick(); } void Courtroom::chat_tick() { // note: this is called fairly often(every 60 ms when char is talking) // do not perform heavy operations here - qDebug() << QTime::currentTime(); QTextCharFormat vp_message_format = ui_vp_message->currentCharFormat(); if (chatbox_message_outline) vp_message_format.setTextOutline(QPen(Qt::black, 1)); @@ -1540,9 +1536,7 @@ void Courtroom::chat_tick() blip_pos = 0; // play blip - qDebug() << "START" << QTime::currentTime(); m_blips_player->blip_tick(); - qDebug() << "END" << QTime::currentTime(); } ++blip_pos; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 20d6b2e34..f1d95b1aa 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -28,9 +28,6 @@ void Courtroom::create_widgets() chat_tick_timer = new QTimer(this); chat_tick_timer->setTimerType(Qt::PreciseTimer); // Prevents drift - text_delay_timer = new QTimer(this); - text_delay_timer->setSingleShot(true); - sfx_delay_timer = new QTimer(this); sfx_delay_timer->setSingleShot(true); @@ -234,7 +231,6 @@ void Courtroom::connect_widgets() connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); - connect(text_delay_timer, SIGNAL(timeout()), this, SLOT(start_chat_ticking())); connect(sfx_delay_timer, SIGNAL(timeout()), this, SLOT(play_sfx())); connect(chat_tick_timer, SIGNAL(timeout()), this, SLOT(chat_tick())); diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 4d2454c52..686756eff 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -800,16 +800,6 @@ int AOApplication::get_sfx_delay(QString p_char, int p_emote) return f_result.toInt(); } -int AOApplication::get_text_delay(QString p_char, QString p_emote) -{ - QString f_result = read_char_ini(p_char, p_emote, "[TextDelay]", "END_OF_FILE"); - - if (f_result == "") - return -1; - else - return f_result.toInt(); -} - bool AOApplication::get_blank_blip() { return config->blank_blips_enabled(); From 4c66ddf4b3bd0ff8aa77be9da330a22855568b0a Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 4 Apr 2021 12:58:21 -0400 Subject: [PATCH 286/842] Fix config not reading saved display empty messages option --- src/aoconfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 99b2334e6..4375947f4 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -127,7 +127,7 @@ void AOConfigPrivate::read_file() log_max_lines = cfg.value("chatlog_limit", 200).toInt(); log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); log_display_timestamp = cfg.value("chatlog_display_timestamp", false).toBool(); - log_display_empty_messages = cfg.value("log_display_empty_messages", false).toBool(); + log_display_empty_messages = cfg.value("chatlog_display_empty_messages", false).toBool(); log_format_use_newline = cfg.value("chatlog_newline", false).toBool(); log_display_music_switch = cfg.value("music_change_log", true).toBool(); log_is_recording = cfg.value("enable_logging", true).toBool(); From 2b48b6e5515de21909f91cfd378e919a50614195 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 5 Apr 2021 20:41:20 -0400 Subject: [PATCH 287/842] Implement get_application_path() --- include/drpather.h | 12 +++++++----- src/aoconfig.cpp | 5 +---- src/aoconfigpanel.cpp | 10 ++++++---- src/aonotepicker.cpp | 4 +++- src/courtroom.cpp | 1 - src/courtroom_widgets.cpp | 1 - src/drpather.cpp | 9 ++++----- src/lobby.cpp | 4 ++-- src/main.cpp | 1 - src/path_functions.cpp | 4 +++- 10 files changed, 26 insertions(+), 25 deletions(-) diff --git a/include/drpather.h b/include/drpather.h index 71eb0f505..65dd7ea1b 100644 --- a/include/drpather.h +++ b/include/drpather.h @@ -9,14 +9,16 @@ class DRPather : QObject public: DRPather(); - /* @brief Sets the current path (as given by QDir::currentPath()) to be the directory containing the base folder. + /* @brief Gets the directory containing the base folder and the application. * - * This function makes running QDir::currentPath() system-independent. The only edge case really is MacOS, where - * due to the way applications are bundled, makes QDir::currentPath() return nonsensical values by default. + * This function is a system-independent QDir::currentPath() system-independent. The only edge case really is MacOS, + * where due to the way applications are bundled, makes QDir::currentPath() return nonsensical values by default and + * QCoreApplication::applicationDirPath() returns the directory to the internal executable of the bundle rather + * than the bundle location itself. * - * @return True if the current path was changed successfully, false if it remained the same. + * @return Directory. */ - static void correctCurrentPath(); + static QString get_application_path(); }; #endif // DRPATHER_H diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 4e6024a1f..16b105dcc 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -6,7 +6,6 @@ // qt #include #include -#include #include #include #include @@ -86,7 +85,7 @@ private slots: }; AOConfigPrivate::AOConfigPrivate() - : QObject(nullptr), cfg(QDir::currentPath() + "/base/config.ini", QSettings::IniFormat), + : QObject(nullptr), cfg(DRPather::get_application_path() + "/base/config.ini", QSettings::IniFormat), audio_engine(new DRAudioEngine(this)) { Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); @@ -238,8 +237,6 @@ static QSharedPointer d; AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) { - DRPather::correctCurrentPath(); - // init if not created yet if (d == nullptr) { diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 8c8d36af4..89008fb92 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -1,5 +1,6 @@ #include "aoconfigpanel.h" #include "datatypes.h" +#include "drpather.h" // qt #include @@ -256,7 +257,7 @@ void AOConfigPanel::refresh_theme_list() w_theme->clear(); // themes - const QString path = QDir::currentPath() + "/base/themes"; + const QString path = DRPather::get_application_path() + "/base/themes"; for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") @@ -282,7 +283,7 @@ void AOConfigPanel::refresh_gamemode_list() // add empty entry indicating no gamemode chosen w_gamemode->addItem(""); // gamemodes - QString path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/"; + QString path = DRPather::get_application_path() + "/base/themes/" + m_config->theme() + "/gamemodes/"; for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") @@ -312,9 +313,10 @@ void AOConfigPanel::refresh_timeofday_list() // gamemode chosen or not QString path; if (m_config->gamemode().isEmpty()) - path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/times/"; + path = DRPather::get_application_path() + "/base/themes/" + m_config->theme() + "/times/"; else - path = QDir::currentPath() + "/base/themes/" + m_config->theme() + "/gamemodes/" + m_config->gamemode() + "/times/"; + path = DRPather::get_application_path() + "/base/themes/" + m_config->theme() + "/gamemodes/" + + m_config->gamemode() + "/times/"; // times of day for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) diff --git a/src/aonotepicker.cpp b/src/aonotepicker.cpp index 738308c69..0d7b61807 100644 --- a/src/aonotepicker.cpp +++ b/src/aonotepicker.cpp @@ -2,6 +2,7 @@ #include "courtroom.h" #include "debug_functions.h" +#include "drpather.h" #include #include @@ -44,7 +45,8 @@ void Courtroom::on_set_file_button_clicked() { AOButton *f_button = static_cast(sender()); AONotePicker *f_notepicker = static_cast(f_button->parent()); - QString f_filename = QFileDialog::getOpenFileName(this, "Open File", QDir::currentPath(), "Text files (*.txt)"); + QString f_filename = + QFileDialog::getOpenFileName(this, "Open File", DRPather::get_application_path(), "Text files (*.txt)"); if (f_filename != "") { diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 315790041..e0018f6d5 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index e15013b1e..a71c0b4a7 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include diff --git a/src/drpather.cpp b/src/drpather.cpp index 608f4026d..05b399fa2 100644 --- a/src/drpather.cpp +++ b/src/drpather.cpp @@ -7,20 +7,19 @@ #include #if defined Q_OS_MACOS -void DRPather::correctCurrentPath() +QString DRPather::get_application_path() { QString oldPath = QDir::currentPath(); QDir l_mac_path(QCoreApplication::applicationDirPath()); for (int i = 0; i < 3; ++i) // equivalent of "/../../.." l_mac_path.cdUp(); - QDir::setCurrent(l_mac_path.canonicalPath()); - return; + return l_mac_path.canonicalPath(); } #else -void DRPather::correctCurrentPath() +QString DRPather::get_application_path() { // For other operating systems (Windows, Linux, etc.) the directory is properly set. - return; + return QDir::currentPath(); } #endif diff --git a/src/lobby.cpp b/src/lobby.cpp index 65b030d03..685290deb 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -3,11 +3,11 @@ #include "aoapplication.h" #include "aosfxplayer.h" #include "debug_functions.h" +#include "drpather.h" #include "drtextedit.h" #include "networkmanager.h" #include -#include #include #include #include @@ -85,7 +85,7 @@ void Lobby::set_widgets() "the DRO Discord including the large 'base' folder.\n" "2. If you did, check that the base folder is in the same folder " "where you launched Danganronpa Online from: " + - QDir::currentPath() + + DRPather::get_application_path() + "\n" "3. If it is there, check that your current theme folder exists in " "base/themes. According to base/config.ini, your current theme is " + diff --git a/src/main.cpp b/src/main.cpp index e8cfaa34c..87c611b90 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,7 +21,6 @@ int main(int argc, char *argv[]) } #endif - DRPather::correctCurrentPath(); AOApplication app(argc, argv); app.construct_lobby(); diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 99727f101..c402f7c62 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -1,6 +1,8 @@ #include "aoapplication.h" #include "courtroom.h" +#include "drpather.h" #include "file_functions.h" + #include #include #include @@ -27,7 +29,7 @@ QString AOApplication::get_base_path() #ifdef BASE_OVERRIDE base_path = base_override; #else - base_path = QDir::currentPath() + "/base/"; + base_path = DRPather::get_application_path() + "/base/"; #endif } return base_path; From 486c215ac24114f7fef2a28cc162660e4094eca5 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 5 Apr 2021 22:03:47 -0400 Subject: [PATCH 288/842] Format a bit+Remove unneeded variable --- src/drpather.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/drpather.cpp b/src/drpather.cpp index 05b399fa2..0f0fa8e95 100644 --- a/src/drpather.cpp +++ b/src/drpather.cpp @@ -1,7 +1,7 @@ -#include "drpather.h" - #include "courtroom.h" +#include "drpather.h" #include "file_functions.h" + #include #include #include @@ -9,7 +9,6 @@ #if defined Q_OS_MACOS QString DRPather::get_application_path() { - QString oldPath = QDir::currentPath(); QDir l_mac_path(QCoreApplication::applicationDirPath()); for (int i = 0; i < 3; ++i) // equivalent of "/../../.." l_mac_path.cdUp(); From e94bcba5b0244c68aab5f85af4ae1fb58b7a01f0 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 11 Apr 2021 15:08:26 -0400 Subject: [PATCH 289/842] Fix SFX/voicelines not being played if preanim was "-" --- src/courtroom.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 3124726a4..30486c841 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1319,6 +1319,11 @@ void Courtroom::append_system_text(QString p_showname, QString p_line) void Courtroom::play_preanim() { + // all time values in char.inis are multiplied by a constant(time_mod) to get + // the actual time + int sfx_delay = m_chatmessage[CMSoundDelay].toInt() * 60; + sfx_delay_timer->start(sfx_delay); + QString f_preanim = m_chatmessage[CMPreAnim]; if (f_preanim.trimmed() == "-") @@ -1329,12 +1334,6 @@ void Courtroom::play_preanim() } QString f_char = m_chatmessage[CMChrName]; - // all time values in char.inis are multiplied by a constant(time_mod) to get - // the actual time - int sfx_delay = m_chatmessage[CMSoundDelay].toInt() * 60; - - sfx_delay_timer->start(sfx_delay); - // set state anim_state = 1; From e923374076e0f22d951b7b8eddb9b50e85345a2d Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 11 Apr 2021 15:49:26 -0400 Subject: [PATCH 290/842] Do showname file I/O only if necessary rather than always --- src/courtroom.cpp | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 30486c841..f925b2b7c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -984,20 +984,25 @@ void Courtroom::handle_chatmessage_3() const QString f_emote = m_chatmessage[CMEmote]; const bool l_hide_emote = (f_emote == "../../misc/blank"); - ui_vp_showname_image->show(); - - // Asset lookup order - // 1. In the theme folder (gamemode-timeofday/main/default), in the character - // folder, look for "showname" + extensions in `exts` in order - // 2. In the character folder, look for - // "showname" + extensions in `exts` in order + QString path; + if (!chatmessage_is_empty && ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") + { + // Asset lookup order + // 1. In the theme folder (gamemode-timeofday/main/default), in the character + // folder, look for "showname" + extensions in `exts` in order + // 2. In the character folder, look for + // "showname" + extensions in `exts` in order - QString path = ao_app->find_theme_asset_path("characters/" + f_char + "/showname", {".png"}); - if (path.isEmpty()) - path = ao_app->find_asset_path({ao_app->get_character_path(f_char, "showname")}, {".png"}); + path = ao_app->find_theme_asset_path("characters/" + f_char + "/showname", {".png"}); + if (path.isEmpty()) + path = ao_app->find_asset_path({ao_app->get_character_path(f_char, "showname")}, {".png"}); + } - if (!path.isEmpty() && !chatmessage_is_empty && - ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") + // Path may be empty if + // 1. Chat message was empty + // 2. Enable showname images was false + // 3. No valid showname image was found + if (!path.isEmpty()) { ui_vp_showname->hide(); ui_vp_showname_image->set_image_from_path(path); @@ -1101,7 +1106,6 @@ void Courtroom::handle_chatmessage_3() } chat_tick_timer->start(ao_app->get_chat_tick_interval()); - chat_tick(); } void Courtroom::on_chat_config_changed() From 5fce3aa5ca2ace861439f5da0833d4f533115c59 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 11 Apr 2021 16:07:18 -0400 Subject: [PATCH 291/842] Make SFXName = "0" play nothing rather than attempt to find (and fail) to find file 0 --- src/courtroom.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f925b2b7c..d52e0895f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -936,7 +936,8 @@ void Courtroom::handle_chatmessage_2() // handles IC void Courtroom::handle_chatmessage_3() { - qDebug() << "handle_chatmessage_3"; + qDebug() << "3 start" << QTime::currentTime(); + // qDebug() << "handle_chatmessage_3"; setup_chat(); @@ -1106,6 +1107,7 @@ void Courtroom::handle_chatmessage_3() } chat_tick_timer->start(ao_app->get_chat_tick_interval()); + // qDebug() << "3 end" << QTime::currentTime(); } void Courtroom::on_chat_config_changed() @@ -1572,7 +1574,7 @@ void Courtroom::hide_testimony() void Courtroom::play_sfx() { QString sfx_name = m_chatmessage[CMSoundName]; - if (sfx_name == "1") + if (sfx_name == "1" || sfx_name == "0") return; m_effects_player->play(sfx_name); From 1ba01ae131be21048a956fbb06789d4e1666dffa Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 12 Apr 2021 17:47:18 -0400 Subject: [PATCH 292/842] Remove support for vanilla effects + Optimize folder jumping check to prevent performing unneeded expensive operation --- src/aoapplication.cpp | 9 +++++++++ src/aocharmovie.cpp | 2 -- src/courtroom.cpp | 42 ++++++++---------------------------------- 3 files changed, 17 insertions(+), 36 deletions(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index f67c4f113..d8172b21f 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -185,6 +185,15 @@ QString AOApplication::get_current_char() QString AOApplication::sanitize_path(QString p_file) { + // We want to avoid allowing directories with \..\ or /../, as those allow us to jump + // We first check if there are any .. at all + if (!p_file.contains("..")) + { + // Don't do expensive check if there are no .. to begin with. + return p_file; + } + // Otherwise, there are .. + // Check if they are actually referring to a directory, as it is possible QStringList list = p_file.split(QRegularExpression("[\\/]")); while (!list.isEmpty()) if (list.takeFirst().contains(QRegularExpression("\\.{2,}"))) diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index ca6724284..e828dcacf 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -84,13 +84,11 @@ bool AOCharMovie::play_pre(QString p_chr, QString p_emote) void AOCharMovie::play_talking(QString p_chr, QString p_emote) { - QString gif_path = ao_app->get_character_path(p_chr, "(b)" + p_emote); play(p_chr, p_emote, "(b)", false); } void AOCharMovie::play_idle(QString p_chr, QString p_emote) { - QString gif_path = ao_app->get_character_path(p_chr, "(a)" + p_emote); play(p_chr, p_emote, "(a)", false); } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d52e0895f..8f56a3800 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -720,7 +720,10 @@ void Courtroom::handle_acknowledged_ms() ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors m_shout_state = 0; + + qDebug() << "Shout start" << QTime::currentTime(); draw_shout_buttons(); + qDebug() << "Shout end" << QTime::currentTime(); m_effect_state = 0; draw_effect_buttons(); @@ -729,6 +732,7 @@ void Courtroom::handle_acknowledged_ms() draw_judge_wtce_buttons(); is_presenting_evidence = false; + ui_evidence_present->set_image("present_disabled.png"); } @@ -804,12 +808,6 @@ void Courtroom::handle_chatmessage(QStringList p_contents) QString f_message = f_showname + ": " + m_chatmessage[CMMessage] + "\n"; - /* - if (f_message == previous_ic_message && is_system_speaking == false) - return; - previous_ic_message = f_message; - */ - m_effects_player->stop_all(); text_state = 0; @@ -936,11 +934,9 @@ void Courtroom::handle_chatmessage_2() // handles IC void Courtroom::handle_chatmessage_3() { - qDebug() << "3 start" << QTime::currentTime(); - // qDebug() << "handle_chatmessage_3"; + qDebug() << "handle_chatmessage_3"; setup_chat(); - int f_evi_id = m_chatmessage[CMEvidenceId].toInt(); QString f_side = m_chatmessage[CMPosition]; @@ -1046,30 +1042,7 @@ void Courtroom::handle_chatmessage_3() QString overlay_name = overlay.at(0); QString overlay_sfx = overlay.at(1); - bool do_it = ao_app->read_theme_ini("non_vanilla_effects", cc_config_ini) == "true"; - - if (effect == 1 && !do_it) - { - if (overlay_sfx == "") - overlay_sfx = ao_app->get_sfx("effect_flash"); - m_effects_player->play(overlay_sfx); - ui_vp_effect->set_play_once(true); - if (overlay_name == "") - overlay_name = "effect_flash"; - ui_vp_effect->play(overlay_name, f_char); - realization_timer->start(60); - } - // else if (effect == 2) - // { - // if (overlay_sfx == "") - // overlay_sfx = ao_app->get_sfx("effect_gloom"); - // m_sfx_player->play(overlay_sfx); - // ui_vp_effect->set_play_once(false); - // if (overlay_name == "") - // overlay_name = "effect_gloom"; - // ui_vp_effect->play(overlay_name, f_char); - // } - else if (do_it && effect > 0 && effect <= ui_effects.size() && effect_names.size() > 0) // check to prevent crashing + if (effect > 0 && effect <= ui_effects.size() && effect_names.size() > 0) // check to prevent crashing { QString s_eff = effect_names.at(effect - 1); QStringList f_eff = ao_app->get_effect(effect); @@ -1107,7 +1080,6 @@ void Courtroom::handle_chatmessage_3() } chat_tick_timer->start(ao_app->get_chat_tick_interval()); - // qDebug() << "3 end" << QTime::currentTime(); } void Courtroom::on_chat_config_changed() @@ -1982,8 +1954,10 @@ void Courtroom::draw_shout_buttons() { for (int i = 0; i < ui_shouts.size(); ++i) { + // qDebug() << "Shout start" << QTime::currentTime(); QString shout_file = shout_names.at(i) + ".png"; ui_shouts[i]->set_image(shout_file); + // qDebug() << "Shout after set_image" << QTime::currentTime(); if (ao_app->find_theme_asset_path(shout_file).isEmpty()) ui_shouts[i]->setText(shout_names.at(i)); else From 92a1919e71fbd22be74fe0c834aa1a683abfb0df Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 13 Apr 2021 23:24:25 -0400 Subject: [PATCH 293/842] Force chatbox message to be repainted on each tick to prevent jagged text --- src/courtroom.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 8f56a3800..863a133f5 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -720,10 +720,7 @@ void Courtroom::handle_acknowledged_ms() ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors m_shout_state = 0; - - qDebug() << "Shout start" << QTime::currentTime(); draw_shout_buttons(); - qDebug() << "Shout end" << QTime::currentTime(); m_effect_state = 0; draw_effect_buttons(); @@ -1521,6 +1518,8 @@ void Courtroom::chat_tick() ++tick_pos; } + + ui_vp_message->repaint(); } void Courtroom::show_testimony() @@ -1954,10 +1953,8 @@ void Courtroom::draw_shout_buttons() { for (int i = 0; i < ui_shouts.size(); ++i) { - // qDebug() << "Shout start" << QTime::currentTime(); QString shout_file = shout_names.at(i) + ".png"; ui_shouts[i]->set_image(shout_file); - // qDebug() << "Shout after set_image" << QTime::currentTime(); if (ao_app->find_theme_asset_path(shout_file).isEmpty()) ui_shouts[i]->setText(shout_names.at(i)); else From a82fe2d4085759f0934b4a5bcc52c8d24e7b949d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 16 Apr 2021 06:53:50 +0200 Subject: [PATCH 294/842] Cleaned up headers and removed superfluous additions * Cleaned up DRPather header (we don't need it to be derived from QObject as of time of writing) * Removed needless headers inside DRPather source files * Cleaned up main.cpp, needless headers --- include/drpather.h | 12 +++--------- src/drpather.cpp | 14 +++----------- src/main.cpp | 7 +------ 3 files changed, 7 insertions(+), 26 deletions(-) diff --git a/include/drpather.h b/include/drpather.h index 65dd7ea1b..c129db819 100644 --- a/include/drpather.h +++ b/include/drpather.h @@ -1,14 +1,10 @@ -#ifndef DRPATHER_H -#define DRPATHER_H +#pragma once -#include +#include -class DRPather : QObject +class DRPather { - Q_OBJECT public: - DRPather(); - /* @brief Gets the directory containing the base folder and the application. * * This function is a system-independent QDir::currentPath() system-independent. The only edge case really is MacOS, @@ -20,5 +16,3 @@ class DRPather : QObject */ static QString get_application_path(); }; - -#endif // DRPATHER_H diff --git a/src/drpather.cpp b/src/drpather.cpp index 0f0fa8e95..e3ec8ebd8 100644 --- a/src/drpather.cpp +++ b/src/drpather.cpp @@ -1,24 +1,16 @@ -#include "courtroom.h" #include "drpather.h" -#include "file_functions.h" -#include +#include #include -#include -#if defined Q_OS_MACOS QString DRPather::get_application_path() { +#ifdef Q_OS_MACOS QDir l_mac_path(QCoreApplication::applicationDirPath()); for (int i = 0; i < 3; ++i) // equivalent of "/../../.." l_mac_path.cdUp(); return l_mac_path.canonicalPath(); -} #else -QString DRPather::get_application_path() -{ - // For other operating systems (Windows, Linux, etc.) the directory is properly set. return QDir::currentPath(); -} - #endif +} diff --git a/src/main.cpp b/src/main.cpp index 87c611b90..7d8b10306 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,14 +1,9 @@ #include "aoapplication.h" -#include "courtroom.h" -#include "datatypes.h" -#include "debug_functions.h" -#include "drpather.h" #include "lobby.h" -#include "networkmanager.h" int main(int argc, char *argv[]) { -#if QT_VERSION > QT_VERSION_CHECK(5, 6, 0) +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) // High-DPI support is for Qt version >=5.6. // However, many Linux distros still haven't brought their stable/LTS // packages up to Qt 5.6, so this is conditional. From 6d111594b8d278fd76c587b59da93d53f6742b8d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 16 Apr 2021 11:53:21 +0200 Subject: [PATCH 295/842] Added helper functions, documented method * Added `read_theme_ini` helper functions for direct conversions of `bool` and `int` types. * Documuted `sanitize_path` --- include/aoapplication.h | 4 ++++ src/aoapplication.cpp | 9 ++------- src/courtroom.cpp | 8 ++++---- src/courtroom_widgets.cpp | 22 +++++++++++----------- src/text_file_functions.cpp | 10 ++++++++++ 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 6cefb198c..a2216e763 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -332,6 +332,10 @@ class AOApplication : public QApplication */ QString read_theme_ini(QString p_identifier, QString p_file); + bool read_theme_ini_bool(QString p_identifier, QString p_file); + + int read_theme_ini_int(QString p_identifier, QString p_file); + // Returns the coordinates of widget with p_identifier from p_file QPoint get_button_spacing(QString p_identifier, QString p_file); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index d8172b21f..e52f1b0ad 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -185,19 +185,14 @@ QString AOApplication::get_current_char() QString AOApplication::sanitize_path(QString p_file) { - // We want to avoid allowing directories with \..\ or /../, as those allow us to jump - // We first check if there are any .. at all if (!p_file.contains("..")) - { - // Don't do expensive check if there are no .. to begin with. return p_file; - } - // Otherwise, there are .. - // Check if they are actually referring to a directory, as it is possible + QStringList list = p_file.split(QRegularExpression("[\\/]")); while (!list.isEmpty()) if (list.takeFirst().contains(QRegularExpression("\\.{2,}"))) return nullptr; + return p_file; } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 863a133f5..51a771bde 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -309,7 +309,7 @@ void Courtroom::handle_music_anim() QFont f_font = ui_vp_music_name->font(); QFontMetrics fm(f_font); int dist; - if (ao_app->read_theme_ini("enable_const_music_speed", cc_config_ini) == "true") + if (ao_app->read_theme_ini_bool("enable_const_music_speed", cc_config_ini)) dist = res_b.width; else dist = fm.horizontalAdvance(ui_vp_music_name->toPlainText()); @@ -979,7 +979,7 @@ void Courtroom::handle_chatmessage_3() const bool l_hide_emote = (f_emote == "../../misc/blank"); QString path; - if (!chatmessage_is_empty && ao_app->read_theme_ini("enable_showname_image", cc_config_ini) == "true") + if (!chatmessage_is_empty && ao_app->read_theme_ini_bool("enable_showname_image", cc_config_ini)) { // Asset lookup order // 1. In the theme folder (gamemode-timeofday/main/default), in the character @@ -1367,7 +1367,7 @@ void Courtroom::setup_chat() // Cache these so chat_tick performs better chatbox_message_outline = (ao_app->get_font_property("message_outline", fonts_ini) == 1); - chatbox_message_enable_highlighting = (ao_app->read_theme_ini("enable_highlighting", cc_config_ini) == "true"); + chatbox_message_enable_highlighting = (ao_app->read_theme_ini_bool("enable_highlighting", cc_config_ini)); chatbox_message_highlight_colors = ao_app->get_highlight_colors(); QString f_gender = ao_app->get_gender(m_chatmessage[CMChrName]); @@ -2011,7 +2011,7 @@ void Courtroom::on_cycle_clicked() break; } - if (ao_app->read_theme_ini("enable_cycle_ding", cc_config_ini) == "true") + if (ao_app->read_theme_ini_bool("enable_cycle_ding", cc_config_ini)) m_system_player->play(ao_app->get_sfx("cycle")); set_shouts(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index f1d95b1aa..96bbb9c88 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -698,7 +698,7 @@ void Courtroom::set_widgets() ui_shout_down->hide(); // courtroom_config.ini necessary + check for crash - if (ao_app->read_theme_ini("enable_single_shout", cc_config_ini) == "true" && ui_shouts.size() > 0) + if (ao_app->read_theme_ini_bool("enable_single_shout", cc_config_ini) && ui_shouts.size() > 0) { for (auto &shout : ui_shouts) move_widget(shout, "bullet"); @@ -726,7 +726,7 @@ void Courtroom::set_widgets() ui_effect_down->set_image("effectdown.png"); ui_effect_down->hide(); - if (ao_app->read_theme_ini("enable_single_effect", cc_config_ini) == "true" && + if (ao_app->read_theme_ini_bool("enable_single_effect", cc_config_ini) && ui_effects.size() > 0) // check to prevent crashing { for (auto &effect : ui_effects) @@ -750,7 +750,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_wtce[i], wtce_names[i]); } - if (ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true") // courtroom_config.ini necessary + if (ao_app->read_theme_ini_bool("enable_single_wtce", cc_config_ini)) // courtroom_config.ini necessary { for (auto &wtce : ui_wtce) move_widget(wtce, "wtce"); @@ -785,7 +785,7 @@ void Courtroom::set_widgets() ui_config_panel->setStyleSheet(""); ui_note_button->setStyleSheet(""); - if (ao_app->read_theme_ini("enable_button_images", cc_config_ini) == "true") + if (ao_app->read_theme_ini_bool("enable_button_images", cc_config_ini)) { // Set files, ask questions later // set_image first tries the gamemode-timeofday folder, then the theme @@ -838,7 +838,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_label_images[i], label_images[i].toLower() + "_image"); } - if (ao_app->read_theme_ini("enable_label_images", cc_config_ini) == "true") + if (ao_app->read_theme_ini_bool("enable_label_images", cc_config_ini)) { for (int i = 0; i < ui_checks.size(); ++i) // loop through checks { @@ -1015,7 +1015,7 @@ int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_it // &item_vector must be a vector of size at least 1! // Redraw the new correct number of items. - int new_item_number = ao_app->read_theme_ini(config_item_number, cc_config_ini).toInt(); + int new_item_number = ao_app->read_theme_ini_int(config_item_number, cc_config_ini); int current_item_number = item_vector.size(); // Note we use the fact that, if config_item_number is not there, // read_theme_ini returns an empty string, which .toInt() would fail to @@ -1163,7 +1163,7 @@ void Courtroom::load_effects() delete_widget(widget); // And create new effects - int effect_number = ao_app->read_theme_ini("effect_number", cc_config_ini).toInt(); + int effect_number = ao_app->read_theme_ini_int("effect_number", cc_config_ini); effects_enabled.resize(effect_number); ui_effects.resize(effect_number); @@ -1198,7 +1198,7 @@ void Courtroom::load_free_blocks() delete_widget(widget); // And create new free block buttons - int free_block_number = ao_app->read_theme_ini("free_block_number", cc_config_ini).toInt(); + int free_block_number = ao_app->read_theme_ini_int("free_block_number", cc_config_ini); free_blocks_enabled.resize(free_block_number); ui_free_blocks.resize(free_block_number); @@ -1230,7 +1230,7 @@ void Courtroom::load_shouts() delete_widget(widget); // And create new shouts - int shout_number = ao_app->read_theme_ini("shout_number", cc_config_ini).toInt(); + int shout_number = ao_app->read_theme_ini_int("shout_number", cc_config_ini); shouts_enabled.resize(shout_number); ui_shouts.resize(shout_number); @@ -1268,7 +1268,7 @@ void Courtroom::load_wtce() delete_widget(widget); // And create new wtce buttons - int wtce_number = ao_app->read_theme_ini("wtce_number", cc_config_ini).toInt(); + int wtce_number = ao_app->read_theme_ini_int("wtce_number", cc_config_ini); wtce_enabled.resize(wtce_number); ui_wtce.resize(wtce_number); @@ -1336,7 +1336,7 @@ void Courtroom::set_judge_wtce() wtce->hide(); // check if we use a single wtce or multiple - const bool is_single_wtce = ao_app->read_theme_ini("enable_single_wtce", cc_config_ini) == "true"; + const bool is_single_wtce = ao_app->read_theme_ini_bool("enable_single_wtce", cc_config_ini); // update visibility for next/previous ui_wtce_up->setVisible(is_judge && is_single_wtce); diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 686756eff..c50216b4c 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -816,3 +816,13 @@ QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) return read_ini(p_identifier, path); // Could be the empty string } + +bool AOApplication::read_theme_ini_bool(QString p_identifier, QString p_file) +{ + return read_theme_ini(p_identifier, p_file) == "true"; +} + +int AOApplication::read_theme_ini_int(QString p_identifier, QString p_file) +{ + return read_theme_ini(p_identifier, p_file).toInt(); +} From 2ceb6126a1777a96b0c5c2460623476a202f65b3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 16 Apr 2021 14:50:29 +0200 Subject: [PATCH 296/842] Basic showname QOL support * Added a basic automatic way for shownames to be updated by the user. * Added new IC showname widget: `ic_chat_name` NOTE: this will very likely require a server change down the line to natively support this instead of the current workaround. --- include/aoconfig.h | 3 ++ include/aoconfigpanel.h | 6 +++ include/courtroom.h | 22 ++++++++- res/ui/config_panel.ui | 26 +++++++---- src/aoconfig.cpp | 16 +++++++ src/aoconfigpanel.cpp | 23 +++++++++- src/courtroom.cpp | 96 +++++++++++++++++++++++++++------------ src/courtroom_widgets.cpp | 16 +++++-- 8 files changed, 164 insertions(+), 44 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 8e3298718..b5e235bc7 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -23,6 +23,7 @@ class AOConfig : public QObject // getters bool autosave() const; QString username() const; + QString showname() const; QString callwords() const; bool server_alerts_enabled() const; bool discord_presence() const; @@ -65,6 +66,7 @@ public slots: public slots: void set_autosave(bool p_enabled); void set_username(QString p_string); + void set_showname(QString p_string); void set_callwords(QString p_string); void set_server_alerts(bool p_enabled); void set_discord_presence(const bool p_enabled); @@ -103,6 +105,7 @@ public slots: signals: void autosave_changed(bool); void username_changed(QString); + void showname_changed(QString); void callwords_changed(QString); void server_alerts_changed(bool); void discord_presence_changed(bool); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 52ff1c51b..914a84821 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -71,6 +71,7 @@ private slots: // general QLineEdit *w_username = nullptr; + QLineEdit *w_showname = nullptr; QLineEdit *w_callwords = nullptr; QCheckBox *w_server_alerts = nullptr; @@ -117,6 +118,11 @@ private slots: QLabel *w_blip_value = nullptr; QSpinBox *w_blip_rate = nullptr; QCheckBox *w_blank_blips = nullptr; + +private slots: + void username_editing_finished(); + void showname_editing_finished(); + void callwords_editing_finished(); }; #endif // AOCONFIGPANEL_H diff --git a/include/courtroom.h b/include/courtroom.h index 2aa733336..00a6b5802 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -131,6 +131,20 @@ class Courtroom : public QMainWindow // sets the character position void set_character_position(QString p_pos); + /** + * @brief Send a OOC packet (CT) out to the server. + * @param ooc_name The username. + * @param ooc_message The message. + */ + void send_ooc_packet(QString ooc_name, QString ooc_message); + + /** + * @brief Send a packet to set the showname of the user to + * the server. + * @param p_showname The showname. + */ + void send_showname_packet(QString p_showname); + // called when a DONE#% from the server was received void done_received(); @@ -324,6 +338,8 @@ class Courtroom : public QMainWindow // used to determine how often blips sound int blip_pos = 0; int rainbow_counter = 0; + QString m_last_showname; + bool m_showname_sent = false; bool rainbow_appended = false; bool note_shown = false; bool contains_add_button = false; @@ -505,10 +521,11 @@ class Courtroom : public QMainWindow QListWidget *ui_music_list = nullptr; QListWidget *ui_sfx_list = nullptr; + QLineEdit *ui_ic_chat_name = nullptr; QLineEdit *ui_ic_chat_message = nullptr; - QLineEdit *ui_ooc_chat_message = nullptr; QLineEdit *ui_ooc_chat_name = nullptr; + QLineEdit *ui_ooc_chat_message = nullptr; QLineEdit *ui_music_search = nullptr; @@ -695,9 +712,12 @@ private slots: void on_mute_list_item_changed(QListWidgetItem *p_item); + void on_showname_changed(); + void on_chat_name_editing_finished(); void on_chat_return_pressed(); void on_chat_config_changed(); + void on_ooc_name_editing_finished(); void on_ooc_return_pressed(); void on_music_search_edited(QString p_text); diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index b451ff59f..36c3e5a35 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -46,7 +46,7 @@ - + Callwords: @@ -56,21 +56,21 @@ - + Separate words by space - + Qt::Horizontal - + @@ -108,7 +108,14 @@ - + + + + Server alerts: + + + + @@ -121,13 +128,16 @@ - - + + - Server alerts: + Showname: + + + diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 4375947f4..44fbba60e 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -46,6 +46,7 @@ private slots: // data bool autosave; QString username; + QString showname; QString callwords; bool server_alerts; bool discord_presence = false; @@ -107,6 +108,7 @@ void AOConfigPrivate::read_file() { autosave = cfg.value("autosave", true).toBool(); username = cfg.value("username").toString(); + showname = cfg.value("showname").toString(); callwords = cfg.value("callwords").toString(); server_alerts = cfg.value("server_alerts", true).toBool(); @@ -165,6 +167,7 @@ void AOConfigPrivate::save_file() { cfg.setValue("autosave", autosave); cfg.setValue("username", username); + cfg.setValue("showname", showname); cfg.setValue("callwords", callwords); cfg.setValue("server_alerts", server_alerts); @@ -279,6 +282,11 @@ QString AOConfig::username() const return d->username; } +QString AOConfig::showname() const +{ + return d->showname; +} + QString AOConfig::callwords() const { return d->callwords; @@ -454,6 +462,14 @@ void AOConfig::set_username(QString p_string) d->invoke_signal("username_changed", Q_ARG(QString, p_string)); } +void AOConfig::set_showname(QString p_string) +{ + if (d->showname == p_string) + return; + d->showname = p_string; + d->invoke_signal("showname_changed", Q_ARG(QString, p_string)); +} + void AOConfig::set_callwords(QString p_string) { if (d->callwords == p_string) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 8c8d36af4..bb762c3cb 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -31,6 +31,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // general w_username = AO_GUI_WIDGET(QLineEdit, "username"); + w_showname = AO_GUI_WIDGET(QLineEdit, "showname"); w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); w_discord_presence = AO_GUI_WIDGET(QGroupBox, "discord_presence"); @@ -85,6 +86,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // input connect(m_config, SIGNAL(autosave_changed(bool)), w_autosave, SLOT(setChecked(bool))); connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); + connect(m_config, SIGNAL(showname_changed(QString)), w_showname, SLOT(setText(QString))); connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); connect(m_config, SIGNAL(discord_presence_changed(bool)), w_discord_presence, SLOT(setChecked(bool))); @@ -133,8 +135,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_close, SIGNAL(clicked()), this, SLOT(close())); connect(w_save, SIGNAL(clicked()), m_config, SLOT(save_file())); connect(w_autosave, SIGNAL(toggled(bool)), m_config, SLOT(set_autosave(bool))); - connect(w_username, SIGNAL(textEdited(QString)), m_config, SLOT(set_username(QString))); - connect(w_callwords, SIGNAL(textEdited(QString)), m_config, SLOT(set_callwords(QString))); + connect(w_username, SIGNAL(editingFinished()), this, SLOT(username_editing_finished())); + connect(w_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); + connect(w_callwords, SIGNAL(editingFinished()), this, SLOT(callwords_editing_finished())); connect(w_server_alerts, SIGNAL(toggled(bool)), m_config, SLOT(set_server_alerts(bool))); connect(w_discord_presence, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_presence(bool))); @@ -179,6 +182,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // set values w_autosave->setChecked(m_config->autosave()); w_username->setText(m_config->username()); + w_showname->setText(m_config->showname()); w_callwords->setText(m_config->callwords()); w_server_alerts->setChecked(m_config->server_alerts_enabled()); w_theme->setCurrentText(m_config->theme()); @@ -448,6 +452,21 @@ void AOConfigPanel::on_blip_value_changed(int p_num) w_blip_value->setText(QString::number(p_num) + "%"); } +void AOConfigPanel::username_editing_finished() +{ + m_config->set_username(w_username->text()); +} + +void AOConfigPanel::showname_editing_finished() +{ + m_config->set_showname(w_showname->text()); +} + +void AOConfigPanel::callwords_editing_finished() +{ + m_config->set_callwords(w_callwords->text()); +} + void AOConfigPanel::on_config_reload_theme_requested() { refresh_theme_list(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 823df2372..53f3f5f95 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -577,6 +577,21 @@ void Courtroom::append_server_chatmessage(QString p_name, QString p_message) save_textlog("(OOC)" + p_name + ": " + p_message); } +void Courtroom::on_showname_changed() +{ + const QString l_showname = ao_config->showname(); + send_showname_packet(l_showname); + ui_ic_chat_name->setText(l_showname); +} + +void Courtroom::send_showname_packet(QString p_showname) +{ + if (p_showname == m_last_showname) + return; + m_last_showname = p_showname; + send_ooc_packet(ao_config->username(), QString("/showname %1").arg(p_showname)); +} + void Courtroom::on_chat_return_pressed() { if (ui_ic_chat_message->text() == "" || is_client_muted) @@ -585,6 +600,16 @@ void Courtroom::on_chat_return_pressed() if ((anim_state < 3 || text_state < 2) && m_shout_state == 0) return; + if (!m_showname_sent) + { + const QString l_showname = ao_config->showname().trimmed(); + if (!l_showname.isEmpty()) + { + m_showname_sent = true; + send_showname_packet(l_showname); + } + } + // qDebug() << "prev_emote = " << prev_emote << "current_emote = " << // current_emote; @@ -1725,6 +1750,33 @@ void Courtroom::set_character_position(QString p_pos) set_judge_enabled(p_pos == "jud"); } +void Courtroom::send_ooc_packet(QString ooc_name, QString ooc_message) +{ + if (ooc_name.trimmed().isEmpty()) + { + bool ok; + do + { + ooc_name = QInputDialog::getText(this, "Enter a name", + "You must have a name to talk in OOC chat. Enter a name: ", QLineEdit::Normal, + "user", &ok); + } while (ok && ooc_name.isEmpty()); + if (!ok) + return; + + ao_config->set_username(ooc_name); + } + + if (ooc_message.trimmed().isEmpty()) + { + append_server_chatmessage("CLIENT", "You cannot send an empty message."); + return; + } + + QStringList l_content{ooc_name, ooc_message}; + ao_app->send_server_packet(new AOPacket("CT", l_content)); +} + void Courtroom::mod_called(QString p_ip) { ui_server_chatlog->append(p_ip); @@ -1737,32 +1789,22 @@ void Courtroom::mod_called(QString p_ip) } } -void Courtroom::on_ooc_return_pressed() +void Courtroom::on_chat_name_editing_finished() { - QString ooc_name = ui_ooc_chat_name->text(); - QString ooc_message = ui_ooc_chat_message->text(); + ao_config->set_showname(ui_ic_chat_name->text()); +} - if (ooc_message.isEmpty()) - { - append_server_chatmessage("CLIENT", "You cannot send an empty message."); - return; - } - if (ooc_name.isEmpty()) - { - bool ok; - QString name; - do - { - ooc_name = QInputDialog::getText(this, "Enter a name", - "You must have a name to talk in OOC chat. Enter a name: ", QLineEdit::Normal, - "user", &ok); - } while (ok && ooc_name.isEmpty()); - if (!ok) - return; +void Courtroom::on_ooc_name_editing_finished() +{ + ao_config->set_username(ui_ooc_chat_name->text()); +} - ao_config->set_username(ooc_name); - } - else if (ooc_message.startsWith("/rainbow") && !rainbow_appended) +void Courtroom::on_ooc_return_pressed() +{ + const QString ooc_name = ui_ooc_chat_name->text(); + const QString ooc_message = ui_ooc_chat_message->text(); + + if (ooc_message.startsWith("/rainbow") && !rainbow_appended) { ui_text_color->addItem("Rainbow"); ui_ooc_chat_message->clear(); @@ -1830,16 +1872,10 @@ void Courtroom::on_ooc_return_pressed() timer_id = ooc_message.mid(space_location + 1).toInt(); pause_timer(timer_id); } - QStringList packet_contents; - packet_contents.append(ooc_name); - packet_contents.append(ooc_message); - - AOPacket *f_packet = new AOPacket("CT", packet_contents); - ao_app->send_server_packet(f_packet); + send_ooc_packet(ooc_name, ooc_message); ui_ooc_chat_message->clear(); - ui_ooc_chat_message->setFocus(); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index e15013b1e..21b761588 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -106,6 +106,11 @@ void Courtroom::create_widgets() ui_music_list = new QListWidget(this); ui_sfx_list = new QListWidget(this); + ui_ic_chat_name = new QLineEdit(this); + ui_ic_chat_name->setFrame(false); + ui_ic_chat_name->setPlaceholderText("Showname"); + ui_ic_chat_name->setText(ao_config->showname()); + ui_ic_chat_message = new QLineEdit(this); ui_ic_chat_message->setFrame(false); @@ -252,10 +257,12 @@ void Courtroom::connect_widgets() connect(ui_mute_list, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(on_mute_list_item_changed(QListWidgetItem *))); + connect(ao_config, SIGNAL(showname_changed(QString)), this, SLOT(on_showname_changed())); + connect(ui_ic_chat_name, SIGNAL(editingFinished()), this, SLOT(on_chat_name_editing_finished())); connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_chat_return_pressed())); - connect(ui_ooc_chat_name, SIGNAL(textEdited(QString)), ao_config, SLOT(set_username(QString))); connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); + connect(ui_ooc_chat_name, SIGNAL(editingFinished()), this, SLOT(on_ooc_name_editing_finished())); connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_music_list_clicked())); @@ -346,6 +353,7 @@ void Courtroom::reset_widget_names() {"area_list", ui_area_list}, {"music_list", ui_music_list}, {"sfx_list", ui_sfx_list}, + {"ic_chat_name", ui_ic_chat_name}, {"ao2_ic_chat_message", ui_ic_chat_message}, // ui_muted {"ooc_chat_message", ui_ooc_chat_message}, @@ -615,7 +623,11 @@ void Courtroom::set_widgets() set_size_and_pos(ui_sfx_list, "sfx_list"); + set_size_and_pos(ui_ic_chat_name, "ic_chat_name"); + ui_ic_chat_name->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message"); + ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); + set_size_and_pos(ui_vp_chatbox, "ao2_chatbox"); set_size_and_pos(ui_vp_music_area, "music_area"); @@ -633,8 +645,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_vp_clock, "clock"); ui_vp_clock->hide(); - ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); - ui_vp_chatbox->set_image("chatmed.png"); ui_vp_chatbox->hide(); From d57cb4679e90ba14aea6355522de68548576c02f Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 16 Apr 2021 15:18:26 +0200 Subject: [PATCH 297/842] Fixed ui element ordering, fixed text editing issue * Fixed UI elements being out of order (pressing tab would switch in improper order) * Replaced textEdited signal by editingFinished signal, fixing the issues when trying to erase text in the middle within the fields --- res/ui/config_panel.ui | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 36c3e5a35..e702150a5 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -46,6 +46,16 @@ + + + + Showname: + + + + + + @@ -128,16 +138,6 @@ - - - - Showname: - - - - - - From 7d0f24557cdc63a5479a0d632336ebcdd13ac601 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 16 Apr 2021 16:46:43 +0200 Subject: [PATCH 298/842] Added missing documentation from previous commit --- include/aoapplication.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/aoapplication.h b/include/aoapplication.h index a2216e763..d4c85e523 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -114,6 +114,16 @@ class AOApplication : public QApplication QString get_background_path(QString p_file); QString get_default_background_path(QString p_file); QString get_evidence_path(QString p_file); + + /** + * @brief Check the path for various known exploits. + * + * In order: + * - Directory traversal (most commonly: "../" jumps) + * @param p_file The path to check. + * @return A sanitized path. If any check fails, the path returned is an empty string. The sanitized path does not + * necessarily exist. + */ QString sanitize_path(QString p_file); /** From 182093b72609867e69ec36e37aa836367508ac63 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 16 Apr 2021 20:12:04 +0200 Subject: [PATCH 299/842] Cleaning up AOApplication source slightly --- src/path_functions.cpp | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index c402f7c62..dd0f2db9f 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -1,4 +1,5 @@ #include "aoapplication.h" + #include "courtroom.h" #include "drpather.h" #include "file_functions.h" @@ -13,26 +14,13 @@ // depends. On Mac, it can be toggled. So, should the time ever come to that, // manually define CASE_SENSITIVE_FILESYSTEM if you're working on a Mac that // has, well, a case-sensitive filesystem. -#if (defined(LINUX) || defined(__linux__)) +#ifdef Q_OS_LINUX #define CASE_SENSITIVE_FILESYSTEM #endif -#ifdef BASE_OVERRIDE -#include "base_override.h" -#endif -QString base_path = ""; - QString AOApplication::get_base_path() { - if (base_path == "") - { -#ifdef BASE_OVERRIDE - base_path = base_override; -#else - base_path = DRPather::get_application_path() + "/base/"; -#endif - } - return base_path; + return DRPather::get_application_path() + "/base/"; } QString AOApplication::get_data_path() From b0cf9f60f9e943f87943913bf9ed5c0924a0a1e4 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 17 Apr 2021 08:52:27 +0200 Subject: [PATCH 300/842] Moved showname to game tab of the cfg panel --- res/ui/config_panel.ui | 124 ++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 42f70bff4..1d8c910d7 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 3 + 0 @@ -47,16 +47,6 @@ - - - Showname: - - - - - - - Callwords: @@ -66,21 +56,21 @@ - + Separate words by space - + Qt::Horizontal - + @@ -118,14 +108,14 @@ - + Server alerts: - + @@ -236,32 +226,43 @@ - - - - Always-anim: - - - true + + + + + + + + + + 0 + 0 + + + + Manual + + + + + + + + + Qt::Horizontal - - - - - 0 - 0 - - + + - - - - false + Showname: + + + @@ -282,7 +283,33 @@ + + + + Always-anim: + + + true + + + + + + + 0 + 0 + + + + + + + false + + + + Qt::Vertical @@ -295,33 +322,6 @@ - - - - - - - - - - 0 - 0 - - - - Manual - - - - - - - - - Qt::Horizontal - - - From bd24f80527a66ae8794c2840dec722b340a265f3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 17 Apr 2021 09:39:18 +0200 Subject: [PATCH 301/842] Added missing dependency to main.cpp --- src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.cpp b/src/main.cpp index 7d8b10306..b58a9eb11 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ #include "aoapplication.h" #include "lobby.h" +#include "networkmanager.h" int main(int argc, char *argv[]) { From 1af7ceb640f59b89de4fc3f4a4d39872fd2da430 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 18 Apr 2021 09:02:15 +0200 Subject: [PATCH 302/842] Added personal highlight color for names * Added personal highlight color for names, this will look for the color (`ic_chatlog_selfname_color`) in `courtroom_fonts.ini`. Failing that, it will simply use the name color (`ic_chatlog_showname_color`). If neither are set, it will use the default color. The self-identification is done by comparing the message's chr_id to the one we currently are. --- include/courtroom.h | 2 +- include/datatypes.h | 11 +++++++++++ src/courtroom.cpp | 31 +++++++++++++++++++++---------- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 2aa733336..91a14a08c 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -226,7 +226,7 @@ class Courtroom : public QMainWindow // selected // or the user isn't already scrolled to the top void update_ic_log(bool p_reset_log); - void append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music); + void append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, bool p_self); /** * @brief Appends a message arriving from system to the IC chatlog. diff --git a/include/datatypes.h b/include/datatypes.h index 81e1254ba..70af94c25 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -30,6 +30,10 @@ class ChatRecord { return message; } + bool is_self() const + { + return self; + } bool is_system() const { return system; @@ -40,6 +44,12 @@ class ChatRecord } // set + void set_self(const bool p_enabled) + { + if (self == p_enabled) + return; + self = p_enabled; + } void set_system(bool p_enabled) { if (system == p_enabled) @@ -57,6 +67,7 @@ class ChatRecord QDateTime timestamp = QDateTime::currentDateTime(); QString name; QString message; + bool self = false; bool system = false; bool music = false; }; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 038544b51..b4bdf16a7 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -823,7 +823,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) if (is_system_speaking) append_system_text(f_showname, m_chatmessage[CMMessage]); else - append_ic_text(f_showname, m_chatmessage[CMMessage], false, false); + append_ic_text(f_showname, m_chatmessage[CMMessage], false, false, f_char_id == m_cid); if (ao_config->log_is_recording_enabled() && (!chatmessage_is_empty || !is_system_speaking)) { @@ -1143,6 +1143,15 @@ void Courtroom::update_ic_log(bool p_reset_log) showname_color = default_color; name_format.setForeground(showname_color); + QTextCharFormat selfname_format = name_format; + + { + QColor selfname_color = ao_app->get_color("ic_chatlog_selfname_color", fonts_ini); + if (selfname_color == not_found_color) + selfname_color = showname_color; + selfname_format.setForeground(selfname_color); + } + QTextCharFormat line_format = ui_ic_chatlog->currentCharFormat(); line_format.setFontWeight(QFont::Normal); QColor message_color = ao_app->get_color("ic_chatlog_message_color", fonts_ini); @@ -1179,6 +1188,7 @@ void Courtroom::update_ic_log(bool p_reset_log) { DR::ChatRecord record = m_ic_record_queue.takeFirst(); m_ic_record_list.append(record); + const QTextCharFormat l_record_name_format = record.is_self() ? selfname_format : name_format; if (record.get_message().trimmed().isEmpty() && !ao_config->log_display_empty_messages_enabled()) continue; @@ -1192,7 +1202,7 @@ void Courtroom::update_ic_log(bool p_reset_log) const QString record_end = (QString(QChar::LineFeed) + (chatlog_newline ? QString(QChar::LineFeed) : "")); if (ao_config->log_display_timestamp_enabled()) - cursor.insertText(QString("[%1] ").arg(record.get_timestamp().toString("hh:mm")), name_format); + cursor.insertText(QString("[%1] ").arg(record.get_timestamp().toString("hh:mm")), l_record_name_format); if (record.is_system()) { @@ -1207,7 +1217,7 @@ void Courtroom::update_ic_log(bool p_reset_log) separator = ": "; else separator = " "; - cursor.insertText(record.get_name() + separator, name_format); + cursor.insertText(record.get_name() + separator, l_record_name_format); cursor.insertText(record.get_message() + record_end, line_format); } } @@ -1283,7 +1293,7 @@ void Courtroom::update_ic_log(bool p_reset_log) } } -void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music) +void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, bool p_self) { if (p_name.trimmed().isEmpty()) p_name = "Anonymous"; @@ -1294,6 +1304,7 @@ void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bo DR::ChatRecord new_record(p_name, p_line); new_record.set_music(p_music); new_record.set_system(p_system); + new_record.set_self(p_self); m_ic_record_queue.append(new_record); update_ic_log(false); } @@ -1303,7 +1314,7 @@ void Courtroom::append_system_text(QString p_showname, QString p_line) if (chatmessage_is_empty) return; - append_ic_text(p_showname, p_line, true, false); + append_ic_text(p_showname, p_line, true, false, false); } void Courtroom::play_preanim() @@ -1613,7 +1624,7 @@ void Courtroom::handle_song(QStringList p_contents) return; QString f_song = p_contents.at(0); - int n_char = p_contents.at(1).toInt(); + int l_chr_id = p_contents.at(1).toInt(); for (auto &ext : audio_extensions()) { @@ -1626,7 +1637,7 @@ void Courtroom::handle_song(QStringList p_contents) } } - if (n_char < 0 || n_char >= char_list.size()) + if (l_chr_id < 0 || l_chr_id >= char_list.size()) { m_music_player->play(f_song); } @@ -1651,16 +1662,16 @@ void Courtroom::handle_song(QStringList p_contents) QString str_char; if (f_showname.isEmpty()) { - str_char = ao_app->get_showname(char_list.at(n_char).name); + str_char = ao_app->get_showname(char_list.at(l_chr_id).name); } else { str_char = f_showname; } - if (!mute_map.value(n_char)) + if (!mute_map.value(l_chr_id)) { - append_ic_text(str_char, "has played a song: " + f_song, false, true); + append_ic_text(str_char, "has played a song: " + f_song, false, true, l_chr_id == m_cid); if (ao_config->log_is_recording_enabled()) save_textlog(str_char + " has played a song: " + f_song); m_music_player->play(f_song); From a4a14ba7b1e61746e4f073da6ff52696f41f9e3f Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 18 Apr 2021 09:26:13 +0200 Subject: [PATCH 303/842] Added config panel setting * Added self-highlight setting checkbox in the configuration panel. --- include/aoconfig.h | 3 +++ include/aoconfigpanel.h | 1 + res/ui/config_panel.ui | 33 ++++++++++++++++++++------------- src/aoconfig.cpp | 18 +++++++++++++++++- src/aoconfigpanel.cpp | 5 +++++ src/courtroom.cpp | 1 + src/courtroom_widgets.cpp | 1 + 7 files changed, 48 insertions(+), 14 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 8e3298718..2cb4779cf 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -37,6 +37,7 @@ class AOConfig : public QObject int chat_tick_interval() const; int log_max_lines() const; bool log_display_timestamp_enabled() const; + bool log_display_self_highlight_enabled() const; bool log_display_empty_messages_enabled() const; bool log_is_topdown_enabled() const; bool log_format_use_newline_enabled() const; @@ -79,6 +80,7 @@ public slots: void set_chat_tick_interval(int p_number); void set_log_max_lines(int p_number); void set_log_display_timestamp(bool p_enabled); + void set_log_display_self_highlight(bool p_enabled); void set_log_display_empty_messages(bool p_enabled); void set_log_is_topdown(bool p_enabled); void set_log_format_use_newline(bool p_enabled); @@ -117,6 +119,7 @@ public slots: void chat_tick_interval_changed(int); void log_max_lines_changed(int); void log_display_timestamp_changed(bool); + void log_display_self_highlight_changed(bool); void log_display_empty_messages_changed(bool); void log_is_topdown_changed(bool); void log_format_use_newline_changed(bool); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 52ff1c51b..84f139b43 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -91,6 +91,7 @@ private slots: // IC Chatlog QSpinBox *w_log_max_lines = nullptr; QCheckBox *w_log_display_timestamp = nullptr; + QCheckBox *w_log_display_self_highlight = nullptr; QCheckBox *w_log_format_use_newline = nullptr; QCheckBox *w_log_display_empty_messages = nullptr; QCheckBox *w_log_display_music_switch = nullptr; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 4e976a6ce..e926555ba 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 3 + 2 @@ -364,23 +364,14 @@ Extra newlines are - Display Timestamp + Display timestamp - - - - 0 - 0 - - - - Extra newlines are - + - Format use newline + Display self-identification highlight @@ -413,6 +404,22 @@ + + + + + 0 + 0 + + + + <html><head/><body><p>Extra newlines are added between the name and message.</p></body></html> + + + Format use newline + + + diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index ef8aaacd6..02aa70e89 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -60,6 +60,7 @@ private slots: int chat_tick_interval; int log_max_lines; bool log_display_timestamp; + bool log_display_self_highlight; bool log_display_empty_messages; bool log_is_topdown; bool log_format_use_newline; @@ -126,7 +127,8 @@ void AOConfigPrivate::read_file() chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); log_max_lines = cfg.value("chatlog_limit", 200).toInt(); log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); - log_display_timestamp = cfg.value("chatlog_display_timestamp", false).toBool(); + log_display_timestamp = cfg.value("chatlog_display_timestamp", true).toBool(); + log_display_self_highlight = cfg.value("chatlog_display_self_highlight", true).toBool(); log_display_empty_messages = cfg.value("chatlog_display_empty_messages", false).toBool(); log_format_use_newline = cfg.value("chatlog_newline", false).toBool(); log_display_music_switch = cfg.value("music_change_log", true).toBool(); @@ -181,6 +183,7 @@ void AOConfigPrivate::save_file() cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("chatlog_limit", log_max_lines); cfg.setValue("chatlog_display_timestamp", log_display_timestamp); + cfg.setValue("chatlog_display_self_highlight", log_display_self_highlight); cfg.setValue("chatlog_newline", log_format_use_newline); cfg.setValue("chatlog_display_empty_messages", log_display_empty_messages); cfg.setValue("music_change_log", log_display_music_switch); @@ -349,6 +352,11 @@ bool AOConfig::log_display_timestamp_enabled() const return d->log_display_timestamp; } +bool AOConfig::log_display_self_highlight_enabled() const +{ + return d->log_display_self_highlight; +} + bool AOConfig::log_display_empty_messages_enabled() const { return d->log_display_empty_messages; @@ -566,6 +574,14 @@ void AOConfig::set_log_display_timestamp(bool p_enabled) d->invoke_signal("log_display_timestamp_changed", Q_ARG(bool, p_enabled)); } +void AOConfig::set_log_display_self_highlight(bool p_enabled) +{ + if (d->log_display_self_highlight == p_enabled) + return; + d->log_display_self_highlight = p_enabled; + d->invoke_signal("log_display_self_highlight_changed", Q_ARG(bool, p_enabled)); +} + void AOConfig::set_log_display_empty_messages(bool p_enabled) { if (d->log_display_empty_messages == p_enabled) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 89008fb92..8be0b7efa 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -51,6 +51,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // IC Chatlog w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); w_log_display_timestamp = AO_GUI_WIDGET(QCheckBox, "log_display_timestamp"); + w_log_display_self_highlight = AO_GUI_WIDGET(QCheckBox, "log_display_self_highlight"); w_log_format_use_newline = AO_GUI_WIDGET(QCheckBox, "log_format_use_newline"); w_log_display_empty_messages = AO_GUI_WIDGET(QCheckBox, "log_display_empty_messages"); w_log_display_music_switch = AO_GUI_WIDGET(QCheckBox, "log_display_music_switch"); @@ -102,6 +103,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // log connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); connect(m_config, SIGNAL(log_display_timestamp_changed(bool)), w_log_display_timestamp, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_self_highlight_changed(bool)), w_log_display_self_highlight, + SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_format_use_newline_changed(bool)), w_log_format_use_newline, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_display_empty_messages_changed(bool)), w_log_display_empty_messages, SLOT(setChecked(bool))); @@ -154,6 +157,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // out, log connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); connect(w_log_display_timestamp, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_timestamp(bool))); + connect(w_log_display_self_highlight, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_self_highlight(bool))); connect(w_log_format_use_newline, SIGNAL(toggled(bool)), m_config, SLOT(set_log_format_use_newline(bool))); connect(w_log_display_empty_messages, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_empty_messages(bool))); connect(w_log_display_music_switch, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_music_switch(bool))); @@ -203,6 +207,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) } w_log_display_timestamp->setChecked(m_config->log_display_timestamp_enabled()); + w_log_display_self_highlight->setChecked(m_config->log_display_self_highlight_enabled()); w_log_format_use_newline->setChecked(m_config->log_format_use_newline_enabled()); w_log_display_empty_messages->setChecked(m_config->log_display_empty_messages_enabled()); w_log_display_music_switch->setChecked(m_config->log_display_music_switch_enabled()); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index b4bdf16a7..4da021e62 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1145,6 +1145,7 @@ void Courtroom::update_ic_log(bool p_reset_log) QTextCharFormat selfname_format = name_format; + if (ao_config->log_display_self_highlight_enabled()) { QColor selfname_color = ao_app->get_color("ic_chatlog_selfname_color", fonts_ini); if (selfname_color == not_found_color) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index a71c0b4a7..bbd5b68e9 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -284,6 +284,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_display_timestamp_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_display_self_highlight_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_format_use_newline_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_display_empty_messages_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_display_music_switch_changed(bool)), this, SLOT(on_chat_config_changed())); From da46065fc94dcb1cb5ef8916f0b2ae2e8db66d22 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 18 Apr 2021 10:58:26 +0200 Subject: [PATCH 304/842] Removed erroneous tooltips --- res/ui/config_panel.ui | 6 ------ 1 file changed, 6 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index e926555ba..1503886b8 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -360,9 +360,6 @@ 0 - - Extra newlines are - Display timestamp @@ -383,9 +380,6 @@ 0 - - Extra newlines are - Display empty messages From 7fd9424b2afcfddc19a3d32176bbe9a2117a3865 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 18 Apr 2021 13:41:08 +0200 Subject: [PATCH 305/842] Clarified checkbox effect for ignoring suppression --- res/ui/config_panel.ui | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 4e976a6ce..10b63b61c 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -790,7 +790,7 @@ <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> - System + Ignore System true @@ -806,7 +806,7 @@ <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<span style=" color:#0000ff;">Suppress Background Audio</span>] is enabled and activated.</p></body></html> - Music + Ignore Music @@ -816,7 +816,7 @@ <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> - Effects + Ignore Effects @@ -826,7 +826,7 @@ <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> - Blips + Ignore Blips From c80279be7df28c3ad669451492a2722d9af9a4a4 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 18 Apr 2021 23:10:10 +0200 Subject: [PATCH 306/842] Added chatbox_self when self-highlight is enabled Resolves #87 --- src/courtroom.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 4da021e62..8293d92d0 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -895,13 +895,24 @@ void Courtroom::handle_chatmessage_2() // handles IC ui_vp_chatbox->hide(); ui_vp_showname_image->hide(); - QString chatbox = ao_app->get_chat(m_chatmessage[CMChrName]); + QString l_chatbox_name = ao_app->get_chat(m_chatmessage[CMChrName]); - if (chatbox == "") - ui_vp_chatbox->set_image("chatmed.png"); + if (l_chatbox_name.isEmpty()) + { + l_chatbox_name = "chatmed.png"; + + if (ao_config->log_display_self_highlight_enabled() && m_chatmessage[CMChrId].toInt() == m_cid) + { + const QString l_chatbox_self_name = "chatbox_self.png"; + if (file_exists(ao_app->find_theme_asset_path(l_chatbox_self_name))) + l_chatbox_name = l_chatbox_self_name; + } + + ui_vp_chatbox->set_image(l_chatbox_name); + } else { - QString chatbox_path = ao_app->get_base_path() + "misc/" + chatbox + ".png"; + QString chatbox_path = ao_app->get_base_path() + "misc/" + l_chatbox_name + ".png"; ui_vp_chatbox->set_image_from_path(chatbox_path); } From 754493a5ddc27af7c46d505a951a728d73966251 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 21 Apr 2021 18:09:28 -0400 Subject: [PATCH 307/842] Create draw_shout_button to draw shout buttons individually --- include/courtroom.h | 9 +++++++++ src/courtroom.cpp | 38 ++++++++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 1d81b88c2..00674a75a 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -773,6 +773,15 @@ private slots: */ void draw_shout_buttons(); + /** + * @brief Set the sprite of the given shout button, using the selected sprite if the button is selected. + * @param p_index Index of button to redraw. + * @details If a sprite cannot be found for the shout button, a regular push button is displayed for it with its + * shout name instead. If the index is out of bounds with respect to the number of shouts available, this method + * does nothing. + */ + void draw_shout_button(int p_index); + /** * @brief a general purpose function to toggle button selection */ diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 51a771bde..ee4151e5c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -719,8 +719,9 @@ void Courtroom::handle_acknowledged_ms() list_sfx(); ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors + int old_shout_state = m_shout_state; m_shout_state = 0; - draw_shout_buttons(); + draw_shout_button(old_shout_state - 1); m_effect_state = 0; draw_effect_buttons(); @@ -1952,24 +1953,32 @@ void Courtroom::on_area_list_double_clicked(QModelIndex p_model) void Courtroom::draw_shout_buttons() { for (int i = 0; i < ui_shouts.size(); ++i) - { - QString shout_file = shout_names.at(i) + ".png"; - ui_shouts[i]->set_image(shout_file); - if (ao_app->find_theme_asset_path(shout_file).isEmpty()) - ui_shouts[i]->setText(shout_names.at(i)); - else - ui_shouts[i]->setText(""); - } + draw_shout_button(i); +} - // Mark selected button as such - if (m_shout_state != 0 && ui_shouts.size() > 0) - ui_shouts.at(m_shout_state - 1)->set_image(shout_names.at(m_shout_state - 1) + "_selected.png"); +void Courtroom::draw_shout_button(int index) +{ + if (index < 0 || index >= ui_shouts.size()) + return; + + QString shout_file; + if (m_shout_state - 1 == index) + shout_file = shout_names.at(index) + "_selected.png"; + else + shout_file = shout_names.at(index) + ".png"; + + ui_shouts[index]->set_image(shout_file); + if (ao_app->find_theme_asset_path(shout_file).isEmpty()) + ui_shouts[index]->setText(shout_names.at(index)); + else + ui_shouts[index]->setText(""); } void Courtroom::on_shout_clicked() { AOButton *f_shout_button = static_cast(sender()); int f_shout_id = f_shout_button->property("shout_id").toInt(); + int old_m_shout_state = m_shout_state; // update based on current button selected if (f_shout_id == m_shout_state) @@ -1977,8 +1986,9 @@ void Courtroom::on_shout_clicked() else m_shout_state = f_shout_id; - draw_shout_buttons(); - + // Redraw old and new buttons + draw_shout_button(old_m_shout_state - 1); + draw_shout_button(m_shout_state - 1); ui_ic_chat_message->setFocus(); } From c6ac0539af0bb8d9f355b5ace7beac95fc06c234 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 21 Apr 2021 18:16:07 -0400 Subject: [PATCH 308/842] Remove needless testimony and cross examination splash button methods --- include/courtroom.h | 2 -- src/courtroom.cpp | 20 -------------------- 2 files changed, 22 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 00674a75a..ccf41966f 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -806,8 +806,6 @@ private slots: void on_text_color_changed(int p_color); - void on_witness_testimony_clicked(); - void on_cross_examination_clicked(); /** * @brief Set the sprites of the splash buttons. * diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ee4151e5c..821a19fcb 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2133,26 +2133,6 @@ void Courtroom::on_text_color_changed(int p_color) ui_ic_chat_message->setFocus(); } -void Courtroom::on_witness_testimony_clicked() -{ - if (is_client_muted) - return; - - ao_app->send_server_packet(new AOPacket("RT#testimony1#%")); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_cross_examination_clicked() -{ - if (is_client_muted) - return; - - ao_app->send_server_packet(new AOPacket("RT#testimony2#%")); - - ui_ic_chat_message->setFocus(); -} - void Courtroom::draw_judge_wtce_buttons() { for (int i = 0; i < wtce_names.size(); ++i) From 54776df56d529833babc6231b9e526f0f055c2ad Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 21 Apr 2021 18:39:57 -0400 Subject: [PATCH 309/842] Create draw_effect_button to draw effect buttons individually --- include/courtroom.h | 10 ++++++++++ src/courtroom.cpp | 48 +++++++++++++++++++++++++++++---------------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index ccf41966f..df765d946 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -795,6 +795,16 @@ private slots: * push button is displayed for it with its shout name instead. */ void draw_effect_buttons(); + + /** + * @brief Set the sprite of the given effect button, using the selected sprite if the button is selected. + * @param p_index Index of button to redraw. + * @details If a sprite cannot be found for the effect button, a regular push button is displayed for it with its + * effect name instead. If the index is out of bounds with respect to the number of effect available, this method + * does nothing. + */ + void draw_effect_button(int p_index); + void on_effect_button_clicked(); void on_mute_clicked(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 821a19fcb..1a898b1a1 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -719,12 +719,13 @@ void Courtroom::handle_acknowledged_ms() list_sfx(); ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors - int old_shout_state = m_shout_state; + int old_m_shout_state = m_shout_state; m_shout_state = 0; - draw_shout_button(old_shout_state - 1); + draw_shout_button(old_m_shout_state - 1); + int old_m_effect_state = m_effect_state; m_effect_state = 0; - draw_effect_buttons(); + draw_effect_button(old_m_effect_state - 1); m_wtce_current = 0; draw_judge_wtce_buttons(); @@ -1987,8 +1988,9 @@ void Courtroom::on_shout_clicked() m_shout_state = f_shout_id; // Redraw old and new buttons - draw_shout_button(old_m_shout_state - 1); draw_shout_button(m_shout_state - 1); + if (m_shout_state != old_m_shout_state) + draw_shout_button(old_m_shout_state - 1); ui_ic_chat_message->setFocus(); } @@ -2051,19 +2053,26 @@ void Courtroom::cycle_wtce(int p_delta) void Courtroom::draw_effect_buttons() { - for (int i = 0; i < effect_names.size(); ++i) - { - QString effect_file = effect_names.at(i) + ".png"; - ui_effects[i]->set_image(effect_file); - if (ao_app->find_theme_asset_path(effect_file).isEmpty()) - ui_effects[i]->setText(effect_names.at(i)); - else - ui_effects[i]->setText(""); - } + for (int i = 0; i < ui_effects.size(); ++i) + draw_effect_button(i); +} - // Mark selected button as such - if (m_effect_state != 0 && ui_effects.size() > 0) - ui_effects[m_effect_state - 1]->set_image(effect_names.at(m_effect_state - 1) + "_pressed.png"); +void Courtroom::draw_effect_button(int index) +{ + if (index < 0 || index >= ui_effects.size()) + return; + + QString effect_file; + if (m_effect_state - 1 == index) + effect_file = effect_names.at(index) + "_pressed.png"; + else + effect_file = effect_names.at(index) + ".png"; + + ui_effects[index]->set_image(effect_file); + if (ao_app->find_theme_asset_path(effect_file).isEmpty()) + ui_effects[index]->setText(effect_names.at(index)); + else + ui_effects[index]->setText(""); } void Courtroom::on_effect_button_clicked() @@ -2071,12 +2080,17 @@ void Courtroom::on_effect_button_clicked() AOButton *f_button = static_cast(this->sender()); int f_effect_id = f_button->property("effect_id").toInt(); + int old_m_effect_state = m_effect_state; + if (m_effect_state == f_effect_id) m_effect_state = 0; else m_effect_state = f_effect_id; - draw_effect_buttons(); + // Redraw old and new buttons + draw_effect_button(m_effect_state - 1); + if (m_effect_state != old_m_effect_state) + draw_effect_button(old_m_effect_state - 1); ui_ic_chat_message->setFocus(); } From 507c66aa43a009c7a055d8abd2a67eb0ceb8ad61 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 21 Apr 2021 18:59:31 -0400 Subject: [PATCH 310/842] Remove redraw splash buttons order from ackMS (not needed, as they are not continuously selectable) --- include/courtroom.h | 5 +++-- src/courtroom.cpp | 3 --- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index df765d946..743ca9fd7 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -800,7 +800,7 @@ private slots: * @brief Set the sprite of the given effect button, using the selected sprite if the button is selected. * @param p_index Index of button to redraw. * @details If a sprite cannot be found for the effect button, a regular push button is displayed for it with its - * effect name instead. If the index is out of bounds with respect to the number of effect available, this method + * effect name instead. If the index is out of bounds with respect to the number of effects available, this method * does nothing. */ void draw_effect_button(int p_index); @@ -819,10 +819,11 @@ private slots: /** * @brief Set the sprites of the splash buttons. * - * @details If a sprite cannot be found for a shout button, a regular + * @details If a sprite cannot be found for a splash button, a regular * push button is displayed for it with its shout name instead. */ void draw_judge_wtce_buttons(); + void on_wtce_clicked(); void on_change_character_clicked(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 1a898b1a1..1242b25cc 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -727,9 +727,6 @@ void Courtroom::handle_acknowledged_ms() m_effect_state = 0; draw_effect_button(old_m_effect_state - 1); - m_wtce_current = 0; - draw_judge_wtce_buttons(); - is_presenting_evidence = false; ui_evidence_present->set_image("present_disabled.png"); From a542555a50c759e5fbd73428554dd3f043235796 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 21 Apr 2021 21:19:10 -0400 Subject: [PATCH 311/842] Set evidence presented button only if needed --- src/courtroom.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 1242b25cc..6936ce65e 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -727,9 +727,11 @@ void Courtroom::handle_acknowledged_ms() m_effect_state = 0; draw_effect_button(old_m_effect_state - 1); - is_presenting_evidence = false; - - ui_evidence_present->set_image("present_disabled.png"); + if (is_presenting_evidence) + { + is_presenting_evidence = false; + ui_evidence_present->set_image("present_disabled.png"); + } } void Courtroom::handle_chatmessage(QStringList p_contents) From df97de15ea8a161780922fde3e982182f7b3f7e9 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 21 Apr 2021 21:45:13 -0400 Subject: [PATCH 312/842] Add prefixes to variables/attributes that need it --- include/courtroom.h | 6 +++--- include/drtextedit.h | 4 ++-- src/courtroom.cpp | 14 +++++++------- src/drtextedit.cpp | 10 +++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 1d81b88c2..c36d4179b 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -401,9 +401,9 @@ class Courtroom : public QMainWindow bool m_msg_is_first_person = false; // Cached values for chat_tick - bool chatbox_message_outline = false; - bool chatbox_message_enable_highlighting = false; - QVector chatbox_message_highlight_colors; + bool m_chatbox_message_outline = false; + bool m_chatbox_message_enable_highlighting = false; + QVector m_chatbox_message_highlight_colors; // cid and this may differ in cases of ini-editing QString current_char; diff --git a/include/drtextedit.h b/include/drtextedit.h index 0a456a9fa..1390e1ef2 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -34,8 +34,8 @@ class DRTextEdit : public QTextEdit Status m_status = Status::Done; bool m_auto_align = true; - int current_document_blocks = 0; - int current_document_height = 0; + int m_current_document_blocks = 0; + int m_current_document_height = 0; void refresh_horizontal_alignment(); void refresh_vertical_alignment(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 51a771bde..7cccd0537 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1366,9 +1366,9 @@ void Courtroom::setup_chat() blip_pos = 0; // Cache these so chat_tick performs better - chatbox_message_outline = (ao_app->get_font_property("message_outline", fonts_ini) == 1); - chatbox_message_enable_highlighting = (ao_app->read_theme_ini_bool("enable_highlighting", cc_config_ini)); - chatbox_message_highlight_colors = ao_app->get_highlight_colors(); + m_chatbox_message_outline = (ao_app->get_font_property("message_outline", fonts_ini) == 1); + m_chatbox_message_enable_highlighting = (ao_app->read_theme_ini_bool("enable_highlighting", cc_config_ini)); + m_chatbox_message_highlight_colors = ao_app->get_highlight_colors(); QString f_gender = ao_app->get_gender(m_chatmessage[CMChrName]); @@ -1384,7 +1384,7 @@ void Courtroom::chat_tick() // note: this is called fairly often(every 60 ms when char is talking) // do not perform heavy operations here QTextCharFormat vp_message_format = ui_vp_message->currentCharFormat(); - if (chatbox_message_outline) + if (m_chatbox_message_outline) vp_message_format.setTextOutline(QPen(Qt::black, 1)); else vp_message_format.setTextOutline(Qt::NoPen); @@ -1443,7 +1443,7 @@ void Courtroom::chat_tick() ui_vp_message->textCursor().insertText(f_character, vp_message_format); } - else if (chatbox_message_enable_highlighting) + else if (m_chatbox_message_enable_highlighting) { bool highlight_found = false; bool render_character = true; @@ -1453,7 +1453,7 @@ void Courtroom::chat_tick() if (m_color_stack.isEmpty()) m_color_stack.push(""); - for (const auto &col : chatbox_message_highlight_colors) + for (const auto &col : m_chatbox_message_highlight_colors) { if (f_character == col[0][0] && m_string_color != col[1]) { @@ -1477,7 +1477,7 @@ void Courtroom::chat_tick() QString m_future_string_color = m_string_color; - for (const auto &col : chatbox_message_highlight_colors) + for (const auto &col : m_chatbox_message_highlight_colors) { if (f_character == col[0][1] && !highlight_found) { diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index 832a908fa..b3db344d7 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -110,13 +110,13 @@ void DRTextEdit::refresh_horizontal_alignment() if (document()->toPlainText().isEmpty()) { // Qt is very special and does not set this to 0 for empty documents. - current_document_blocks = 0; + m_current_document_blocks = 0; // We also don't need to do any adjusting for empty documents, so return immediately return; } // If we have not changed the number of blocks in the document with this new incoming text change, // We do not need to update anything, so we exit early. - if (new_document_blocks == current_document_blocks) + if (new_document_blocks == m_current_document_blocks) return; // Otherwise, we have changed the number of blocks. By induction only the current block needs to be @@ -132,16 +132,16 @@ void DRTextEdit::refresh_vertical_alignment() if (document()->toPlainText().isEmpty()) { // Qt is very special and does not set this to 0 for empty documents. - current_document_height = 0; + m_current_document_height = 0; // We also don't need to do any adjusting for empty documents, so return immediately return; } // If we have not changed the document height with this new incoming text change, // We do not need to update anything, so we exit early. - if (new_document_height == current_document_height) + if (new_document_height == m_current_document_height) return; - current_document_height = new_document_height; + m_current_document_height = new_document_height; // The way we will simulate vertical alignment is by adjusting the top margin to simulate // center alignment, or bottom alignment. From 4d2b0d339608366bf5cf697d21cb7daea3178490 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 22 Apr 2021 00:02:19 -0400 Subject: [PATCH 313/842] Remove override_color setting for shownames --- include/courtroom.h | 7 +------ src/courtroom.cpp | 7 ------- src/courtroom_widgets.cpp | 29 +++++++---------------------- 3 files changed, 8 insertions(+), 35 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index c36d4179b..dcebecbc0 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -96,13 +96,8 @@ class Courtroom : public QMainWindow void set_widgets(); // sets font properties based on theme ini files void set_font(QWidget *widget, QString p_identifier); - // same as above, but use override color as color if it is not an empty - // string, otherwise use normal logic for color of set_font - void set_font(QWidget *widget, QString p_identifier, QString override_color); - // sets font properties for DRTextEdit (same as above but also text outline) + // sets font properties for DRTextEdit (same as above but also text outline, and alignments) void set_drtextedit_font(DRTextEdit *widget, QString p_identifier); - // same as second set_font but for drtextedit - void set_drtextedit_font(DRTextEdit *widget, QString p_identifier, QString override_color); // helper function that calls above function on the relevant widgets void set_fonts(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 7cccd0537..bad868a50 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -803,8 +803,6 @@ void Courtroom::handle_chatmessage(QStringList p_contents) f_showname = m_chatmessage[CMShowName]; } - QString f_message = f_showname + ": " + m_chatmessage[CMMessage] + "\n"; - m_effects_player->stop_all(); text_state = 0; @@ -880,11 +878,6 @@ void Courtroom::handle_chatmessage_2() // handles IC f_showname = m_chatmessage[CMShowName]; } - // Check if char.ini has color property, which overrides the theme's default - // showname color - QString f_color = ao_app->read_char_ini(real_name, "color", "[Options]", "[Time]"); - set_drtextedit_font(ui_vp_showname, "showname", f_color); - ui_vp_showname->setText(f_showname); ui_vp_message->clear(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 96bbb9c88..ff8205527 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1386,11 +1386,6 @@ void Courtroom::set_dropdowns() } void Courtroom::set_font(QWidget *widget, QString p_identifier) -{ - set_font(widget, p_identifier, ""); -} - -void Courtroom::set_font(QWidget *widget, QString p_identifier, QString override_color) { QString design_file = fonts_ini; QString class_name = widget->metaObject()->className(); @@ -1409,32 +1404,22 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, QString override } widget->setFont(QFont(font_name, f_weight)); - if (override_color.isEmpty()) - { - QString color = ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); - if (color.isEmpty()) - color = "255, 255, 255"; - override_color = "rgba(" + color + ", 255)"; - } + QString font_color = ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); + if (font_color.isEmpty()) + font_color = "255, 255, 255"; + QString color = "rgba(" + font_color + ", 255)"; int bold = ao_app->get_font_property(p_identifier + "_bold", design_file); QString is_bold = (bold == 1 ? "bold" : ""); - QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: " + override_color + - ";\n" - "font: " + - is_bold + "; }"; + QString style_sheet_string = class_name + " { " + "background-color: rgba(0, 0, 0, 0);\n" + "color: " + color + + ";\n" + "font: " + is_bold + ";" + " }"; widget->setStyleSheet(style_sheet_string); } void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) { - set_drtextedit_font(widget, p_identifier, ""); -} - -void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier, QString override_color) -{ - set_font(widget, p_identifier, override_color); + set_font(widget, p_identifier); // Do outlines bool outline = (ao_app->get_font_property(p_identifier + "_outline", fonts_ini) == 1); widget->set_outline(outline); From 9c0bf641a8d24860b83a2c1eb697f7d32c1854c1 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 7 May 2021 14:35:30 -0400 Subject: [PATCH 314/842] Make showname box gray by default+Make placeholder text be char.ini showname --- include/aoapplication.h | 2 ++ src/courtroom.cpp | 2 ++ src/courtroom_widgets.cpp | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index e8c1e78c5..b8b7d79b4 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -51,6 +51,8 @@ class AOApplication : public QApplication bool ackMS_enabled = false; #endif + bool m_FL_showname_enabled = false; + ///////////////loading info/////////////////// // player number, it's hardly used but might be needed for some old servers diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ad403ce75..ff3cfd608 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -53,6 +53,7 @@ void Courtroom::enter_courtroom(int p_cid) { ao_app->discord->clear_character_name(); f_char = ""; + ui_ic_chat_name->setPlaceholderText(""); } else { @@ -61,6 +62,7 @@ void Courtroom::enter_courtroom(int p_cid) if (showname_char.isEmpty()) showname_char = f_char; ao_app->discord->set_character_name(showname_char); + ui_ic_chat_name->setPlaceholderText(showname_char); } current_char = f_char; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 34a12f2f9..8bf85ec58 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -624,7 +624,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_sfx_list, "sfx_list"); set_size_and_pos(ui_ic_chat_name, "ic_chat_name"); - ui_ic_chat_name->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); + ui_ic_chat_name->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message"); ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); From 69e79c371d9cebcc83d21b79ed934d31424f5235 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 7 May 2021 18:25:31 -0400 Subject: [PATCH 315/842] Add fill iniedit showname box option if empty showname box --- include/aoapplication.h | 4 ++++ include/aoconfig.h | 14 ++++++++++++-- include/aoconfigpanel.h | 3 ++- include/courtroom.h | 4 ++++ res/ui/config_panel.ui | 26 ++++++++++++++++++++------ src/aoapplication.cpp | 6 ++++++ src/aoconfig.cpp | 18 +++++++++++++++++- src/aoconfigpanel.cpp | 27 ++++++++++++++++++++++----- src/courtroom.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/courtroom_widgets.cpp | 1 + 10 files changed, 124 insertions(+), 15 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index b8b7d79b4..c241af52f 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -250,6 +250,10 @@ class AOApplication : public QApplication // callwords) should actually tigger a server alert or not bool get_server_alerts_enabled(); + // returns whether if playing an iniedited character, blanking out the showname box fills it with the + // iniedited shownamed + bool get_fill_iniedit_showname_enabled(); + // returns whatever preanimations should always play or not bool get_always_pre_enabled(); diff --git a/include/aoconfig.h b/include/aoconfig.h index 72221fbbb..a024c311b 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -34,6 +34,7 @@ class AOConfig : public QObject bool manual_gamemode_enabled() const; QString timeofday() const; bool manual_timeofday_enabled() const; + bool fill_iniedit_showname_enabled() const; bool always_pre_enabled() const; int chat_tick_interval() const; int log_max_lines() const; @@ -73,12 +74,13 @@ public slots: void set_discord_presence(const bool p_enabled); void set_discord_hide_server(const bool p_enabled); void set_discord_hide_character(const bool p_enabled); - void set_always_pre(bool p_enabled); void set_theme(QString p_string); void set_gamemode(QString p_string); void set_manual_gamemode(bool p_enabled); void set_timeofday(QString p_string); void set_manual_timeofday(bool p_enabled); + void set_fill_iniedit_showname(bool p_enabled); + void set_always_pre(bool p_enabled); void set_chat_tick_interval(int p_number); void set_log_max_lines(int p_number); void set_log_display_timestamp(bool p_enabled); @@ -105,21 +107,29 @@ public slots: // signals signals: + // meta void autosave_changed(bool); + + // general void username_changed(QString); - void showname_changed(QString); void callwords_changed(QString); void server_alerts_changed(bool); void discord_presence_changed(bool); void discord_hide_server_changed(bool); void discord_hide_character_changed(bool); + + // game void theme_changed(QString); void gamemode_changed(QString); void manual_gamemode_changed(bool); void timeofday_changed(QString); void manual_timeofday_changed(bool); + void showname_changed(QString); + void fill_iniedit_showname_changed(bool); void always_pre_changed(bool); void chat_tick_interval_changed(int); + + // log void log_max_lines_changed(int); void log_display_timestamp_changed(bool); void log_display_self_highlight_changed(bool); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 1221da8e2..fcfad85c8 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -71,7 +71,6 @@ private slots: // general QLineEdit *w_username = nullptr; - QLineEdit *w_showname = nullptr; QLineEdit *w_callwords = nullptr; QCheckBox *w_server_alerts = nullptr; @@ -86,6 +85,8 @@ private slots: QCheckBox *w_manual_gamemode = nullptr; QComboBox *w_timeofday = nullptr; QCheckBox *w_manual_timeofday = nullptr; + QLineEdit *w_showname = nullptr; + QCheckBox *w_fill_iniedit_showname = nullptr; QCheckBox *w_always_pre = nullptr; QSpinBox *w_chat_tick_interval = nullptr; diff --git a/include/courtroom.h b/include/courtroom.h index 43c155e58..7802d8395 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -693,6 +693,9 @@ class Courtroom : public QMainWindow void set_char_rpc(); + bool is_self_iniedited(); + bool check_fill_iniedit_showname(); + public slots: void objection_done(); void preanim_done(); @@ -716,6 +719,7 @@ private slots: void on_chat_name_editing_finished(); void on_chat_return_pressed(); void on_chat_config_changed(); + void on_fill_iniedit_showname_changed(); void on_ooc_name_editing_finished(); void on_ooc_return_pressed(); diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 45537daf4..421a6195e 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 0 + 1 @@ -263,14 +263,14 @@ - + Chat-tick interval: - + ms @@ -283,7 +283,7 @@ - + Always-anim: @@ -293,7 +293,7 @@ - + @@ -309,7 +309,7 @@ - + Qt::Vertical @@ -322,6 +322,20 @@ + + + + Fill iniedit showname: + + + + + + + + + + diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index f67c4f113..f43249488 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -228,6 +228,11 @@ bool AOApplication::get_manual_timeofday_enabled() return config->manual_timeofday_enabled(); } +bool AOApplication::get_fill_iniedit_showname_enabled() +{ + return config->fill_iniedit_showname_enabled(); +} + bool AOApplication::get_always_pre_enabled() { return config->always_pre_enabled(); @@ -237,6 +242,7 @@ bool AOApplication::get_first_person_enabled() { return config->get_bool("first_person", false); } + bool AOApplication::get_chatlog_scrolldown() { return config->log_is_topdown_enabled(); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index cbd656b77..31e6315ed 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -46,7 +46,6 @@ private slots: // data bool autosave; QString username; - QString showname; QString callwords; bool server_alerts; bool discord_presence = false; @@ -57,6 +56,8 @@ private slots: bool manual_gamemode; QString timeofday; bool manual_timeofday; + QString showname; + bool fill_iniedit_showname; bool always_pre; int chat_tick_interval; int log_max_lines; @@ -125,6 +126,7 @@ void AOConfigPrivate::read_file() manual_gamemode = cfg.value("manual_gamemode", false).toBool(); timeofday = cfg.value("timeofday", "").toString(); manual_timeofday = cfg.value("manual_timeofday", false).toBool(); + fill_iniedit_showname = cfg.value("fill_iniedit_showname", true).toBool(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); log_max_lines = cfg.value("chatlog_limit", 200).toInt(); @@ -182,6 +184,7 @@ void AOConfigPrivate::save_file() cfg.setValue("manual_gamemode", manual_gamemode); cfg.setValue("timeofday", timeofday); cfg.setValue("manual_timeofday", manual_timeofday); + cfg.setValue("fill_iniedit_showname", fill_iniedit_showname); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("chatlog_limit", log_max_lines); @@ -340,6 +343,11 @@ bool AOConfig::manual_timeofday_enabled() const return d->manual_timeofday; } +bool AOConfig::fill_iniedit_showname_enabled() const +{ + return d->fill_iniedit_showname; +} + bool AOConfig::always_pre_enabled() const { return d->always_pre; @@ -558,6 +566,14 @@ void AOConfig::set_manual_timeofday(bool p_enabled) d->invoke_signal("manual_timeofday_changed", Q_ARG(bool, p_enabled)); } +void AOConfig::set_fill_iniedit_showname(bool p_enabled) +{ + if (d->fill_iniedit_showname == p_enabled) + return; + d->fill_iniedit_showname = p_enabled; + d->invoke_signal("fill_iniedit_showname_changed", Q_ARG(bool, p_enabled)); +} + void AOConfig::set_always_pre(bool p_enabled) { if (d->always_pre == p_enabled) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index fd009e964..5c59d3298 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -32,7 +32,6 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // general w_username = AO_GUI_WIDGET(QLineEdit, "username"); - w_showname = AO_GUI_WIDGET(QLineEdit, "showname"); w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); w_discord_presence = AO_GUI_WIDGET(QGroupBox, "discord_presence"); @@ -46,6 +45,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_manual_gamemode = AO_GUI_WIDGET(QCheckBox, "manual_gamemode"); w_timeofday = AO_GUI_WIDGET(QComboBox, "timeofday"); w_manual_timeofday = AO_GUI_WIDGET(QCheckBox, "manual_timeofday"); + w_showname = AO_GUI_WIDGET(QLineEdit, "showname"); + w_fill_iniedit_showname = AO_GUI_WIDGET(QCheckBox, "fill_iniedit_showname"); w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); @@ -86,19 +87,25 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) refresh_timeofday_list(); // input + // meta connect(m_config, SIGNAL(autosave_changed(bool)), w_autosave, SLOT(setChecked(bool))); + + // general connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); - connect(m_config, SIGNAL(showname_changed(QString)), w_showname, SLOT(setText(QString))); connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); connect(m_config, SIGNAL(discord_presence_changed(bool)), w_discord_presence, SLOT(setChecked(bool))); connect(m_config, SIGNAL(discord_hide_server_changed(bool)), w_discord_hide_server, SLOT(setChecked(bool))); connect(m_config, SIGNAL(discord_hide_character_changed(bool)), w_discord_hide_character, SLOT(setChecked(bool))); + + // game connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, SLOT(setChecked(bool))); connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(showname_changed(QString)), w_showname, SLOT(setText(QString))); + connect(m_config, SIGNAL(fill_iniedit_showname_changed(bool)), w_fill_iniedit_showname, SLOT(setChecked(bool))); connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); @@ -136,24 +143,28 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) SLOT(on_favorite_audio_device_changed(DRAudioDevice))); // output + // meta connect(w_close, SIGNAL(clicked()), this, SLOT(close())); connect(w_save, SIGNAL(clicked()), m_config, SLOT(save_file())); connect(w_autosave, SIGNAL(toggled(bool)), m_config, SLOT(set_autosave(bool))); + + // general connect(w_username, SIGNAL(editingFinished()), this, SLOT(username_editing_finished())); - connect(w_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); connect(w_callwords, SIGNAL(editingFinished()), this, SLOT(callwords_editing_finished())); connect(w_server_alerts, SIGNAL(toggled(bool)), m_config, SLOT(set_server_alerts(bool))); - connect(w_discord_presence, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_presence(bool))); connect(w_discord_hide_server, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_server(const bool))); connect(w_discord_hide_character, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_character(const bool))); + // game connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); connect(w_manual_gamemode, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_gamemode(bool))); connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); connect(w_manual_timeofday, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_timeofday(bool))); + connect(w_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); + connect(w_fill_iniedit_showname, SIGNAL(toggled(bool)), m_config, SLOT(set_fill_iniedit_showname(bool))); connect(w_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); @@ -185,16 +196,22 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_blank_blips, SIGNAL(toggled(bool)), m_config, SLOT(set_blank_blips(bool))); // set values + // meta w_autosave->setChecked(m_config->autosave()); + + // general w_username->setText(m_config->username()); - w_showname->setText(m_config->showname()); w_callwords->setText(m_config->callwords()); w_server_alerts->setChecked(m_config->server_alerts_enabled()); + + // game w_theme->setCurrentText(m_config->theme()); w_gamemode->setCurrentText(m_config->gamemode()); w_manual_gamemode->setChecked(m_config->manual_gamemode_enabled()); w_timeofday->setCurrentText(m_config->timeofday()); w_manual_timeofday->setChecked(m_config->manual_timeofday_enabled()); + w_showname->setText(m_config->showname()); + w_fill_iniedit_showname->setChecked(m_config->fill_iniedit_showname_enabled()); w_always_pre->setChecked(m_config->always_pre_enabled()); w_chat_tick_interval->setValue(m_config->chat_tick_interval()); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ff3cfd608..5f76558e8 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -149,6 +149,7 @@ void Courtroom::enter_courtroom(int p_cid) set_widget_names(); set_widget_layers(); + check_fill_iniedit_showname(); } void Courtroom::done_received() @@ -580,6 +581,8 @@ void Courtroom::append_server_chatmessage(QString p_name, QString p_message) void Courtroom::on_showname_changed() { + if (check_fill_iniedit_showname()) + return; const QString l_showname = ao_config->showname(); send_showname_packet(l_showname); ui_ic_chat_name->setText(l_showname); @@ -593,6 +596,39 @@ void Courtroom::send_showname_packet(QString p_showname) send_ooc_packet(ao_config->username(), QString("/showname %1").arg(p_showname)); } +bool Courtroom::is_self_iniedited() +{ + const QString l_selected_folder_name = char_list.at(m_cid).name; + return !(l_selected_folder_name == current_char); +} + +void Courtroom::on_fill_iniedit_showname_changed() +{ + check_fill_iniedit_showname(); +} + +bool Courtroom::check_fill_iniedit_showname() +{ + // We only care about the case where all of the following are true: + // 1. Player has checked Fill Iniedit Showname + // 2. Player is iniedited + // 3. Player has an empty showname box + const bool l_fill = ao_config->fill_iniedit_showname_enabled(); + if (!l_fill) + return false; + + if (!is_self_iniedited()) + return false; + + if (!ao_config->showname().isEmpty()) + return false; + + QString l_char = ao_app->get_char_name(char_list.at(m_cid).name); + QString l_showname = ao_app->get_showname(l_char); + ao_config->set_showname(l_showname); + return true; +} + void Courtroom::on_chat_return_pressed() { if (ui_ic_chat_message->text() == "" || is_client_muted) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 8bf85ec58..8544e15fd 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -259,6 +259,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(showname_changed(QString)), this, SLOT(on_showname_changed())); connect(ui_ic_chat_name, SIGNAL(editingFinished()), this, SLOT(on_chat_name_editing_finished())); connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_chat_return_pressed())); + connect(ao_config, SIGNAL(fill_iniedit_showname_changed(bool)), this, SLOT(on_fill_iniedit_showname_changed())); connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); connect(ui_ooc_chat_name, SIGNAL(editingFinished()), this, SLOT(on_ooc_name_editing_finished())); From 277ed2392eaee6f52ca6bc454e35a61741f030d9 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 7 May 2021 20:22:30 -0400 Subject: [PATCH 316/842] Implement SN packet --- include/courtroom.h | 7 +++++++ src/courtroom.cpp | 26 +++++++++++++++++++++++--- src/packet_distribution.cpp | 35 +++++++++++++++++++++++++++++------ 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 7802d8395..4a78e350d 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -181,6 +181,13 @@ class Courtroom : public QMainWindow return current_char; } + // Update the last showname set by the client. Returns true if p_showname is different from the current last showname + // set, false otherwise. + bool update_last_showname(QString p_showname); + + // Set the showname of the client + void set_showname(QString p_showname); + // properly sets up some varibles: resets user state void enter_courtroom(int p_cid); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 5f76558e8..490b6f62c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -588,12 +588,26 @@ void Courtroom::on_showname_changed() ui_ic_chat_name->setText(l_showname); } +bool Courtroom::update_last_showname(QString p_showname) +{ + if (p_showname == m_last_showname) + return false; + m_last_showname = p_showname; + return true; +} + void Courtroom::send_showname_packet(QString p_showname) { if (p_showname == m_last_showname) return; m_last_showname = p_showname; - send_ooc_packet(ao_config->username(), QString("/showname %1").arg(p_showname)); + if (ao_app->m_FL_showname_enabled) + { + QStringList packet_contents = {p_showname}; + ao_app->send_server_packet(new AOPacket("SN", packet_contents)); + } + else + send_ooc_packet(ao_config->username(), QString("/showname %1").arg(p_showname)); } bool Courtroom::is_self_iniedited() @@ -625,7 +639,8 @@ bool Courtroom::check_fill_iniedit_showname() QString l_char = ao_app->get_char_name(char_list.at(m_cid).name); QString l_showname = ao_app->get_showname(l_char); - ao_config->set_showname(l_showname); + + set_showname(l_showname); return true; } @@ -1851,7 +1866,12 @@ void Courtroom::mod_called(QString p_ip) void Courtroom::on_chat_name_editing_finished() { - ao_config->set_showname(ui_ic_chat_name->text()); + set_showname(ui_ic_chat_name->text()); +} + +void Courtroom::set_showname(QString p_showname) +{ + ao_config->set_showname(p_showname); } void Courtroom::on_ooc_name_editing_finished() diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 5c76f2be7..642a7278e 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -142,6 +142,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release ackMS_enabled = false; #endif + m_FL_showname_enabled = false; AOPacket *hi_packet = new AOPacket("HI#" + f_hdid + "#%"); send_server_packet(hi_packet); @@ -169,6 +170,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release if (f_packet.contains("ackMS", Qt::CaseInsensitive)) ackMS_enabled = true; + if (f_packet.contains("showname", Qt::CaseInsensitive)) + m_FL_showname_enabled = true; #endif } else if (header == "PN") @@ -661,7 +664,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Timer resume if (f_contents.size() != 1) goto end; - + if (!courtroom_constructed) + goto end; int timer_id = f_contents.at(0).toInt(); w_courtroom->resume_timer(timer_id); } @@ -670,7 +674,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Timer set time if (f_contents.size() != 2) goto end; - + if (!courtroom_constructed) + goto end; int timer_id = f_contents.at(0).toInt(); int new_time = f_contents.at(1).toInt(); w_courtroom->set_timer_time(timer_id, new_time); @@ -680,7 +685,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Timer set timeStep length if (f_contents.size() != 2) goto end; - + if (!courtroom_constructed) + goto end; int timer_id = f_contents.at(0).toInt(); int timestep_length = f_contents.at(1).toInt(); w_courtroom->set_timer_timestep(timer_id, timestep_length); @@ -690,7 +696,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Timer set Firing interval if (f_contents.size() != 2) goto end; - + if (!courtroom_constructed) + goto end; int timer_id = f_contents.at(0).toInt(); int firing_interval = f_contents.at(1).toInt(); w_courtroom->set_timer_firing(timer_id, firing_interval); @@ -700,7 +707,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Timer pause if (f_contents.size() != 1) goto end; - + if (!courtroom_constructed) + goto end; int timer_id = f_contents.at(0).toInt(); w_courtroom->pause_timer(timer_id); } @@ -709,9 +717,24 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Set position if (f_contents.size() != 1) goto end; - + if (!courtroom_constructed) + goto end; w_courtroom->set_character_position(f_contents.at(0)); } + else if (header == "SN") + { + // Server-set showname. + // This has priority over user-set showname. + if (f_contents.size() != 1) + goto end; + if (!courtroom_constructed) + goto end; + QString l_showname = f_contents.at(0); + // By updating now, we prevent the client from sending an SN back when we later on go ahead and modify the + // showname box. + w_courtroom->update_last_showname(l_showname); + w_courtroom->set_showname(l_showname); + } end: From 75fdc52ac59329b6b8731b15a161396f2dcd3f7f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 7 May 2021 20:37:41 -0400 Subject: [PATCH 317/842] Fix out of bounds error if switching to spectator --- src/courtroom.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 490b6f62c..f4eb79bcb 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -612,6 +612,9 @@ void Courtroom::send_showname_packet(QString p_showname) bool Courtroom::is_self_iniedited() { + // Check if spectator first. Spectators are never iniedited + if (m_cid == -1) + return false; const QString l_selected_folder_name = char_list.at(m_cid).name; return !(l_selected_folder_name == current_char); } @@ -625,7 +628,7 @@ bool Courtroom::check_fill_iniedit_showname() { // We only care about the case where all of the following are true: // 1. Player has checked Fill Iniedit Showname - // 2. Player is iniedited + // 2. Player is iniedited (and thus not spectator) // 3. Player has an empty showname box const bool l_fill = ao_config->fill_iniedit_showname_enabled(); if (!l_fill) From 25aa06f594176a2d16bdf456f3826fb41b581452 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 10 May 2021 15:43:35 +0200 Subject: [PATCH 318/842] Initial implementation --- src/courtroom.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 8293d92d0..7d05be14b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -61,6 +61,10 @@ void Courtroom::enter_courtroom(int p_cid) if (showname_char.isEmpty()) showname_char = f_char; ao_app->discord->set_character_name(showname_char); + + QStringList l_content{f_char, showname_char}; + AOPacket *l_packet = new AOPacket("chrini", l_content); + ao_app->send_server_packet(l_packet); } current_char = f_char; From 6d6576126e3fa03e115d791c75c203cbd827f245 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 10 May 2021 17:57:42 +0200 Subject: [PATCH 319/842] Reworked implementation to match what was discussed --- include/aoapplication.h | 4 -- include/aoconfig.h | 7 +-- include/aoconfigpanel.h | 2 +- include/courtroom.h | 18 +++---- res/ui/config_panel.ui | 24 ++------- src/aoapplication.cpp | 5 -- src/aoconfig.cpp | 35 ++++++------ src/aoconfigpanel.cpp | 14 +++-- src/courtroom.cpp | 103 ++++++++++-------------------------- src/courtroom_widgets.cpp | 23 ++++---- src/emotes.cpp | 4 +- src/packet_distribution.cpp | 5 +- 12 files changed, 90 insertions(+), 154 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index c241af52f..b8b7d79b4 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -250,10 +250,6 @@ class AOApplication : public QApplication // callwords) should actually tigger a server alert or not bool get_server_alerts_enabled(); - // returns whether if playing an iniedited character, blanking out the showname box fills it with the - // iniedited shownamed - bool get_fill_iniedit_showname_enabled(); - // returns whatever preanimations should always play or not bool get_always_pre_enabled(); diff --git a/include/aoconfig.h b/include/aoconfig.h index a024c311b..301474855 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -24,6 +24,7 @@ class AOConfig : public QObject bool autosave() const; QString username() const; QString showname() const; + QString showname_placeholder() const; QString callwords() const; bool server_alerts_enabled() const; bool discord_presence() const; @@ -34,7 +35,6 @@ class AOConfig : public QObject bool manual_gamemode_enabled() const; QString timeofday() const; bool manual_timeofday_enabled() const; - bool fill_iniedit_showname_enabled() const; bool always_pre_enabled() const; int chat_tick_interval() const; int log_max_lines() const; @@ -69,6 +69,8 @@ public slots: void set_autosave(bool p_enabled); void set_username(QString p_string); void set_showname(QString p_string); + void set_showname_placeholder(QString p_string); + void clear_showname_placeholder(); void set_callwords(QString p_string); void set_server_alerts(bool p_enabled); void set_discord_presence(const bool p_enabled); @@ -79,7 +81,6 @@ public slots: void set_manual_gamemode(bool p_enabled); void set_timeofday(QString p_string); void set_manual_timeofday(bool p_enabled); - void set_fill_iniedit_showname(bool p_enabled); void set_always_pre(bool p_enabled); void set_chat_tick_interval(int p_number); void set_log_max_lines(int p_number); @@ -125,7 +126,7 @@ public slots: void timeofday_changed(QString); void manual_timeofday_changed(bool); void showname_changed(QString); - void fill_iniedit_showname_changed(bool); + void showname_placeholder_changed(QString); void always_pre_changed(bool); void chat_tick_interval_changed(int); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index fcfad85c8..60ef31fae 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -45,6 +45,7 @@ private slots: void on_reload_theme_clicked(); void on_gamemode_index_changed(QString p_text); void on_timeofday_index_changed(QString p_text); + void on_showname_placeholder_changed(QString p_text); void on_log_is_topdown_changed(bool p_enabled); void on_device_current_index_changed(int p_index); void on_audio_device_changed(DRAudioDevice p_device); @@ -86,7 +87,6 @@ private slots: QComboBox *w_timeofday = nullptr; QCheckBox *w_manual_timeofday = nullptr; QLineEdit *w_showname = nullptr; - QCheckBox *w_fill_iniedit_showname = nullptr; QCheckBox *w_always_pre = nullptr; QSpinBox *w_chat_tick_interval = nullptr; diff --git a/include/courtroom.h b/include/courtroom.h index 4a78e350d..760862c24 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -181,10 +181,6 @@ class Courtroom : public QMainWindow return current_char; } - // Update the last showname set by the client. Returns true if p_showname is different from the current last showname - // set, false otherwise. - bool update_last_showname(QString p_showname); - // Set the showname of the client void set_showname(QString p_showname); @@ -345,7 +341,6 @@ class Courtroom : public QMainWindow // used to determine how often blips sound int blip_pos = 0; int rainbow_counter = 0; - QString m_last_showname; bool m_showname_sent = false; bool rainbow_appended = false; bool note_shown = false; @@ -528,7 +523,7 @@ class Courtroom : public QMainWindow QListWidget *ui_music_list = nullptr; QListWidget *ui_sfx_list = nullptr; - QLineEdit *ui_ic_chat_name = nullptr; + QLineEdit *ui_ic_chat_showname = nullptr; QLineEdit *ui_ic_chat_message = nullptr; QLineEdit *ui_ooc_chat_name = nullptr; @@ -700,8 +695,7 @@ class Courtroom : public QMainWindow void set_char_rpc(); - bool is_self_iniedited(); - bool check_fill_iniedit_showname(); + bool is_spectating(); public slots: void objection_done(); @@ -722,11 +716,11 @@ private slots: void on_mute_list_item_changed(QListWidgetItem *p_item); - void on_showname_changed(); - void on_chat_name_editing_finished(); - void on_chat_return_pressed(); + void on_showname_changed(QString); + void on_showname_placeholder_changed(QString); + void on_ic_showname_editing_finished(); + void on_ic_message_return_pressed(); void on_chat_config_changed(); - void on_fill_iniedit_showname_changed(); void on_ooc_name_editing_finished(); void on_ooc_return_pressed(); diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 421a6195e..e3d8aa682 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -263,14 +263,14 @@ - + Chat-tick interval: - + ms @@ -283,7 +283,7 @@ - + Always-anim: @@ -293,7 +293,7 @@ - + @@ -309,7 +309,7 @@ - + Qt::Vertical @@ -322,20 +322,6 @@ - - - - Fill iniedit showname: - - - - - - - - - - diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index f43249488..b8ab075b4 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -228,11 +228,6 @@ bool AOApplication::get_manual_timeofday_enabled() return config->manual_timeofday_enabled(); } -bool AOApplication::get_fill_iniedit_showname_enabled() -{ - return config->fill_iniedit_showname_enabled(); -} - bool AOApplication::get_always_pre_enabled() { return config->always_pre_enabled(); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 31e6315ed..3d300e1ee 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -57,7 +57,7 @@ private slots: QString timeofday; bool manual_timeofday; QString showname; - bool fill_iniedit_showname; + QString showname_placeholder; bool always_pre; int chat_tick_interval; int log_max_lines; @@ -126,7 +126,6 @@ void AOConfigPrivate::read_file() manual_gamemode = cfg.value("manual_gamemode", false).toBool(); timeofday = cfg.value("timeofday", "").toString(); manual_timeofday = cfg.value("manual_timeofday", false).toBool(); - fill_iniedit_showname = cfg.value("fill_iniedit_showname", true).toBool(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); log_max_lines = cfg.value("chatlog_limit", 200).toInt(); @@ -184,7 +183,6 @@ void AOConfigPrivate::save_file() cfg.setValue("manual_gamemode", manual_gamemode); cfg.setValue("timeofday", timeofday); cfg.setValue("manual_timeofday", manual_timeofday); - cfg.setValue("fill_iniedit_showname", fill_iniedit_showname); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("chatlog_limit", log_max_lines); @@ -293,6 +291,11 @@ QString AOConfig::showname() const return d->showname; } +QString AOConfig::showname_placeholder() const +{ + return d->showname_placeholder; +} + QString AOConfig::callwords() const { return d->callwords; @@ -343,11 +346,6 @@ bool AOConfig::manual_timeofday_enabled() const return d->manual_timeofday; } -bool AOConfig::fill_iniedit_showname_enabled() const -{ - return d->fill_iniedit_showname; -} - bool AOConfig::always_pre_enabled() const { return d->always_pre; @@ -486,6 +484,19 @@ void AOConfig::set_showname(QString p_string) d->invoke_signal("showname_changed", Q_ARG(QString, p_string)); } +void AOConfig::set_showname_placeholder(QString p_string) +{ + if (d->showname_placeholder == p_string) + return; + d->showname_placeholder = p_string; + d->invoke_signal("showname_placeholder_changed", Q_ARG(QString, p_string)); +} + +void AOConfig::clear_showname_placeholder() +{ + set_showname_placeholder(nullptr); +} + void AOConfig::set_callwords(QString p_string) { if (d->callwords == p_string) @@ -566,14 +577,6 @@ void AOConfig::set_manual_timeofday(bool p_enabled) d->invoke_signal("manual_timeofday_changed", Q_ARG(bool, p_enabled)); } -void AOConfig::set_fill_iniedit_showname(bool p_enabled) -{ - if (d->fill_iniedit_showname == p_enabled) - return; - d->fill_iniedit_showname = p_enabled; - d->invoke_signal("fill_iniedit_showname_changed", Q_ARG(bool, p_enabled)); -} - void AOConfig::set_always_pre(bool p_enabled) { if (d->always_pre == p_enabled) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 5c59d3298..30ae658e6 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -46,7 +46,6 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_timeofday = AO_GUI_WIDGET(QComboBox, "timeofday"); w_manual_timeofday = AO_GUI_WIDGET(QCheckBox, "manual_timeofday"); w_showname = AO_GUI_WIDGET(QLineEdit, "showname"); - w_fill_iniedit_showname = AO_GUI_WIDGET(QCheckBox, "fill_iniedit_showname"); w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); @@ -105,7 +104,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, SLOT(setCurrentText(QString))); connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, SLOT(setChecked(bool))); connect(m_config, SIGNAL(showname_changed(QString)), w_showname, SLOT(setText(QString))); - connect(m_config, SIGNAL(fill_iniedit_showname_changed(bool)), w_fill_iniedit_showname, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(showname_placeholder_changed(QString)), this, + SLOT(on_showname_placeholder_changed(QString))); connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); @@ -164,7 +164,6 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); connect(w_manual_timeofday, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_timeofday(bool))); connect(w_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); - connect(w_fill_iniedit_showname, SIGNAL(toggled(bool)), m_config, SLOT(set_fill_iniedit_showname(bool))); connect(w_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); @@ -211,7 +210,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) w_timeofday->setCurrentText(m_config->timeofday()); w_manual_timeofday->setChecked(m_config->manual_timeofday_enabled()); w_showname->setText(m_config->showname()); - w_fill_iniedit_showname->setChecked(m_config->fill_iniedit_showname_enabled()); + on_showname_placeholder_changed(m_config->showname_placeholder()); w_always_pre->setChecked(m_config->always_pre_enabled()); w_chat_tick_interval->setValue(m_config->chat_tick_interval()); @@ -411,6 +410,13 @@ void AOConfigPanel::on_timeofday_index_changed(QString p_text) m_config->set_timeofday(w_timeofday->currentData().toString()); } +void AOConfigPanel::on_showname_placeholder_changed(QString p_text) +{ + const QString l_showname(p_text.trimmed().isEmpty() ? "Showname" : p_text); + w_showname->setPlaceholderText(l_showname); + w_showname->setToolTip(l_showname); +} + void AOConfigPanel::on_log_is_topdown_changed(bool p_enabled) { w_log_orientation_top_down->setChecked(p_enabled); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f4eb79bcb..0b6a13372 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -45,32 +45,30 @@ void Courtroom::enter_courtroom(int p_cid) bool changed_character = (m_cid != p_cid); m_cid = p_cid; - QString f_char; + QString l_chr_name; set_char_rpc(); - if (m_cid == -1) + if (is_spectating()) { ao_app->discord->clear_character_name(); - f_char = ""; - ui_ic_chat_name->setPlaceholderText(""); + ao_config->clear_showname_placeholder(); } else { - f_char = ao_app->get_char_name(char_list.at(m_cid).name); - QString showname_char = ao_app->get_showname(f_char); - if (showname_char.isEmpty()) - showname_char = f_char; - ao_app->discord->set_character_name(showname_char); - ui_ic_chat_name->setPlaceholderText(showname_char); + l_chr_name = ao_app->get_char_name(char_list.at(m_cid).name); + const QString l_ini_showname = ao_app->get_showname(l_chr_name); + const QString l_final_showname = l_ini_showname.trimmed().isEmpty() ? l_chr_name : l_ini_showname; + ao_app->discord->set_character_name(l_final_showname); + ao_config->set_showname_placeholder(l_final_showname); } - current_char = f_char; + current_char = l_chr_name; current_emote_page = 0; current_emote = 0; - if (m_cid == -1) + if (is_spectating()) ui_emotes->hide(); else ui_emotes->show(); @@ -99,7 +97,7 @@ void Courtroom::enter_courtroom(int p_cid) // can be omitted), I am keeping it in case we expand set_character_position // to do more. if (changed_character) - set_character_position(ao_app->get_char_side(f_char)); + set_character_position(ao_app->get_char_side(l_chr_name)); else set_character_position(ui_pos_dropdown->currentText()); @@ -141,7 +139,7 @@ void Courtroom::enter_courtroom(int p_cid) ui_char_select_background->hide(); - ui_ic_chat_message->setEnabled(m_cid != -1); + ui_ic_chat_message->setEnabled(!is_spectating()); ui_ic_chat_message->setFocus(); for (int i = 0; i < ui_timers.length(); ++i) @@ -149,7 +147,6 @@ void Courtroom::enter_courtroom(int p_cid) set_widget_names(); set_widget_layers(); - check_fill_iniedit_showname(); } void Courtroom::done_received() @@ -579,75 +576,38 @@ void Courtroom::append_server_chatmessage(QString p_name, QString p_message) save_textlog("(OOC)" + p_name + ": " + p_message); } -void Courtroom::on_showname_changed() +void Courtroom::on_showname_changed(QString p_showname) { - if (check_fill_iniedit_showname()) - return; - const QString l_showname = ao_config->showname(); - send_showname_packet(l_showname); - ui_ic_chat_name->setText(l_showname); -} - -bool Courtroom::update_last_showname(QString p_showname) -{ - if (p_showname == m_last_showname) - return false; - m_last_showname = p_showname; - return true; + ui_ic_chat_showname->setText(p_showname); + send_showname_packet(p_showname); } void Courtroom::send_showname_packet(QString p_showname) { - if (p_showname == m_last_showname) - return; - m_last_showname = p_showname; if (ao_app->m_FL_showname_enabled) { - QStringList packet_contents = {p_showname}; - ao_app->send_server_packet(new AOPacket("SN", packet_contents)); + QStringList l_content = {p_showname}; + ao_app->send_server_packet(new AOPacket("SN", l_content)); } else + { send_ooc_packet(ao_config->username(), QString("/showname %1").arg(p_showname)); + } } -bool Courtroom::is_self_iniedited() -{ - // Check if spectator first. Spectators are never iniedited - if (m_cid == -1) - return false; - const QString l_selected_folder_name = char_list.at(m_cid).name; - return !(l_selected_folder_name == current_char); -} - -void Courtroom::on_fill_iniedit_showname_changed() +bool Courtroom::is_spectating() { - check_fill_iniedit_showname(); + return m_cid == -1; } -bool Courtroom::check_fill_iniedit_showname() +void Courtroom::on_showname_placeholder_changed(QString p_showname_placeholder) { - // We only care about the case where all of the following are true: - // 1. Player has checked Fill Iniedit Showname - // 2. Player is iniedited (and thus not spectator) - // 3. Player has an empty showname box - const bool l_fill = ao_config->fill_iniedit_showname_enabled(); - if (!l_fill) - return false; - - if (!is_self_iniedited()) - return false; - - if (!ao_config->showname().isEmpty()) - return false; - - QString l_char = ao_app->get_char_name(char_list.at(m_cid).name); - QString l_showname = ao_app->get_showname(l_char); - - set_showname(l_showname); - return true; + const QString l_showname(p_showname_placeholder.trimmed().isEmpty() ? "Showname" : p_showname_placeholder); + ui_ic_chat_showname->setPlaceholderText(l_showname); + ui_ic_chat_showname->setToolTip(l_showname); } -void Courtroom::on_chat_return_pressed() +void Courtroom::on_ic_message_return_pressed() { if (ui_ic_chat_message->text() == "" || is_client_muted) return; @@ -657,12 +617,7 @@ void Courtroom::on_chat_return_pressed() if (!m_showname_sent) { - const QString l_showname = ao_config->showname().trimmed(); - if (!l_showname.isEmpty()) - { - m_showname_sent = true; - send_showname_packet(l_showname); - } + send_showname_packet(ao_config->showname()); } // qDebug() << "prev_emote = " << prev_emote << "current_emote = " << @@ -1867,9 +1822,9 @@ void Courtroom::mod_called(QString p_ip) } } -void Courtroom::on_chat_name_editing_finished() +void Courtroom::on_ic_showname_editing_finished() { - set_showname(ui_ic_chat_name->text()); + set_showname(ui_ic_chat_showname->text()); } void Courtroom::set_showname(QString p_showname) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 8544e15fd..1a6a622af 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -105,10 +105,10 @@ void Courtroom::create_widgets() ui_music_list = new QListWidget(this); ui_sfx_list = new QListWidget(this); - ui_ic_chat_name = new QLineEdit(this); - ui_ic_chat_name->setFrame(false); - ui_ic_chat_name->setPlaceholderText("Showname"); - ui_ic_chat_name->setText(ao_config->showname()); + ui_ic_chat_showname = new QLineEdit(this); + ui_ic_chat_showname->setFrame(false); + ui_ic_chat_showname->setPlaceholderText("Showname"); + ui_ic_chat_showname->setText(ao_config->showname()); ui_ic_chat_message = new QLineEdit(this); ui_ic_chat_message->setFrame(false); @@ -256,10 +256,11 @@ void Courtroom::connect_widgets() connect(ui_mute_list, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(on_mute_list_item_changed(QListWidgetItem *))); - connect(ao_config, SIGNAL(showname_changed(QString)), this, SLOT(on_showname_changed())); - connect(ui_ic_chat_name, SIGNAL(editingFinished()), this, SLOT(on_chat_name_editing_finished())); - connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_chat_return_pressed())); - connect(ao_config, SIGNAL(fill_iniedit_showname_changed(bool)), this, SLOT(on_fill_iniedit_showname_changed())); + connect(ao_config, SIGNAL(showname_changed(QString)), this, SLOT(on_showname_changed(QString))); + connect(ao_config, SIGNAL(showname_placeholder_changed(QString)), this, + SLOT(on_showname_placeholder_changed(QString))); + connect(ui_ic_chat_showname, SIGNAL(editingFinished()), ao_config, SLOT(on_ic_showname_editing_finished())); + connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ic_message_return_pressed())); connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); connect(ui_ooc_chat_name, SIGNAL(editingFinished()), this, SLOT(on_ooc_name_editing_finished())); @@ -354,7 +355,7 @@ void Courtroom::reset_widget_names() {"area_list", ui_area_list}, {"music_list", ui_music_list}, {"sfx_list", ui_sfx_list}, - {"ic_chat_name", ui_ic_chat_name}, + {"ic_chat_name", ui_ic_chat_showname}, {"ao2_ic_chat_message", ui_ic_chat_message}, // ui_muted {"ooc_chat_message", ui_ooc_chat_message}, @@ -624,8 +625,8 @@ void Courtroom::set_widgets() set_size_and_pos(ui_sfx_list, "sfx_list"); - set_size_and_pos(ui_ic_chat_name, "ic_chat_name"); - ui_ic_chat_name->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + set_size_and_pos(ui_ic_chat_showname, "ic_chat_name"); + ui_ic_chat_showname->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message"); ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); diff --git a/src/emotes.cpp b/src/emotes.cpp index dcdf31254..1dac658b5 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -77,7 +77,7 @@ void Courtroom::reset_emote_page() current_emote_page = 0; current_emote = 0; - if (m_cid == -1) + if (is_spectating()) ui_emotes->hide(); else ui_emotes->show(); @@ -91,7 +91,7 @@ void Courtroom::set_emote_page() ui_emote_left->hide(); ui_emote_right->hide(); - if (m_cid == -1) + if (is_spectating()) return; int total_emotes = ao_app->get_emote_number(current_char); diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 642a7278e..f4d97590e 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -729,11 +729,10 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; if (!courtroom_constructed) goto end; - QString l_showname = f_contents.at(0); + const QString l_showname = f_contents.at(0); // By updating now, we prevent the client from sending an SN back when we later on go ahead and modify the // showname box. - w_courtroom->update_last_showname(l_showname); - w_courtroom->set_showname(l_showname); + config->set_showname(l_showname); } end: From f8b85a391a052cbdf3ae1db8b1997e8e44e2f23f Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 03:15:10 +0200 Subject: [PATCH 320/842] Reworked button logic --- include/courtroom.h | 12 ++-- src/courtroom.cpp | 132 ++++++++++++++++++++++---------------- src/courtroom_widgets.cpp | 54 +++++++++------- src/path_functions.cpp | 2 +- 4 files changed, 117 insertions(+), 83 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index dcebecbc0..fd17f52ae 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -766,12 +766,13 @@ private slots: * @details If a sprite cannot be found for a shout button, a regular * push button is displayed for it with its shout name instead. */ - void draw_shout_buttons(); + void reset_shout_buttons(); /** * @brief a general purpose function to toggle button selection */ - void on_shout_clicked(); + void on_shout_button_clicked(const bool); + void on_shout_button_toggled(const bool); /** * @brief Set the sprites of the effect buttons, and mark the currently @@ -780,8 +781,9 @@ private slots: * @details If a sprite cannot be found for a shout button, a regular * push button is displayed for it with its shout name instead. */ - void draw_effect_buttons(); - void on_effect_button_clicked(); + void reset_effect_buttons(); + void on_effect_button_clicked(const bool); + void on_effect_button_toggled(const bool); void on_mute_clicked(); @@ -800,7 +802,7 @@ private slots: * @details If a sprite cannot be found for a shout button, a regular * push button is displayed for it with its shout name instead. */ - void draw_judge_wtce_buttons(); + void reset_wtce_buttons(); void on_wtce_clicked(); void on_change_character_clicked(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index bad868a50..cecd0dc49 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -86,7 +86,7 @@ void Courtroom::enter_courtroom(int p_cid) m_effect_state = 0; m_effect_current = 0; m_wtce_current = 0; - draw_judge_wtce_buttons(); + reset_wtce_buttons(); // setup chat on_chat_config_changed(); @@ -719,17 +719,11 @@ void Courtroom::handle_acknowledged_ms() list_sfx(); ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors - m_shout_state = 0; - draw_shout_buttons(); - - m_effect_state = 0; - draw_effect_buttons(); - - m_wtce_current = 0; - draw_judge_wtce_buttons(); + reset_shout_buttons(); + reset_effect_buttons(); + reset_wtce_buttons(); is_presenting_evidence = false; - ui_evidence_present->set_image("present_disabled.png"); } @@ -1942,37 +1936,51 @@ void Courtroom::on_area_list_double_clicked(QModelIndex p_model) ui_ic_chat_message->setFocus(); } -void Courtroom::draw_shout_buttons() +void Courtroom::reset_shout_buttons() { - for (int i = 0; i < ui_shouts.size(); ++i) + for (AOButton *i_button : qAsConst(ui_shouts)) + i_button->setChecked(false); + m_shout_state = 0; +} + +void Courtroom::on_shout_button_clicked(const bool p_checked) +{ + AOButton *l_button = dynamic_cast(sender()); + if (l_button == nullptr) + return; + + bool l_ok = false; + const int l_id = l_button->property("shout_id").toInt(&l_ok); + if (!l_ok) + return; + + // disable all other buttons + for (AOButton *i_button : qAsConst(ui_shouts)) { - QString shout_file = shout_names.at(i) + ".png"; - ui_shouts[i]->set_image(shout_file); - if (ao_app->find_theme_asset_path(shout_file).isEmpty()) - ui_shouts[i]->setText(shout_names.at(i)); - else - ui_shouts[i]->setText(""); + if (i_button == l_button) + continue; + i_button->setChecked(false); } - // Mark selected button as such - if (m_shout_state != 0 && ui_shouts.size() > 0) - ui_shouts.at(m_shout_state - 1)->set_image(shout_names.at(m_shout_state - 1) + "_selected.png"); + m_shout_state = p_checked ? l_id : 0; + + ui_ic_chat_message->setFocus(); } -void Courtroom::on_shout_clicked() +void Courtroom::on_shout_button_toggled(const bool p_checked) { - AOButton *f_shout_button = static_cast(sender()); - int f_shout_id = f_shout_button->property("shout_id").toInt(); - - // update based on current button selected - if (f_shout_id == m_shout_state) - m_shout_state = 0; - else - m_shout_state = f_shout_id; + AOButton *l_button = dynamic_cast(sender()); + if (l_button == nullptr) + return; - draw_shout_buttons(); + const QString l_name = l_button->property("shout_name").toString(); + if (l_name.isEmpty()) + return; - ui_ic_chat_message->setFocus(); + const QString l_image_name(QString("%1%2.png").arg(l_name, QString(p_checked ? "_selected" : nullptr))); + l_button->set_image(l_image_name); + if (ao_app->find_theme_asset_path(l_image_name).isEmpty()) + l_button->setText(l_name); } void Courtroom::on_cycle_clicked() @@ -2032,36 +2040,50 @@ void Courtroom::cycle_wtce(int p_delta) set_judge_wtce(); } -void Courtroom::draw_effect_buttons() +void Courtroom::reset_effect_buttons() +{ + for (AOButton *i_button : qAsConst(ui_effects)) + i_button->setChecked(false); + m_effect_state = 0; +} + +void Courtroom::on_effect_button_clicked(const bool p_checked) { - for (int i = 0; i < effect_names.size(); ++i) + AOButton *l_button = dynamic_cast(sender()); + if (l_button == nullptr) + return; + + bool l_ok = false; + const int l_id = l_button->property("effect_id").toInt(&l_ok); + if (!l_ok) + return; + + // disable all other buttons + for (AOButton *i_button : qAsConst(ui_effects)) { - QString effect_file = effect_names.at(i) + ".png"; - ui_effects[i]->set_image(effect_file); - if (ao_app->find_theme_asset_path(effect_file).isEmpty()) - ui_effects[i]->setText(effect_names.at(i)); - else - ui_effects[i]->setText(""); + if (i_button == l_button) + continue; + i_button->setChecked(false); } - // Mark selected button as such - if (m_effect_state != 0 && ui_effects.size() > 0) - ui_effects[m_effect_state - 1]->set_image(effect_names.at(m_effect_state - 1) + "_pressed.png"); + m_effect_state = p_checked ? l_id : 0; + ui_ic_chat_message->setFocus(); } -void Courtroom::on_effect_button_clicked() +void Courtroom::on_effect_button_toggled(const bool p_checked) { - AOButton *f_button = static_cast(this->sender()); - - int f_effect_id = f_button->property("effect_id").toInt(); - if (m_effect_state == f_effect_id) - m_effect_state = 0; - else - m_effect_state = f_effect_id; + AOButton *l_button = dynamic_cast(sender()); + if (l_button == nullptr) + return; - draw_effect_buttons(); + const QString l_name = l_button->property("effect_name").toString(); + if (l_name.isEmpty()) + return; - ui_ic_chat_message->setFocus(); + const QString l_image_name(QString("%1%2.png").arg(l_name, QString(p_checked ? "_pressed" : nullptr))); + l_button->set_image(l_image_name); + if (ao_app->find_theme_asset_path(l_image_name).isEmpty()) + l_button->setText(l_name); } void Courtroom::on_mute_clicked() @@ -2136,7 +2158,7 @@ void Courtroom::on_cross_examination_clicked() ui_ic_chat_message->setFocus(); } -void Courtroom::draw_judge_wtce_buttons() +void Courtroom::reset_wtce_buttons() { for (int i = 0; i < wtce_names.size(); ++i) { @@ -2148,6 +2170,8 @@ void Courtroom::draw_judge_wtce_buttons() ui_wtce[i]->setText(""); } + m_wtce_current = 0; + // Unlike the other reset functions, the judge buttons are of immediate // action and thus are immediately unpressed after being pressed. // Therefore, we do not need to handle displaying a "_selected.png" diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index ff8205527..f47b75da3 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -688,7 +688,7 @@ void Courtroom::set_widgets() // ui_shouts[0]->show(); // ui_shouts[1]->show(); // ui_shouts[2]->show(); - draw_shout_buttons(); + reset_shout_buttons(); set_size_and_pos(ui_shout_up, "shout_up"); ui_shout_up->set_image("shoutup.png"); @@ -717,7 +717,7 @@ void Courtroom::set_widgets() { set_size_and_pos(ui_effects[i], effect_names[i]); } - draw_effect_buttons(); + reset_effect_buttons(); set_size_and_pos(ui_effect_up, "effect_up"); ui_effect_up->set_image("effectup.png"); @@ -757,7 +757,7 @@ void Courtroom::set_widgets() qDebug() << "AA: single wtce"; } set_judge_wtce(); - draw_judge_wtce_buttons(); + reset_wtce_buttons(); for (int i = 0; i < free_block_names.size(); ++i) { @@ -1169,16 +1169,17 @@ void Courtroom::load_effects() for (int i = 0; i < ui_effects.size(); ++i) { - ui_effects[i] = new AOButton(this, ao_app); - ui_effects[i]->setProperty("effect_id", i + 1); - ui_effects[i]->stackUnder(ui_effect_up); - ui_effects[i]->stackUnder(ui_effect_down); + AOButton *l_button = new AOButton(this, ao_app); + ui_effects.replace(i, l_button); + l_button->setCheckable(true); + l_button->setProperty("effect_id", i + 1); + l_button->stackUnder(ui_effect_up); + l_button->stackUnder(ui_effect_down); + + connect(l_button, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked(bool))); + connect(l_button, SIGNAL(toggled(bool)), this, SLOT(on_effect_button_toggled(bool))); } - // And connect their actions - for (auto &effect : ui_effects) - connect(effect, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked())); - // And add names effect_names.clear(); for (int i = 1; i <= ui_effects.size(); ++i) @@ -1186,8 +1187,11 @@ void Courtroom::load_effects() QStringList names = ao_app->get_effect(i); if (!names.isEmpty()) { - QString name = names.at(0).trimmed(); - effect_names.append(name); + const QString l_name = names.at(0).trimmed(); + effect_names.append(l_name); + AOButton *l_button = ui_effects.at(i - 1); + l_button->setProperty("effect_name", l_name); + Q_EMIT l_button->toggled(l_button->isChecked()); } } } @@ -1236,16 +1240,17 @@ void Courtroom::load_shouts() for (int i = 0; i < ui_shouts.size(); ++i) { - ui_shouts[i] = new AOButton(this, ao_app); - ui_shouts[i]->setProperty("shout_id", i + 1); - ui_shouts[i]->stackUnder(ui_shout_up); - ui_shouts[i]->stackUnder(ui_shout_down); + AOButton *l_button = new AOButton(this, ao_app); + ui_shouts.replace(i, l_button); + l_button->setCheckable(true); + l_button->setProperty("shout_id", i + 1); + l_button->stackUnder(ui_shout_up); + l_button->stackUnder(ui_shout_down); + + connect(l_button, SIGNAL(clicked(bool)), this, SLOT(on_shout_button_clicked())); + connect(l_button, SIGNAL(toggled(bool)), this, SLOT(on_shout_button_toggled(bool))); } - // And connect their actions - for (auto &shout : ui_shouts) - connect(shout, SIGNAL(clicked(bool)), this, SLOT(on_shout_clicked())); - // And add names shout_names.clear(); for (int i = 1; i <= ui_shouts.size(); ++i) @@ -1255,8 +1260,11 @@ void Courtroom::load_shouts() { qDebug() << "SHOUT " << name << " " << ui_shouts[i - 1]; shout_names.append(name); - widget_names[name] = ui_shouts[i - 1]; - ui_shouts[i - 1]->setObjectName(name); + AOButton *l_button = ui_shouts.at(i - 1); + widget_names.insert(name, l_button); + l_button->setObjectName(name); + l_button->setProperty("shout_name", name); + Q_EMIT l_button->toggled(l_button->isChecked()); } } qDebug() << widget_names; diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 2c73ccb4a..34f1d3512 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -11,7 +11,7 @@ // depends. On Mac, it can be toggled. So, should the time ever come to that, // manually define CASE_SENSITIVE_FILESYSTEM if you're working on a Mac that // has, well, a case-sensitive filesystem. -#if (defined(LINUX) || defined(__linux__)) +#ifdef Q_OS_LINUX #define CASE_SENSITIVE_FILESYSTEM #endif From 0c2ccbd832998c1577e0f2d224a865361f196372 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 15:35:31 +0200 Subject: [PATCH 321/842] Improved ackMS method performance --- include/courtroom.h | 18 ++-- include/datatypes.h | 13 +++ src/courtroom.cpp | 201 +++++++++++++++----------------------- src/courtroom_widgets.cpp | 6 +- 4 files changed, 107 insertions(+), 131 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index fd17f52ae..5cd042729 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -171,7 +171,10 @@ class Courtroom : public QMainWindow void list_areas(); - void list_sfx(); + QString current_sfx_file(); + void update_sfx_list(); + void update_sfx_widget_list(); + void clear_sfx_widget_list_selection(); void list_note_files(); @@ -303,7 +306,6 @@ class Courtroom : public QMainWindow QVector evidence_list; QVector music_list; QVector area_list; - QVector sfx_names; QVector area_names; QVector note_list; @@ -447,7 +449,6 @@ class Courtroom : public QMainWindow int current_clock = -1; int timer_number = 0; - int current_sfx_id = -1; QString current_background = "gs4"; @@ -500,7 +501,11 @@ class Courtroom : public QMainWindow QListWidget *ui_mute_list = nullptr; QListWidget *ui_area_list = nullptr; QListWidget *ui_music_list = nullptr; + QListWidget *ui_sfx_list = nullptr; + QVector m_sfx_list; + QColor m_sfx_color_found; + QColor m_sfx_color_missing; QLineEdit *ui_ic_chat_message = nullptr; @@ -697,13 +702,14 @@ private slots: void on_ooc_return_pressed(); - void on_music_search_edited(QString p_text); + void on_music_search_edited(); void on_music_list_clicked(); void on_area_list_clicked(); void on_music_list_double_clicked(QModelIndex p_model); void on_area_list_double_clicked(QModelIndex p_model); - void on_sfx_search_edited(QString p_text); + void on_sfx_search_edited(); + void on_sfx_widget_list_row_changed(); void select_emote(int p_id); @@ -822,8 +828,6 @@ private slots: void on_flip_clicked(); void on_hidden_clicked(); - void on_sfx_list_clicked(QModelIndex p_index); - void on_evidence_button_clicked(); void on_evidence_delete_clicked(); diff --git a/include/datatypes.h b/include/datatypes.h index 81e1254ba..30ba1e87e 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -60,6 +60,19 @@ class ChatRecord bool system = false; bool music = false; }; + +struct SFX +{ +public: + SFX() = default; + SFX(QString p_name, QString p_file, bool p_is_found = false) + : name(p_name.trimmed()), file(p_file.trimmed()), is_found(p_is_found) + {} + + QString name; + QString file; + bool is_found; +}; } // namespace DR struct server_type diff --git a/src/courtroom.cpp b/src/courtroom.cpp index cecd0dc49..4d86ada87 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -127,7 +127,7 @@ void Courtroom::enter_courtroom(int p_cid) list_music(); list_areas(); - list_sfx(); + update_sfx_list(); ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors @@ -437,68 +437,96 @@ void Courtroom::list_areas() } } -void Courtroom::list_sfx() +QString Courtroom::current_sfx_file() { - ui_sfx_list->clear(); - sfx_names.clear(); - current_sfx_id = -1; // Restart current SFX, because it may no longer be valid + QListWidgetItem *l_item = ui_sfx_list->currentItem(); + if (l_item == nullptr) + return "0"; + const QString l_file = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).file; + return l_file.isEmpty() ? "0" : l_file; +} - QString f_file = design_ini; +void Courtroom::update_sfx_list() +{ + // colors + m_sfx_color_found = ao_app->get_color("found_song_color", design_ini); + m_sfx_color_missing = ao_app->get_color("missing_song_color", design_ini); - QStringList sfx_list = ao_app->get_sfx_list(); + // items + m_sfx_list.clear(); + m_sfx_list.append(DR::SFX("Default", nullptr)); + m_sfx_list.append(DR::SFX("Silence", nullptr)); - QBrush found_brush(ao_app->get_color("found_song_color", f_file)); - QBrush missing_brush(ao_app->get_color("missing_song_color", f_file)); + const QStringList l_sfx_list = ao_app->get_sfx_list(); + for (const QString &i_sfx_line : l_sfx_list) + { + const QStringList l_sfx_entry = i_sfx_line.split("=", DR::SkipEmptyParts); - // Add hardcoded items - // FIXME: Rewrite - ui_sfx_list->addItem("Default"); - ui_sfx_list->addItem("Silence"); + const QString l_name = l_sfx_entry.at(l_sfx_entry.size() - 1).trimmed(); + const QString l_file = QString(l_sfx_entry.size() >= 2 ? l_sfx_entry.at(0) : nullptr).trimmed(); + const bool l_is_found = !ao_app->find_asset_path({ao_app->get_sounds_path(l_file)}, audio_extensions()).isEmpty(); + m_sfx_list.append(DR::SFX(l_name, l_file, l_is_found)); + } - sfx_names.append("1"); // Default - sfx_names.append("1"); // Silence + update_sfx_widget_list(); +} - QString default_sfx_root = ao_app->get_sounds_path("1"); - QString default_sfx_path = ao_app->find_asset_path({default_sfx_root}, audio_extensions()); - if (!default_sfx_path.isEmpty()) - { - ui_sfx_list->item(0)->setBackground(found_brush); - ui_sfx_list->item(1)->setBackground(found_brush); - } - else +void Courtroom::update_sfx_widget_list() +{ + QSignalBlocker l_blocker(ui_sfx_list); + ui_sfx_list->clear(); + + const QString l_name_filter = ui_sfx_search->text(); + for (int i = 0; i < m_sfx_list.length(); ++i) { - ui_sfx_list->item(0)->setBackground(missing_brush); - ui_sfx_list->item(1)->setBackground(missing_brush); + const DR::SFX &i_sfx = m_sfx_list.at(i); + if (!i_sfx.name.contains(l_name_filter, Qt::CaseInsensitive)) + continue; + QListWidgetItem *l_item = new QListWidgetItem; + l_item->setText(i_sfx.name); + l_item->setData(Qt::UserRole, i); + ui_sfx_list->addItem(l_item); } - // Now add the other SFXs given by the character's sound.ini - for (int n_sfx = 0; n_sfx < sfx_list.size(); ++n_sfx) - { - QStringList sfx = sfx_list.at(n_sfx).split("="); - QString i_sfx = sfx.at(0).trimmed(); - QString d_sfx = ""; - sfx_names.append(i_sfx); - if (sfx_list.at(n_sfx).split("=").size() < 2) - d_sfx = i_sfx; - else - d_sfx = sfx.at(1).trimmed(); + on_sfx_widget_list_row_changed(); +} - if (i_sfx.toLower().contains(ui_sfx_search->text().toLower())) - { - ui_sfx_list->addItem(d_sfx); - int last_index = ui_sfx_list->count() - 1; - ui_sfx_list->item(last_index)->setStatusTip(QString::number(n_sfx + 2)); +void Courtroom::clear_sfx_widget_list_selection() +{ + ui_sfx_list->setCurrentRow(-1); +} - // Apply appropriate color whether SFX exists or not - QString sfx_root = ao_app->get_sounds_path(i_sfx); - QString sfx_path = ao_app->find_asset_path({sfx_root}, audio_extensions()); +void Courtroom::on_sfx_search_edited() +{ + update_sfx_list(); +} - if (!sfx_path.isEmpty()) - ui_sfx_list->item(last_index)->setBackground(found_brush); - else - ui_sfx_list->item(last_index)->setBackground(missing_brush); +void Courtroom::on_sfx_widget_list_row_changed() +{ + const int p_current_row = ui_sfx_list->currentRow(); + ui_pre->setChecked(p_current_row != -1); + + for (int i = 0; i < ui_sfx_list->count(); ++i) + { + QListWidgetItem *l_item = ui_sfx_list->item(i); + const bool l_is_found = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).is_found; + + QColor i_color = l_is_found ? m_sfx_color_found : m_sfx_color_missing; + if (i == p_current_row) + { + // Calculate the amount of lightness it would take to light up the row. We + // also limit it to 1.0, as giving lightness values above 1.0 to QColor does + // nothing. +0.4 is just an arbitrarily chosen number. + const double l_final_lightness = qMin(1.0, i_color.lightnessF() + 0.4); + + // This is just the reverse of the above, basically. We set the colour, and we + // set the brush to have that colour. + i_color.setHslF(i_color.hueF(), i_color.saturationF(), l_final_lightness); } + + l_item->setBackground(i_color); } + ui_ic_chat_message->setFocus(); } void Courtroom::list_note_files() @@ -631,20 +659,8 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(f_side); - // packet_contents.append(ao_app->get_sfx_name(current_char, current_emote)); - // packet_contents.append(ui_sfx_search->text()); - - int row = ui_sfx_list->currentRow(); - if (row == -1 || row == 0) // default - packet_contents.append(ao_app->get_sfx_name(current_char, current_emote)); - else if (QListWidgetItem *item = ui_sfx_list->item(row)) // selection - { - double d_ind = item->statusTip().toDouble(); - int ind = int(d_ind); - qDebug() << ind; - packet_contents.append(sfx_names.at(ind)); - // packet_contents.append(sfx_names.at(row)); - } + // sfx file + packet_contents.append(current_sfx_file()); int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); @@ -716,12 +732,11 @@ void Courtroom::handle_acknowledged_ms() // reset states ui_pre->setChecked(ao_config->always_pre_enabled()); - list_sfx(); - ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors reset_shout_buttons(); reset_effect_buttons(); reset_wtce_buttons(); + clear_sfx_widget_list_selection(); is_presenting_evidence = false; ui_evidence_present->set_image("present_disabled.png"); @@ -1820,21 +1835,12 @@ void Courtroom::on_ooc_return_pressed() ui_ooc_chat_message->setFocus(); } -void Courtroom::on_music_search_edited(QString p_text) +void Courtroom::on_music_search_edited() { - // preventing compiler warnings - p_text += "a"; list_music(); list_areas(); } -void Courtroom::on_sfx_search_edited(QString p_text) -{ - // preventing compiler warnings - p_text += "a"; - list_sfx(); -} - void Courtroom::on_pos_dropdown_changed(int p_index) { ui_ic_chat_message->setFocus(); @@ -1961,7 +1967,6 @@ void Courtroom::on_shout_button_clicked(const bool p_checked) continue; i_button->setChecked(false); } - m_shout_state = p_checked ? l_id : 0; ui_ic_chat_message->setFocus(); @@ -2362,52 +2367,6 @@ void Courtroom::closeEvent(QCloseEvent *event) QMainWindow::closeEvent(event); } -void Courtroom::on_sfx_list_clicked(QModelIndex p_index) -{ - if (p_index.isValid()) - ui_pre->setChecked(p_index.isValid()); - - QListWidgetItem *new_sfx = ui_sfx_list->currentItem(); - - QBrush found_brush(ao_app->get_color("found_song_color", design_ini)); - QBrush missing_brush(ao_app->get_color("missing_song_color", design_ini)); - - if (current_sfx_id != -1) - { - QListWidgetItem *old_sfx = ui_sfx_list->item(current_sfx_id); - - // Apply appropriate color whether SFX exists or not - QString sfx_root = ao_app->get_sounds_path(sfx_names.at(current_sfx_id)); - QString sfx_path = ao_app->find_asset_path({sfx_root}, audio_extensions()); - - if (!sfx_path.isEmpty()) - old_sfx->setBackground(found_brush); - else - old_sfx->setBackground(missing_brush); - } - - // Grab the colour of the selected row's brush. - QBrush selected_brush = new_sfx->background(); - QColor selected_col = selected_brush.color(); - - // Calculate the amount of lightness it would take to light up the row. We - // also limit it to 1.0, as giving lightness values above 1.0 to QColor does - // nothing. +0.4 is just an arbitrarily chosen number. - double final_lightness = qMin(1.0, selected_col.lightnessF() + 0.4); - - // This is just the reverse of the above, basically. We set the colour, and we - // set the brush to have that colour. - selected_col.setHslF(selected_col.hueF(), selected_col.saturationF(), final_lightness); - selected_brush.setColor(selected_col); - - // Finally, we set the selected SFX's background to be the lightened-up brush. - new_sfx->setBackground(selected_brush); - - current_sfx_id = ui_sfx_list->currentRow(); - - ui_ic_chat_message->setFocus(); -} - void Courtroom::on_set_notes_clicked() { if (note_scroll_area->isHidden()) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index f47b75da3..dc075035a 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -288,7 +288,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); - connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited(QString))); + connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited())); connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); @@ -304,7 +304,7 @@ void Courtroom::connect_widgets() connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); - connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_sfx_list_clicked(QModelIndex))); + connect(ui_sfx_list, SIGNAL(currentRowChanged(int)), this, SLOT(on_sfx_widget_list_row_changed())); connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); @@ -1247,7 +1247,7 @@ void Courtroom::load_shouts() l_button->stackUnder(ui_shout_up); l_button->stackUnder(ui_shout_down); - connect(l_button, SIGNAL(clicked(bool)), this, SLOT(on_shout_button_clicked())); + connect(l_button, SIGNAL(clicked(bool)), this, SLOT(on_shout_button_clicked(bool))); connect(l_button, SIGNAL(toggled(bool)), this, SLOT(on_shout_button_toggled(bool))); } From b5b9ce297b577bed332b5eac5cdeaf3d4c470a7a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 15:44:31 +0200 Subject: [PATCH 322/842] Revert "Improved ackMS method performance" This reverts commit 0c2ccbd832998c1577e0f2d224a865361f196372. --- include/courtroom.h | 18 ++-- include/datatypes.h | 13 --- src/courtroom.cpp | 201 +++++++++++++++++++++++--------------- src/courtroom_widgets.cpp | 6 +- 4 files changed, 131 insertions(+), 107 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 5cd042729..fd17f52ae 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -171,10 +171,7 @@ class Courtroom : public QMainWindow void list_areas(); - QString current_sfx_file(); - void update_sfx_list(); - void update_sfx_widget_list(); - void clear_sfx_widget_list_selection(); + void list_sfx(); void list_note_files(); @@ -306,6 +303,7 @@ class Courtroom : public QMainWindow QVector evidence_list; QVector music_list; QVector area_list; + QVector sfx_names; QVector area_names; QVector note_list; @@ -449,6 +447,7 @@ class Courtroom : public QMainWindow int current_clock = -1; int timer_number = 0; + int current_sfx_id = -1; QString current_background = "gs4"; @@ -501,11 +500,7 @@ class Courtroom : public QMainWindow QListWidget *ui_mute_list = nullptr; QListWidget *ui_area_list = nullptr; QListWidget *ui_music_list = nullptr; - QListWidget *ui_sfx_list = nullptr; - QVector m_sfx_list; - QColor m_sfx_color_found; - QColor m_sfx_color_missing; QLineEdit *ui_ic_chat_message = nullptr; @@ -702,14 +697,13 @@ private slots: void on_ooc_return_pressed(); - void on_music_search_edited(); + void on_music_search_edited(QString p_text); void on_music_list_clicked(); void on_area_list_clicked(); void on_music_list_double_clicked(QModelIndex p_model); void on_area_list_double_clicked(QModelIndex p_model); - void on_sfx_search_edited(); - void on_sfx_widget_list_row_changed(); + void on_sfx_search_edited(QString p_text); void select_emote(int p_id); @@ -828,6 +822,8 @@ private slots: void on_flip_clicked(); void on_hidden_clicked(); + void on_sfx_list_clicked(QModelIndex p_index); + void on_evidence_button_clicked(); void on_evidence_delete_clicked(); diff --git a/include/datatypes.h b/include/datatypes.h index 30ba1e87e..81e1254ba 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -60,19 +60,6 @@ class ChatRecord bool system = false; bool music = false; }; - -struct SFX -{ -public: - SFX() = default; - SFX(QString p_name, QString p_file, bool p_is_found = false) - : name(p_name.trimmed()), file(p_file.trimmed()), is_found(p_is_found) - {} - - QString name; - QString file; - bool is_found; -}; } // namespace DR struct server_type diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 4d86ada87..cecd0dc49 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -127,7 +127,7 @@ void Courtroom::enter_courtroom(int p_cid) list_music(); list_areas(); - update_sfx_list(); + list_sfx(); ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors @@ -437,96 +437,68 @@ void Courtroom::list_areas() } } -QString Courtroom::current_sfx_file() +void Courtroom::list_sfx() { - QListWidgetItem *l_item = ui_sfx_list->currentItem(); - if (l_item == nullptr) - return "0"; - const QString l_file = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).file; - return l_file.isEmpty() ? "0" : l_file; -} - -void Courtroom::update_sfx_list() -{ - // colors - m_sfx_color_found = ao_app->get_color("found_song_color", design_ini); - m_sfx_color_missing = ao_app->get_color("missing_song_color", design_ini); + ui_sfx_list->clear(); + sfx_names.clear(); + current_sfx_id = -1; // Restart current SFX, because it may no longer be valid - // items - m_sfx_list.clear(); - m_sfx_list.append(DR::SFX("Default", nullptr)); - m_sfx_list.append(DR::SFX("Silence", nullptr)); + QString f_file = design_ini; - const QStringList l_sfx_list = ao_app->get_sfx_list(); - for (const QString &i_sfx_line : l_sfx_list) - { - const QStringList l_sfx_entry = i_sfx_line.split("=", DR::SkipEmptyParts); + QStringList sfx_list = ao_app->get_sfx_list(); - const QString l_name = l_sfx_entry.at(l_sfx_entry.size() - 1).trimmed(); - const QString l_file = QString(l_sfx_entry.size() >= 2 ? l_sfx_entry.at(0) : nullptr).trimmed(); - const bool l_is_found = !ao_app->find_asset_path({ao_app->get_sounds_path(l_file)}, audio_extensions()).isEmpty(); - m_sfx_list.append(DR::SFX(l_name, l_file, l_is_found)); - } + QBrush found_brush(ao_app->get_color("found_song_color", f_file)); + QBrush missing_brush(ao_app->get_color("missing_song_color", f_file)); - update_sfx_widget_list(); -} + // Add hardcoded items + // FIXME: Rewrite + ui_sfx_list->addItem("Default"); + ui_sfx_list->addItem("Silence"); -void Courtroom::update_sfx_widget_list() -{ - QSignalBlocker l_blocker(ui_sfx_list); - ui_sfx_list->clear(); + sfx_names.append("1"); // Default + sfx_names.append("1"); // Silence - const QString l_name_filter = ui_sfx_search->text(); - for (int i = 0; i < m_sfx_list.length(); ++i) + QString default_sfx_root = ao_app->get_sounds_path("1"); + QString default_sfx_path = ao_app->find_asset_path({default_sfx_root}, audio_extensions()); + if (!default_sfx_path.isEmpty()) { - const DR::SFX &i_sfx = m_sfx_list.at(i); - if (!i_sfx.name.contains(l_name_filter, Qt::CaseInsensitive)) - continue; - QListWidgetItem *l_item = new QListWidgetItem; - l_item->setText(i_sfx.name); - l_item->setData(Qt::UserRole, i); - ui_sfx_list->addItem(l_item); + ui_sfx_list->item(0)->setBackground(found_brush); + ui_sfx_list->item(1)->setBackground(found_brush); + } + else + { + ui_sfx_list->item(0)->setBackground(missing_brush); + ui_sfx_list->item(1)->setBackground(missing_brush); } - on_sfx_widget_list_row_changed(); -} - -void Courtroom::clear_sfx_widget_list_selection() -{ - ui_sfx_list->setCurrentRow(-1); -} - -void Courtroom::on_sfx_search_edited() -{ - update_sfx_list(); -} - -void Courtroom::on_sfx_widget_list_row_changed() -{ - const int p_current_row = ui_sfx_list->currentRow(); - ui_pre->setChecked(p_current_row != -1); - - for (int i = 0; i < ui_sfx_list->count(); ++i) + // Now add the other SFXs given by the character's sound.ini + for (int n_sfx = 0; n_sfx < sfx_list.size(); ++n_sfx) { - QListWidgetItem *l_item = ui_sfx_list->item(i); - const bool l_is_found = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).is_found; + QStringList sfx = sfx_list.at(n_sfx).split("="); + QString i_sfx = sfx.at(0).trimmed(); + QString d_sfx = ""; + sfx_names.append(i_sfx); + if (sfx_list.at(n_sfx).split("=").size() < 2) + d_sfx = i_sfx; + else + d_sfx = sfx.at(1).trimmed(); - QColor i_color = l_is_found ? m_sfx_color_found : m_sfx_color_missing; - if (i == p_current_row) + if (i_sfx.toLower().contains(ui_sfx_search->text().toLower())) { - // Calculate the amount of lightness it would take to light up the row. We - // also limit it to 1.0, as giving lightness values above 1.0 to QColor does - // nothing. +0.4 is just an arbitrarily chosen number. - const double l_final_lightness = qMin(1.0, i_color.lightnessF() + 0.4); - - // This is just the reverse of the above, basically. We set the colour, and we - // set the brush to have that colour. - i_color.setHslF(i_color.hueF(), i_color.saturationF(), l_final_lightness); - } + ui_sfx_list->addItem(d_sfx); + int last_index = ui_sfx_list->count() - 1; + ui_sfx_list->item(last_index)->setStatusTip(QString::number(n_sfx + 2)); + + // Apply appropriate color whether SFX exists or not + QString sfx_root = ao_app->get_sounds_path(i_sfx); + QString sfx_path = ao_app->find_asset_path({sfx_root}, audio_extensions()); - l_item->setBackground(i_color); + if (!sfx_path.isEmpty()) + ui_sfx_list->item(last_index)->setBackground(found_brush); + else + ui_sfx_list->item(last_index)->setBackground(missing_brush); + } } - ui_ic_chat_message->setFocus(); } void Courtroom::list_note_files() @@ -659,8 +631,20 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(f_side); - // sfx file - packet_contents.append(current_sfx_file()); + // packet_contents.append(ao_app->get_sfx_name(current_char, current_emote)); + // packet_contents.append(ui_sfx_search->text()); + + int row = ui_sfx_list->currentRow(); + if (row == -1 || row == 0) // default + packet_contents.append(ao_app->get_sfx_name(current_char, current_emote)); + else if (QListWidgetItem *item = ui_sfx_list->item(row)) // selection + { + double d_ind = item->statusTip().toDouble(); + int ind = int(d_ind); + qDebug() << ind; + packet_contents.append(sfx_names.at(ind)); + // packet_contents.append(sfx_names.at(row)); + } int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); @@ -732,11 +716,12 @@ void Courtroom::handle_acknowledged_ms() // reset states ui_pre->setChecked(ao_config->always_pre_enabled()); + list_sfx(); + ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors reset_shout_buttons(); reset_effect_buttons(); reset_wtce_buttons(); - clear_sfx_widget_list_selection(); is_presenting_evidence = false; ui_evidence_present->set_image("present_disabled.png"); @@ -1835,12 +1820,21 @@ void Courtroom::on_ooc_return_pressed() ui_ooc_chat_message->setFocus(); } -void Courtroom::on_music_search_edited() +void Courtroom::on_music_search_edited(QString p_text) { + // preventing compiler warnings + p_text += "a"; list_music(); list_areas(); } +void Courtroom::on_sfx_search_edited(QString p_text) +{ + // preventing compiler warnings + p_text += "a"; + list_sfx(); +} + void Courtroom::on_pos_dropdown_changed(int p_index) { ui_ic_chat_message->setFocus(); @@ -1967,6 +1961,7 @@ void Courtroom::on_shout_button_clicked(const bool p_checked) continue; i_button->setChecked(false); } + m_shout_state = p_checked ? l_id : 0; ui_ic_chat_message->setFocus(); @@ -2367,6 +2362,52 @@ void Courtroom::closeEvent(QCloseEvent *event) QMainWindow::closeEvent(event); } +void Courtroom::on_sfx_list_clicked(QModelIndex p_index) +{ + if (p_index.isValid()) + ui_pre->setChecked(p_index.isValid()); + + QListWidgetItem *new_sfx = ui_sfx_list->currentItem(); + + QBrush found_brush(ao_app->get_color("found_song_color", design_ini)); + QBrush missing_brush(ao_app->get_color("missing_song_color", design_ini)); + + if (current_sfx_id != -1) + { + QListWidgetItem *old_sfx = ui_sfx_list->item(current_sfx_id); + + // Apply appropriate color whether SFX exists or not + QString sfx_root = ao_app->get_sounds_path(sfx_names.at(current_sfx_id)); + QString sfx_path = ao_app->find_asset_path({sfx_root}, audio_extensions()); + + if (!sfx_path.isEmpty()) + old_sfx->setBackground(found_brush); + else + old_sfx->setBackground(missing_brush); + } + + // Grab the colour of the selected row's brush. + QBrush selected_brush = new_sfx->background(); + QColor selected_col = selected_brush.color(); + + // Calculate the amount of lightness it would take to light up the row. We + // also limit it to 1.0, as giving lightness values above 1.0 to QColor does + // nothing. +0.4 is just an arbitrarily chosen number. + double final_lightness = qMin(1.0, selected_col.lightnessF() + 0.4); + + // This is just the reverse of the above, basically. We set the colour, and we + // set the brush to have that colour. + selected_col.setHslF(selected_col.hueF(), selected_col.saturationF(), final_lightness); + selected_brush.setColor(selected_col); + + // Finally, we set the selected SFX's background to be the lightened-up brush. + new_sfx->setBackground(selected_brush); + + current_sfx_id = ui_sfx_list->currentRow(); + + ui_ic_chat_message->setFocus(); +} + void Courtroom::on_set_notes_clicked() { if (note_scroll_area->isHidden()) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index dc075035a..f47b75da3 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -288,7 +288,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); - connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited())); + connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited(QString))); connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); @@ -304,7 +304,7 @@ void Courtroom::connect_widgets() connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); - connect(ui_sfx_list, SIGNAL(currentRowChanged(int)), this, SLOT(on_sfx_widget_list_row_changed())); + connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_sfx_list_clicked(QModelIndex))); connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); @@ -1247,7 +1247,7 @@ void Courtroom::load_shouts() l_button->stackUnder(ui_shout_up); l_button->stackUnder(ui_shout_down); - connect(l_button, SIGNAL(clicked(bool)), this, SLOT(on_shout_button_clicked(bool))); + connect(l_button, SIGNAL(clicked(bool)), this, SLOT(on_shout_button_clicked())); connect(l_button, SIGNAL(toggled(bool)), this, SLOT(on_shout_button_toggled(bool))); } From 0a5215c8d00148586654516af19dabf50f714150 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 15:52:33 +0200 Subject: [PATCH 323/842] Improved ackMS performance method --- include/courtroom.h | 67 +++---- include/datatypes.h | 13 ++ src/courtroom.cpp | 387 ++++++++++++++++++-------------------- src/courtroom_widgets.cpp | 87 ++++----- 4 files changed, 258 insertions(+), 296 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 743ca9fd7..5cd042729 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -96,13 +96,8 @@ class Courtroom : public QMainWindow void set_widgets(); // sets font properties based on theme ini files void set_font(QWidget *widget, QString p_identifier); - // same as above, but use override color as color if it is not an empty - // string, otherwise use normal logic for color of set_font - void set_font(QWidget *widget, QString p_identifier, QString override_color); - // sets font properties for DRTextEdit (same as above but also text outline) + // sets font properties for DRTextEdit (same as above but also text outline, and alignments) void set_drtextedit_font(DRTextEdit *widget, QString p_identifier); - // same as second set_font but for drtextedit - void set_drtextedit_font(DRTextEdit *widget, QString p_identifier, QString override_color); // helper function that calls above function on the relevant widgets void set_fonts(); @@ -176,7 +171,10 @@ class Courtroom : public QMainWindow void list_areas(); - void list_sfx(); + QString current_sfx_file(); + void update_sfx_list(); + void update_sfx_widget_list(); + void clear_sfx_widget_list_selection(); void list_note_files(); @@ -308,7 +306,6 @@ class Courtroom : public QMainWindow QVector evidence_list; QVector music_list; QVector area_list; - QVector sfx_names; QVector area_names; QVector note_list; @@ -401,9 +398,9 @@ class Courtroom : public QMainWindow bool m_msg_is_first_person = false; // Cached values for chat_tick - bool chatbox_message_outline = false; - bool chatbox_message_enable_highlighting = false; - QVector chatbox_message_highlight_colors; + bool m_chatbox_message_outline = false; + bool m_chatbox_message_enable_highlighting = false; + QVector m_chatbox_message_highlight_colors; // cid and this may differ in cases of ini-editing QString current_char; @@ -452,7 +449,6 @@ class Courtroom : public QMainWindow int current_clock = -1; int timer_number = 0; - int current_sfx_id = -1; QString current_background = "gs4"; @@ -505,7 +501,11 @@ class Courtroom : public QMainWindow QListWidget *ui_mute_list = nullptr; QListWidget *ui_area_list = nullptr; QListWidget *ui_music_list = nullptr; + QListWidget *ui_sfx_list = nullptr; + QVector m_sfx_list; + QColor m_sfx_color_found; + QColor m_sfx_color_missing; QLineEdit *ui_ic_chat_message = nullptr; @@ -702,13 +702,14 @@ private slots: void on_ooc_return_pressed(); - void on_music_search_edited(QString p_text); + void on_music_search_edited(); void on_music_list_clicked(); void on_area_list_clicked(); void on_music_list_double_clicked(QModelIndex p_model); void on_area_list_double_clicked(QModelIndex p_model); - void on_sfx_search_edited(QString p_text); + void on_sfx_search_edited(); + void on_sfx_widget_list_row_changed(); void select_emote(int p_id); @@ -771,21 +772,13 @@ private slots: * @details If a sprite cannot be found for a shout button, a regular * push button is displayed for it with its shout name instead. */ - void draw_shout_buttons(); - - /** - * @brief Set the sprite of the given shout button, using the selected sprite if the button is selected. - * @param p_index Index of button to redraw. - * @details If a sprite cannot be found for the shout button, a regular push button is displayed for it with its - * shout name instead. If the index is out of bounds with respect to the number of shouts available, this method - * does nothing. - */ - void draw_shout_button(int p_index); + void reset_shout_buttons(); /** * @brief a general purpose function to toggle button selection */ - void on_shout_clicked(); + void on_shout_button_clicked(const bool); + void on_shout_button_toggled(const bool); /** * @brief Set the sprites of the effect buttons, and mark the currently @@ -794,18 +787,9 @@ private slots: * @details If a sprite cannot be found for a shout button, a regular * push button is displayed for it with its shout name instead. */ - void draw_effect_buttons(); - - /** - * @brief Set the sprite of the given effect button, using the selected sprite if the button is selected. - * @param p_index Index of button to redraw. - * @details If a sprite cannot be found for the effect button, a regular push button is displayed for it with its - * effect name instead. If the index is out of bounds with respect to the number of effects available, this method - * does nothing. - */ - void draw_effect_button(int p_index); - - void on_effect_button_clicked(); + void reset_effect_buttons(); + void on_effect_button_clicked(const bool); + void on_effect_button_toggled(const bool); void on_mute_clicked(); @@ -816,14 +800,15 @@ private slots: void on_text_color_changed(int p_color); + void on_witness_testimony_clicked(); + void on_cross_examination_clicked(); /** * @brief Set the sprites of the splash buttons. * - * @details If a sprite cannot be found for a splash button, a regular + * @details If a sprite cannot be found for a shout button, a regular * push button is displayed for it with its shout name instead. */ - void draw_judge_wtce_buttons(); - + void reset_wtce_buttons(); void on_wtce_clicked(); void on_change_character_clicked(); @@ -843,8 +828,6 @@ private slots: void on_flip_clicked(); void on_hidden_clicked(); - void on_sfx_list_clicked(QModelIndex p_index); - void on_evidence_button_clicked(); void on_evidence_delete_clicked(); diff --git a/include/datatypes.h b/include/datatypes.h index 81e1254ba..30ba1e87e 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -60,6 +60,19 @@ class ChatRecord bool system = false; bool music = false; }; + +struct SFX +{ +public: + SFX() = default; + SFX(QString p_name, QString p_file, bool p_is_found = false) + : name(p_name.trimmed()), file(p_file.trimmed()), is_found(p_is_found) + {} + + QString name; + QString file; + bool is_found; +}; } // namespace DR struct server_type diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6936ce65e..4d86ada87 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -86,7 +86,7 @@ void Courtroom::enter_courtroom(int p_cid) m_effect_state = 0; m_effect_current = 0; m_wtce_current = 0; - draw_judge_wtce_buttons(); + reset_wtce_buttons(); // setup chat on_chat_config_changed(); @@ -127,7 +127,7 @@ void Courtroom::enter_courtroom(int p_cid) list_music(); list_areas(); - list_sfx(); + update_sfx_list(); ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors @@ -437,68 +437,96 @@ void Courtroom::list_areas() } } -void Courtroom::list_sfx() +QString Courtroom::current_sfx_file() { - ui_sfx_list->clear(); - sfx_names.clear(); - current_sfx_id = -1; // Restart current SFX, because it may no longer be valid + QListWidgetItem *l_item = ui_sfx_list->currentItem(); + if (l_item == nullptr) + return "0"; + const QString l_file = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).file; + return l_file.isEmpty() ? "0" : l_file; +} - QString f_file = design_ini; +void Courtroom::update_sfx_list() +{ + // colors + m_sfx_color_found = ao_app->get_color("found_song_color", design_ini); + m_sfx_color_missing = ao_app->get_color("missing_song_color", design_ini); - QStringList sfx_list = ao_app->get_sfx_list(); + // items + m_sfx_list.clear(); + m_sfx_list.append(DR::SFX("Default", nullptr)); + m_sfx_list.append(DR::SFX("Silence", nullptr)); - QBrush found_brush(ao_app->get_color("found_song_color", f_file)); - QBrush missing_brush(ao_app->get_color("missing_song_color", f_file)); + const QStringList l_sfx_list = ao_app->get_sfx_list(); + for (const QString &i_sfx_line : l_sfx_list) + { + const QStringList l_sfx_entry = i_sfx_line.split("=", DR::SkipEmptyParts); - // Add hardcoded items - // FIXME: Rewrite - ui_sfx_list->addItem("Default"); - ui_sfx_list->addItem("Silence"); + const QString l_name = l_sfx_entry.at(l_sfx_entry.size() - 1).trimmed(); + const QString l_file = QString(l_sfx_entry.size() >= 2 ? l_sfx_entry.at(0) : nullptr).trimmed(); + const bool l_is_found = !ao_app->find_asset_path({ao_app->get_sounds_path(l_file)}, audio_extensions()).isEmpty(); + m_sfx_list.append(DR::SFX(l_name, l_file, l_is_found)); + } - sfx_names.append("1"); // Default - sfx_names.append("1"); // Silence + update_sfx_widget_list(); +} - QString default_sfx_root = ao_app->get_sounds_path("1"); - QString default_sfx_path = ao_app->find_asset_path({default_sfx_root}, audio_extensions()); - if (!default_sfx_path.isEmpty()) - { - ui_sfx_list->item(0)->setBackground(found_brush); - ui_sfx_list->item(1)->setBackground(found_brush); - } - else +void Courtroom::update_sfx_widget_list() +{ + QSignalBlocker l_blocker(ui_sfx_list); + ui_sfx_list->clear(); + + const QString l_name_filter = ui_sfx_search->text(); + for (int i = 0; i < m_sfx_list.length(); ++i) { - ui_sfx_list->item(0)->setBackground(missing_brush); - ui_sfx_list->item(1)->setBackground(missing_brush); + const DR::SFX &i_sfx = m_sfx_list.at(i); + if (!i_sfx.name.contains(l_name_filter, Qt::CaseInsensitive)) + continue; + QListWidgetItem *l_item = new QListWidgetItem; + l_item->setText(i_sfx.name); + l_item->setData(Qt::UserRole, i); + ui_sfx_list->addItem(l_item); } - // Now add the other SFXs given by the character's sound.ini - for (int n_sfx = 0; n_sfx < sfx_list.size(); ++n_sfx) - { - QStringList sfx = sfx_list.at(n_sfx).split("="); - QString i_sfx = sfx.at(0).trimmed(); - QString d_sfx = ""; - sfx_names.append(i_sfx); - if (sfx_list.at(n_sfx).split("=").size() < 2) - d_sfx = i_sfx; - else - d_sfx = sfx.at(1).trimmed(); + on_sfx_widget_list_row_changed(); +} - if (i_sfx.toLower().contains(ui_sfx_search->text().toLower())) - { - ui_sfx_list->addItem(d_sfx); - int last_index = ui_sfx_list->count() - 1; - ui_sfx_list->item(last_index)->setStatusTip(QString::number(n_sfx + 2)); +void Courtroom::clear_sfx_widget_list_selection() +{ + ui_sfx_list->setCurrentRow(-1); +} - // Apply appropriate color whether SFX exists or not - QString sfx_root = ao_app->get_sounds_path(i_sfx); - QString sfx_path = ao_app->find_asset_path({sfx_root}, audio_extensions()); +void Courtroom::on_sfx_search_edited() +{ + update_sfx_list(); +} - if (!sfx_path.isEmpty()) - ui_sfx_list->item(last_index)->setBackground(found_brush); - else - ui_sfx_list->item(last_index)->setBackground(missing_brush); +void Courtroom::on_sfx_widget_list_row_changed() +{ + const int p_current_row = ui_sfx_list->currentRow(); + ui_pre->setChecked(p_current_row != -1); + + for (int i = 0; i < ui_sfx_list->count(); ++i) + { + QListWidgetItem *l_item = ui_sfx_list->item(i); + const bool l_is_found = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).is_found; + + QColor i_color = l_is_found ? m_sfx_color_found : m_sfx_color_missing; + if (i == p_current_row) + { + // Calculate the amount of lightness it would take to light up the row. We + // also limit it to 1.0, as giving lightness values above 1.0 to QColor does + // nothing. +0.4 is just an arbitrarily chosen number. + const double l_final_lightness = qMin(1.0, i_color.lightnessF() + 0.4); + + // This is just the reverse of the above, basically. We set the colour, and we + // set the brush to have that colour. + i_color.setHslF(i_color.hueF(), i_color.saturationF(), l_final_lightness); } + + l_item->setBackground(i_color); } + ui_ic_chat_message->setFocus(); } void Courtroom::list_note_files() @@ -631,20 +659,8 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(f_side); - // packet_contents.append(ao_app->get_sfx_name(current_char, current_emote)); - // packet_contents.append(ui_sfx_search->text()); - - int row = ui_sfx_list->currentRow(); - if (row == -1 || row == 0) // default - packet_contents.append(ao_app->get_sfx_name(current_char, current_emote)); - else if (QListWidgetItem *item = ui_sfx_list->item(row)) // selection - { - double d_ind = item->statusTip().toDouble(); - int ind = int(d_ind); - qDebug() << ind; - packet_contents.append(sfx_names.at(ind)); - // packet_contents.append(sfx_names.at(row)); - } + // sfx file + packet_contents.append(current_sfx_file()); int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); @@ -716,22 +732,14 @@ void Courtroom::handle_acknowledged_ms() // reset states ui_pre->setChecked(ao_config->always_pre_enabled()); - list_sfx(); - ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors - - int old_m_shout_state = m_shout_state; - m_shout_state = 0; - draw_shout_button(old_m_shout_state - 1); - int old_m_effect_state = m_effect_state; - m_effect_state = 0; - draw_effect_button(old_m_effect_state - 1); + reset_shout_buttons(); + reset_effect_buttons(); + reset_wtce_buttons(); + clear_sfx_widget_list_selection(); - if (is_presenting_evidence) - { - is_presenting_evidence = false; - ui_evidence_present->set_image("present_disabled.png"); - } + is_presenting_evidence = false; + ui_evidence_present->set_image("present_disabled.png"); } void Courtroom::handle_chatmessage(QStringList p_contents) @@ -804,8 +812,6 @@ void Courtroom::handle_chatmessage(QStringList p_contents) f_showname = m_chatmessage[CMShowName]; } - QString f_message = f_showname + ": " + m_chatmessage[CMMessage] + "\n"; - m_effects_player->stop_all(); text_state = 0; @@ -881,11 +887,6 @@ void Courtroom::handle_chatmessage_2() // handles IC f_showname = m_chatmessage[CMShowName]; } - // Check if char.ini has color property, which overrides the theme's default - // showname color - QString f_color = ao_app->read_char_ini(real_name, "color", "[Options]", "[Time]"); - set_drtextedit_font(ui_vp_showname, "showname", f_color); - ui_vp_showname->setText(f_showname); ui_vp_message->clear(); @@ -1367,9 +1368,9 @@ void Courtroom::setup_chat() blip_pos = 0; // Cache these so chat_tick performs better - chatbox_message_outline = (ao_app->get_font_property("message_outline", fonts_ini) == 1); - chatbox_message_enable_highlighting = (ao_app->read_theme_ini_bool("enable_highlighting", cc_config_ini)); - chatbox_message_highlight_colors = ao_app->get_highlight_colors(); + m_chatbox_message_outline = (ao_app->get_font_property("message_outline", fonts_ini) == 1); + m_chatbox_message_enable_highlighting = (ao_app->read_theme_ini_bool("enable_highlighting", cc_config_ini)); + m_chatbox_message_highlight_colors = ao_app->get_highlight_colors(); QString f_gender = ao_app->get_gender(m_chatmessage[CMChrName]); @@ -1385,7 +1386,7 @@ void Courtroom::chat_tick() // note: this is called fairly often(every 60 ms when char is talking) // do not perform heavy operations here QTextCharFormat vp_message_format = ui_vp_message->currentCharFormat(); - if (chatbox_message_outline) + if (m_chatbox_message_outline) vp_message_format.setTextOutline(QPen(Qt::black, 1)); else vp_message_format.setTextOutline(Qt::NoPen); @@ -1444,7 +1445,7 @@ void Courtroom::chat_tick() ui_vp_message->textCursor().insertText(f_character, vp_message_format); } - else if (chatbox_message_enable_highlighting) + else if (m_chatbox_message_enable_highlighting) { bool highlight_found = false; bool render_character = true; @@ -1454,7 +1455,7 @@ void Courtroom::chat_tick() if (m_color_stack.isEmpty()) m_color_stack.push(""); - for (const auto &col : chatbox_message_highlight_colors) + for (const auto &col : m_chatbox_message_highlight_colors) { if (f_character == col[0][0] && m_string_color != col[1]) { @@ -1478,7 +1479,7 @@ void Courtroom::chat_tick() QString m_future_string_color = m_string_color; - for (const auto &col : chatbox_message_highlight_colors) + for (const auto &col : m_chatbox_message_highlight_colors) { if (f_character == col[0][1] && !highlight_found) { @@ -1834,21 +1835,12 @@ void Courtroom::on_ooc_return_pressed() ui_ooc_chat_message->setFocus(); } -void Courtroom::on_music_search_edited(QString p_text) +void Courtroom::on_music_search_edited() { - // preventing compiler warnings - p_text += "a"; list_music(); list_areas(); } -void Courtroom::on_sfx_search_edited(QString p_text) -{ - // preventing compiler warnings - p_text += "a"; - list_sfx(); -} - void Courtroom::on_pos_dropdown_changed(int p_index) { ui_ic_chat_message->setFocus(); @@ -1950,47 +1942,50 @@ void Courtroom::on_area_list_double_clicked(QModelIndex p_model) ui_ic_chat_message->setFocus(); } -void Courtroom::draw_shout_buttons() +void Courtroom::reset_shout_buttons() { - for (int i = 0; i < ui_shouts.size(); ++i) - draw_shout_button(i); + for (AOButton *i_button : qAsConst(ui_shouts)) + i_button->setChecked(false); + m_shout_state = 0; } -void Courtroom::draw_shout_button(int index) +void Courtroom::on_shout_button_clicked(const bool p_checked) { - if (index < 0 || index >= ui_shouts.size()) + AOButton *l_button = dynamic_cast(sender()); + if (l_button == nullptr) return; - QString shout_file; - if (m_shout_state - 1 == index) - shout_file = shout_names.at(index) + "_selected.png"; - else - shout_file = shout_names.at(index) + ".png"; + bool l_ok = false; + const int l_id = l_button->property("shout_id").toInt(&l_ok); + if (!l_ok) + return; - ui_shouts[index]->set_image(shout_file); - if (ao_app->find_theme_asset_path(shout_file).isEmpty()) - ui_shouts[index]->setText(shout_names.at(index)); - else - ui_shouts[index]->setText(""); + // disable all other buttons + for (AOButton *i_button : qAsConst(ui_shouts)) + { + if (i_button == l_button) + continue; + i_button->setChecked(false); + } + m_shout_state = p_checked ? l_id : 0; + + ui_ic_chat_message->setFocus(); } -void Courtroom::on_shout_clicked() +void Courtroom::on_shout_button_toggled(const bool p_checked) { - AOButton *f_shout_button = static_cast(sender()); - int f_shout_id = f_shout_button->property("shout_id").toInt(); - int old_m_shout_state = m_shout_state; + AOButton *l_button = dynamic_cast(sender()); + if (l_button == nullptr) + return; - // update based on current button selected - if (f_shout_id == m_shout_state) - m_shout_state = 0; - else - m_shout_state = f_shout_id; + const QString l_name = l_button->property("shout_name").toString(); + if (l_name.isEmpty()) + return; - // Redraw old and new buttons - draw_shout_button(m_shout_state - 1); - if (m_shout_state != old_m_shout_state) - draw_shout_button(old_m_shout_state - 1); - ui_ic_chat_message->setFocus(); + const QString l_image_name(QString("%1%2.png").arg(l_name, QString(p_checked ? "_selected" : nullptr))); + l_button->set_image(l_image_name); + if (ao_app->find_theme_asset_path(l_image_name).isEmpty()) + l_button->setText(l_name); } void Courtroom::on_cycle_clicked() @@ -2050,48 +2045,50 @@ void Courtroom::cycle_wtce(int p_delta) set_judge_wtce(); } -void Courtroom::draw_effect_buttons() +void Courtroom::reset_effect_buttons() { - for (int i = 0; i < ui_effects.size(); ++i) - draw_effect_button(i); + for (AOButton *i_button : qAsConst(ui_effects)) + i_button->setChecked(false); + m_effect_state = 0; } -void Courtroom::draw_effect_button(int index) +void Courtroom::on_effect_button_clicked(const bool p_checked) { - if (index < 0 || index >= ui_effects.size()) + AOButton *l_button = dynamic_cast(sender()); + if (l_button == nullptr) return; - QString effect_file; - if (m_effect_state - 1 == index) - effect_file = effect_names.at(index) + "_pressed.png"; - else - effect_file = effect_names.at(index) + ".png"; + bool l_ok = false; + const int l_id = l_button->property("effect_id").toInt(&l_ok); + if (!l_ok) + return; - ui_effects[index]->set_image(effect_file); - if (ao_app->find_theme_asset_path(effect_file).isEmpty()) - ui_effects[index]->setText(effect_names.at(index)); - else - ui_effects[index]->setText(""); + // disable all other buttons + for (AOButton *i_button : qAsConst(ui_effects)) + { + if (i_button == l_button) + continue; + i_button->setChecked(false); + } + + m_effect_state = p_checked ? l_id : 0; + ui_ic_chat_message->setFocus(); } -void Courtroom::on_effect_button_clicked() +void Courtroom::on_effect_button_toggled(const bool p_checked) { - AOButton *f_button = static_cast(this->sender()); - - int f_effect_id = f_button->property("effect_id").toInt(); - int old_m_effect_state = m_effect_state; - - if (m_effect_state == f_effect_id) - m_effect_state = 0; - else - m_effect_state = f_effect_id; + AOButton *l_button = dynamic_cast(sender()); + if (l_button == nullptr) + return; - // Redraw old and new buttons - draw_effect_button(m_effect_state - 1); - if (m_effect_state != old_m_effect_state) - draw_effect_button(old_m_effect_state - 1); + const QString l_name = l_button->property("effect_name").toString(); + if (l_name.isEmpty()) + return; - ui_ic_chat_message->setFocus(); + const QString l_image_name(QString("%1%2.png").arg(l_name, QString(p_checked ? "_pressed" : nullptr))); + l_button->set_image(l_image_name); + if (ao_app->find_theme_asset_path(l_image_name).isEmpty()) + l_button->setText(l_name); } void Courtroom::on_mute_clicked() @@ -2146,7 +2143,27 @@ void Courtroom::on_text_color_changed(int p_color) ui_ic_chat_message->setFocus(); } -void Courtroom::draw_judge_wtce_buttons() +void Courtroom::on_witness_testimony_clicked() +{ + if (is_client_muted) + return; + + ao_app->send_server_packet(new AOPacket("RT#testimony1#%")); + + ui_ic_chat_message->setFocus(); +} + +void Courtroom::on_cross_examination_clicked() +{ + if (is_client_muted) + return; + + ao_app->send_server_packet(new AOPacket("RT#testimony2#%")); + + ui_ic_chat_message->setFocus(); +} + +void Courtroom::reset_wtce_buttons() { for (int i = 0; i < wtce_names.size(); ++i) { @@ -2158,6 +2175,8 @@ void Courtroom::draw_judge_wtce_buttons() ui_wtce[i]->setText(""); } + m_wtce_current = 0; + // Unlike the other reset functions, the judge buttons are of immediate // action and thus are immediately unpressed after being pressed. // Therefore, we do not need to handle displaying a "_selected.png" @@ -2348,52 +2367,6 @@ void Courtroom::closeEvent(QCloseEvent *event) QMainWindow::closeEvent(event); } -void Courtroom::on_sfx_list_clicked(QModelIndex p_index) -{ - if (p_index.isValid()) - ui_pre->setChecked(p_index.isValid()); - - QListWidgetItem *new_sfx = ui_sfx_list->currentItem(); - - QBrush found_brush(ao_app->get_color("found_song_color", design_ini)); - QBrush missing_brush(ao_app->get_color("missing_song_color", design_ini)); - - if (current_sfx_id != -1) - { - QListWidgetItem *old_sfx = ui_sfx_list->item(current_sfx_id); - - // Apply appropriate color whether SFX exists or not - QString sfx_root = ao_app->get_sounds_path(sfx_names.at(current_sfx_id)); - QString sfx_path = ao_app->find_asset_path({sfx_root}, audio_extensions()); - - if (!sfx_path.isEmpty()) - old_sfx->setBackground(found_brush); - else - old_sfx->setBackground(missing_brush); - } - - // Grab the colour of the selected row's brush. - QBrush selected_brush = new_sfx->background(); - QColor selected_col = selected_brush.color(); - - // Calculate the amount of lightness it would take to light up the row. We - // also limit it to 1.0, as giving lightness values above 1.0 to QColor does - // nothing. +0.4 is just an arbitrarily chosen number. - double final_lightness = qMin(1.0, selected_col.lightnessF() + 0.4); - - // This is just the reverse of the above, basically. We set the colour, and we - // set the brush to have that colour. - selected_col.setHslF(selected_col.hueF(), selected_col.saturationF(), final_lightness); - selected_brush.setColor(selected_col); - - // Finally, we set the selected SFX's background to be the lightened-up brush. - new_sfx->setBackground(selected_brush); - - current_sfx_id = ui_sfx_list->currentRow(); - - ui_ic_chat_message->setFocus(); -} - void Courtroom::on_set_notes_clicked() { if (note_scroll_area->isHidden()) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 96bbb9c88..dc075035a 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -288,7 +288,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); - connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited(QString))); + connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited())); connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); @@ -304,7 +304,7 @@ void Courtroom::connect_widgets() connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); - connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_sfx_list_clicked(QModelIndex))); + connect(ui_sfx_list, SIGNAL(currentRowChanged(int)), this, SLOT(on_sfx_widget_list_row_changed())); connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); @@ -688,7 +688,7 @@ void Courtroom::set_widgets() // ui_shouts[0]->show(); // ui_shouts[1]->show(); // ui_shouts[2]->show(); - draw_shout_buttons(); + reset_shout_buttons(); set_size_and_pos(ui_shout_up, "shout_up"); ui_shout_up->set_image("shoutup.png"); @@ -717,7 +717,7 @@ void Courtroom::set_widgets() { set_size_and_pos(ui_effects[i], effect_names[i]); } - draw_effect_buttons(); + reset_effect_buttons(); set_size_and_pos(ui_effect_up, "effect_up"); ui_effect_up->set_image("effectup.png"); @@ -757,7 +757,7 @@ void Courtroom::set_widgets() qDebug() << "AA: single wtce"; } set_judge_wtce(); - draw_judge_wtce_buttons(); + reset_wtce_buttons(); for (int i = 0; i < free_block_names.size(); ++i) { @@ -1169,16 +1169,17 @@ void Courtroom::load_effects() for (int i = 0; i < ui_effects.size(); ++i) { - ui_effects[i] = new AOButton(this, ao_app); - ui_effects[i]->setProperty("effect_id", i + 1); - ui_effects[i]->stackUnder(ui_effect_up); - ui_effects[i]->stackUnder(ui_effect_down); + AOButton *l_button = new AOButton(this, ao_app); + ui_effects.replace(i, l_button); + l_button->setCheckable(true); + l_button->setProperty("effect_id", i + 1); + l_button->stackUnder(ui_effect_up); + l_button->stackUnder(ui_effect_down); + + connect(l_button, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked(bool))); + connect(l_button, SIGNAL(toggled(bool)), this, SLOT(on_effect_button_toggled(bool))); } - // And connect their actions - for (auto &effect : ui_effects) - connect(effect, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked())); - // And add names effect_names.clear(); for (int i = 1; i <= ui_effects.size(); ++i) @@ -1186,8 +1187,11 @@ void Courtroom::load_effects() QStringList names = ao_app->get_effect(i); if (!names.isEmpty()) { - QString name = names.at(0).trimmed(); - effect_names.append(name); + const QString l_name = names.at(0).trimmed(); + effect_names.append(l_name); + AOButton *l_button = ui_effects.at(i - 1); + l_button->setProperty("effect_name", l_name); + Q_EMIT l_button->toggled(l_button->isChecked()); } } } @@ -1236,16 +1240,17 @@ void Courtroom::load_shouts() for (int i = 0; i < ui_shouts.size(); ++i) { - ui_shouts[i] = new AOButton(this, ao_app); - ui_shouts[i]->setProperty("shout_id", i + 1); - ui_shouts[i]->stackUnder(ui_shout_up); - ui_shouts[i]->stackUnder(ui_shout_down); + AOButton *l_button = new AOButton(this, ao_app); + ui_shouts.replace(i, l_button); + l_button->setCheckable(true); + l_button->setProperty("shout_id", i + 1); + l_button->stackUnder(ui_shout_up); + l_button->stackUnder(ui_shout_down); + + connect(l_button, SIGNAL(clicked(bool)), this, SLOT(on_shout_button_clicked(bool))); + connect(l_button, SIGNAL(toggled(bool)), this, SLOT(on_shout_button_toggled(bool))); } - // And connect their actions - for (auto &shout : ui_shouts) - connect(shout, SIGNAL(clicked(bool)), this, SLOT(on_shout_clicked())); - // And add names shout_names.clear(); for (int i = 1; i <= ui_shouts.size(); ++i) @@ -1255,8 +1260,11 @@ void Courtroom::load_shouts() { qDebug() << "SHOUT " << name << " " << ui_shouts[i - 1]; shout_names.append(name); - widget_names[name] = ui_shouts[i - 1]; - ui_shouts[i - 1]->setObjectName(name); + AOButton *l_button = ui_shouts.at(i - 1); + widget_names.insert(name, l_button); + l_button->setObjectName(name); + l_button->setProperty("shout_name", name); + Q_EMIT l_button->toggled(l_button->isChecked()); } } qDebug() << widget_names; @@ -1386,11 +1394,6 @@ void Courtroom::set_dropdowns() } void Courtroom::set_font(QWidget *widget, QString p_identifier) -{ - set_font(widget, p_identifier, ""); -} - -void Courtroom::set_font(QWidget *widget, QString p_identifier, QString override_color) { QString design_file = fonts_ini; QString class_name = widget->metaObject()->className(); @@ -1409,32 +1412,22 @@ void Courtroom::set_font(QWidget *widget, QString p_identifier, QString override } widget->setFont(QFont(font_name, f_weight)); - if (override_color.isEmpty()) - { - QString color = ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); - if (color.isEmpty()) - color = "255, 255, 255"; - override_color = "rgba(" + color + ", 255)"; - } + QString font_color = ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); + if (font_color.isEmpty()) + font_color = "255, 255, 255"; + QString color = "rgba(" + font_color + ", 255)"; int bold = ao_app->get_font_property(p_identifier + "_bold", design_file); QString is_bold = (bold == 1 ? "bold" : ""); - QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: " + override_color + - ";\n" - "font: " + - is_bold + "; }"; + QString style_sheet_string = class_name + " { " + "background-color: rgba(0, 0, 0, 0);\n" + "color: " + color + + ";\n" + "font: " + is_bold + ";" + " }"; widget->setStyleSheet(style_sheet_string); } void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) { - set_drtextedit_font(widget, p_identifier, ""); -} - -void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier, QString override_color) -{ - set_font(widget, p_identifier, override_color); + set_font(widget, p_identifier); // Do outlines bool outline = (ao_app->get_font_property(p_identifier + "_outline", fonts_ini) == 1); widget->set_outline(outline); From 5dd988c3511717b4dd746da9c6954251779c9947 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 17:26:45 +0200 Subject: [PATCH 324/842] Current sfx now return the proper default sound if selected --- src/courtroom.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 65f42bed3..95d3777c6 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -441,9 +441,9 @@ QString Courtroom::current_sfx_file() { QListWidgetItem *l_item = ui_sfx_list->currentItem(); if (l_item == nullptr) - return "0"; - const QString l_file = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).file; - return l_file.isEmpty() ? "0" : l_file; + return nullptr; + return ui_sfx_list->currentRow() == 0 ? ao_app->get_sfx_name(current_char, current_emote) + : m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).file; } void Courtroom::update_sfx_list() @@ -1968,7 +1968,7 @@ void Courtroom::on_shout_button_clicked(const bool p_checked) i_button->setChecked(false); } m_shout_state = p_checked ? l_id : 0; - + ui_ic_chat_message->setFocus(); } From 5612521545755a67419d6cb9e5d66a80567c7e43 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 17:30:30 +0200 Subject: [PATCH 325/842] Default should now always be properly sent --- src/courtroom.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 95d3777c6..ad05d1a1f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -442,8 +442,7 @@ QString Courtroom::current_sfx_file() QListWidgetItem *l_item = ui_sfx_list->currentItem(); if (l_item == nullptr) return nullptr; - return ui_sfx_list->currentRow() == 0 ? ao_app->get_sfx_name(current_char, current_emote) - : m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).file; + return m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).file; } void Courtroom::update_sfx_list() @@ -454,7 +453,7 @@ void Courtroom::update_sfx_list() // items m_sfx_list.clear(); - m_sfx_list.append(DR::SFX("Default", nullptr)); + m_sfx_list.append(DR::SFX("Default", ao_app->get_sfx_name(current_char, current_emote))); m_sfx_list.append(DR::SFX("Silence", nullptr)); const QStringList l_sfx_list = ao_app->get_sfx_list(); From 2641df07e6c0c3ae8242396d1635e18de689d544 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 17:55:06 +0200 Subject: [PATCH 326/842] Fixed showname attempting to update forever --- src/courtroom.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 0b6a13372..e98741588 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -617,6 +617,7 @@ void Courtroom::on_ic_message_return_pressed() if (!m_showname_sent) { + m_showname_sent = true; send_showname_packet(ao_config->showname()); } From 5f089b6e5a341186f7e017c042b860a3e5004de3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 18:05:27 +0200 Subject: [PATCH 327/842] SFX search should now be more responsive --- include/courtroom.h | 2 +- src/courtroom.cpp | 2 +- src/courtroom_widgets.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 5cd042729..094fad409 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -708,7 +708,7 @@ private slots: void on_music_list_double_clicked(QModelIndex p_model); void on_area_list_double_clicked(QModelIndex p_model); - void on_sfx_search_edited(); + void on_sfx_search_editing_finished(); void on_sfx_widget_list_row_changed(); void select_emote(int p_id); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ad05d1a1f..e7217cbe6 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -495,7 +495,7 @@ void Courtroom::clear_sfx_widget_list_selection() ui_sfx_list->setCurrentRow(-1); } -void Courtroom::on_sfx_search_edited() +void Courtroom::on_sfx_search_editing_finished() { update_sfx_list(); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index dc075035a..5f72e1bbb 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -288,7 +288,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); - connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited())); + connect(ui_sfx_search, SIGNAL(editingFinished()), this, SLOT(on_sfx_search_editing_finished())); connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); From 4dce5c979a25c76edcd65c596a1a4e33f6b703a7 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 19:24:50 +0200 Subject: [PATCH 328/842] Fixed default sound not properly being updated based on emote --- include/courtroom.h | 1 + src/courtroom.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 094fad409..12a67921f 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -504,6 +504,7 @@ class Courtroom : public QMainWindow QListWidget *ui_sfx_list = nullptr; QVector m_sfx_list; + const QString m_sfx_default_file = "__DEFAULT__"; QColor m_sfx_color_found; QColor m_sfx_color_missing; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index e7217cbe6..70f3aa3df 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -442,7 +442,8 @@ QString Courtroom::current_sfx_file() QListWidgetItem *l_item = ui_sfx_list->currentItem(); if (l_item == nullptr) return nullptr; - return m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).file; + const QString l_file = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).file; + return l_file == m_sfx_default_file ? ao_app->get_sfx_name(current_char, current_emote) : l_file; } void Courtroom::update_sfx_list() @@ -453,7 +454,7 @@ void Courtroom::update_sfx_list() // items m_sfx_list.clear(); - m_sfx_list.append(DR::SFX("Default", ao_app->get_sfx_name(current_char, current_emote))); + m_sfx_list.append(DR::SFX("Default", m_sfx_default_file)); m_sfx_list.append(DR::SFX("Silence", nullptr)); const QStringList l_sfx_list = ao_app->get_sfx_list(); @@ -659,6 +660,7 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(f_side); // sfx file + qDebug() << "foo" << current_sfx_file(); packet_contents.append(current_sfx_file()); int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); From 5c2411f4c849e079d1607043b6bdb6ddfae7166b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 19:50:11 +0200 Subject: [PATCH 329/842] Emote switching should now properly reset to default sound and enable pre if needed --- include/courtroom.h | 3 ++- src/courtroom.cpp | 17 ++++++++++++----- src/emotes.cpp | 2 ++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 12a67921f..3753dc95e 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -174,7 +174,8 @@ class Courtroom : public QMainWindow QString current_sfx_file(); void update_sfx_list(); void update_sfx_widget_list(); - void clear_sfx_widget_list_selection(); + void select_default_sfx(); + void clear_sfx_selection(); void list_note_files(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 70f3aa3df..298d06f42 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -128,8 +128,7 @@ void Courtroom::enter_courtroom(int p_cid) list_music(); list_areas(); update_sfx_list(); - - ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors + select_default_sfx(); // unmute audio suppress_audio(false); @@ -491,7 +490,14 @@ void Courtroom::update_sfx_widget_list() on_sfx_widget_list_row_changed(); } -void Courtroom::clear_sfx_widget_list_selection() +void Courtroom::select_default_sfx() +{ + if (ui_sfx_list->count() == 0) + return; + ui_sfx_list->setCurrentRow(0); +} + +void Courtroom::clear_sfx_selection() { ui_sfx_list->setCurrentRow(-1); } @@ -504,7 +510,6 @@ void Courtroom::on_sfx_search_editing_finished() void Courtroom::on_sfx_widget_list_row_changed() { const int p_current_row = ui_sfx_list->currentRow(); - ui_pre->setChecked(p_current_row != -1); for (int i = 0; i < ui_sfx_list->count(); ++i) { @@ -514,6 +519,8 @@ void Courtroom::on_sfx_widget_list_row_changed() QColor i_color = l_is_found ? m_sfx_color_found : m_sfx_color_missing; if (i == p_current_row) { + ui_pre->setChecked(ui_pre->isChecked() || l_is_found); + // Calculate the amount of lightness it would take to light up the row. We // also limit it to 1.0, as giving lightness values above 1.0 to QColor does // nothing. +0.4 is just an arbitrarily chosen number. @@ -737,7 +744,7 @@ void Courtroom::handle_acknowledged_ms() reset_shout_buttons(); reset_effect_buttons(); reset_wtce_buttons(); - clear_sfx_widget_list_selection(); + clear_sfx_selection(); is_presenting_evidence = false; ui_evidence_present->set_image("present_disabled.png"); diff --git a/src/emotes.cpp b/src/emotes.cpp index dcdf31254..8048051ef 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -170,6 +170,8 @@ void Courtroom::select_emote(int p_id) else ui_pre->setChecked(false); + select_default_sfx(); + ui_emote_dropdown->setCurrentIndex(current_emote); ui_ic_chat_message->setFocus(); From 17f3a13fcae576085b464045ce8e7165c04ae830 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 20:01:23 +0200 Subject: [PATCH 330/842] Added networkmanager.h just in case --- src/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 7d8b10306..21ff3cea5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,7 @@ #include "aoapplication.h" + #include "lobby.h" +#include "networkmanager.h" int main(int argc, char *argv[]) { From 6f6c9ea9427ff4e2b999cfe1a38c69b7d76ac4a0 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 21:19:20 +0200 Subject: [PATCH 331/842] Fixed a mistake when resolving conflicts --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 376529603..49a661014 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -63,7 +63,7 @@ void Courtroom::enter_courtroom(int p_cid) ao_config->set_showname_placeholder(l_final_showname); QStringList l_content{l_chr_name, l_final_showname}; - AOPacket *l_packet = new AOPacket("chrini", l_final_showname); + AOPacket *l_packet = new AOPacket("chrini", l_content); ao_app->send_server_packet(l_packet); } From e8693c91e3ee61798d9813a8584fae6016dea1da Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 21:25:13 +0200 Subject: [PATCH 332/842] Removed debug information that was unneeded --- src/courtroom.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 49a661014..a72598bb6 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -707,7 +707,6 @@ void Courtroom::on_ic_message_return_pressed() packet_contents.append(f_side); // sfx file - qDebug() << "foo" << current_sfx_file(); packet_contents.append(current_sfx_file()); int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); From aa631d2e2c7db5f1f8a3b1d7199f52104fb37967 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 11 May 2021 22:32:52 +0200 Subject: [PATCH 333/842] Added chrini as FL field --- include/aoapplication.h | 1 + src/courtroom.cpp | 9 ++++++--- src/packet_distribution.cpp | 8 ++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index f2affb9ed..f3bdecbe5 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -52,6 +52,7 @@ class AOApplication : public QApplication #endif bool m_FL_showname_enabled = false; + bool m_FL_chrini_enabled = false; ///////////////loading info/////////////////// diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a72598bb6..217bd9309 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -62,9 +62,12 @@ void Courtroom::enter_courtroom(int p_cid) ao_app->discord->set_character_name(l_final_showname); ao_config->set_showname_placeholder(l_final_showname); - QStringList l_content{l_chr_name, l_final_showname}; - AOPacket *l_packet = new AOPacket("chrini", l_content); - ao_app->send_server_packet(l_packet); + if (ao_app->m_FL_chrini_enabled) + { + QStringList l_content{l_chr_name, l_final_showname}; + AOPacket *l_packet = new AOPacket("chrini", l_content); + ao_app->send_server_packet(l_packet); + } } current_char = l_chr_name; diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index f4d97590e..829031bfe 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -143,6 +143,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ackMS_enabled = false; #endif m_FL_showname_enabled = false; + m_FL_chrini_enabled = false; AOPacket *hi_packet = new AOPacket("HI#" + f_hdid + "#%"); send_server_packet(hi_packet); @@ -168,10 +169,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) else if (header == "FL") { #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - if (f_packet.contains("ackMS", Qt::CaseInsensitive)) - ackMS_enabled = true; - if (f_packet.contains("showname", Qt::CaseInsensitive)) - m_FL_showname_enabled = true; + ackMS_enabled = f_packet.contains("ackMS", Qt::CaseInsensitive); + m_FL_showname_enabled = f_packet.contains("showname", Qt::CaseInsensitive); + m_FL_chrini_enabled = f_packet.contains("chrini", Qt::CaseInsensitive); #endif } else if (header == "PN") From b5b1da2183d5f09f3c6ef0902b286356b8da924c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 12 May 2021 00:54:20 +0200 Subject: [PATCH 334/842] Added character sound pathing to SfxPlayer --- include/aosfxplayer.h | 3 +++ src/aoevidencedisplay.cpp | 2 +- src/aosfxplayer.cpp | 31 ++++++++++++++++++++++++++++--- src/courtroom.cpp | 18 +++++++++--------- src/file_functions.cpp | 4 ++-- 5 files changed, 43 insertions(+), 15 deletions(-) diff --git a/include/aosfxplayer.h b/include/aosfxplayer.h index 77932c2c9..ef2b5b465 100644 --- a/include/aosfxplayer.h +++ b/include/aosfxplayer.h @@ -10,5 +10,8 @@ class AOSfxPlayer : public AOObject AOSfxPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); void play(QString p_file); + void play_effect(QString p_effect); + void play_character_effect(QString p_character, QString p_effect); + void play_character_effect(QStringList p_character_list, QString p_effect); void stop_all(); }; diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index 35887f620..f236b2c8c 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -54,7 +54,7 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid this->setMovie(evidence_movie); evidence_movie->start(); - sfx_player->play(ao_app->get_sfx("evidence_present")); + sfx_player->play_effect(ao_app->get_sfx("evidence_present")); } void AOEvidenceDisplay::frame_change(int p_frame) diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index ada9912b4..62b148595 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -10,10 +10,35 @@ AOSfxPlayer::AOSfxPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) {} -void AOSfxPlayer::play(QString p_name) +void AOSfxPlayer::play(QString p_file) { - const QString file = ao_app->find_asset_path({ao_app->get_sounds_path(p_name)}, audio_extensions()); - DRAudioEngine::get_family(DRAudio::Family::FEffect)->play_stream(file); + DRAudioEngine::get_family(DRAudio::Family::FEffect)->play_stream(p_file); +} + +void AOSfxPlayer::play_effect(QString p_effect) +{ + const QString l_target_file = ao_app->find_asset_path({ao_app->get_sounds_path(p_effect)}, audio_extensions()); + play(l_target_file); +} + +void AOSfxPlayer::play_character_effect(QString p_chr, QString p_effect) +{ + play_character_effect(QStringList{p_chr}, p_effect); +} + +void AOSfxPlayer::play_character_effect(QStringList p_chr_list, QString p_effect) +{ + QStringList l_path_list; + for (const QString &i_character : p_chr_list) + l_path_list.append(ao_app->get_character_path(i_character, QString("sounds/%1").arg(p_effect))); + + const QString l_target_file = ao_app->find_asset_path(l_path_list, audio_extensions()); + if (l_target_file.isEmpty()) + { + play_effect(p_effect); + return; + } + play(l_target_file); } void AOSfxPlayer::stop_all() diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 217bd9309..4d1955085 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1113,7 +1113,7 @@ void Courtroom::handle_chatmessage_3() if (overlay_sfx == "") overlay_sfx = ao_app->get_sfx(s_eff); // qDebug() << overlay_sfx << ao_app->get_sfx(s_eff); - m_effects_player->play(overlay_sfx); + m_effects_player->play_effect(overlay_sfx); ui_vp_effect->set_play_once(once); if (overlay_name == "") overlay_name = s_eff; @@ -1619,11 +1619,11 @@ void Courtroom::hide_testimony() void Courtroom::play_sfx() { - QString sfx_name = m_chatmessage[CMSoundName]; - if (sfx_name == "1" || sfx_name == "0") + const QString l_effect = m_chatmessage[CMSoundName]; + if (l_effect.isEmpty() || l_effect == "0" || l_effect == "1") return; - - m_effects_player->play(sfx_name); + const QString l_chr = m_chatmessage[CMChrName]; + m_effects_player->play_character_effect(l_chr, l_effect); } void Courtroom::set_text_color() @@ -1751,7 +1751,7 @@ void Courtroom::handle_wtce(QString p_wtce) p_wtce.chop(1); // looking for the 'testimony' part if (p_wtce == "testimony") { - m_effects_player->play(ao_app->get_sfx(wtce_names[index - 1])); + m_effects_player->play_effect(ao_app->get_sfx(wtce_names[index - 1])); ui_vp_wtce->play(wtce_names[index - 1]); if (index == 1) { @@ -1864,15 +1864,15 @@ void Courtroom::on_ooc_return_pressed() } else if (ooc_message.startsWith("/rollp")) { - m_effects_player->play(ao_app->get_sfx("dice")); + m_effects_player->play_effect(ao_app->get_sfx("dice")); } else if (ooc_message.startsWith("/roll")) { - m_effects_player->play(ao_app->get_sfx("dice")); + m_effects_player->play_effect(ao_app->get_sfx("dice")); } else if (ooc_message.startsWith("/coinflip")) { - m_effects_player->play(ao_app->get_sfx("coinflip")); + m_effects_player->play_effect(ao_app->get_sfx("coinflip")); } else if (ooc_message.startsWith("/tr ")) { diff --git a/src/file_functions.cpp b/src/file_functions.cpp index 1c893a984..e542a3b6e 100644 --- a/src/file_functions.cpp +++ b/src/file_functions.cpp @@ -1,8 +1,8 @@ +#include "file_functions.h" + #include #include -#include "file_functions.h" - QStringList animated_or_static_extensions() { return QStringList{".webp", ".apng", ".gif", ".png"}; From fb6e5787b0add65f3c5a8e6e24ece3b5794243d9 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 12 May 2021 01:12:46 +0200 Subject: [PATCH 335/842] Small optimization, needless variable declaration --- src/aosfxplayer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index 62b148595..5deef0023 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -17,8 +17,7 @@ void AOSfxPlayer::play(QString p_file) void AOSfxPlayer::play_effect(QString p_effect) { - const QString l_target_file = ao_app->find_asset_path({ao_app->get_sounds_path(p_effect)}, audio_extensions()); - play(l_target_file); + play(ao_app->find_asset_path({ao_app->get_sounds_path(p_effect)}, audio_extensions())); } void AOSfxPlayer::play_character_effect(QString p_chr, QString p_effect) From cedf6245ef7c047e1b8938d86bffd4d25565a7d9 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 11 May 2021 20:27:51 -0400 Subject: [PATCH 336/842] Make find_theme_asset_path ignore gamemodes and/or time of day folders if empty --- src/path_functions.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index dd0f2db9f..807e714ef 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -135,14 +135,23 @@ QString AOApplication::find_asset_path(QStringList possible_roots, QStringList p QString AOApplication::find_theme_asset_path(QString p_file, QStringList exts) { - QStringList paths{ - get_base_path() + "themes/" + get_theme() + "/gamemodes/" + get_gamemode() + "/times/" + get_timeofday() + "/" + - p_file, - get_base_path() + "themes/" + get_theme() + "/gamemodes/" + get_gamemode() + "/" + p_file, - get_base_path() + "themes/" + get_theme() + "/times/" + get_timeofday() + "/" + p_file, - get_base_path() + "themes/" + get_theme() + "/" + p_file, - get_base_path() + "themes/default/" + p_file, - }; + QStringList paths; + + // Only add gamemode and/or time of day if non empty. + QString gamemode = get_gamemode(); + QString timeofday = get_timeofday(); + QString root = get_base_path() + "themes/" + get_theme(); + if (!gamemode.isEmpty()) { + if (!timeofday.isEmpty()) { + paths.append(root + "/gamemodes/" + gamemode + "/times/" + timeofday + "/" + p_file); + } + paths.append(root + "/gamemodes/" + get_gamemode() + "/" + p_file); + } + if (!timeofday.isEmpty()) { + paths.append(root + "/times/" + timeofday + "/" + p_file); + } + paths.append(root + "/" + p_file); + paths.append(get_base_path() + "themes/default/" + p_file); return find_asset_path(paths, exts); } From 2a9052ec8015ea112ee0d6695176a58e0dec39f4 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 11 May 2021 21:34:23 -0400 Subject: [PATCH 337/842] Assert parent folder exists for root in find_asset_path+Assert default exists during find_theme_asset_path --- src/path_functions.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 807e714ef..865c7d0a7 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -122,6 +122,12 @@ QString AOApplication::find_asset_path(QStringList possible_roots, QStringList p { for (QString &root : possible_roots) { + // Check if parent folder actually exists. If it does not, none of the following files would exist + QFileInfo file(root); + QString file_parent_dir = sanitize_path(get_case_sensitive_path(file.absolutePath())); + if (!dir_exists(file_parent_dir)) + continue; + for (QString &ext : possible_exts) { QString full_path = sanitize_path(get_case_sensitive_path(root + ext)); @@ -145,13 +151,18 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList exts) if (!timeofday.isEmpty()) { paths.append(root + "/gamemodes/" + gamemode + "/times/" + timeofday + "/" + p_file); } - paths.append(root + "/gamemodes/" + get_gamemode() + "/" + p_file); + paths.append(root + "/gamemodes/" + gamemode + "/" + p_file); } if (!timeofday.isEmpty()) { paths.append(root + "/times/" + timeofday + "/" + p_file); } paths.append(root + "/" + p_file); - paths.append(get_base_path() + "themes/default/" + p_file); + + // Check if default folder exists. We do this here as it is cheaper than doing it in find_asset_path + // (as we know there should not be capitalization or folder jumping shenanigans here. + QString default_theme_path = get_base_path() + "themes/default/"; + if (dir_exists(default_theme_path)) + paths.append(default_theme_path + p_file); return find_asset_path(paths, exts); } From eaf5cce256469ee1924784bf5ef596359351799b Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 11 May 2021 21:50:17 -0400 Subject: [PATCH 338/842] Make sanitize_path only be called for root in find_asset_path --- src/path_functions.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 865c7d0a7..1866c9110 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -122,15 +122,21 @@ QString AOApplication::find_asset_path(QStringList possible_roots, QStringList p { for (QString &root : possible_roots) { + // We can assume that possible_exts will only be populated with hardcoded strings. + // Therefore, the only place where sanitize_path could catch something bad is in the root. + // So, check that now, so we don't need to check later. + if (sanitize_path(root).isEmpty()) + continue; + // Check if parent folder actually exists. If it does not, none of the following files would exist QFileInfo file(root); - QString file_parent_dir = sanitize_path(get_case_sensitive_path(file.absolutePath())); + QString file_parent_dir = get_case_sensitive_path(file.absolutePath()); if (!dir_exists(file_parent_dir)) continue; for (QString &ext : possible_exts) { - QString full_path = sanitize_path(get_case_sensitive_path(root + ext)); + QString full_path = get_case_sensitive_path(root + ext); if (file_exists(full_path)) return full_path; } From 47e5c0d0b1844d8ea7700be4335f54ca9e09dfeb Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 11 May 2021 22:01:30 -0400 Subject: [PATCH 339/842] Fix invalid slot names --- src/courtroom_widgets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index dc879f73d..700c1755a 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -256,7 +256,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(showname_changed(QString)), this, SLOT(on_showname_changed(QString))); connect(ao_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); - connect(ui_ic_chat_showname, SIGNAL(editingFinished()), ao_config, SLOT(on_ic_showname_editing_finished())); + connect(ui_ic_chat_showname, SIGNAL(editingFinished()), this, SLOT(on_ic_showname_editing_finished())); connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ic_message_return_pressed())); connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); @@ -296,7 +296,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(log_display_music_switch_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); - connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); + connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited())); connect(ui_sfx_search, SIGNAL(editingFinished()), this, SLOT(on_sfx_search_editing_finished())); connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); From 9596741b96b9e9163f61eba2b7047a2106a05fa6 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 12 May 2021 21:14:46 +0200 Subject: [PATCH 340/842] Initial implementation --- include/aoapplication.h | 38 ++-- include/aocharmovie.h | 14 +- include/aoemotebutton.h | 3 +- include/aosfxplayer.h | 1 - include/aoshoutplayer.h | 2 +- include/courtroom.h | 14 +- include/datatypes.h | 14 ++ src/aocharmovie.cpp | 67 +++---- src/aoemotebutton.cpp | 15 +- src/aosfxplayer.cpp | 14 +- src/aoshoutplayer.cpp | 23 +-- src/courtroom.cpp | 93 ++++----- src/emotes.cpp | 68 +++---- src/packet_distribution.cpp | 5 +- src/path_functions.cpp | 12 +- src/text_file_functions.cpp | 369 +++++++++++++----------------------- 16 files changed, 302 insertions(+), 450 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index f3bdecbe5..e94f35085 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -45,12 +45,11 @@ class AOApplication : public QApplication void send_ms_packet(AOPacket *p_packet); void send_server_packet(AOPacket *p_packet, bool encoded = true); -#ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release ///////////////server metadata//////////////// +#ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - bool ackMS_enabled = false; + bool m_FL_ackMS_enabled = false; #endif - bool m_FL_showname_enabled = false; bool m_FL_chrini_enabled = false; @@ -110,6 +109,7 @@ class AOApplication : public QApplication // implementation in path_functions.cpp QString get_base_path(); QString get_data_path(); + QString get_character_folder_path(QString character); QString get_character_path(QString p_character, QString p_file); // QString get_demothings_path(); QString get_sounds_path(QString p_file); @@ -368,7 +368,9 @@ class AOApplication : public QApplication QString get_sfx(QString p_identifier); // Returns the value of p_search_line within target_tag and terminator_tag - QString read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag); + + QVariant read_char_ini(QString character, QString group, QString key, QVariant default_value); + QVariant read_char_ini(QString character, QString group, QString key); // Returns the text between target_tag and terminator_tag in p_file QString get_stylesheet(QString target_tag, QString p_file); @@ -403,17 +405,14 @@ class AOApplication : public QApplication // Returns the name of p_char QString get_char_name(QString p_char); - // Returns the total amount of emotes of p_char - int get_emote_number(QString p_char); + QStringList get_char_include(QString character); - // Returns the emote comment of p_char's p_emote - QString get_emote_comment(QString p_char, int p_emote); + QStringList get_char_include_tree(QString character); - // Returns the base name of p_char's p_emote - QString get_emote(QString p_char, int p_emote); + // Returns p_char's gender + QString get_gender(QString p_char); - // Returns the preanimation name of p_char's p_emote - QString get_pre_emote(QString p_char, int p_emote); + QVector get_emote_list(QString p_chr); // Returns x,y offset for effect p_effect QStringList get_effect_offset(QString p_char, int p_effect); @@ -421,21 +420,6 @@ class AOApplication : public QApplication // Returns overlay at p_effect in char_path/overlay QStringList get_overlay(QString p_char, int p_effect); - // Returns the sfx of p_char's p_emote - QString get_sfx_name(QString p_char, int p_emote); - - // Not in use - int get_sfx_delay(QString p_char, int p_emote); - - // Returns the modifier for p_char's p_emote - int get_emote_mod(QString p_char, int p_emote); - - // Returns the desk modifier for p_char's p_emote - int get_desk_mod(QString p_char, int p_emote); - - // Returns p_char's gender - QString get_gender(QString p_char); - signals: void reload_theme(); diff --git a/include/aocharmovie.h b/include/aocharmovie.h index 78312acfe..ad2f0ff47 100644 --- a/include/aocharmovie.h +++ b/include/aocharmovie.h @@ -16,13 +16,13 @@ class AOCharMovie : public QLabel public: AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app); - void play(QString p_chr, QString p_emote, QString emote_prefix, bool p_play_once); - void play(QString p_chr, QString p_emote, bool p_play_once); - bool play_pre(QString p_chr, QString p_emote); - void play_talking(QString p_chr, QString p_emote); - void play_idle(QString p_chr, QString p_emote); - void set_mirror_enabled(bool p_enabled); - void combo_resize(QSize p_size); + bool play(QString character, QString emote, QString prefix, bool play_once); + bool play(QString character, QString emote, bool play_once); + bool play_pre(QString character, QString emote); + bool play_talk(QString character, QString emote); + bool play_idle(QString character, QString emote); + void set_mirror_enabled(bool enabled); + void combo_resize(QSize size); void stop(); signals: diff --git a/include/aoemotebutton.h b/include/aoemotebutton.h index 69f136099..864ec2749 100644 --- a/include/aoemotebutton.h +++ b/include/aoemotebutton.h @@ -3,6 +3,7 @@ // src #include "aoapplication.h" +#include "datatypes.h" // qt #include @@ -17,7 +18,7 @@ class AOEmoteButton : public QPushButton int get_emote_number(); void set_emote_number(int p_emote_number); - void set_image(QString p_chr, int p_emote_number, bool p_enabled); + void set_image(DREmote emote, bool enabled); signals: void emote_clicked(int p_emote_number); diff --git a/include/aosfxplayer.h b/include/aosfxplayer.h index ef2b5b465..268b2025f 100644 --- a/include/aosfxplayer.h +++ b/include/aosfxplayer.h @@ -12,6 +12,5 @@ class AOSfxPlayer : public AOObject void play(QString p_file); void play_effect(QString p_effect); void play_character_effect(QString p_character, QString p_effect); - void play_character_effect(QStringList p_character_list, QString p_effect); void stop_all(); }; diff --git a/include/aoshoutplayer.h b/include/aoshoutplayer.h index ad7e62c9f..cdb657156 100644 --- a/include/aoshoutplayer.h +++ b/include/aoshoutplayer.h @@ -10,7 +10,7 @@ class AOShoutPlayer : public AOObject public: AOShoutPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); - void play(QString p_name, QString p_char); + void play(QString character, QString shout); }; #endif // AOSHOUTPLAYER_HPP diff --git a/include/courtroom.h b/include/courtroom.h index 22a6378fa..30de1b04d 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -88,7 +88,6 @@ class Courtroom : public QMainWindow QString malplaced = area_list.last(); area_list.removeLast(); append_music(malplaced); - // qDebug() << "what" << malplaced; } } @@ -449,9 +448,9 @@ class Courtroom : public QMainWindow int char_rows = 9; int max_chars_on_page = 90; + QVector m_emote_list; + int m_current_emote_id = 0; int current_emote_page = 0; - int current_emote = 0; - int prev_emote = 0; int emote_columns = 5; int emote_rows = 2; int max_emotes_on_page = 10; @@ -482,11 +481,6 @@ class Courtroom : public QMainWindow AONoteArea *ui_note_area = nullptr; - // AONotepad *ui_vp_notepad = nullptr; - // list of characters that require a second application ID - // note that since it's hardcoded, it won't be of much use in other servers - QVector rpc_char_list; - AOImageDisplay *ui_vp_notepad_image = nullptr; DRTextEdit *ui_vp_notepad = nullptr; @@ -689,6 +683,8 @@ class Courtroom : public QMainWindow void reset_emote_page(); void set_emote_page(); void set_emote_dropdown(); + DREmote get_emote(const int id); + DREmote get_current_emote(); void construct_evidence(); void set_evidence_page(); @@ -697,8 +693,6 @@ class Courtroom : public QMainWindow void save_note(); void save_textlog(QString p_text); - void set_char_rpc(); - bool is_spectating(); public slots: diff --git a/include/datatypes.h b/include/datatypes.h index ba48d979b..8db6f8bee 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -8,6 +8,20 @@ #include +class DREmote +{ +public: + QString character; + QString key; + QString comment; + QString anim; + QString dialog; + int modifier = 0; + int desk_modifier = -1; + QString sound_file; + int sound_delay = 0; +}; + namespace DR { class ChatRecord diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index e828dcacf..88937fe2d 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -24,7 +24,7 @@ AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_ connect(m_frame_timer, SIGNAL(timeout()), this, SLOT(on_timer_timeout())); } -void AOCharMovie::play(QString p_chr, QString p_emote, QString p_emote_prefix, bool p_play_once) +bool AOCharMovie::play(QString p_chr, QString p_emote, QString p_prefix, bool p_play_once) { // Asset lookup order // 1. In the character folder, look for @@ -34,62 +34,49 @@ void AOCharMovie::play(QString p_chr, QString p_emote, QString p_emote_prefix, b // 3. In the theme folder (gamemode-timeofday/main/default), look for // "placeholder" + extensions in `exts` in order - QString target_path = ao_app->find_asset_path( - { - ao_app->get_character_path(p_chr, p_emote_prefix + p_emote), - ao_app->get_character_path(p_chr, p_emote), - }, - animated_or_static_extensions()); - if (target_path.isEmpty()) - target_path = ao_app->find_theme_asset_path("placeholder", animated_extensions()); + bool r_exist = true; + + QStringList l_file_list; + for (const QString &i_chr : ao_app->get_char_include_tree(p_chr)) + { + if (!p_prefix.isEmpty()) + l_file_list.append(ao_app->get_character_path(i_chr, QString("%1%2").arg(p_emote, p_prefix))); + l_file_list.append(ao_app->get_character_path(i_chr, p_emote)); + } + + QString l_file = ao_app->find_asset_path(l_file_list, animated_or_static_extensions()); + if (l_file.isEmpty()) + { + l_file = ao_app->find_theme_asset_path("placeholder", animated_extensions()); + r_exist = false; + } stop(); - m_movie->setFileName(target_path); + m_movie->setFileName(l_file); m_play_once = p_play_once; m_movie->start(); + + return r_exist; } -void AOCharMovie::play(QString p_chr, QString p_emote, bool p_play_once) +bool AOCharMovie::play(QString p_chr, QString p_emote, bool p_play_once) { - play(p_chr, p_emote, QString(), p_play_once); + return play(p_chr, p_emote, nullptr, p_play_once); } bool AOCharMovie::play_pre(QString p_chr, QString p_emote) { - QString f_file_path = ao_app->get_character_path(p_chr, p_emote); - bool f_file_exist = false; - - { // figure out what extension the animation is using - QString f_source_path = ao_app->get_character_path(p_chr, p_emote); - for (QString &i_ext : animated_or_static_extensions()) - { - QString f_target_path = ao_app->get_case_sensitive_path(f_source_path + i_ext); - if (file_exists(f_target_path)) - { - f_file_path = f_target_path; - f_file_exist = true; - break; - } - } - } - - // play if it exist - if (f_file_exist) - { - play(p_chr, p_emote, true); - } - - return f_file_exist; + return play(p_chr, p_emote, true); } -void AOCharMovie::play_talking(QString p_chr, QString p_emote) +bool AOCharMovie::play_talk(QString p_chr, QString p_emote) { - play(p_chr, p_emote, "(b)", false); + return play(p_chr, p_emote, "(b)", false); } -void AOCharMovie::play_idle(QString p_chr, QString p_emote) +bool AOCharMovie::play_idle(QString p_chr, QString p_emote) { - play(p_chr, p_emote, "(a)", false); + return play(p_chr, p_emote, "(a)", false); } void AOCharMovie::set_mirror_enabled(bool p_enabled) diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index af0912a05..9c6c4a19f 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -28,13 +28,10 @@ int AOEmoteButton::get_emote_number() return m_emote_number; } -void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) +void AOEmoteButton::set_image(DREmote p_emote, bool p_enabled) { - // In the land of AO2, cohesion and consistency is unknown to the world for - // the sake of lazy-users convenience! - const int true_emote_number = p_emote_number + 1; - - QString texture_path = ao_app->get_character_path(p_chr, QString("emotions/button%1_off.png").arg(true_emote_number)); + QString texture_path = + ao_app->get_character_path(p_emote.character, QString("emotions/button%1_off.png").arg(p_emote.key)); // reset states w_selected->hide(); @@ -43,7 +40,7 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) if (p_enabled) { const QString enabled_texture_path = - ao_app->get_character_path(p_chr, QString("emotions/button%1_on.png").arg(true_emote_number)); + ao_app->get_character_path(p_emote.character, QString("emotions/button%1_on.png").arg(p_emote.key)); if (file_exists(enabled_texture_path)) { @@ -51,7 +48,7 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) } else { - const QString selected_texture_path = ao_app->get_character_path(p_chr, "emotions/selected.png"); + const QString selected_texture_path = ao_app->get_character_path(p_emote.character, "emotions/selected.png"); if (file_exists(selected_texture_path)) { @@ -68,7 +65,7 @@ void AOEmoteButton::set_image(QString p_chr, int p_emote_number, bool p_enabled) } const bool texture_exist = file_exists(texture_path); - setText(texture_exist ? QString() : ao_app->get_emote_comment(p_chr, p_emote_number)); + setText(texture_exist ? nullptr : p_emote.comment); setStyleSheet(texture_exist ? QString("%1 { border-image: url(\"%2\"); }").arg(metaObject()->className()).arg(texture_path) : QString()); diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index 5deef0023..27ae215d6 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -22,21 +22,17 @@ void AOSfxPlayer::play_effect(QString p_effect) void AOSfxPlayer::play_character_effect(QString p_chr, QString p_effect) { - play_character_effect(QStringList{p_chr}, p_effect); -} - -void AOSfxPlayer::play_character_effect(QStringList p_chr_list, QString p_effect) -{ - QStringList l_path_list; - for (const QString &i_character : p_chr_list) - l_path_list.append(ao_app->get_character_path(i_character, QString("sounds/%1").arg(p_effect))); + QStringList l_file_list; + for (const QString &i_chr : ao_app->get_char_include_tree(p_chr)) + l_file_list.append(ao_app->get_character_path(i_chr, QString("sounds/%1").arg(p_effect))); - const QString l_target_file = ao_app->find_asset_path(l_path_list, audio_extensions()); + const QString l_target_file = ao_app->find_asset_path(l_file_list, audio_extensions()); if (l_target_file.isEmpty()) { play_effect(p_effect); return; } + play(l_target_file); } diff --git a/src/aoshoutplayer.cpp b/src/aoshoutplayer.cpp index 152881043..4768636c6 100644 --- a/src/aoshoutplayer.cpp +++ b/src/aoshoutplayer.cpp @@ -8,22 +8,17 @@ AOShoutPlayer::AOShoutPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) {} -void AOShoutPlayer::play(QString p_name, QString p_char) +void AOShoutPlayer::play(QString p_chr, QString p_shout) { - QString file; + QStringList l_file_list; + for (const QString &i_chr : ao_app->get_char_include_tree(p_chr)) + l_file_list.append(ao_app->get_character_path(i_chr, p_shout)); - QString char_path = ao_app->get_character_path(p_char, p_name); - QString theme_path = ao_app->find_theme_asset_path(p_name); + QString l_file = ao_app->find_asset_path(l_file_list, audio_extensions()); + if (l_file.isEmpty()) + l_file = ao_app->find_theme_asset_path(p_shout, audio_extensions()); - qDebug() << char_path; - qDebug() << theme_path; - - if (file_exists(char_path)) - file = char_path; - else if (file_exists(theme_path)) - file = theme_path; - - if (file.isNull()) + if (l_file.isEmpty()) return; - DRAudioEngine::get_family(DRAudio::Family::FEffect)->play_stream(file); + DRAudioEngine::get_family(DRAudio::Family::FEffect)->play_stream(l_file); } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 4d1955085..b57774bd2 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -47,8 +47,6 @@ void Courtroom::enter_courtroom(int p_cid) QString l_chr_name; - set_char_rpc(); - if (is_spectating()) { ao_app->discord->clear_character_name(); @@ -69,11 +67,11 @@ void Courtroom::enter_courtroom(int p_cid) ao_app->send_server_packet(l_packet); } } - current_char = l_chr_name; + m_emote_list = ao_app->get_emote_list(current_char); + m_current_emote_id = 0; current_emote_page = 0; - current_emote = 0; if (is_spectating()) ui_emotes->hide(); @@ -255,31 +253,6 @@ void Courtroom::set_scene() ui_vp_desk->set_image(f_desk_image); } -void Courtroom::set_char_rpc() -{ - rpc_char_list.clear(); - - QFile config_file(ao_app->get_base_path() + rpc_ini); - if (!config_file.open(QIODevice::ReadOnly)) - { - qDebug() << "Error reading" << ao_app->get_base_path() + rpc_ini; - return; - } - - QTextStream in(&config_file); - - while (!in.atEnd()) - { - QString f_line = in.readLine().trimmed(); - - QStringList line_elements = f_line.split("-"); - - rpc_char_list.append(line_elements.at(1).trimmed().toLower()); - } - - config_file.close(); -} - void Courtroom::set_taken(int n_char, bool p_taken) { if (n_char >= char_list.size()) @@ -448,7 +421,7 @@ QString Courtroom::current_sfx_file() if (l_item == nullptr) return nullptr; const QString l_file = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).file; - return l_file == m_sfx_default_file ? ao_app->get_sfx_name(current_char, current_emote) : l_file; + return l_file == m_sfx_default_file ? get_current_emote().sound_file : l_file; } void Courtroom::update_sfx_list() @@ -686,59 +659,60 @@ void Courtroom::on_ic_message_return_pressed() QStringList packet_contents; - QString f_side = ao_app->get_char_side(current_char); - - QString f_desk_mod = "chat"; + const DREmote &l_emote = get_emote(m_current_emote_id); - f_desk_mod = QString::number(ao_app->get_desk_mod(current_char, current_emote)); - if (f_desk_mod == "-1") - f_desk_mod = "chat"; + const QString l_desk_modifier = + l_emote.desk_modifier == -1 ? QString("chat") : QString::number(l_emote.desk_modifier); + packet_contents.append(l_desk_modifier); - packet_contents.append(f_desk_mod); - - packet_contents.append(ao_app->get_pre_emote(current_char, current_emote)); + packet_contents.append(l_emote.anim); packet_contents.append(current_char); if (ui_hidden->isChecked()) packet_contents.append("../../misc/blank"); else - packet_contents.append(ao_app->get_emote(current_char, current_emote)); + packet_contents.append(l_emote.dialog); packet_contents.append(ui_ic_chat_message->text()); - packet_contents.append(f_side); + const QString l_side = ao_app->get_char_side(current_char); + packet_contents.append(l_side); // sfx file - packet_contents.append(current_sfx_file()); - - int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); + const QString l_sound_file = current_sfx_file(); + packet_contents.append(l_sound_file.isEmpty() ? "0" : l_sound_file); + // TODO remove empty string workaround for pre-DRO 1.0.0 + int l_emote_modifier = l_emote.modifier; // needed or else legacy won't understand what we're saying if (m_shout_state > 0) { - if (f_emote_mod == 5) - f_emote_mod = 6; + if (l_emote_modifier == 5) + l_emote_modifier = 6; else - f_emote_mod = 2; + l_emote_modifier = 2; } else if (ui_pre->isChecked()) { - if (f_emote_mod == 0) - f_emote_mod = 1; + if (l_emote_modifier == 0) + l_emote_modifier = 1; } else { - if (f_emote_mod == 1) - f_emote_mod = 0; - else if (f_emote_mod == 4) - f_emote_mod = 5; + if (l_emote_modifier == 1) + l_emote_modifier = 0; + else if (l_emote_modifier == 4) + l_emote_modifier = 5; } - packet_contents.append(QString::number(f_emote_mod)); + packet_contents.append(QString::number(l_emote_modifier)); packet_contents.append(QString::number(m_cid)); - packet_contents.append(QString::number(ao_app->get_sfx_delay(current_char, current_emote))); + if (l_emote.sound_file == current_sfx_file()) + packet_contents.append(QString::number(l_emote.sound_delay)); + else + packet_contents.append("0"); QString f_obj_state; @@ -771,8 +745,6 @@ void Courtroom::on_ic_message_return_pressed() f_text_color = QString::number(m_text_color); packet_contents.append(f_text_color); - prev_emote = current_emote; - ao_app->send_server_packet(new AOPacket("MS", packet_contents)); } @@ -832,7 +804,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) // possible the server crafted a message with the same char_id // as the client, but the client did not send that message, but it is // the best we can do. - if (!ao_app->ackMS_enabled) + if (!ao_app->m_FL_ackMS_enabled) { handle_acknowledged_ms(); } @@ -897,7 +869,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) if (objection_mod >= 1 && objection_mod <= ui_shouts.size() && ui_shouts.size() > 0) // check to prevent crashing { ui_vp_objection->play_interjection(f_char, shout_names.at(objection_mod - 1)); - m_shouts_player->play(shout_names.at(objection_mod - 1) + ".wav", f_char); + m_shouts_player->play(f_char, shout_names.at(objection_mod - 1)); } else qDebug() << "W: Shout identifier unknown" << objection_mod; @@ -1077,7 +1049,7 @@ void Courtroom::handle_chatmessage_3() case 2: if (m_msg_is_first_person == false) { - ui_vp_player_char->play_talking(f_char, f_emote); + ui_vp_player_char->play_talk(f_char, f_emote); } anim_state = 2; break; @@ -1095,7 +1067,6 @@ void Courtroom::handle_chatmessage_3() int effect = m_chatmessage[CMEffectState].toInt(); QStringList offset = ao_app->get_effect_offset(f_char, effect); - ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), ui_viewport->y() + offset.at(1).toInt()); QStringList overlay = ao_app->get_overlay(f_char, effect); diff --git a/src/emotes.cpp b/src/emotes.cpp index 224571028..0c2dc0c33 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -75,7 +75,7 @@ void Courtroom::reconstruct_emotes() void Courtroom::reset_emote_page() { current_emote_page = 0; - current_emote = 0; + m_current_emote_id = 0; if (is_spectating()) ui_emotes->hide(); @@ -94,12 +94,9 @@ void Courtroom::set_emote_page() if (is_spectating()) return; - int total_emotes = ao_app->get_emote_number(current_char); - + const int total_emotes = m_emote_list.length(); for (AOEmoteButton *i_button : ui_emote_list) - { i_button->hide(); - } int total_pages = total_emotes / max_emotes_on_page; int emotes_on_page = 0; @@ -124,55 +121,62 @@ void Courtroom::set_emote_page() for (int n_emote = 0; n_emote < emotes_on_page; ++n_emote) { - int n_real_emote = n_emote + current_emote_page * max_emotes_on_page; - AOEmoteButton *f_emote = ui_emote_list.at(n_emote); - f_emote->set_image(current_char, n_real_emote, n_real_emote == current_emote); - f_emote->show(); + const int l_real_id = n_emote + current_emote_page * max_emotes_on_page; + AOEmoteButton *l_button = ui_emote_list.at(n_emote); + l_button->set_image(get_emote(l_real_id), l_real_id == m_current_emote_id); + l_button->show(); } } void Courtroom::set_emote_dropdown() { + QSignalBlocker l_blocker(ui_emote_dropdown); ui_emote_dropdown->clear(); - int total_emotes = ao_app->get_emote_number(current_char); - QStringList emote_list; + QStringList l_emote_list; + for (const DREmote &i_emote : m_emote_list) + l_emote_list.append(i_emote.comment); + ui_emote_dropdown->addItems(l_emote_list); +} - for (int n = 0; n < total_emotes; ++n) - { - emote_list.append(ao_app->get_emote_comment(current_char, n)); - } +DREmote Courtroom::get_emote(const int id) +{ + if (id < 0 || id >= m_emote_list.length()) + return DREmote(); + return m_emote_list.at(id); +} - ui_emote_dropdown->addItems(emote_list); +DREmote Courtroom::get_current_emote() +{ + return get_emote(m_current_emote_id); } void Courtroom::select_emote(int p_id) { - int min = current_emote_page * max_emotes_on_page; - int max = (max_emotes_on_page - 1) + current_emote_page * max_emotes_on_page; - - if (current_emote >= min && current_emote <= max) - ui_emote_list.at(current_emote % max_emotes_on_page)->set_image(current_char, current_emote, false); - - int old_emote = current_emote; + const int l_min = current_emote_page * max_emotes_on_page; + const int l_max = (max_emotes_on_page - 1) + current_emote_page * max_emotes_on_page; - current_emote = p_id; + qDebug() << m_emote_list.length() << m_current_emote_id; + const DREmote &l_prev_emote = get_emote(m_current_emote_id); + if (m_current_emote_id >= l_min && m_current_emote_id <= l_max) + ui_emote_list.at(m_current_emote_id % max_emotes_on_page)->set_image(l_prev_emote, false); - if (current_emote >= min && current_emote <= max) - ui_emote_list.at(current_emote % max_emotes_on_page)->set_image(current_char, current_emote, true); + const int l_prev_emote_id = m_current_emote_id; + m_current_emote_id = p_id; + const DREmote &l_emote = get_emote(m_current_emote_id); - int emote_mod = ao_app->get_emote_mod(current_char, current_emote); + if (m_current_emote_id >= l_min && m_current_emote_id <= l_max) + ui_emote_list.at(m_current_emote_id % max_emotes_on_page)->set_image(l_emote, true); - if (old_emote == current_emote) // toggle + const int emote_mod = l_emote.modifier; + if (l_prev_emote_id == m_current_emote_id) // toggle ui_pre->setChecked(!ui_pre->isChecked()); - else if (emote_mod == 1 || ao_app->get_always_pre_enabled()) - ui_pre->setChecked(true); else - ui_pre->setChecked(false); + ui_pre->setChecked(emote_mod == 1 || ao_app->get_always_pre_enabled()); select_default_sfx(); - ui_emote_dropdown->setCurrentIndex(current_emote); + ui_emote_dropdown->setCurrentIndex(m_current_emote_id); ui_ic_chat_message->setFocus(); } diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 829031bfe..252309b24 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -140,7 +140,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) f_hdid = get_hdid(); #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - ackMS_enabled = false; + m_FL_ackMS_enabled = false; #endif m_FL_showname_enabled = false; m_FL_chrini_enabled = false; @@ -169,7 +169,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) else if (header == "FL") { #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - ackMS_enabled = f_packet.contains("ackMS", Qt::CaseInsensitive); + m_FL_ackMS_enabled = f_packet.contains("ackMS", Qt::CaseInsensitive); m_FL_showname_enabled = f_packet.contains("showname", Qt::CaseInsensitive); m_FL_chrini_enabled = f_packet.contains("chrini", Qt::CaseInsensitive); #endif @@ -503,7 +503,6 @@ void AOApplication::server_packet_received(AOPacket *p_packet) w_courtroom->fix_last_area(); w_courtroom->append_music(f_contents.at(n_element)); areas--; - // qDebug() << "wtf!!" << f_contents.at(n_element); } else { diff --git a/src/path_functions.cpp b/src/path_functions.cpp index dd0f2db9f..b344a0691 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -28,10 +28,16 @@ QString AOApplication::get_data_path() return get_base_path() + "data/"; } -QString AOApplication::get_character_path(QString p_character, QString p_file) +QString AOApplication::get_character_folder_path(QString p_chr) { - QString path = get_base_path() + "characters/" + p_character + "/" + p_file; - return get_case_sensitive_path(path); + QString r_path = get_base_path() + "characters/" + p_chr; + return get_case_sensitive_path(r_path); +} + +QString AOApplication::get_character_path(QString p_chr, QString p_file) +{ + const QString r_path = get_character_folder_path(p_chr) + "/" + p_file; + return get_case_sensitive_path(r_path); } QString AOApplication::get_sounds_path(QString p_file) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index c50216b4c..2ca21f286 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -515,289 +515,194 @@ QStringList AOApplication::get_effect(int index) QStringList AOApplication::get_sfx_list() { - QStringList return_value; - QFile base_sfx_list_ini; - QFile char_sfx_list_ini; + QStringList r_sfx_list; - base_sfx_list_ini.setFileName(get_base_path() + "configs/sounds.ini"); - char_sfx_list_ini.setFileName(get_character_path(get_current_char(), "sounds.ini")); + QStringList l_file_list; + l_file_list.append(get_base_path() + "configs/sounds.ini"); + for (const QString &i_chr : get_char_include_tree(get_current_char())) + l_file_list.append(get_character_path(i_chr, "sounds.ini")); - if (!char_sfx_list_ini.open(QIODevice::ReadOnly) && !base_sfx_list_ini.open(QIODevice::ReadOnly)) + for (const QString &i_file_path : qAsConst(l_file_list)) { - return return_value; - } - - QTextStream in_a(&base_sfx_list_ini); - QTextStream in_b(&char_sfx_list_ini); - - while (!in_a.atEnd()) - { - QString line = in_a.readLine(); - return_value.append(line); - } - - while (!in_b.atEnd()) - { - QString line = in_b.readLine(); - return_value.append(line); + QFile l_file(i_file_path); + if (l_file.open(QIODevice::ReadOnly)) + { + QTextStream l_in(&l_file); + while (!l_in.atEnd()) + r_sfx_list.append(l_in.readLine()); + } } - return return_value; + return r_sfx_list; } // returns whatever is to the right of "search_line =" within target_tag and // terminator_tag, trimmed returns the empty string if the search line couldnt // be found -QString AOApplication::read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag) -{ - QString char_ini_path = get_character_path(p_char, "char.ini"); - - QFile char_ini; - - char_ini.setFileName(char_ini_path); - - if (!char_ini.open(QIODevice::ReadOnly)) - return ""; - - QTextStream in(&char_ini); - - bool tag_found = false; - - while (!in.atEnd()) - { - QString line = in.readLine(); - - if (QString::compare(line, terminator_tag, Qt::CaseInsensitive) == 0) - break; - - if (line.startsWith(target_tag, Qt::CaseInsensitive)) - { - tag_found = true; - continue; - } - - if (!line.startsWith(p_search_line, Qt::CaseInsensitive)) - continue; - - QStringList line_elements = line.split("="); - - if (QString::compare(line_elements.at(0).trimmed(), p_search_line, Qt::CaseInsensitive) != 0) - continue; - - if (line_elements.size() < 2) - continue; - - if (tag_found) - { - char_ini.close(); - return line_elements.at(1).trimmed(); - } - } - char_ini.close(); - return ""; -} - -QString AOApplication::get_char_name(QString p_char) +QVariant AOApplication::read_char_ini(QString p_chr, QString p_group, QString p_key, QVariant p_def) { - QString f_result = read_char_ini(p_char, "name", "[Options]", "[Time]"); - - if (f_result == "") - return p_char; - else - return f_result; + QSettings s(get_character_path(p_chr, "char.ini"), QSettings::IniFormat); + s.setIniCodec("UTF-8"); + s.beginGroup(p_group); + return s.value(p_key, p_def); } -QString AOApplication::get_showname(QString p_char) +QVariant AOApplication::read_char_ini(QString p_chr, QString p_group, QString p_key) { - QString f_result = read_showname(p_char); - if (f_result == "") - f_result = read_char_ini(p_char, "showname", "[Options]", "[Time]"); - - if (f_result == "") - return p_char; - else - return f_result; + return read_char_ini(p_chr, p_group, p_key, QVariant()); } -QString AOApplication::read_showname(QString p_char) +QString AOApplication::get_char_name(QString p_chr) { - QString f_filename = get_base_path() + "configs/shownames.ini"; - QFile f_file(f_filename); - if (!f_file.open(QIODevice::ReadOnly)) - { - qDebug() << "Error reading" << f_filename; - return ""; - } - - QTextStream in(&f_file); - while (!in.atEnd()) - { - QString f_line = in.readLine(); - if (!f_line.startsWith(p_char)) - continue; - - QStringList line_elements = f_line.split("="); - if (line_elements.at(0).trimmed() == p_char) - return line_elements.at(1).trimmed(); - } - return ""; + return read_char_ini(p_chr, "options", "name", p_chr).toString(); } -QString AOApplication::get_char_side(QString p_char) +QStringList AOApplication::get_char_include(QString p_chr) { - QString f_result = read_char_ini(p_char, "side", "[Options]", "[Time]"); - - if (f_result == "") - return "wit"; - else - return f_result; + QStringList r_list = + read_char_ini(p_chr, "options", "include").toStringList().join(",").split(",", DR::SkipEmptyParts); + for (QString &i_chr : r_list) + i_chr = i_chr.trimmed(); + return r_list; } -QString AOApplication::get_gender(QString p_char) +QStringList AOApplication::get_char_include_tree(QString p_chr) { - QString f_result = read_char_ini(p_char, "gender", "[Options]", "[Time]"); - - if (f_result == "") - return "male"; - else - return f_result; + QStringList r_list = get_char_include(p_chr); + r_list.prepend(p_chr); + return r_list; } -QString AOApplication::get_chat(QString p_char) +QString AOApplication::get_showname(QString p_chr) { - QString f_result = read_char_ini(p_char, "chat", "[Options]", "[Time]"); - - // handling the correct order of chat is a bit complicated, we let the caller - // do it - return f_result.toLower(); + return read_char_ini(p_chr, "options", "showname", p_chr).toString(); } -int AOApplication::get_emote_number(QString p_char) +QString AOApplication::get_char_side(QString p_chr) { - QString f_result = read_char_ini(p_char, "number", "[Emotions]", "[Offsets]"); - - if (f_result == "") - return 0; - else - return f_result.toInt(); + return read_char_ini(p_chr, "options", "side", "wit").toString(); } -QString AOApplication::get_emote_comment(QString p_char, int p_emote) +QString AOApplication::get_gender(QString p_chr) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); - - QStringList result_contents = f_result.split("#"); - - if (result_contents.size() < 4) - { - qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; - return "normal"; - } - else - return result_contents.at(0); + return read_char_ini(p_chr, "options", "gender", "male").toString(); } -QString AOApplication::get_pre_emote(QString p_char, int p_emote) +QString AOApplication::get_chat(QString p_chr) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); - - QStringList result_contents = f_result.split("#"); - - if (result_contents.size() < 4) - { - qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; - return ""; - } - else - return result_contents.at(1); + return read_char_ini(p_chr, "options", "chat").toString().toLower(); } -QString AOApplication::get_emote(QString p_char, int p_emote) +QVector AOApplication::get_emote_list(QString p_chr) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); + QVector r_emote_list; - QStringList result_contents = f_result.split("#"); + QStringList l_chr_list = get_char_include(p_chr); + l_chr_list.append(p_chr); - if (result_contents.size() < 4) + for (const QString &i_chr : l_chr_list) { - qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; - return "normal"; - } - else - return result_contents.at(2); -} - -int AOApplication::get_emote_mod(QString p_char, int p_emote) -{ - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); - - QStringList result_contents = f_result.split("#"); + if (!dir_exists(get_character_folder_path(i_chr))) + { + qWarning().noquote() + << QString("Parent character <%1> not found, character <%2> cannot use it.").arg(i_chr, p_chr); + continue; + } + qDebug().noquote() << QString("Adding <%1>").arg(i_chr); + + QSettings l_chrini(get_character_path(i_chr, "char.ini"), QSettings::IniFormat); + l_chrini.setIniCodec("UTF-8"); + + QStringList l_keys; + { // recover all numbered keys, ignore words + l_chrini.beginGroup("emotions"); + l_keys = l_chrini.childKeys(); + l_chrini.endGroup(); + + // remove keywords + l_keys.removeAll("firstmode"); + l_keys.removeAll("number"); + + // remove all negative and non-numbers + for (int i = 0; i < l_keys.length(); ++i) + { + const QString &i_key = l_keys.at(i); + bool ok = false; + const int l_num = i_key.toInt(&ok); + if (ok && l_num >= 0) + continue; + l_keys.removeAt(i--); + } + + std::stable_sort(l_keys.begin(), l_keys.end(), [](const QString &a, const QString &b) -> bool { + // if 0s are added at the beginning of the key, consider a whole number + if (a.length() < b.length()) + return true; + return a.toInt() < b.toInt(); + }); + } - if (result_contents.size() < 4) - { - qDebug() << "W: misformatted char.ini: " << p_char << ", " << QString::number(p_emote); - return 0; + for (const QString &i_key : qAsConst(l_keys)) + { + l_chrini.beginGroup("emotions"); + const QStringList l_emotions = l_chrini.value(i_key).toString().split("#", DR::KeepEmptyParts); + l_chrini.endGroup(); + + if (l_emotions.length() < 4) + { + qWarning().noquote() << QString("Emote <%2> of <%1>; emote is malformed.").arg(i_chr, i_key); + continue; + } + enum EmoteField + { + Comment, + Animation, + Dialog, + Modifier, + DeskModifier, + }; + + DREmote l_emote; + l_emote.key = i_key; + l_emote.character = i_chr; + l_emote.comment = l_emotions.at(Comment); + l_emote.anim = l_emotions.at(Animation); + l_emote.dialog = l_emotions.at(Dialog); + l_emote.modifier = qMax(l_emotions.at(Modifier).toInt(), 0); + if (DeskModifier < l_emotions.length()) + l_emote.desk_modifier = l_emotions.at(DeskModifier).toInt(); + + l_chrini.beginGroup("soundn"); + l_emote.sound_file = l_chrini.value(i_key).toString(); + l_chrini.endGroup(); + + l_chrini.beginGroup("soundt"); + l_emote.sound_delay = qMax(l_chrini.value(i_key).toInt(), 0); + l_chrini.endGroup(); + + // add the emote + r_emote_list.append(l_emote); + } } - else - return result_contents.at(3).toInt(); -} - -int AOApplication::get_desk_mod(QString p_char, int p_emote) -{ - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); - - QStringList result_contents = f_result.split("#"); - - if (result_contents.size() < 5) - return -1; - - QString string_result = result_contents.at(4); - if (string_result == "") - return -1; - - else - return string_result.toInt(); -} - -QStringList AOApplication::get_effect_offset(QString p_char, int p_effect) -{ - QStringList f_result = read_char_ini(p_char, QString::number(p_effect), "[Offsets]", "[Overlay]").split(","); - - if (f_result.size() < 2) - return decltype(f_result){0, 0}; - return f_result; + return r_emote_list; } -QStringList AOApplication::get_overlay(QString p_char, int p_effect) +QStringList AOApplication::get_effect_offset(QString p_chr, int p_effect) { - QStringList f_result = read_char_ini(p_char, QString::number(p_effect), "[Overlay]", "[SoundN]").split("#"); - - if (f_result.size() < 2) - f_result.push_back(""); - - return f_result; -} - -QString AOApplication::get_sfx_name(QString p_char, int p_emote) -{ - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[SoundN]", "[SoundT]"); - - if (f_result == "") - return "1"; - else - return f_result; + QStringList r_offset = read_char_ini(p_chr, "offsets", QString::number(p_effect)).toString().split(","); + while (r_offset.length() < 2) + r_offset.append(nullptr); + return r_offset; } -int AOApplication::get_sfx_delay(QString p_char, int p_emote) +QStringList AOApplication::get_overlay(QString p_chr, int p_overlay) { - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[SoundT]", "[TextDelay]"); - - if (f_result == "") - return 1; - else - return f_result.toInt(); + QStringList r_overlay = read_char_ini(p_chr, "overlay", QString::number(p_overlay)).toString().split("#"); + while (r_overlay.length() < 2) + r_overlay.append(nullptr); + return r_overlay; } bool AOApplication::get_blank_blip() From c40df4e2bd514fe4a65f45014582fa147bf7f70a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 12 May 2021 21:23:32 +0200 Subject: [PATCH 341/842] Removed debug that... was used for... something? --- src/courtroom.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index b57774bd2..6c3dca3c5 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -820,7 +820,6 @@ void Courtroom::handle_chatmessage(QStringList p_contents) QString f_showname; qDebug() << "handle_chatmessage"; - qDebug() << m_chatmessage[CMShowName] << ao_app->get_showname(char_list.at(f_char_id).name); // We actually DO wanna fail here if the showname is empty but the system is // speaking. From 63888563293c1d70c2d436a4a0a5e7d92ef1cbd2 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 12 May 2021 21:27:08 +0200 Subject: [PATCH 342/842] Prevent duplicate inclusions --- src/text_file_functions.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 2ca21f286..efdfe2994 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -564,6 +564,7 @@ QStringList AOApplication::get_char_include(QString p_chr) read_char_ini(p_chr, "options", "include").toStringList().join(",").split(",", DR::SkipEmptyParts); for (QString &i_chr : r_list) i_chr = i_chr.trimmed(); + r_list.removeDuplicates(); return r_list; } @@ -600,6 +601,7 @@ QVector AOApplication::get_emote_list(QString p_chr) QStringList l_chr_list = get_char_include(p_chr); l_chr_list.append(p_chr); + l_chr_list.removeDuplicates(); for (const QString &i_chr : l_chr_list) { From 52a11877fc307f0cad7970c25a0c84bda50cce8e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 12 May 2021 22:04:27 +0200 Subject: [PATCH 343/842] Added parent of parent functionality --- src/text_file_functions.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index efdfe2994..cb40212f5 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -558,13 +558,26 @@ QString AOApplication::get_char_name(QString p_chr) return read_char_ini(p_chr, "options", "name", p_chr).toString(); } +#include + QStringList AOApplication::get_char_include(QString p_chr) { - QStringList r_list = - read_char_ini(p_chr, "options", "include").toStringList().join(",").split(",", DR::SkipEmptyParts); + QStringList r_list; + + QStringList l_queue{p_chr}; + while (!l_queue.isEmpty()) + { + const QString l_target_chr = l_queue.takeFirst().trimmed(); + if (r_list.contains(l_target_chr) || l_target_chr.isEmpty()) + continue; + r_list.append(l_target_chr); + l_queue.append(read_char_ini(l_target_chr, "options", "include").toStringList()); + } + for (QString &i_chr : r_list) i_chr = i_chr.trimmed(); - r_list.removeDuplicates(); + r_list.removeAll(p_chr); + return r_list; } @@ -601,7 +614,6 @@ QVector AOApplication::get_emote_list(QString p_chr) QStringList l_chr_list = get_char_include(p_chr); l_chr_list.append(p_chr); - l_chr_list.removeDuplicates(); for (const QString &i_chr : l_chr_list) { From 9f2128d53ad7ae931ee060efddf8fa67c1265532 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 12 May 2021 22:06:36 +0200 Subject: [PATCH 344/842] Removed extra trim method call --- src/text_file_functions.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index cb40212f5..022547f9d 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -573,9 +573,6 @@ QStringList AOApplication::get_char_include(QString p_chr) r_list.append(l_target_chr); l_queue.append(read_char_ini(l_target_chr, "options", "include").toStringList()); } - - for (QString &i_chr : r_list) - i_chr = i_chr.trimmed(); r_list.removeAll(p_chr); return r_list; From 612a2c4965df5215e292c1e0cf7d27368c1592db Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 13 May 2021 01:07:14 +0200 Subject: [PATCH 345/842] Changed debug slightly and added emote filter list for buttons --- src/emotes.cpp | 1 - src/text_file_functions.cpp | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/emotes.cpp b/src/emotes.cpp index 0c2dc0c33..b5dead68c 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -156,7 +156,6 @@ void Courtroom::select_emote(int p_id) const int l_min = current_emote_page * max_emotes_on_page; const int l_max = (max_emotes_on_page - 1) + current_emote_page * max_emotes_on_page; - qDebug() << m_emote_list.length() << m_current_emote_id; const DREmote &l_prev_emote = get_emote(m_current_emote_id); if (m_current_emote_id >= l_min && m_current_emote_id <= l_max) ui_emote_list.at(m_current_emote_id % max_emotes_on_page)->set_image(l_prev_emote, false); diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 022547f9d..44e397f92 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -612,6 +612,9 @@ QVector AOApplication::get_emote_list(QString p_chr) QStringList l_chr_list = get_char_include(p_chr); l_chr_list.append(p_chr); +#ifdef QT_DEBUG + qDebug().noquote() << QString("Compiling char.ini for character <%1>").arg(p_chr); +#endif for (const QString &i_chr : l_chr_list) { if (!dir_exists(get_character_folder_path(i_chr))) @@ -620,7 +623,9 @@ QVector AOApplication::get_emote_list(QString p_chr) << QString("Parent character <%1> not found, character <%2> cannot use it.").arg(i_chr, p_chr); continue; } +#ifdef QT_DEBUG qDebug().noquote() << QString("Adding <%1>").arg(i_chr); +#endif QSettings l_chrini(get_character_path(i_chr, "char.ini"), QSettings::IniFormat); l_chrini.setIniCodec("UTF-8"); From 094c02627fa203c582acc0f0f786e4211caf396f Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 13 May 2021 01:11:44 +0200 Subject: [PATCH 346/842] Fixed filtering --- src/text_file_functions.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 44e397f92..2900175b5 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -702,6 +702,19 @@ QVector AOApplication::get_emote_list(QString p_chr) } } + // remove duplicate emotes + QVector l_filtered_list; + QStringList l_filter_list; + while (!r_emote_list.isEmpty()) + { + const DREmote i_emote = r_emote_list.takeFirst(); + if (l_filter_list.contains(i_emote.dialog)) + continue; + l_filter_list.append(i_emote.dialog); + l_filtered_list.append(std::move(i_emote)); + } + r_emote_list = std::move(l_filtered_list); + return r_emote_list; } From 77f185bffae92d2b737b428e6bf9880295a80e15 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 13 May 2021 01:41:25 +0200 Subject: [PATCH 347/842] Improved filtering of emotes --- src/text_file_functions.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 2900175b5..8034e5c5a 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -702,19 +702,28 @@ QVector AOApplication::get_emote_list(QString p_chr) } } - // remove duplicate emotes + // remove duplicate emotes and bring the last one to the front QVector l_filtered_list; - QStringList l_filter_list; - while (!r_emote_list.isEmpty()) + QStringList l_dialog_filter_list; + for (auto it = r_emote_list.cbegin(); it != r_emote_list.cend(); ++it) { - const DREmote i_emote = r_emote_list.takeFirst(); - if (l_filter_list.contains(i_emote.dialog)) + const DREmote &it_emote = *it; + if (l_dialog_filter_list.contains(it_emote.dialog)) continue; - l_filter_list.append(i_emote.dialog); - l_filtered_list.append(std::move(i_emote)); + l_dialog_filter_list.append(it_emote.dialog); + + for (auto rit = r_emote_list.crbegin(); rit != r_emote_list.crend(); ++rit) + { + const DREmote &rit_emote = *rit; + + if (it_emote.dialog == rit_emote.dialog) + { + l_filtered_list.append(rit_emote); + break; + } + } } r_emote_list = std::move(l_filtered_list); - return r_emote_list; } From 09bbb53f1e3c81330869de80e373e75840506536 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 13 May 2021 01:44:27 +0200 Subject: [PATCH 348/842] Removal of spaces for funsies! --- src/text_file_functions.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 8034e5c5a..2538964d2 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -715,7 +715,6 @@ QVector AOApplication::get_emote_list(QString p_chr) for (auto rit = r_emote_list.crbegin(); rit != r_emote_list.crend(); ++rit) { const DREmote &rit_emote = *rit; - if (it_emote.dialog == rit_emote.dialog) { l_filtered_list.append(rit_emote); From d8cd5348d6888d41228a57e7497b36c15017f7fe Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 13 May 2021 02:34:49 +0200 Subject: [PATCH 349/842] Slightly reformatted + very small optimization --- src/path_functions.cpp | 51 +++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 1866c9110..419a376d9 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -118,25 +118,23 @@ QString AOApplication::get_case_sensitive_path(QString p_file) } #endif -QString AOApplication::find_asset_path(QStringList possible_roots, QStringList possible_exts) +QString AOApplication::find_asset_path(QStringList p_root_list, QStringList p_ext_list) { - for (QString &root : possible_roots) + for (QString &i_root : p_root_list) { // We can assume that possible_exts will only be populated with hardcoded strings. // Therefore, the only place where sanitize_path could catch something bad is in the root. // So, check that now, so we don't need to check later. - if (sanitize_path(root).isEmpty()) + if (sanitize_path(i_root).isEmpty()) continue; // Check if parent folder actually exists. If it does not, none of the following files would exist - QFileInfo file(root); - QString file_parent_dir = get_case_sensitive_path(file.absolutePath()); - if (!dir_exists(file_parent_dir)) + if (!dir_exists(QFileInfo(i_root).absolutePath())) continue; - for (QString &ext : possible_exts) + for (QString &i_ext : p_ext_list) { - QString full_path = get_case_sensitive_path(root + ext); + QString full_path = get_case_sensitive_path(i_root + i_ext); if (file_exists(full_path)) return full_path; } @@ -145,30 +143,31 @@ QString AOApplication::find_asset_path(QStringList possible_roots, QStringList p return nullptr; } -QString AOApplication::find_theme_asset_path(QString p_file, QStringList exts) +QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_ext_list) { - QStringList paths; + QStringList l_path_list; // Only add gamemode and/or time of day if non empty. - QString gamemode = get_gamemode(); - QString timeofday = get_timeofday(); - QString root = get_base_path() + "themes/" + get_theme(); - if (!gamemode.isEmpty()) { - if (!timeofday.isEmpty()) { - paths.append(root + "/gamemodes/" + gamemode + "/times/" + timeofday + "/" + p_file); - } - paths.append(root + "/gamemodes/" + gamemode + "/" + p_file); - } - if (!timeofday.isEmpty()) { - paths.append(root + "/times/" + timeofday + "/" + p_file); + const QString l_gamemode = get_gamemode(); + const QString l_timeofday = get_timeofday(); + const QString l_theme_root = get_base_path() + "themes/" + get_theme(); + + if (!l_gamemode.isEmpty()) + { + if (!l_timeofday.isEmpty()) + l_path_list.append(l_theme_root + "/gamemodes/" + l_gamemode + "/times/" + l_timeofday + "/" + p_file); + l_path_list.append(l_theme_root + "/gamemodes/" + l_gamemode + "/" + p_file); } - paths.append(root + "/" + p_file); + + if (!l_timeofday.isEmpty()) + l_path_list.append(l_theme_root + "/times/" + l_timeofday + "/" + p_file); + l_path_list.append(l_theme_root + "/" + p_file); // Check if default folder exists. We do this here as it is cheaper than doing it in find_asset_path // (as we know there should not be capitalization or folder jumping shenanigans here. - QString default_theme_path = get_base_path() + "themes/default/"; - if (dir_exists(default_theme_path)) - paths.append(default_theme_path + p_file); + const QString l_default_theme_path = get_base_path() + "themes/default/"; + if (dir_exists(l_default_theme_path)) + l_path_list.append(l_default_theme_path + p_file); - return find_asset_path(paths, exts); + return find_asset_path(l_path_list, p_ext_list); } From 64db1fbe85acc39c87edc498585cf901583c18e8 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 13 May 2021 02:44:47 +0200 Subject: [PATCH 350/842] Added newline --- src/path_functions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 54e516cde..2e8865869 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -167,6 +167,7 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_ext_l if (!l_timeofday.isEmpty()) l_path_list.append(l_theme_root + "/times/" + l_timeofday + "/" + p_file); + l_path_list.append(l_theme_root + "/" + p_file); // Check if default folder exists. We do this here as it is cheaper than doing it in find_asset_path From ad74e8e4723dc472dc91c2fd5e47e168af506a48 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 14 May 2021 19:07:20 +0200 Subject: [PATCH 351/842] Initial implementation of #160 --- src/courtroom.cpp | 53 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6c3dca3c5..cde01fe49 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -42,11 +42,15 @@ Courtroom::~Courtroom() void Courtroom::enter_courtroom(int p_cid) { - bool changed_character = (m_cid != p_cid); + bool l_changed_chr_id = (m_cid != p_cid); m_cid = p_cid; - QString l_chr_name; + QLineEdit *l_current_field = ui_ic_chat_message; + if (ui_ooc_chat_message->hasFocus()) + l_current_field = ui_ooc_chat_message; + const int l_current_cursor_pos = l_current_field->cursorPosition(); + QString l_chr_name; if (is_spectating()) { ao_app->discord->clear_character_name(); @@ -67,20 +71,36 @@ void Courtroom::enter_courtroom(int p_cid) ao_app->send_server_packet(l_packet); } } + const bool l_changed_chr = l_chr_name != current_char; current_char = l_chr_name; - m_emote_list = ao_app->get_emote_list(current_char); + const int l_prev_emote_count = m_emote_list.count(); + m_emote_list = ao_app->get_emote_list(current_char); m_current_emote_id = 0; current_emote_page = 0; - if (is_spectating()) - ui_emotes->hide(); - else - ui_emotes->show(); - + if (l_prev_emote_count >= m_emote_list.count()) + current_emote_page = 0; set_emote_page(); + + const QString l_prev_emote = ui_emote_dropdown->currentText(); set_emote_dropdown(); - ui_pre->setChecked(ui_pre || ao_config->always_pre_enabled()); + + if (l_changed_chr) + { + ui_pre->setChecked(ui_pre->isChecked() || ao_config->always_pre_enabled()); + } + else + { + for (int i = 0; i < ui_emote_dropdown->count(); ++i) + { + if (l_prev_emote == ui_emote_dropdown->itemText(i)) + { + ui_emote_dropdown->setCurrentIndex(i); + break; + } + } + } current_evidence_page = 0; current_evidence = 0; @@ -101,7 +121,7 @@ void Courtroom::enter_courtroom(int p_cid) // position, otherwise use the old one. Even if the else is useless now (it // can be omitted), I am keeping it in case we expand set_character_position // to do more. - if (changed_character) + if (l_changed_chr_id) set_character_position(ao_app->get_char_side(l_chr_name)); else set_character_position(ui_pos_dropdown->currentText()); @@ -139,18 +159,21 @@ void Courtroom::enter_courtroom(int p_cid) testimony_in_progress = false; - // ui_server_chatlog->setHtml(ui_server_chatlog->toHtml()); - ui_char_select_background->hide(); - ui_ic_chat_message->setEnabled(!is_spectating()); - ui_ic_chat_message->setFocus(); - for (int i = 0; i < ui_timers.length(); ++i) ui_timers[i]->redraw(); set_widget_names(); set_widget_layers(); + + ui_emotes->setHidden(is_spectating()); + ui_emote_dropdown->setHidden(is_spectating()); + ui_ic_chat_message->setEnabled(!is_spectating()); + + // restore line field focus + l_current_field->setFocus(); + l_current_field->setCursorPosition(l_current_cursor_pos); } void Courtroom::done_received() From 3eb6efd91ce6666707a5df28600938cf102df92f Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 14 May 2021 20:48:32 +0200 Subject: [PATCH 352/842] Fixed missing emote switching logic --- src/courtroom.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index cde01fe49..f67ca2f58 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -85,6 +85,7 @@ void Courtroom::enter_courtroom(int p_cid) const QString l_prev_emote = ui_emote_dropdown->currentText(); set_emote_dropdown(); + ui_emote_dropdown->setCurrentText(l_prev_emote); if (l_changed_chr) { From 3d515af49ef0f22765d212960e3202d7395ce6e9 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 14 May 2021 20:50:03 +0200 Subject: [PATCH 353/842] Removed deprecated code --- src/courtroom.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f67ca2f58..3f8bcf13b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -91,17 +91,6 @@ void Courtroom::enter_courtroom(int p_cid) { ui_pre->setChecked(ui_pre->isChecked() || ao_config->always_pre_enabled()); } - else - { - for (int i = 0; i < ui_emote_dropdown->count(); ++i) - { - if (l_prev_emote == ui_emote_dropdown->itemText(i)) - { - ui_emote_dropdown->setCurrentIndex(i); - break; - } - } - } current_evidence_page = 0; current_evidence = 0; From fb370975a319283a994d6570d86557755a08c280 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 14 May 2021 20:55:15 +0200 Subject: [PATCH 354/842] Do not always restore the previous emote, only if the character is unchanged --- src/courtroom.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 3f8bcf13b..04a77371c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -85,12 +85,15 @@ void Courtroom::enter_courtroom(int p_cid) const QString l_prev_emote = ui_emote_dropdown->currentText(); set_emote_dropdown(); - ui_emote_dropdown->setCurrentText(l_prev_emote); if (l_changed_chr) { ui_pre->setChecked(ui_pre->isChecked() || ao_config->always_pre_enabled()); } + else + { + ui_emote_dropdown->setCurrentText(l_prev_emote); + } current_evidence_page = 0; current_evidence = 0; From ef8e72ba47682b38bf556417239579a6f29facbd Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 14 May 2021 21:26:37 +0200 Subject: [PATCH 355/842] Reworked order of items being reloaded --- include/courtroom.h | 4 +- src/courtroom.cpp | 127 +++++++++++++++++++++++--------------------- src/emotes.cpp | 46 ++++++++-------- 3 files changed, 90 insertions(+), 87 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 30de1b04d..ff1305594 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -449,8 +449,8 @@ class Courtroom : public QMainWindow int max_chars_on_page = 90; QVector m_emote_list; - int m_current_emote_id = 0; - int current_emote_page = 0; + int m_emote_id = 0; + int m_emote_page = 0; int emote_columns = 5; int emote_rows = 2; int max_emotes_on_page = 10; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 04a77371c..ed5c479a4 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -42,7 +42,66 @@ Courtroom::~Courtroom() void Courtroom::enter_courtroom(int p_cid) { - bool l_changed_chr_id = (m_cid != p_cid); + // unmute audio + suppress_audio(false); + + const int l_prev_emote_id = m_emote_id; + const int l_prev_emote_page = m_emote_page; + + // widgets =================================================================== + current_evidence_page = 0; + current_evidence = 0; + + m_shout_state = 0; + m_shout_current = 0; + m_effect_state = 0; + m_effect_current = 0; + m_wtce_current = 0; + reset_wtce_buttons(); + + // setup chat + on_chat_config_changed(); + + set_evidence_page(); + + // Update widgets first, then check if everything is valid + // This will also handle showing the correct shouts, effects and wtce buttons, + // and cycling through them if the buttons that are supposed to be displayed + // do not exist It will also take care of free blocks + + set_widgets(); + + check_shouts(); + if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) + cycle_shout(1); + + check_effects(); + if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) + cycle_effect(1); + + check_wtce(); + if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) + cycle_wtce(1); + + check_free_blocks(); + + ui_flip->show(); + + list_music(); + list_areas(); + + testimony_in_progress = false; + + set_widget_names(); + set_widget_layers(); + + for (int i = 0; i < ui_timers.length(); ++i) + ui_timers[i]->redraw(); + + ui_char_select_background->hide(); + + // character ================================================================= + const bool l_changed_chr_id = (m_cid != p_cid); m_cid = p_cid; QLineEdit *l_current_field = ui_ic_chat_message; @@ -76,39 +135,21 @@ void Courtroom::enter_courtroom(int p_cid) const int l_prev_emote_count = m_emote_list.count(); m_emote_list = ao_app->get_emote_list(current_char); - m_current_emote_id = 0; - current_emote_page = 0; - - if (l_prev_emote_count >= m_emote_list.count()) - current_emote_page = 0; - set_emote_page(); const QString l_prev_emote = ui_emote_dropdown->currentText(); set_emote_dropdown(); - if (l_changed_chr) + if (l_changed_chr || l_prev_emote_count != m_emote_list.count()) { ui_pre->setChecked(ui_pre->isChecked() || ao_config->always_pre_enabled()); } else { + m_emote_id = l_prev_emote_id; + m_emote_page = l_prev_emote_page; ui_emote_dropdown->setCurrentText(l_prev_emote); } - - current_evidence_page = 0; - current_evidence = 0; - - m_shout_state = 0; - m_shout_current = 0; - m_effect_state = 0; - m_effect_current = 0; - m_wtce_current = 0; - reset_wtce_buttons(); - - // setup chat - on_chat_config_changed(); - - set_evidence_page(); + set_emote_page(); // Refresh character position. If the character was changed, use the new // position, otherwise use the old one. Even if the else is useless now (it @@ -119,47 +160,9 @@ void Courtroom::enter_courtroom(int p_cid) else set_character_position(ui_pos_dropdown->currentText()); - // Update widgets first, then check if everything is valid - // This will also handle showing the correct shouts, effects and wtce buttons, - // and cycling through them if the buttons that are supposed to be displayed - // do not exist It will also take care of free blocks - - set_widgets(); - - check_shouts(); - if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) - cycle_shout(1); - - check_effects(); - if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) - cycle_effect(1); - - check_wtce(); - if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) - cycle_wtce(1); - - check_free_blocks(); - - ui_flip->show(); - - list_music(); - list_areas(); update_sfx_list(); select_default_sfx(); - // unmute audio - suppress_audio(false); - - testimony_in_progress = false; - - ui_char_select_background->hide(); - - for (int i = 0; i < ui_timers.length(); ++i) - ui_timers[i]->redraw(); - - set_widget_names(); - set_widget_layers(); - ui_emotes->setHidden(is_spectating()); ui_emote_dropdown->setHidden(is_spectating()); ui_ic_chat_message->setEnabled(!is_spectating()); @@ -675,7 +678,7 @@ void Courtroom::on_ic_message_return_pressed() QStringList packet_contents; - const DREmote &l_emote = get_emote(m_current_emote_id); + const DREmote &l_emote = get_emote(m_emote_id); const QString l_desk_modifier = l_emote.desk_modifier == -1 ? QString("chat") : QString::number(l_emote.desk_modifier); diff --git a/src/emotes.cpp b/src/emotes.cpp index b5dead68c..55f4b7b51 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -74,8 +74,8 @@ void Courtroom::reconstruct_emotes() void Courtroom::reset_emote_page() { - current_emote_page = 0; - m_current_emote_id = 0; + m_emote_page = 0; + m_emote_id = 0; if (is_spectating()) ui_emotes->hide(); @@ -105,7 +105,7 @@ void Courtroom::set_emote_page() { ++total_pages; // i. e. not on the last page - if (total_pages > current_emote_page + 1) + if (total_pages > m_emote_page + 1) emotes_on_page = max_emotes_on_page; else emotes_on_page = total_emotes % max_emotes_on_page; @@ -113,17 +113,17 @@ void Courtroom::set_emote_page() else emotes_on_page = max_emotes_on_page; - if (total_pages > current_emote_page + 1) + if (total_pages > m_emote_page + 1) ui_emote_right->show(); - if (current_emote_page > 0) + if (m_emote_page > 0) ui_emote_left->show(); for (int n_emote = 0; n_emote < emotes_on_page; ++n_emote) { - const int l_real_id = n_emote + current_emote_page * max_emotes_on_page; + const int l_real_id = n_emote + m_emote_page * max_emotes_on_page; AOEmoteButton *l_button = ui_emote_list.at(n_emote); - l_button->set_image(get_emote(l_real_id), l_real_id == m_current_emote_id); + l_button->set_image(get_emote(l_real_id), l_real_id == m_emote_id); l_button->show(); } } @@ -148,46 +148,46 @@ DREmote Courtroom::get_emote(const int id) DREmote Courtroom::get_current_emote() { - return get_emote(m_current_emote_id); + return get_emote(m_emote_id); } void Courtroom::select_emote(int p_id) { - const int l_min = current_emote_page * max_emotes_on_page; - const int l_max = (max_emotes_on_page - 1) + current_emote_page * max_emotes_on_page; + const int l_min = m_emote_page * max_emotes_on_page; + const int l_max = (max_emotes_on_page - 1) + m_emote_page * max_emotes_on_page; - const DREmote &l_prev_emote = get_emote(m_current_emote_id); - if (m_current_emote_id >= l_min && m_current_emote_id <= l_max) - ui_emote_list.at(m_current_emote_id % max_emotes_on_page)->set_image(l_prev_emote, false); + const DREmote &l_prev_emote = get_emote(m_emote_id); + if (m_emote_id >= l_min && m_emote_id <= l_max) + ui_emote_list.at(m_emote_id % max_emotes_on_page)->set_image(l_prev_emote, false); - const int l_prev_emote_id = m_current_emote_id; - m_current_emote_id = p_id; - const DREmote &l_emote = get_emote(m_current_emote_id); + const int l_prev_emote_id = m_emote_id; + m_emote_id = p_id; + const DREmote &l_emote = get_emote(m_emote_id); - if (m_current_emote_id >= l_min && m_current_emote_id <= l_max) - ui_emote_list.at(m_current_emote_id % max_emotes_on_page)->set_image(l_emote, true); + if (m_emote_id >= l_min && m_emote_id <= l_max) + ui_emote_list.at(m_emote_id % max_emotes_on_page)->set_image(l_emote, true); const int emote_mod = l_emote.modifier; - if (l_prev_emote_id == m_current_emote_id) // toggle + if (l_prev_emote_id == m_emote_id) // toggle ui_pre->setChecked(!ui_pre->isChecked()); else ui_pre->setChecked(emote_mod == 1 || ao_app->get_always_pre_enabled()); select_default_sfx(); - ui_emote_dropdown->setCurrentIndex(m_current_emote_id); + ui_emote_dropdown->setCurrentIndex(m_emote_id); ui_ic_chat_message->setFocus(); } void Courtroom::on_emote_clicked(int p_id) { - select_emote(p_id + max_emotes_on_page * current_emote_page); + select_emote(p_id + max_emotes_on_page * m_emote_page); } void Courtroom::on_emote_left_clicked() { - --current_emote_page; + --m_emote_page; set_emote_page(); @@ -197,7 +197,7 @@ void Courtroom::on_emote_left_clicked() void Courtroom::on_emote_right_clicked() { qDebug() << "emote right clicked"; - ++current_emote_page; + ++m_emote_page; set_emote_page(); From c8dcfdd2dd39b47435a5b932ae91f79d7d61a5b8 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 15 May 2021 01:34:44 +0200 Subject: [PATCH 356/842] Initial implementation of #164 --- include/aoapplication.h | 99 ------------------------------------- include/courtroom.h | 15 +----- include/lobby.h | 2 + src/aoapplication.cpp | 62 ----------------------- src/aoconfig.cpp | 36 ++++++++++++++ src/courtroom.cpp | 38 +++++--------- src/emotes.cpp | 2 +- src/lobby.cpp | 3 +- src/packet_distribution.cpp | 12 +++-- src/path_functions.cpp | 6 +-- src/text_file_functions.cpp | 49 +----------------- 11 files changed, 68 insertions(+), 256 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index e94f35085..a2f26088f 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -47,7 +47,6 @@ class AOApplication : public QApplication ///////////////server metadata//////////////// #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - bool m_FL_ackMS_enabled = false; #endif bool m_FL_showname_enabled = false; @@ -198,101 +197,12 @@ class AOApplication : public QApplication // Returns text from note file QString read_note(QString filename); - // Returns the blip rate from config.ini - int read_blip_rate(); - - // returns whatever we want newlines or ':' to be appended in front of names - // in the ic chat log - bool read_chatlog_newline(); - - // returns the user name - QString get_username(); - // returns a list of call words QStringList get_callwords(); - /** - * @brief Return the current theme name. - * @return Name of current theme. - */ - QString get_theme(); - - /** - * @brief Return the current gamemode. If no gamemode is set, return the - * empty string. - * - * @return Current gamemode, or empty string if not set. - */ - QString get_gamemode(); - - /** - * @brief Returns the current manual gamemode status. - * - * @details If true, a player can change gamemodes manually and their client - * will ignore orders to change gamemode from the server. If false, neither is - * possible and the client will follow orders from the server to change - * gamemode. - * - * @return Current manual gamemode status. - */ - bool get_manual_gamemode_enabled(); - - /** - * @brief Returns the current time of day. If no time of day is set, return - * the empty string. - * - * @return Current gamemode, or empty string if not set. - */ - QString get_timeofday(); - - /** - * @brief Returns the current manual time of day status. - * - * @details If true, a player can change time of day manually and their client - * will ignore orders to change time of day from the server. If false, neither - * is possible and the client will follow orders from the server to change - * time of day. - * - * @return Current manual time of day status. - */ - bool get_manual_timeofday_enabled(); - - // returns whether server alerts (ones that trigger a client alert other than - // callwords) should actually tigger a server alert or not - bool get_server_alerts_enabled(); - - // returns whatever preanimations should always play or not - bool get_always_pre_enabled(); - // returns whatever the client should simulate first person dialog bool get_first_person_enabled(); - // returns if chatlog goes downward - bool get_chatlog_scrolldown(); - - int get_chatlog_max_lines(); - - int get_chat_tick_interval(); - - bool get_chatlog_newline(); - - bool get_enable_logging_enabled(); - - // Returns the value of default_sfx in config.ini - int get_default_sfx(); - - // Returns the value of default_music in config.ini - int get_default_music(); - - // returns whatever music is logged within the chatlog - bool get_music_change_log_enabled(); - - // Returns the value of default_blip in config.ini - int get_default_blip(); - - // Returns true if blank blips is enabled in config.ini and false otherwise - bool get_blank_blip(); - // TODO document what this does QStringList get_sfx_list(); @@ -305,15 +215,6 @@ class AOApplication : public QApplication // appends to note file void append_note(QString p_line, QString filename); - // Overwrites config.ini with new theme - void write_theme(QString theme); - - // Set the gamemode - void set_gamemode(QString m_gamemode); - - // Set the time of day - void set_timeofday(QString m_timeofday); - // Returns the contents of serverlist.txt QVector read_serverlist_txt(); diff --git a/include/courtroom.h b/include/courtroom.h index ff1305594..13d8eab0f 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -262,20 +262,6 @@ class Courtroom : public QMainWindow // handle server-side clock animation and display void handle_clock(QString time); - /** - * @brief Handle server request to change to given gamemode. If manual - * gamemode configuration is on, this method does nothing. - * @param gamemode Gamemode to change to. - */ - void handle_gamemode(QString gamemode); - - /** - * @brief Handle server request to change to given time of day. If manual - * time of day configuration is on, this method does nothing. - * @param timeofday Time of day to change to. - */ - void handle_timeofday(QString timeofday); - void play_preanim(); // plays the witness testimony or cross examination animation based on @@ -309,6 +295,7 @@ class Courtroom : public QMainWindow private: AOApplication *ao_app = nullptr; AOConfig *ao_config = nullptr; + QTimer *m_reload_delay = nullptr; int m_courtroom_width = 714; int m_courtroom_height = 668; diff --git a/include/lobby.h b/include/lobby.h index 82498efcc..770783034 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -2,6 +2,7 @@ #define LOBBY_H #include "aobutton.h" +#include "aoconfig.h" #include "aoimagedisplay.h" #include "aopacket.h" #include "aotextarea.h" @@ -54,6 +55,7 @@ class Lobby : public QMainWindow private: AOApplication *ao_app = nullptr; + AOConfig *ao_config = nullptr; AOImageDisplay *ui_background = nullptr; diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 0e57e39ef..cde6a43b3 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -138,18 +138,6 @@ QString AOApplication::get_version_string() return QString::number(RELEASE) + "." + QString::number(MAJOR_VERSION) + "." + QString::number(MINOR_VERSION); } -void AOApplication::set_gamemode(QString p_gamemode) -{ - config->set_gamemode(p_gamemode); - Q_EMIT reload_theme(); -} - -void AOApplication::set_timeofday(QString p_timeofday) -{ - config->set_timeofday(p_timeofday); - Q_EMIT reload_theme(); -} - void AOApplication::on_config_theme_changed() { Q_EMIT reload_theme(); @@ -217,61 +205,11 @@ void AOApplication::toggle_config_panel() } } -bool AOApplication::get_server_alerts_enabled() -{ - return config->server_alerts_enabled(); -} - -bool AOApplication::get_manual_gamemode_enabled() -{ - return config->manual_gamemode_enabled(); -} - -bool AOApplication::get_manual_timeofday_enabled() -{ - return config->manual_timeofday_enabled(); -} - -bool AOApplication::get_always_pre_enabled() -{ - return config->always_pre_enabled(); -} - bool AOApplication::get_first_person_enabled() { return config->get_bool("first_person", false); } -bool AOApplication::get_chatlog_scrolldown() -{ - return config->log_is_topdown_enabled(); -} - -int AOApplication::get_chatlog_max_lines() -{ - return config->log_max_lines(); -} - -int AOApplication::get_chat_tick_interval() -{ - return config->chat_tick_interval(); -} - -bool AOApplication::get_chatlog_newline() -{ - return config->log_format_use_newline_enabled(); -} - -bool AOApplication::get_enable_logging_enabled() -{ - return config->log_is_recording_enabled(); -} - -bool AOApplication::get_music_change_log_enabled() -{ - return config->log_display_music_switch_enabled(); -} - void AOApplication::add_favorite_server(int p_server) { if (p_server < 0 || p_server >= server_list.size()) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 3d300e1ee..4a13d09df 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -321,26 +321,62 @@ bool AOConfig::discord_hide_character() const return d->discord_hide_character; } +/** + * @brief Return the current theme name. + * @return Name of current theme. + */ QString AOConfig::theme() const { return d->theme; } +/** + * @brief Return the current gamemode. If no gamemode is set, return the + * empty string. + * + * @return Current gamemode, or empty string if not set. + */ QString AOConfig::gamemode() const { return d->gamemode; } +/** + * @brief Returns the current manual gamemode status. + * + * @details If true, a player can change gamemodes manually and their client + * will ignore orders to change gamemode from the server. If false, neither is + * possible and the client will follow orders from the server to change + * gamemode. + * + * @return Current manual gamemode status. + */ bool AOConfig::manual_gamemode_enabled() const { return d->manual_gamemode; } +/** + * @brief Returns the current time of day. If no time of day is set, return + * the empty string. + * + * @return Current gamemode, or empty string if not set. + */ QString AOConfig::timeofday() const { return d->timeofday; } +/** + * @brief Returns the current manual time of day status. + * + * @details If true, a player can change time of day manually and their client + * will ignore orders to change time of day from the server. If false, neither + * is possible and the client will follow orders from the server to change + * time of day. + * + * @return Current manual time of day status. + */ bool AOConfig::manual_timeofday_enabled() const { return d->manual_timeofday; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ed5c479a4..f5be310a8 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -27,6 +27,14 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ao_app = p_ao_app; ao_config = new AOConfig(this); + m_reload_delay = new QTimer(this); + m_reload_delay->setInterval(200); + m_reload_delay->setSingleShot(true); + connect(m_reload_delay, SIGNAL(timeout()), this, SLOT(on_app_reload_theme_requested())); + connect(ao_config, SIGNAL(theme_changed(QString)), m_reload_delay, SLOT(start())); + connect(ao_config, SIGNAL(gamemode_changed(QString)), m_reload_delay, SLOT(start())); + connect(ao_config, SIGNAL(timeofday_changed(QString)), m_reload_delay, SLOT(start())); + create_widgets(); connect_widgets(); @@ -42,6 +50,8 @@ Courtroom::~Courtroom() void Courtroom::enter_courtroom(int p_cid) { + qDebug() << "enter_courtroom"; + // unmute audio suppress_audio(false); @@ -345,28 +355,6 @@ void Courtroom::handle_clock(QString time) ui_vp_clock->show(); } -void Courtroom::handle_gamemode(QString gamemode) -{ - // only manual gamemode changes are allowed - if (ao_app->get_manual_gamemode_enabled()) - return; - if (ao_app->get_gamemode() == gamemode) - return; - ao_app->set_gamemode(gamemode); - on_app_reload_theme_requested(); -} - -void Courtroom::handle_timeofday(QString timeofday) -{ - // only manual gamemode changes are allowed - if (ao_app->get_manual_timeofday_enabled()) - return; - if (ao_app->get_timeofday() == timeofday) - return; - ao_app->set_timeofday(timeofday); - on_app_reload_theme_requested(); -} - void Courtroom::list_music() { ui_music_list->clear(); @@ -1128,7 +1116,7 @@ void Courtroom::handle_chatmessage_3() } } - chat_tick_timer->start(ao_app->get_chat_tick_interval()); + chat_tick_timer->start(ao_config->chat_tick_interval()); } void Courtroom::on_chat_config_changed() @@ -1569,7 +1557,7 @@ void Courtroom::chat_tick() if ((f_message.at(tick_pos) != ' ' || ao_config->blank_blips_enabled())) { - if (blip_pos % ao_app->read_blip_rate() == 0) + if (blip_pos % ao_config->blip_rate() == 0) { blip_pos = 0; @@ -1809,7 +1797,7 @@ void Courtroom::send_ooc_packet(QString ooc_name, QString ooc_message) void Courtroom::mod_called(QString p_ip) { ui_server_chatlog->append(p_ip); - if (ao_app->get_server_alerts_enabled()) + if (ao_config->server_alerts_enabled()) { m_system_player->play(ao_app->get_sfx("mod_call")); ao_app->alert(this); diff --git a/src/emotes.cpp b/src/emotes.cpp index 55f4b7b51..564258774 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -171,7 +171,7 @@ void Courtroom::select_emote(int p_id) if (l_prev_emote_id == m_emote_id) // toggle ui_pre->setChecked(!ui_pre->isChecked()); else - ui_pre->setChecked(emote_mod == 1 || ao_app->get_always_pre_enabled()); + ui_pre->setChecked(emote_mod == 1 || ao_config->always_pre_enabled()); select_default_sfx(); diff --git a/src/lobby.cpp b/src/lobby.cpp index 685290deb..a27717797 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -15,6 +15,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; + ao_config = new AOConfig(this); this->setWindowTitle("Danganronpa Online"); @@ -89,7 +90,7 @@ void Lobby::set_widgets() "\n" "3. If it is there, check that your current theme folder exists in " "base/themes. According to base/config.ini, your current theme is " + - ao_app->get_theme()); + ao_config->theme()); this->resize(517, 666); } diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 252309b24..9fd6eb2bc 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -648,15 +648,19 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "GM") { - if (!courtroom_constructed) + if (f_contents.length() < 1) + goto end; + if (config->manual_gamemode_enabled()) goto end; - w_courtroom->handle_gamemode(f_contents.at(0)); + config->set_gamemode(f_contents.at(0)); } else if (header == "TOD") { - if (!courtroom_constructed) + if (f_contents.length() < 1) + goto end; + if (config->manual_timeofday_enabled()) goto end; - w_courtroom->handle_timeofday(f_contents.at(0)); + config->set_timeofday(f_contents.at(0)); } else if (header == "TR") { diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 2e8865869..a35de83fc 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -154,9 +154,9 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_ext_l QStringList l_path_list; // Only add gamemode and/or time of day if non empty. - const QString l_gamemode = get_gamemode(); - const QString l_timeofday = get_timeofday(); - const QString l_theme_root = get_base_path() + "themes/" + get_theme(); + const QString l_gamemode = config->gamemode(); + const QString l_timeofday = config->timeofday(); + const QString l_theme_root = get_base_path() + "themes/" + config->theme(); if (!l_gamemode.isEmpty()) { diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 2538964d2..dca99d4a6 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -11,46 +11,6 @@ #include #include -QString AOApplication::get_theme() -{ - return config->theme(); -} - -QString AOApplication::get_gamemode() -{ - return config->gamemode(); -} - -QString AOApplication::get_timeofday() -{ - return config->timeofday(); -} - -int AOApplication::read_blip_rate() -{ - return config->blip_rate(); -} - -bool AOApplication::read_chatlog_newline() -{ - return config->log_format_use_newline_enabled(); -} - -int AOApplication::get_default_music() -{ - return config->music_volume(); -} - -int AOApplication::get_default_sfx() -{ - return config->effect_volume(); -} - -int AOApplication::get_default_blip() -{ - return config->blip_volume(); -} - QStringList AOApplication::get_callwords() { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) @@ -330,11 +290,11 @@ QMap AOApplication::get_chatmessage_colors() if (path.isEmpty()) { qInfo().noquote() << QString("[color] theme %1 is missing file: %2, using default colors instead") - .arg(get_theme()) + .arg(config->theme()) .arg(file_name); return color_map; } - qInfo().noquote() << QString("[color] loading colors for theme %1").arg(get_theme()); + qInfo().noquote() << QString("[color] loading colors for theme %1").arg(config->theme()); QSettings color_settings(path, QSettings::IniFormat); @@ -742,11 +702,6 @@ QStringList AOApplication::get_overlay(QString p_chr, int p_overlay) return r_overlay; } -bool AOApplication::get_blank_blip() -{ - return config->blank_blips_enabled(); -} - QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) { // File lookup order From 6839beb144593f45f888b7103bbb5404437e876e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 15 May 2021 17:31:55 +0200 Subject: [PATCH 357/842] First push of #166, removed unused classes, methods and variables --- include/aoapplication.h | 16 +------- include/aoevidencedisplay.h | 1 - include/aoimagedisplay.h | 1 - include/aopacket.h | 10 +---- include/datatypes.h | 35 ----------------- include/file_functions.h | 1 - include/lobby.h | 1 - include/networkmanager.h | 18 --------- src/aoapplication.cpp | 10 +++++ src/aoevidencedisplay.cpp | 5 --- src/aopacket.cpp | 10 +++++ src/lobby.cpp | 7 ---- src/networkmanager.cpp | 77 ------------------------------------- src/path_functions.cpp | 5 --- 14 files changed, 24 insertions(+), 173 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index a2f26088f..53df91df9 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -87,27 +87,16 @@ class AOApplication : public QApplication /////////////////////////////////////////// void set_favorite_list(); - QVector &get_favorite_list() - { - return favorite_list; - } + QVector &get_favorite_list(); void add_favorite_server(int p_server); - void set_server_list(); - QVector &get_server_list() - { - return server_list; - } - - // reads the theme from config.ini and sets it accordingly - void set_theme_name(QString p_name); + QVector &get_server_list(); // Returns the character the player has currently selected QString get_current_char(); // implementation in path_functions.cpp QString get_base_path(); - QString get_data_path(); QString get_character_folder_path(QString character); QString get_character_path(QString p_character, QString p_file); // QString get_demothings_path(); @@ -188,7 +177,6 @@ class AOApplication : public QApplication QString get_case_sensitive_path(QString p_file); ////// Functions for accessing the config panel ////// - void toggle_config_panel(); ////// Functions for reading and writing files ////// diff --git a/include/aoevidencedisplay.h b/include/aoevidencedisplay.h index 1cd5ed5ce..d9c86a242 100644 --- a/include/aoevidencedisplay.h +++ b/include/aoevidencedisplay.h @@ -16,7 +16,6 @@ class AOEvidenceDisplay : public QLabel AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app); void show_evidence(QString p_evidence_image, bool is_left_side); - QLabel *get_evidence_icon(); void reset(); private: diff --git a/include/aoimagedisplay.h b/include/aoimagedisplay.h index 3007dffd3..9148e6e90 100644 --- a/include/aoimagedisplay.h +++ b/include/aoimagedisplay.h @@ -14,7 +14,6 @@ class AOImageDisplay : public QLabel void set_image(QString p_image); void set_image_from_path(QString p_path); - void set_size_and_pos(QString identifier); AOApplication *ao_app = nullptr; QString image_path; diff --git a/include/aopacket.h b/include/aopacket.h index 3f5fd0d3c..6889fb6d5 100644 --- a/include/aopacket.h +++ b/include/aopacket.h @@ -10,14 +10,8 @@ class AOPacket AOPacket(QString p_packet_string); AOPacket(QString header, QStringList &p_contents); - QString get_header() - { - return m_header; - } - QStringList &get_contents() - { - return m_contents; - } + QString get_header(); + QStringList &get_contents(); QString to_string(); void net_encode(); diff --git a/include/datatypes.h b/include/datatypes.h index 8db6f8bee..37a312529 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -108,17 +108,6 @@ struct server_type int port; }; -struct emote_type -{ - QString comment; - QString preanim; - QString anim; - int mod; - QString sfx_name; - int sfx_delay; - int sfx_duration; -}; - struct char_type { QString name; @@ -134,30 +123,6 @@ struct evi_type QString image; }; -struct chatmessage_type -{ - QString message; - QString character; - QString side; - QString sfx_name; - QString pre_emote; - QString emote; - int emote_modifier; - int objection_modifier; - int realization; - int text_color; - int evidence; - int cid; - int sfx_delay; - int flip; -}; - -struct area_type -{ - QString name; - QString background; -}; - struct pos_size_type { int x = 0; diff --git a/include/file_functions.h b/include/file_functions.h index 75c46461c..77038edd0 100644 --- a/include/file_functions.h +++ b/include/file_functions.h @@ -3,7 +3,6 @@ #include #include -#include QStringList animated_or_static_extensions(); QStringList animated_extensions(); diff --git a/include/lobby.h b/include/lobby.h index 770783034..12fe6c0d0 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -46,7 +46,6 @@ class Lobby : public QMainWindow { ui_loading_background->hide(); } - QString get_chatlog(); int get_selected_server(); void set_loading_value(int p_value); diff --git a/include/networkmanager.h b/include/networkmanager.h index 2ee18a890..e4a2af7ca 100644 --- a/include/networkmanager.h +++ b/include/networkmanager.h @@ -1,12 +1,6 @@ #ifndef NETWORKMANAGER_H #define NETWORKMANAGER_H -//#define LOCAL_MS - -#ifdef LOCAL_MS -#undef MS_FAILOVER_SUPPORTED -#endif - #include "aoapplication.h" #include "aopacket.h" @@ -26,19 +20,13 @@ class NetworkManager : public QObject AOApplication *ao_app = nullptr; QTcpSocket *ms_socket = nullptr; QTcpSocket *server_socket = nullptr; - QDnsLookup *ms_dns = nullptr; QTimer *ms_reconnect_timer = nullptr; const QString ms_srv_hostname = "_aoms._tcp.aceattorneyonline.com"; -#ifdef LOCAL_MS - const QString ms_nosrv_hostname = "localhost"; -#else const QString ms_nosrv_hostname = "master.aceattorneyonline.com"; -#endif static const int ms_port = 27016; static const int timeout_milliseconds = 2000; - static const int ms_reconnect_delay_ms = 7000; bool ms_partial_packet = false; @@ -47,8 +35,6 @@ class NetworkManager : public QObject bool partial_packet = false; QString temp_packet; - unsigned int s_decryptor = 5; - void connect_to_master(); void connect_to_master_nosrv(); void connect_to_server(server_type p_server); @@ -61,11 +47,7 @@ public slots: signals: void ms_connect_finished(bool success, bool will_retry); -private: - void perform_srv_lookup(); - private slots: - void on_srv_lookup(); void handle_ms_packet(); void handle_server_packet(); void on_ms_nosrv_connect_success(); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index cde6a43b3..3fceda0ea 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -163,6 +163,11 @@ void AOApplication::set_favorite_list() favorite_list = read_serverlist_txt(); } +QVector &AOApplication::get_favorite_list() +{ + return favorite_list; +} + QString AOApplication::get_current_char() { if (courtroom_constructed) @@ -224,6 +229,11 @@ void AOApplication::add_favorite_server(int p_server) write_to_serverlist_txt(server_line); } +QVector &AOApplication::get_server_list() +{ + return server_list; +} + void AOApplication::server_disconnected() { if (!courtroom_constructed) diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index f236b2c8c..398b89cce 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -77,8 +77,3 @@ void AOEvidenceDisplay::reset() evidence_icon->hide(); this->clear(); } - -QLabel *AOEvidenceDisplay::get_evidence_icon() -{ - return evidence_icon; -} diff --git a/src/aopacket.cpp b/src/aopacket.cpp index 833d1da58..d02da3623 100644 --- a/src/aopacket.cpp +++ b/src/aopacket.cpp @@ -20,6 +20,16 @@ AOPacket::AOPacket(QString p_header, QStringList &p_contents) m_contents = p_contents; } +QString AOPacket::get_header() +{ + return m_header; +} + +QStringList &AOPacket::get_contents() +{ + return m_contents; +} + QString AOPacket::to_string() { QString f_string = m_header; diff --git a/src/lobby.cpp b/src/lobby.cpp index a27717797..fbba9a54f 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -310,13 +310,6 @@ void Lobby::set_loading_text(QString p_text) ui_loading_text->append(p_text); } -QString Lobby::get_chatlog() -{ - QString return_value = ui_chatbox->toPlainText(); - - return return_value; -} - int Lobby::get_selected_server() { return ui_server_list->currentRow(); diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp index f3439f990..8adf7611a 100644 --- a/src/networkmanager.cpp +++ b/src/networkmanager.cpp @@ -29,12 +29,7 @@ void NetworkManager::connect_to_master() { ms_socket->close(); ms_socket->abort(); - -#ifdef MS_FAILOVER_SUPPORTED - perform_srv_lookup(); -#else connect_to_master_nosrv(); -#endif } void NetworkManager::connect_to_master_nosrv() @@ -107,78 +102,6 @@ void NetworkManager::handle_ms_packet() } } -void NetworkManager::perform_srv_lookup() -{ -#ifdef MS_FAILOVER_SUPPORTED - ms_dns = new QDnsLookup(QDnsLookup::SRV, ms_srv_hostname, this); - - connect(ms_dns, SIGNAL(finished()), this, SLOT(on_srv_lookup())); - ms_dns->lookup(); -#endif -} - -void NetworkManager::on_srv_lookup() -{ -#ifdef MS_FAILOVER_SUPPORTED - bool connected = false; - if (ms_dns->error() != QDnsLookup::NoError) - { - qWarning("SRV lookup of the master server DNS failed."); - ms_dns->deleteLater(); - } - else - { - const auto srv_records = ms_dns->serviceRecords(); - - for (const QDnsServiceRecord &record : srv_records) - { - qDebug() << "Connecting to " << record.target() << ":" << record.port(); - ms_socket->connectToHost(record.target(), record.port()); - QTime timer; - timer.start(); - do - { - ao_app->processEvents(); - if (ms_socket->state() == QAbstractSocket::ConnectedState) - { - connected = true; - break; - } - else if (ms_socket->state() != QAbstractSocket::ConnectingState && - ms_socket->state() != QAbstractSocket::HostLookupState && ms_socket->error() != -1) - { - qDebug() << ms_socket->error(); - qWarning() << "Error connecting to master server:" << ms_socket->errorString(); - ms_socket->abort(); - ms_socket->close(); - break; - } - } while (timer.elapsed() < timeout_milliseconds); // Very expensive spin-wait loop - it will - // bring CPU to 100%! - if (connected) - { - // Connect a one-shot signal in case the master server disconnects - // randomly - QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, - SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); - break; - } - else - { - ms_socket->abort(); - ms_socket->close(); - } - } - } - - // Failover to non-SRV connection - if (!connected) - connect_to_master_nosrv(); - else - Q_EMIT ms_connect_finished(connected, false); -#endif -} - void NetworkManager::on_ms_nosrv_connect_success() { Q_EMIT ms_connect_finished(true, false); diff --git a/src/path_functions.cpp b/src/path_functions.cpp index a35de83fc..82b6a66c0 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -23,11 +23,6 @@ QString AOApplication::get_base_path() return DRPather::get_application_path() + "/base/"; } -QString AOApplication::get_data_path() -{ - return get_base_path() + "data/"; -} - QString AOApplication::get_character_folder_path(QString p_chr) { QString r_path = get_base_path() + "characters/" + p_chr; From ba00dcf8431d6ace5ddb83f42efeaea5aa3b40b7 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 17 May 2021 14:20:29 +0200 Subject: [PATCH 358/842] Comment removal, removed variables, ... Removed useless comments. Removed `timer_number` not being used for anything Moved versioning methods to independent header and source file Beter wtce button creations and calling --- dronline-client.pro | 6 ++-- include/aoapplication.h | 20 ----------- include/courtroom.h | 1 - include/version.h | 8 +++++ src/aoapplication.cpp | 5 --- src/courtroom.cpp | 55 +++++++++++++++-------------- src/courtroom_widgets.cpp | 69 +++++++++++++++---------------------- src/lobby.cpp | 5 +-- src/packet_distribution.cpp | 5 +-- src/text_file_functions.cpp | 2 +- src/version.cpp | 22 ++++++++++++ 11 files changed, 95 insertions(+), 103 deletions(-) create mode 100644 include/version.h create mode 100644 src/version.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 733f6d00c..a02477f4f 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -61,7 +61,8 @@ HEADERS += \ include/hardware_functions.h \ include/lobby.h \ include/misc_functions.h \ - include/networkmanager.h + include/networkmanager.h \ + include/version.h SOURCES += \ src/aoapplication.cpp \ @@ -119,7 +120,8 @@ SOURCES += \ src/networkmanager.cpp \ src/packet_distribution.cpp \ src/path_functions.cpp \ - src/text_file_functions.cpp + src/text_file_functions.cpp \ + src/version.cpp # 1. You need to get BASS and put the x86 bass DLL/headers in the project root folder # AND the compilation output folder. If you want a static link, you'll probably diff --git a/include/aoapplication.h b/include/aoapplication.h index 53df91df9..40e14fd51 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -68,22 +68,6 @@ class AOApplication : public QApplication bool courtroom_loaded = false; - //////////////////versioning/////////////// - - int get_release() - { - return RELEASE; - } - int get_major_version() - { - return MAJOR_VERSION; - } - int get_minor_version() - { - return MINOR_VERSION; - } - QString get_version_string(); - /////////////////////////////////////////// void set_favorite_list(); @@ -313,10 +297,6 @@ class AOApplication : public QApplication void reload_theme(); private: - const int RELEASE = 1; - const int MAJOR_VERSION = 0; - const int MINOR_VERSION = 0; - QVector server_list; QVector favorite_list; diff --git a/include/courtroom.h b/include/courtroom.h index 13d8eab0f..b200ceb8c 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -453,7 +453,6 @@ class Courtroom : public QMainWindow int max_evidence_on_page = 18; int current_clock = -1; - int timer_number = 0; QString current_background = "gs4"; diff --git a/include/version.h b/include/version.h new file mode 100644 index 000000000..0c0758f67 --- /dev/null +++ b/include/version.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +int get_release_version(); +int get_major_version(); +int get_minor_version(); +QString get_version_string(); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 3fceda0ea..01cddf691 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -133,11 +133,6 @@ void AOApplication::destruct_courtroom() net_manager->disconnect_from_server(); } -QString AOApplication::get_version_string() -{ - return QString::number(RELEASE) + "." + QString::number(MAJOR_VERSION) + "." + QString::number(MINOR_VERSION); -} - void AOApplication::on_config_theme_changed() { Q_EMIT reload_theme(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f5be310a8..5ce13a5b8 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -105,8 +105,8 @@ void Courtroom::enter_courtroom(int p_cid) set_widget_names(); set_widget_layers(); - for (int i = 0; i < ui_timers.length(); ++i) - ui_timers[i]->redraw(); + for (AOTimer *i_timer : ui_timers) + i_timer->redraw(); ui_char_select_background->hide(); @@ -2233,12 +2233,11 @@ void Courtroom::reset_wtce_buttons() { for (int i = 0; i < wtce_names.size(); ++i) { - QString wtce_file = wtce_names.at(i) + ".png"; - ui_wtce[i]->set_image(wtce_file); - if (ao_app->find_theme_asset_path(wtce_file).isEmpty()) - ui_wtce[i]->setText(wtce_names.at(i)); - else - ui_wtce[i]->setText(""); + const QString l_name = wtce_names.at(i); + const QString l_file = l_name + ".png"; + AOButton *l_button = ui_wtce.at(i); + l_button->set_image(l_file); + l_button->setText(ao_app->find_theme_asset_path(l_file).isEmpty() ? l_name : nullptr); } m_wtce_current = 0; @@ -2441,42 +2440,42 @@ void Courtroom::on_set_notes_clicked() note_scroll_area->hide(); } -void Courtroom::resume_timer(int timer_id) +void Courtroom::resume_timer(int p_id) { - if (timer_id >= timer_number || timer_id < 0) + if (p_id < 0 || p_id >= ui_timers.length()) return; - - ui_timers[timer_id]->resume(); + AOTimer *l_timer = ui_timers.at(p_id); + l_timer->resume(); } -void Courtroom::set_timer_time(int timer_id, int new_time) +void Courtroom::set_timer_time(int p_id, int new_time) { - if (timer_id >= timer_number || timer_id < 0) + if (p_id < 0 || p_id >= ui_timers.length()) return; - - ui_timers[timer_id]->set_time(QTime(0, 0).addMSecs(new_time)); + AOTimer *l_timer = ui_timers.at(p_id); + l_timer->set_time(QTime(0, 0).addMSecs(new_time)); } -void Courtroom::set_timer_timestep(int timer_id, int timestep_length) +void Courtroom::set_timer_timestep(int p_id, int timestep_length) { - if (timer_id >= timer_number || timer_id < 0) + if (p_id < 0 || p_id >= ui_timers.length()) return; - - ui_timers[timer_id]->set_timestep_length(timestep_length); + AOTimer *l_timer = ui_timers.at(p_id); + l_timer->set_timestep_length(timestep_length); } -void Courtroom::set_timer_firing(int timer_id, int firing_interval) +void Courtroom::set_timer_firing(int p_id, int firing_interval) { - if (timer_id >= timer_number || timer_id < 0) + if (p_id < 0 || p_id >= ui_timers.length()) return; - - ui_timers[timer_id]->set_firing_interval(firing_interval); + AOTimer *l_timer = ui_timers.at(p_id); + l_timer->set_firing_interval(firing_interval); } -void Courtroom::pause_timer(int timer_id) +void Courtroom::pause_timer(int p_id) { - if (timer_id >= timer_number || timer_id < 0) + if (p_id < 0 || p_id >= ui_timers.length()) return; - - ui_timers[timer_id]->pause(); + AOTimer *l_timer = ui_timers.at(p_id); + l_timer->pause(); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 700c1755a..de41867f2 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -412,16 +412,18 @@ void Courtroom::reset_widget_names() void Courtroom::insert_widget_name(QString p_widget_name, QWidget *p_widget) { - // insert entry - widget_names[p_widget_name] = p_widget; - // set name + if (widget_names.contains(p_widget_name)) + qWarning() << QString("[WARNING] Widget <%1> is already defined").arg(p_widget_name); + widget_names.insert(p_widget_name, p_widget); p_widget->setObjectName(p_widget_name); } -void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) +void Courtroom::insert_widget_names(QVector &p_name_list, QVector &p_widget_list) { - for (int i = 0; i < p_widgets.length(); ++i) - insert_widget_name(p_widget_names[i], p_widgets[i]); + if (p_name_list.length() != p_widget_list.length()) + qFatal("[WARNING] Length of names and widgets differs!"); + for (int i = 0; i < p_widget_list.length(); ++i) + insert_widget_name(p_name_list[i], p_widget_list[i]); } void Courtroom::set_widget_names() @@ -683,23 +685,10 @@ void Courtroom::set_widgets() set_size_and_pos(ui_prosecution_bar, "prosecution_bar"); ui_prosecution_bar->set_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); - // set_size_and_pos(ui_shouts[0], "hold_it"); - // ui_shouts[0]->show(); - // set_size_and_pos(ui_shouts[1], "objection"); - // ui_shouts[1]->show(); - // set_size_and_pos(ui_shouts[2], "take_that"); - // ui_shouts[2]->show(); - // set_size_and_pos(ui_shouts[3], "custom_objection"); - // set_size_and_pos(ui_shouts[4], "got_it"); - // set_size_and_pos(ui_shouts[5], "cross_swords"); - // set_size_and_pos(ui_shouts[6], "counter_alt"); for (int i = 0; i < shout_names.size(); ++i) { set_size_and_pos(ui_shouts[i], shout_names[i]); } - // ui_shouts[0]->show(); - // ui_shouts[1]->show(); - // ui_shouts[2]->show(); reset_shout_buttons(); set_size_and_pos(ui_shout_up, "shout_up"); @@ -721,10 +710,6 @@ void Courtroom::set_widgets() ui_shout_down->show(); } - // set_size_and_pos(ui_effects[0], "effect_flash"); - // set_size_and_pos(ui_effects[1], "effect_gloom"); - // set_size_and_pos(ui_effects[2], "effect_question"); - // set_size_and_pos(ui_effects[3], "effect_pow"); for (int i = 0; i < effect_names.size(); ++i) { set_size_and_pos(ui_effects[i], effect_names[i]); @@ -981,7 +966,7 @@ void Courtroom::set_widgets() contains_add_button = true; } - timer_number = adapt_numbered_items(ui_timers, "timer_number", "timer"); + adapt_numbered_items(ui_timers, "timer_number", "timer"); set_dropdowns(); set_fonts(); } @@ -1228,14 +1213,14 @@ void Courtroom::load_free_blocks() // And add names free_block_names.clear(); - for (int i = 1; i <= ui_free_blocks.size(); ++i) + for (int i = 0; i <= ui_free_blocks.size(); ++i) { - QString name = "free_block_" + ao_app->get_spbutton("[FREE BLOCKS]", i).trimmed(); + QString name = "free_block_" + ao_app->get_spbutton("[FREE BLOCKS]", i + 1).trimmed(); if (!name.isEmpty()) { free_block_names.append(name); - widget_names[name] = ui_free_blocks[i - 1]; - ui_free_blocks[i - 1]->setObjectName(name); + widget_names[name] = ui_free_blocks[i]; + ui_free_blocks[i]->setObjectName(name); } } } @@ -1288,22 +1273,20 @@ void Courtroom::load_wtce() delete_widget(widget); // And create new wtce buttons - int wtce_number = ao_app->read_theme_ini_int("wtce_number", cc_config_ini); - wtce_enabled.resize(wtce_number); - ui_wtce.resize(wtce_number); + const int l_wtce_count = ao_app->read_theme_ini_int("wtce_number", cc_config_ini); + wtce_enabled.resize(l_wtce_count); - for (int i = 0; i < ui_wtce.size(); ++i) + ui_wtce.clear(); + for (int i = 0; i < l_wtce_count; ++i) { - ui_wtce[i] = new AOButton(this, ao_app); - ui_wtce[i]->setProperty("wtce_id", i + 1); - ui_wtce[i]->stackUnder(ui_wtce_up); - ui_wtce[i]->stackUnder(ui_wtce_down); + AOButton *l_button = new AOButton(this, ao_app); + ui_wtce.append(l_button); + l_button->setProperty("wtce_id", i + 1); + l_button->stackUnder(ui_wtce_up); + l_button->stackUnder(ui_wtce_down); + connect(l_button, SIGNAL(clicked(bool)), this, SLOT(on_wtce_clicked())); } - // And connect their actions - for (auto &wtce : ui_wtce) - connect(wtce, SIGNAL(clicked(bool)), this, SLOT(on_wtce_clicked())); - // And add names wtce_names.clear(); for (int i = 1; i <= ui_wtce.size(); ++i) @@ -1495,9 +1478,11 @@ void Courtroom::set_fonts() set_font(ui_sfx_list, "sfx_list"); set_drtextedit_font(ui_vp_music_name, "music_name"); set_drtextedit_font(ui_vp_notepad, "notepad"); - for (int i = 0; i < timer_number; i++) + + for (int i = 0; i < ui_timers.length(); ++i) { - set_drtextedit_font(ui_timers[i], "timer_" + QString::number(i)); + AOTimer *i_timer = ui_timers.at(i); + set_drtextedit_font(i_timer, QString("timer_%1").arg(i)); } } diff --git a/src/lobby.cpp b/src/lobby.cpp index fbba9a54f..ee965ba30 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -6,6 +6,7 @@ #include "drpather.h" #include "drtextedit.h" #include "networkmanager.h" +#include "version.h" #include #include @@ -118,7 +119,7 @@ void Lobby::set_widgets() ui_connect->set_image("connect.png"); set_size_and_pos(ui_version, "version"); - ui_version->setText("Version: " + ao_app->get_version_string()); + ui_version->setText("Version: " + get_version_string()); set_size_and_pos(ui_about, "about"); ui_about->set_image("about.png"); @@ -406,7 +407,7 @@ void Lobby::on_about_clicked() "

Running on Qt version %2 with the BASS audio engine.
" "APNG plugin loaded: %3" "

Built on %4") - .arg(ao_app->get_version_string()) + .arg(get_version_string()) .arg(QLatin1String(QT_VERSION_STR)) .arg(hasApng ? tr("Yes") : tr("No")) .arg(QLatin1String(__DATE__)); diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 9fd6eb2bc..8740062f8 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -5,6 +5,7 @@ #include "hardware_functions.h" #include "lobby.h" #include "networkmanager.h" +#include "version.h" #include #include @@ -86,9 +87,9 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) int f_major = version_contents.at(1).toInt(); int f_minor = version_contents.at(2).toInt(); - if (get_release() > f_release) + if (get_release_version() > f_release) goto end; - else if (get_release() == f_release) + else if (get_release_version() == f_release) { if (get_major_version() > f_major) goto end; diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index dca99d4a6..417e84832 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -711,7 +711,7 @@ QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) if (path.isEmpty()) return ""; - return read_ini(p_identifier, path); // Could be the empty string + return read_ini(p_identifier, path); // may be an empty string } bool AOApplication::read_theme_ini_bool(QString p_identifier, QString p_file) diff --git a/src/version.cpp b/src/version.cpp new file mode 100644 index 000000000..82fadba75 --- /dev/null +++ b/src/version.cpp @@ -0,0 +1,22 @@ +#include "version.h" + +int get_release_version() +{ + return 1; +} + +int get_major_version() +{ + return 0; +} + +int get_minor_version() +{ + return 0; +} + +QString get_version_string() +{ + return QString::number(get_release_version()) + "." + QString::number(get_major_version()) + "." + + QString::number(get_minor_version()); +} From b89bb63942e86fa9c2a6948fac76bf26509f8b9d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 17 May 2021 18:09:38 +0200 Subject: [PATCH 359/842] Moved documentation, removed DOOM, ... * Moved documentation to source files to clear up headers * Removed DR namespace from a couple classes and incorporated them in their name * Reworked alert windows to not leak memory * Removed DOOM packet section from master server (supposedly removed, but who knows) * Fixed OOB error for free blocks trying to iterate through more than existing --- include/aoapplication.h | 87 ------------------------------------ include/aoevidencedisplay.h | 5 ++- include/aoimagedisplay.h | 1 - include/courtroom.h | 89 ++----------------------------------- include/datatypes.h | 15 +++---- include/drpather.h | 9 ---- src/aoapplication.cpp | 11 ++++- src/aoevidencedisplay.cpp | 50 ++++++++++++--------- src/aoimagedisplay.cpp | 4 ++ src/charselect.cpp | 1 + src/courtroom.cpp | 67 ++++++++++++++++++++++++---- src/courtroom_widgets.cpp | 17 ++++++- src/debug_functions.cpp | 31 ++++++------- src/drpather.cpp | 9 ++++ src/packet_distribution.cpp | 8 ---- src/path_functions.cpp | 56 ++++++++++++++++++++++- src/text_file_functions.cpp | 24 ++++++++++ 17 files changed, 232 insertions(+), 252 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 40e14fd51..52a9e8875 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -90,74 +90,11 @@ class AOApplication : public QApplication QString get_default_background_path(QString p_file); QString get_evidence_path(QString p_file); - /** - * @brief Check the path for various known exploits. - * - * In order: - * - Directory traversal (most commonly: "../" jumps) - * @param p_file The path to check. - * @return A sanitized path. If any check fails, the path returned is an empty string. The sanitized path does not - * necessarily exist. - */ QString sanitize_path(QString p_file); - /** - * @brief Returns the first case-sensitive file that is the combination of one - * of the given root and extensions, or empty string if no combination exists. - * - * @details A root is matched to all given extensions in order before - * continuing to the next root. - * - * @param possible_roots The potential roots the filepath could have. - * Case-insensitive. - * @param possible_exts The potential extensions the filepath could have. - * Case-insensitive. - * - * @return The first case-sensitive root+extension path for which a file - * exists, or an empty string, if not one does. - */ QString find_asset_path(QStringList possible_roots, QStringList possible_exts = {""}); - - /** - * @brief Returns the first case-sensitive file in the theme folder that is - * of the form name+extension, or empty string if it fails. - * - * @details The p_exts list is browsed in order. A name+extension file is - * searched in order in the following directories before checking the next - * extension: - * 1. The current time of day folder in the current gamemode folder - * 2. The current gamemode folder - * 3. The current time of day folder - * 4. The current theme folder. - * The first path that is matched is the one that is returned. If no file - * is found at all, it returns an empty string. - * - * @param p_name Name of the file to look for. Case-insensitive. - * @param p_exts The potential extensions the filepath could have. - * Case-insensitive. - * - * @return The first case-sensitive root+extension path that corresponds to an - * actual file, or an empty string, if not one does. - */ QString find_theme_asset_path(QString p_root, QStringList p_exts = {""}); - /** - * @brief Returns the 'correct' path for the file given as the parameter by - * trying to match the case of the actual path. - * - * @details This function is mostly used on case-sensitive file systems, like - * ext4, generally used on Linux. On FAT, there is no difference between - * "file" and "FILE". On ext4, those are two different files. This results in - * assets that are detected correctly on Windows not being detected on Linux. - * - * For this reason, the implementation of this function is system-dependent: - * on case-insensitive systems, it just returns the parameter itself. - * - * @param p_file The path whose casing must be checked against the actual - * directory structure. - * - * @return The parameter path with fixed casing. - */ QString get_case_sensitive_path(QString p_file); ////// Functions for accessing the config panel ////// @@ -190,32 +127,8 @@ class AOApplication : public QApplication // Returns the contents of serverlist.txt QVector read_serverlist_txt(); - /** - * @brief Reads p_path and returns the value associated with key - * p_identifier. If the file or key do not exist, return empty. - * - * @param p_identifier Key to look for. - * @param p_path Full path to ini file - * @return Value associated with key, or empty if not found. - */ QString read_ini(QString p_identifier, QString p_path); - /** - * @brief Searches p_file in theme folder and returns the value associated - * with key p_identifier. If the file or key do not exist, return empty. - * - * @details p_file is looked for in the following directories. The earliest - * directory where it is found is the one that is considered. - * 1. The current time of day folder in the current gamemode folder - * 2. The current gamemode folder - * 3. The current time of day folder - * 4. The current theme folder. - * 5. The default theme folder. - * - * @param p_identifier Key to look for. - * @param p_file Name of file+ini to look for. - * @return Value associated with key, or empty if not found. - */ QString read_theme_ini(QString p_identifier, QString p_file); bool read_theme_ini_bool(QString p_identifier, QString p_file); diff --git a/include/aoevidencedisplay.h b/include/aoevidencedisplay.h index d9c86a242..f5cdda535 100644 --- a/include/aoevidencedisplay.h +++ b/include/aoevidencedisplay.h @@ -20,9 +20,10 @@ class AOEvidenceDisplay : public QLabel private: AOApplication *ao_app = nullptr; - QMovie *evidence_movie = nullptr; - QLabel *evidence_icon = nullptr; AOSfxPlayer *sfx_player = nullptr; + QMovie *m_movie = nullptr; + QLabel *w_icon = nullptr; + int m_loop_number = 0; private slots: void frame_change(int p_frame); diff --git a/include/aoimagedisplay.h b/include/aoimagedisplay.h index 9148e6e90..2a9e3eaff 100644 --- a/include/aoimagedisplay.h +++ b/include/aoimagedisplay.h @@ -6,7 +6,6 @@ #include -// This class represents a static theme-dependent image class AOImageDisplay : public QLabel { public: diff --git a/include/courtroom.h b/include/courtroom.h index b200ceb8c..d3a0c7f14 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -125,18 +125,8 @@ class Courtroom : public QMainWindow // sets the character position void set_character_position(QString p_pos); - /** - * @brief Send a OOC packet (CT) out to the server. - * @param ooc_name The username. - * @param ooc_message The message. - */ void send_ooc_packet(QString ooc_name, QString ooc_message); - /** - * @brief Send a packet to set the showname of the user to - * the server. - * @param p_showname The showname. - */ void send_showname_packet(QString p_showname); // called when a DONE#% from the server was received @@ -199,27 +189,12 @@ class Courtroom : public QMainWindow void move_widget(QWidget *p_widget, QString p_identifier); - /** - * @brief Show the currently selected shout button, hide the remaining ones. - * If no shouts exist, this method does nothing. - */ void set_shouts(); - /** - * @brief Show the currently selected effect button, hide the remaining ones. - * If no effects exist, this method does nothing. - */ void set_effects(); - /** - * @brief Show the currently selected splash button, hide the remaining ones. - * If no splashes exist, this method does nothing. - */ void set_judge_wtce(); - /** - * @brief Show all free blocks and restart their animations. - */ void set_free_blocks(); void set_judge_enabled(bool p_enabled); @@ -243,12 +218,6 @@ class Courtroom : public QMainWindow void update_ic_log(bool p_reset_log); void append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, bool p_self); - /** - * @brief Appends a message arriving from system to the IC chatlog. - * - * @param p_showname The showname used by the system. Can be an empty string. - * @param p_line The message that the system is sending. - */ void append_system_text(QString p_showname, QString p_line); // prints who played the song to IC chat and plays said song(if found on local @@ -492,8 +461,8 @@ class Courtroom : public QMainWindow QVector ui_timers; DRTextEdit *ui_ic_chatlog = nullptr; - QList m_ic_record_list; - QQueue m_ic_record_queue; + QList m_ic_record_list; + QQueue m_ic_record_queue; AOTextArea *ui_server_chatlog = nullptr; @@ -502,7 +471,7 @@ class Courtroom : public QMainWindow QListWidget *ui_music_list = nullptr; QListWidget *ui_sfx_list = nullptr; - QVector m_sfx_list; + QVector m_sfx_list; const QString m_sfx_default_file = "__DEFAULT__"; QColor m_sfx_color_found; QColor m_sfx_color_missing; @@ -548,18 +517,12 @@ class Courtroom : public QMainWindow QVector ui_free_blocks; // holds all the names for sound files for the shouts - // QVector shout_names = {"holdit", "objection", "takethat", - // "custom", "gotit", "crossswords", "counteralt"}; QVector shout_names; // holds all the names for sound/anim files for the effects - // QVector effect_names = {"effect_flash", "effect_gloom", - // "effect_question", "effect_pow"}; QVector effect_names; // holds all the names for sound/anim files for the shouts - // QVector shout_names = {"witnesstestimony", "crossexamination", - // "investigation", "nonstop"}; QVector wtce_names; // holds all the names for free blocks @@ -571,14 +534,6 @@ class Courtroom : public QMainWindow QVector wtce_enabled; QVector free_blocks_enabled; - // AOButton* ui_shout_hold_it = nullptr; // 1 - // AOButton* ui_shout_objection = nullptr; // 2 - // AOButton* ui_shout_take_that = nullptr; // 3 - // AOButton* ui_shout_custom = nullptr; // 4 - // AOButton* ui_shout_got_it = nullptr; // 5 - // AOButton* ui_shout_cross_swords = nullptr; // 6 - // AOButton* ui_shout_counter_alt = nullptr; // 7 - AOButton *ui_witness_testimony = nullptr; AOButton *ui_cross_examination = nullptr; AOButton *ui_investigation = nullptr; @@ -742,23 +697,8 @@ private slots: void on_cycle_clicked(); - /** - * @brief Selects the shout p_delta slots ahead of the current one, wrapping - * around if needed. If p_delta is negative, look -p_delta slots behind. - * @param Shout slots to advance. May be negative. - */ void cycle_shout(int p_delta); - /** - * @brief Selects the effect p_delta slots ahead of the current one, wrapping - * around if needed. If p_delta is negative, look -p_delta slots behind. - * @param Shout slots to advance. May be negative. - */ void cycle_effect(int p_delta); - /** - * @brief Selects the splash p_delta slots ahead of the current one, wrapping - * around if needed. If p_delta is negative, look -p_delta slots behind. - * @param Shout slots to advance. May be negative. - */ void cycle_wtce(int p_delta); void on_add_button_clicked(); @@ -772,28 +712,11 @@ private slots: void load_effects(); void load_wtce(); void load_free_blocks(); - /** - * @brief Set the sprites of the shout buttons, and mark the currently - * selected shout as such. - * - * @details If a sprite cannot be found for a shout button, a regular - * push button is displayed for it with its shout name instead. - */ void reset_shout_buttons(); - /** - * @brief a general purpose function to toggle button selection - */ void on_shout_button_clicked(const bool); void on_shout_button_toggled(const bool); - /** - * @brief Set the sprites of the effect buttons, and mark the currently - * selected effect as such. - * - * @details If a sprite cannot be found for a shout button, a regular - * push button is displayed for it with its shout name instead. - */ void reset_effect_buttons(); void on_effect_button_clicked(const bool); void on_effect_button_toggled(const bool); @@ -809,12 +732,6 @@ private slots: void on_witness_testimony_clicked(); void on_cross_examination_clicked(); - /** - * @brief Set the sprites of the splash buttons. - * - * @details If a sprite cannot be found for a shout button, a regular - * push button is displayed for it with its shout name instead. - */ void reset_wtce_buttons(); void on_wtce_clicked(); diff --git a/include/datatypes.h b/include/datatypes.h index 37a312529..7388fbb03 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -22,14 +22,12 @@ class DREmote int sound_delay = 0; }; -namespace DR -{ -class ChatRecord +class DRChatRecord { public: - using list = QVector; + using list = QVector; - ChatRecord(QString p_name, QString p_message) : name(p_name), message(p_message) + DRChatRecord(QString p_name, QString p_message) : name(p_name), message(p_message) {} QDateTime get_timestamp() const @@ -86,11 +84,11 @@ class ChatRecord bool music = false; }; -struct SFX +struct DRSfx { public: - SFX() = default; - SFX(QString p_name, QString p_file, bool p_is_found = false) + DRSfx() = default; + DRSfx(QString p_name, QString p_file, bool p_is_found = false) : name(p_name.trimmed()), file(p_file.trimmed()), is_found(p_is_found) {} @@ -98,7 +96,6 @@ struct SFX QString file; bool is_found; }; -} // namespace DR struct server_type { diff --git a/include/drpather.h b/include/drpather.h index c129db819..ff7be17a9 100644 --- a/include/drpather.h +++ b/include/drpather.h @@ -5,14 +5,5 @@ class DRPather { public: - /* @brief Gets the directory containing the base folder and the application. - * - * This function is a system-independent QDir::currentPath() system-independent. The only edge case really is MacOS, - * where due to the way applications are bundled, makes QDir::currentPath() return nonsensical values by default and - * QCoreApplication::applicationDirPath() returns the directory to the internal executable of the bundle rather - * than the bundle location itself. - * - * @return Directory. - */ static QString get_application_path(); }; diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 01cddf691..4cc262bd4 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -1,7 +1,7 @@ #include "aoapplication.h" -#include "courtroom.h" #include "debug_functions.h" +#include "courtroom.h" #include "lobby.h" #include "networkmanager.h" @@ -171,6 +171,15 @@ QString AOApplication::get_current_char() return ""; } +/** + * @brief Check the path for various known exploits. + * + * In order: + * - Directory traversal (most commonly: "../" jumps) + * @param p_file The path to check. + * @return A sanitized path. If any check fails, the path returned is an empty string. The sanitized path does not + * necessarily exist. + */ QString AOApplication::sanitize_path(QString p_file) { if (!p_file.contains("..")) diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index 398b89cce..5b6c0ecb1 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -10,11 +10,11 @@ AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) { ao_app = p_ao_app; - evidence_movie = new QMovie(this); - evidence_icon = new QLabel(this); + m_movie = new QMovie(this); + w_icon = new QLabel(this); sfx_player = new AOSfxPlayer(ao_app, this); - connect(evidence_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); + connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); } void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_side) @@ -42,38 +42,48 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid pos_size_type icon_dimensions = ao_app->get_element_dimensions(icon_identifier, "courtroom_design.ini"); - evidence_icon->move(icon_dimensions.x, icon_dimensions.y); - evidence_icon->resize(icon_dimensions.width, icon_dimensions.height); - evidence_icon->setPixmap(f_pixmap.scale(evidence_icon->size())); + w_icon->move(icon_dimensions.x, icon_dimensions.y); + w_icon->resize(icon_dimensions.width, icon_dimensions.height); + w_icon->setPixmap(f_pixmap.scale(w_icon->size())); QString f_path = ao_app->find_theme_asset_path(gif_name); - evidence_movie->setFileName(f_path); - if (evidence_movie->frameCount() < 1) + m_movie->setFileName(f_path); + if (m_movie->frameCount() < 1) return; - this->setMovie(evidence_movie); + this->setMovie(m_movie); - evidence_movie->start(); + m_loop_number = 0; + m_movie->start(); sfx_player->play_effect(ao_app->get_sfx("evidence_present")); } -void AOEvidenceDisplay::frame_change(int p_frame) +void AOEvidenceDisplay::frame_change(int p_frame_index) { - if (p_frame == (evidence_movie->frameCount() - 1)) + const int l_frame_num = p_frame_index + 1; + if (l_frame_num < m_movie->frameCount()) + return; + + if ((p_frame_index + 1) < m_movie->frameCount()) + return; + if (p_frame_index == (m_movie->frameCount() - 1)) { + QTimer::singleShot(m_movie->nextFrameDelay(), this, [this]() { + m_movie->stop(); + w_icon->show(); + clear(); + }); // we need this or else the last frame wont show - delay(evidence_movie->nextFrameDelay()); - - evidence_movie->stop(); - this->clear(); - - evidence_icon->show(); + delay(m_movie->nextFrameDelay()); + m_movie->stop(); + w_icon->show(); + clear(); } } void AOEvidenceDisplay::reset() { - evidence_movie->stop(); - evidence_icon->hide(); + m_movie->stop(); + w_icon->hide(); this->clear(); } diff --git a/src/aoimagedisplay.cpp b/src/aoimagedisplay.cpp index 7b277d09b..1110aba5d 100644 --- a/src/aoimagedisplay.cpp +++ b/src/aoimagedisplay.cpp @@ -4,6 +4,10 @@ #include +/*! + * @class AOImageDisplay + * @brief Represents a static theme-dependent image. + */ AOImageDisplay::AOImageDisplay(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) { ao_app = p_ao_app; diff --git a/src/charselect.cpp b/src/charselect.cpp index 77d08e070..96f49f337 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -1,4 +1,5 @@ #include "courtroom.h" + #include "debug_functions.h" #include "file_functions.h" #include "hardware_functions.h" diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 5ce13a5b8..a5530bba4 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -439,8 +439,8 @@ void Courtroom::update_sfx_list() // items m_sfx_list.clear(); - m_sfx_list.append(DR::SFX("Default", m_sfx_default_file)); - m_sfx_list.append(DR::SFX("Silence", nullptr)); + m_sfx_list.append(DRSfx("Default", m_sfx_default_file)); + m_sfx_list.append(DRSfx("Silence", nullptr)); const QStringList l_sfx_list = ao_app->get_sfx_list(); for (const QString &i_sfx_line : l_sfx_list) @@ -450,7 +450,7 @@ void Courtroom::update_sfx_list() const QString l_name = l_sfx_entry.at(l_sfx_entry.size() - 1).trimmed(); const QString l_file = QString(l_sfx_entry.size() >= 2 ? l_sfx_entry.at(0) : nullptr).trimmed(); const bool l_is_found = !ao_app->find_asset_path({ao_app->get_sounds_path(l_file)}, audio_extensions()).isEmpty(); - m_sfx_list.append(DR::SFX(l_name, l_file, l_is_found)); + m_sfx_list.append(DRSfx(l_name, l_file, l_is_found)); } update_sfx_widget_list(); @@ -464,7 +464,7 @@ void Courtroom::update_sfx_widget_list() const QString l_name_filter = ui_sfx_search->text(); for (int i = 0; i < m_sfx_list.length(); ++i) { - const DR::SFX &i_sfx = m_sfx_list.at(i); + const DRSfx &i_sfx = m_sfx_list.at(i); if (!i_sfx.name.contains(l_name_filter, Qt::CaseInsensitive)) continue; QListWidgetItem *l_item = new QListWidgetItem; @@ -604,6 +604,11 @@ void Courtroom::on_showname_changed(QString p_showname) send_showname_packet(p_showname); } +/** + * @brief Send a packet to set the showname of the user to + * the server. + * @param p_showname The showname. + */ void Courtroom::send_showname_packet(QString p_showname) { if (ao_app->m_FL_showname_enabled) @@ -1137,7 +1142,7 @@ void Courtroom::update_ic_log(bool p_reset_log) // but we don't necessarily care the intermediate states are not aligned ui_ic_chatlog->set_auto_align(false); // we need all recordings - QQueue new_queue; + QQueue new_queue; while (!m_ic_record_list.isEmpty()) new_queue.append(m_ic_record_list.takeFirst()); new_queue.append(m_ic_record_queue); @@ -1207,7 +1212,7 @@ void Courtroom::update_ic_log(bool p_reset_log) while (!m_ic_record_queue.isEmpty()) { - DR::ChatRecord record = m_ic_record_queue.takeFirst(); + DRChatRecord record = m_ic_record_queue.takeFirst(); m_ic_record_list.append(record); const QTextCharFormat l_record_name_format = record.is_self() ? selfname_format : name_format; @@ -1247,7 +1252,7 @@ void Courtroom::update_ic_log(bool p_reset_log) // this is always going to amount to at least the current length of records int block_count = m_ic_record_list.length() + 1; // there's always one extra block // to do that, we need to go through the records - for (DR::ChatRecord &record : m_ic_record_list) + for (DRChatRecord &record : m_ic_record_list) if (!record.is_system()) if (chatlog_newline) block_count += 2; // if newline is actived, it always inserts two extra @@ -1328,7 +1333,7 @@ void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bo if (p_line.trimmed().isEmpty()) p_line = p_line.trimmed(); - DR::ChatRecord new_record(p_name, p_line); + DRChatRecord new_record(p_name, p_line); new_record.set_music(p_music); new_record.set_system(p_system); new_record.set_self(p_self); @@ -1336,6 +1341,12 @@ void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bo update_ic_log(false); } +/** + * @brief Appends a message arriving from system to the IC chatlog. + * + * @param p_showname The showname used by the system. Can be an empty string. + * @param p_line The message that the system is sending. + */ void Courtroom::append_system_text(QString p_showname, QString p_line) { if (chatmessage_is_empty) @@ -1767,6 +1778,11 @@ void Courtroom::set_character_position(QString p_pos) set_judge_enabled(p_pos == "jud"); } +/** + * @brief Send a OOC packet (CT) out to the server. + * @param ooc_name The username. + * @param ooc_message The message. + */ void Courtroom::send_ooc_packet(QString ooc_name, QString ooc_message) { if (ooc_name.trimmed().isEmpty()) @@ -2008,6 +2024,13 @@ void Courtroom::on_area_list_double_clicked(QModelIndex p_model) ui_ic_chat_message->setFocus(); } +/** + * @brief Set the sprites of the shout buttons, and mark the currently + * selected shout as such. + * + * @details If a sprite cannot be found for a shout button, a regular + * push button is displayed for it with its shout name instead. + */ void Courtroom::reset_shout_buttons() { for (AOButton *i_button : qAsConst(ui_shouts)) @@ -2090,6 +2113,11 @@ void Courtroom::on_cycle_clicked() ui_ic_chat_message->setFocus(); } +/** + * @brief Selects the shout p_delta slots ahead of the current one, wrapping + * around if needed. If p_delta is negative, look -p_delta slots behind. + * @param Shout slots to advance. May be negative. + */ void Courtroom::cycle_shout(int p_delta) { int n = ui_shouts.size(); @@ -2097,6 +2125,11 @@ void Courtroom::cycle_shout(int p_delta) set_shouts(); } +/** + * @brief Selects the effect p_delta slots ahead of the current one, wrapping + * around if needed. If p_delta is negative, look -p_delta slots behind. + * @param Shout slots to advance. May be negative. + */ void Courtroom::cycle_effect(int p_delta) { int n = ui_effects.size(); @@ -2104,6 +2137,11 @@ void Courtroom::cycle_effect(int p_delta) set_effects(); } +/** + * @brief Selects the splash p_delta slots ahead of the current one, wrapping + * around if needed. If p_delta is negative, look -p_delta slots behind. + * @param Shout slots to advance. May be negative. + */ void Courtroom::cycle_wtce(int p_delta) { int n = ui_wtce.size(); @@ -2111,6 +2149,13 @@ void Courtroom::cycle_wtce(int p_delta) set_judge_wtce(); } +/** + * @brief Set the sprites of the effect buttons, and mark the currently + * selected effect as such. + * + * @details If a sprite cannot be found for a shout button, a regular + * push button is displayed for it with its shout name instead. + */ void Courtroom::reset_effect_buttons() { for (AOButton *i_button : qAsConst(ui_effects)) @@ -2229,6 +2274,12 @@ void Courtroom::on_cross_examination_clicked() ui_ic_chat_message->setFocus(); } +/** + * @brief Set the sprites of the splash buttons. + * + * @details If a sprite cannot be found for a shout button, a regular + * push button is displayed for it with its shout name instead. + */ void Courtroom::reset_wtce_buttons() { for (int i = 0; i < wtce_names.size(); ++i) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index de41867f2..9353d2812 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1213,7 +1213,7 @@ void Courtroom::load_free_blocks() // And add names free_block_names.clear(); - for (int i = 0; i <= ui_free_blocks.size(); ++i) + for (int i = 0; i < ui_free_blocks.size(); ++i) { QString name = "free_block_" + ao_app->get_spbutton("[FREE BLOCKS]", i + 1).trimmed(); if (!name.isEmpty()) @@ -1301,6 +1301,10 @@ void Courtroom::load_wtce() } } +/** + * @brief Show the currently selected shout button, hide the remaining ones. + * If no shouts exist, this method does nothing. + */ void Courtroom::set_shouts() { for (auto &shout : ui_shouts) @@ -1309,6 +1313,10 @@ void Courtroom::set_shouts() ui_shouts[m_shout_current]->show(); // check to prevent crashing } +/** + * @brief Show the currently selected effect button, hide the remaining ones. + * If no effects exist, this method does nothing. + */ void Courtroom::set_effects() { for (auto &effect : ui_effects) @@ -1332,6 +1340,10 @@ void Courtroom::set_judge_enabled(bool p_enabled) set_judge_wtce(); } +/** + * @brief Show the currently selected splash button, hide the remaining ones. + * If no splashes exist, this method does nothing. + */ void Courtroom::set_judge_wtce() { // hide all wtce before enabling visibility @@ -1361,6 +1373,9 @@ void Courtroom::set_judge_wtce() } } +/** + * @brief Show all free blocks and restart their animations. + */ void Courtroom::set_free_blocks() { for (int i = 0; i < ui_free_blocks.size(); i++) diff --git a/src/debug_functions.cpp b/src/debug_functions.cpp index 0da26fc3f..993af094d 100644 --- a/src/debug_functions.cpp +++ b/src/debug_functions.cpp @@ -3,28 +3,23 @@ #include #include -void call_error(QString p_message) +void drMessageBox(QString p_message, bool p_error) { - QMessageBox *f_box = new QMessageBox; - - f_box->setText("Error: " + p_message); - f_box->setWindowTitle("Error"); - qDebug() << f_box->text(); + QMessageBox l_box; + l_box.setWindowTitle(p_error ? "Error" : "Notice"); + l_box.setIcon(p_error ? QMessageBox::Critical : QMessageBox::Information); + l_box.setText(p_message); + l_box.exec(); +} - // msgBox->setWindowModality(Qt::NonModal); - f_box->exec(); - delete f_box; +void call_error(QString p_message) +{ + drMessageBox(p_message, true); + qWarning() << "ERROR" << p_message; } void call_notice(QString p_message) { - QMessageBox *f_box = new QMessageBox; - - f_box->setText(p_message); - f_box->setWindowTitle("Notice"); - qDebug() << f_box->text(); - - // msgBox->setWindowModality(Qt::NonModal); - f_box->exec(); - delete f_box; + drMessageBox(p_message, false); + qInfo() << p_message; } diff --git a/src/drpather.cpp b/src/drpather.cpp index e3ec8ebd8..616f016b0 100644 --- a/src/drpather.cpp +++ b/src/drpather.cpp @@ -3,6 +3,15 @@ #include #include +/* @brief Gets the directory containing the base folder and the application. + * + * This function is a system-independent QDir::currentPath() system-independent. The only edge case really is MacOS, + * where due to the way applications are bundled, makes QDir::currentPath() return nonsensical values by default and + * QCoreApplication::applicationDirPath() returns the directory to the internal executable of the bundle rather + * than the bundle location itself. + * + * @return Directory. + */ QString DRPather::get_application_path() { #ifdef Q_OS_MACOS diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 8740062f8..e70006e07 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -105,16 +105,8 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) destruct_courtroom(); destruct_lobby(); } - else if (header == "DOOM") - { - call_notice("You have been exiled from AO." - "Have a nice day."); - destruct_courtroom(); - destruct_lobby(); - } end: - delete p_packet; } diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 82b6a66c0..22f9937b3 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -80,13 +80,29 @@ QString Courtroom::get_background_path(QString p_file) return ao_app->get_base_path() + "background/" + current_background + "/" + p_file; } -#ifndef CASE_SENSITIVE_FILESYSTEM +/** + * @brief Returns the 'correct' path for the file given as the parameter by + * trying to match the case of the actual path. + * + * @details This function is mostly used on case-sensitive file systems, like + * ext4, generally used on Linux. On FAT, there is no difference between + * "file" and "FILE". On ext4, those are two different files. This results in + * assets that are detected correctly on Windows not being detected on Linux. + * + * For this reason, the implementation of this function is system-dependent: + * on case-insensitive systems, it just returns the parameter itself. + * + * @param p_file The path whose casing must be checked against the actual + * directory structure. + * + * @return The parameter path with fixed casing. + */ QString AOApplication::get_case_sensitive_path(QString p_file) +#ifndef CASE_SENSITIVE_FILESYSTEM { return p_file.replace("//", "/"); } #else -QString AOApplication::get_case_sensitive_path(QString p_file) { // First, check to see if the file already exists as it is. if (QFile(p_file).exists()) @@ -119,6 +135,21 @@ QString AOApplication::get_case_sensitive_path(QString p_file) } #endif +/** + * @brief Returns the first case-sensitive file that is the combination of one + * of the given root and extensions, or empty string if no combination exists. + * + * @details A root is matched to all given extensions in order before + * continuing to the next root. + * + * @param possible_roots The potential roots the filepath could have. + * Case-insensitive. + * @param possible_exts The potential extensions the filepath could have. + * Case-insensitive. + * + * @return The first case-sensitive root+extension path for which a file + * exists, or an empty string, if not one does. + */ QString AOApplication::find_asset_path(QStringList p_root_list, QStringList p_ext_list) { for (QString &i_root : p_root_list) @@ -144,6 +175,27 @@ QString AOApplication::find_asset_path(QStringList p_root_list, QStringList p_ex return nullptr; } +/** + * @brief Returns the first case-sensitive file in the theme folder that is + * of the form name+extension, or empty string if it fails. + * + * @details The p_exts list is browsed in order. A name+extension file is + * searched in order in the following directories before checking the next + * extension: + * 1. The current time of day folder in the current gamemode folder + * 2. The current gamemode folder + * 3. The current time of day folder + * 4. The current theme folder. + * The first path that is matched is the one that is returned. If no file + * is found at all, it returns an empty string. + * + * @param p_name Name of the file to look for. Case-insensitive. + * @param p_exts The potential extensions the filepath could have. + * Case-insensitive. + * + * @return The first case-sensitive root+extension path that corresponds to an + * actual file, or an empty string, if not one does. + */ QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_ext_list) { QStringList l_path_list; diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 417e84832..6f5d3237e 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -119,6 +119,14 @@ QVector AOApplication::read_serverlist_txt() return f_server_list; } +/** + * @brief Reads p_path and returns the value associated with key + * p_identifier. If the file or key do not exist, return empty. + * + * @param p_identifier Key to look for. + * @param p_path Full path to ini file + * @return Value associated with key, or empty if not found. + */ QString AOApplication::read_ini(QString p_identifier, QString p_path) { QFile ini; @@ -702,6 +710,22 @@ QStringList AOApplication::get_overlay(QString p_chr, int p_overlay) return r_overlay; } +/** + * @brief Searches p_file in theme folder and returns the value associated + * with key p_identifier. If the file or key do not exist, return empty. + * + * @details p_file is looked for in the following directories. The earliest + * directory where it is found is the one that is considered. + * 1. The current time of day folder in the current gamemode folder + * 2. The current gamemode folder + * 3. The current time of day folder + * 4. The current theme folder. + * 5. The default theme folder. + * + * @param p_identifier Key to look for. + * @param p_file Name of file+ini to look for. + * @return Value associated with key, or empty if not found. + */ QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) { // File lookup order From 1ca6b508bba0b1ac09ed7c3accfab6f9faeec2c2 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 17 May 2021 18:39:04 +0200 Subject: [PATCH 360/842] Renamed multiple member variables --- include/aoapplication.h | 13 ++-- include/aoevidencedisplay.h | 3 +- include/courtroom.h | 30 ++------ src/aoapplication.cpp | 100 ++++++++++++------------- src/aoblipplayer.cpp | 10 +-- src/aoevidencedisplay.cpp | 24 ++---- src/courtroom.cpp | 8 +- src/main.cpp | 2 +- src/packet_distribution.cpp | 142 ++++++++++++++++++------------------ src/path_functions.cpp | 8 +- src/text_file_functions.cpp | 6 +- 11 files changed, 160 insertions(+), 186 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 52a9e8875..2276abdab 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -7,6 +7,7 @@ #include #include +#include #include class NetworkManager; @@ -23,14 +24,16 @@ class AOApplication : public QApplication AOApplication(int &argc, char **argv); ~AOApplication(); + AOConfig *ao_config = nullptr; + AOConfigPanel *ao_config_panel = nullptr; + DRDiscord *dr_discord = nullptr; + NetworkManager *net_manager = nullptr; - Lobby *w_lobby = nullptr; - Courtroom *w_courtroom = nullptr; - DRDiscord *discord = nullptr; - AOConfig *config = nullptr; - AOConfigPanel *config_panel = nullptr; + QPointer m_lobby = nullptr; bool lobby_constructed = false; + + QPointer m_courtroom = nullptr; bool courtroom_constructed = false; void construct_lobby(); diff --git a/include/aoevidencedisplay.h b/include/aoevidencedisplay.h index f5cdda535..448bd3d3f 100644 --- a/include/aoevidencedisplay.h +++ b/include/aoevidencedisplay.h @@ -20,10 +20,9 @@ class AOEvidenceDisplay : public QLabel private: AOApplication *ao_app = nullptr; - AOSfxPlayer *sfx_player = nullptr; + AOSfxPlayer *dr_sfx = nullptr; QMovie *m_movie = nullptr; QLabel *w_icon = nullptr; - int m_loop_number = 0; private slots: void frame_change(int p_frame); diff --git a/include/courtroom.h b/include/courtroom.h index d3a0c7f14..08524eda9 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -56,30 +56,12 @@ class Courtroom : public QMainWindow explicit Courtroom(AOApplication *p_ao_app); ~Courtroom(); - void append_char(char_type p_char) - { - char_list.append(p_char); - } - void append_evidence(evi_type p_evi) - { - evidence_list.append(p_evi); - } - void append_music(QString f_music) - { - music_list.append(f_music); - } - void append_area(QString f_area) - { - area_list.append(f_area); - } - void clear_music() - { - music_list.clear(); - } - void clear_areas() - { - area_list.clear(); - } + void append_char(char_type p_char); + void append_evidence(evi_type p_evi); + void append_music(QString f_music); + void append_area(QString f_area); + void clear_music(); + void clear_areas(); void fix_last_area() { diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 4cc262bd4..d5b427df2 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -1,7 +1,7 @@ #include "aoapplication.h" -#include "debug_functions.h" #include "courtroom.h" +#include "debug_functions.h" #include "lobby.h" #include "networkmanager.h" @@ -24,23 +24,23 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) net_manager = new NetworkManager(this); connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), SLOT(ms_connect_finished(bool, bool))); - config = new AOConfig(this); - connect(config, SIGNAL(theme_changed(QString)), this, SLOT(on_config_theme_changed())); - connect(config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_config_gamemode_changed())); - connect(config, SIGNAL(timeofday_changed(QString)), this, SLOT(on_config_timeofday_changed())); - - config_panel = new AOConfigPanel(this); - connect(config_panel, SIGNAL(reload_theme()), this, SLOT(on_config_reload_theme_requested())); - connect(this, SIGNAL(reload_theme()), config_panel, SLOT(on_config_reload_theme_requested())); - config_panel->hide(); - - discord = new DRDiscord(this); - discord->set_presence(config->discord_presence()); - discord->set_hide_server(config->discord_hide_server()); - discord->set_hide_character(config->discord_hide_character()); - connect(config, SIGNAL(discord_presence_changed(bool)), discord, SLOT(set_presence(bool))); - connect(config, SIGNAL(discord_hide_server_changed(bool)), discord, SLOT(set_hide_server(bool))); - connect(config, SIGNAL(discord_hide_character_changed(bool)), discord, SLOT(set_hide_character(bool))); + ao_config = new AOConfig(this); + connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(on_config_theme_changed())); + connect(ao_config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_config_gamemode_changed())); + connect(ao_config, SIGNAL(timeofday_changed(QString)), this, SLOT(on_config_timeofday_changed())); + + ao_config_panel = new AOConfigPanel(this); + connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(on_config_reload_theme_requested())); + connect(this, SIGNAL(reload_theme()), ao_config_panel, SLOT(on_config_reload_theme_requested())); + ao_config_panel->hide(); + + dr_discord = new DRDiscord(this); + dr_discord->set_presence(ao_config->discord_presence()); + dr_discord->set_hide_server(ao_config->discord_hide_server()); + dr_discord->set_hide_character(ao_config->discord_hide_character()); + connect(ao_config, SIGNAL(discord_presence_changed(bool)), dr_discord, SLOT(set_presence(bool))); + connect(ao_config, SIGNAL(discord_hide_server_changed(bool)), dr_discord, SLOT(set_hide_server(bool))); + connect(ao_config, SIGNAL(discord_hide_character_changed(bool)), dr_discord, SLOT(set_hide_character(bool))); } AOApplication::~AOApplication() @@ -57,25 +57,25 @@ void AOApplication::construct_lobby() return; } - w_lobby = new Lobby(this); + m_lobby = new Lobby(this); lobby_constructed = true; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QRect screen_geometry = QApplication::desktop()->screenGeometry(); #else - QScreen *screen = QApplication::screenAt(w_lobby->pos()); + QScreen *screen = QApplication::screenAt(m_lobby->pos()); if (screen == nullptr) return; QRect screen_geometry = screen->geometry(); #endif - int x = (screen_geometry.width() - w_lobby->width()) / 2; - int y = (screen_geometry.height() - w_lobby->height()) / 2; - w_lobby->move(x, y); + int x = (screen_geometry.width() - m_lobby->width()) / 2; + int y = (screen_geometry.height() - m_lobby->height()) / 2; + m_lobby->move(x, y); - w_lobby->show(); + m_lobby->show(); - discord->set_state(DRDiscord::State::Idle); - discord->clear_server_name(); + dr_discord->set_state(DRDiscord::State::Idle); + dr_discord->clear_server_name(); } void AOApplication::destruct_lobby() @@ -86,7 +86,7 @@ void AOApplication::destruct_lobby() return; } - delete w_lobby; + delete m_lobby; lobby_constructed = false; } @@ -98,22 +98,22 @@ void AOApplication::construct_courtroom() return; } - w_courtroom = new Courtroom(this); - connect(w_courtroom, SIGNAL(closing()), this, SLOT(on_courtroom_closing())); - connect(w_courtroom, SIGNAL(destroyed()), this, SLOT(on_courtroom_destroyed())); + m_courtroom = new Courtroom(this); + connect(m_courtroom, SIGNAL(closing()), this, SLOT(on_courtroom_closing())); + connect(m_courtroom, SIGNAL(destroyed()), this, SLOT(on_courtroom_destroyed())); courtroom_constructed = true; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QRect screen_geometry = QApplication::desktop()->screenGeometry(); #else - QScreen *screen = QApplication::screenAt(w_courtroom->pos()); + QScreen *screen = QApplication::screenAt(m_courtroom->pos()); if (screen == nullptr) return; QRect screen_geometry = screen->geometry(); #endif - int x = (screen_geometry.width() - w_courtroom->width()) / 2; - int y = (screen_geometry.height() - w_courtroom->height()) / 2; - w_courtroom->move(x, y); + int x = (screen_geometry.width() - m_courtroom->width()) / 2; + int y = (screen_geometry.height() - m_courtroom->height()) / 2; + m_courtroom->move(x, y); } void AOApplication::destruct_courtroom() @@ -121,7 +121,7 @@ void AOApplication::destruct_courtroom() // destruct courtroom if (courtroom_constructed) { - delete w_courtroom; + delete m_courtroom; courtroom_constructed = false; } else @@ -166,7 +166,7 @@ QVector &AOApplication::get_favorite_list() QString AOApplication::get_current_char() { if (courtroom_constructed) - return w_courtroom->get_current_char(); + return m_courtroom->get_current_char(); else return ""; } @@ -195,28 +195,28 @@ QString AOApplication::sanitize_path(QString p_file) void AOApplication::toggle_config_panel() { - config_panel->setVisible(!config_panel->isVisible()); - if (config_panel->isVisible()) + ao_config_panel->setVisible(!ao_config_panel->isVisible()); + if (ao_config_panel->isVisible()) { #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QRect screen_geometry = QApplication::desktop()->screenGeometry(); #else - QScreen *screen = QApplication::screenAt(config_panel->pos()); + QScreen *screen = QApplication::screenAt(ao_config_panel->pos()); if (screen == nullptr) return; QRect screen_geometry = screen->geometry(); #endif - int x = (screen_geometry.width() - config_panel->width()) / 2; - int y = (screen_geometry.height() - config_panel->height()) / 2; - config_panel->setFocus(); - config_panel->raise(); - config_panel->move(x, y); + int x = (screen_geometry.width() - ao_config_panel->width()) / 2; + int y = (screen_geometry.height() - ao_config_panel->height()) / 2; + ao_config_panel->setFocus(); + ao_config_panel->raise(); + ao_config_panel->move(x, y); } } bool AOApplication::get_first_person_enabled() { - return config->get_bool("first_person", false); + return ao_config->get_bool("first_person", false); } void AOApplication::add_favorite_server(int p_server) @@ -242,7 +242,7 @@ void AOApplication::server_disconnected() { if (!courtroom_constructed) return; - w_courtroom->stop_all_audio(); + m_courtroom->stop_all_audio(); call_notice("Disconnected from server."); construct_lobby(); destruct_courtroom(); @@ -252,7 +252,7 @@ void AOApplication::loading_cancelled() { destruct_courtroom(); - w_lobby->hide_loading_overlay(); + m_lobby->hide_loading_overlay(); } void AOApplication::ms_connect_finished(bool connected, bool will_retry) @@ -270,7 +270,7 @@ void AOApplication::ms_connect_finished(bool connected, bool will_retry) } else if (will_retry) { - w_lobby->append_error("Error connecting to master server. Will try again in " + + m_lobby->append_error("Error connecting to master server. Will try again in " + QString::number(net_manager->ms_reconnect_delay_ms / 1000.f) + " seconds."); } else @@ -289,10 +289,10 @@ void AOApplication::ms_connect_finished(bool connected, bool will_retry) void AOApplication::on_courtroom_closing() { - config_panel->hide(); + ao_config_panel->hide(); } void AOApplication::on_courtroom_destroyed() { - config_panel->hide(); + ao_config_panel->hide(); } diff --git a/src/aoblipplayer.cpp b/src/aoblipplayer.cpp index 2837b9290..8f81e387f 100644 --- a/src/aoblipplayer.cpp +++ b/src/aoblipplayer.cpp @@ -8,16 +8,16 @@ AOBlipPlayer::AOBlipPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObjec void AOBlipPlayer::set_blips(QString p_blip) { - if (m_blipName.has_value() && m_blipName.value() == p_blip) + if (m_name.has_value() && m_name.value() == p_blip) return; - m_blipName = p_blip; - m_blipFile = ao_app->get_sounds_path(m_blipName.value()); + m_name = p_blip; + m_file = ao_app->get_sounds_path(m_name.value()); } void AOBlipPlayer::blip_tick() { - if (!m_blipFile.has_value()) + if (!m_file.has_value()) return; - m_family->play_stream(m_blipFile.value()); + m_family->play_stream(m_file.value()); } diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index 5b6c0ecb1..70dfc2ba6 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -12,7 +12,7 @@ AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) m_movie = new QMovie(this); w_icon = new QLabel(this); - sfx_player = new AOSfxPlayer(ao_app, this); + dr_sfx = new AOSfxPlayer(ao_app, this); connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); } @@ -53,31 +53,21 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid this->setMovie(m_movie); - m_loop_number = 0; m_movie->start(); - sfx_player->play_effect(ao_app->get_sfx("evidence_present")); + dr_sfx->play_effect(ao_app->get_sfx("evidence_present")); } -void AOEvidenceDisplay::frame_change(int p_frame_index) +void AOEvidenceDisplay::frame_change(int p_frame) { - const int l_frame_num = p_frame_index + 1; - if (l_frame_num < m_movie->frameCount()) - return; - - if ((p_frame_index + 1) < m_movie->frameCount()) - return; - if (p_frame_index == (m_movie->frameCount() - 1)) + if (p_frame == (m_movie->frameCount() - 1)) { - QTimer::singleShot(m_movie->nextFrameDelay(), this, [this]() { - m_movie->stop(); - w_icon->show(); - clear(); - }); // we need this or else the last frame wont show delay(m_movie->nextFrameDelay()); + m_movie->stop(); + this->clear(); + w_icon->show(); - clear(); } } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a5530bba4..e4fe67346 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -122,7 +122,7 @@ void Courtroom::enter_courtroom(int p_cid) QString l_chr_name; if (is_spectating()) { - ao_app->discord->clear_character_name(); + ao_app->dr_discord->clear_character_name(); ao_config->clear_showname_placeholder(); } else @@ -130,7 +130,7 @@ void Courtroom::enter_courtroom(int p_cid) l_chr_name = ao_app->get_char_name(char_list.at(m_cid).name); const QString l_ini_showname = ao_app->get_showname(l_chr_name); const QString l_final_showname = l_ini_showname.trimmed().isEmpty() ? l_chr_name : l_ini_showname; - ao_app->discord->set_character_name(l_final_showname); + ao_app->dr_discord->set_character_name(l_final_showname); ao_config->set_showname_placeholder(l_final_showname); if (ao_app->m_FL_chrini_enabled) @@ -2352,8 +2352,8 @@ void Courtroom::on_back_to_lobby_clicked() hide(); ao_app->construct_lobby(); - ao_app->w_lobby->list_servers(); - ao_app->w_lobby->set_choose_a_server(); + ao_app->m_lobby->list_servers(); + ao_app->m_lobby->set_choose_a_server(); ao_app->destruct_courtroom(); } diff --git a/src/main.cpp b/src/main.cpp index 21ff3cea5..ac410b49c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,7 +24,7 @@ int main(int argc, char *argv[]) #ifdef QT_NO_DEBUG app.net_manager->connect_to_master(); #endif - app.w_lobby->show(); + app.m_lobby->show(); return app.exec(); } diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index e70006e07..f6c1b9d8a 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -45,7 +45,7 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) if (lobby_constructed) { - w_lobby->list_servers(); + m_lobby->list_servers(); } } else if (header == "CT") @@ -67,7 +67,7 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) if (lobby_constructed) { - w_lobby->append_chatmessage(f_name, f_message); + m_lobby->append_chatmessage(f_name, f_message); } } else if (header == "AO2CHECK") @@ -157,7 +157,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; if (courtroom_constructed) - w_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1)); + m_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1)); } else if (header == "FL") { @@ -172,7 +172,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (f_contents.size() < 2) goto end; - w_lobby->set_player_count(f_contents.at(0).toInt(), f_contents.at(1).toInt()); + m_lobby->set_player_count(f_contents.at(0).toInt(), f_contents.at(1).toInt()); } else if (header == "SI") { @@ -195,11 +195,11 @@ void AOApplication::server_packet_received(AOPacket *p_packet) courtroom_loaded = false; QString window_title = "Danganronpa Online"; - int selected_server = w_lobby->get_selected_server(); + int selected_server = m_lobby->get_selected_server(); QString server_name, server_address; bool is_favorite = false; - if (w_lobby->public_servers_selected) + if (m_lobby->public_servers_selected) { if (selected_server >= 0 && selected_server < server_list.size()) { @@ -221,11 +221,11 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } } - w_courtroom->set_window_title(window_title); + m_courtroom->set_window_title(window_title); - w_lobby->show_loading_overlay(); - w_lobby->set_loading_text("Loading"); - w_lobby->set_loading_value(0); + m_lobby->show_loading_overlay(); + m_lobby->set_loading_text("Loading"); + m_lobby->set_loading_value(0); AOPacket *f_packet = nullptr; @@ -245,8 +245,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } } } - discord->set_state(DRDiscord::State::Connected); - discord->set_server_name(server_name); + dr_discord->set_state(DRDiscord::State::Connected); + dr_discord->set_server_name(server_name); } else if (header == "CI") { @@ -276,15 +276,15 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ++loaded_chars; - w_lobby->set_loading_text("Loading chars:\n" + QString::number(loaded_chars) + "/" + + m_lobby->set_loading_text("Loading chars:\n" + QString::number(loaded_chars) + "/" + QString::number(char_list_size)); - w_courtroom->append_char(f_char); + m_courtroom->append_char(f_char); } int total_loading_size = char_list_size + evidence_list_size + music_list_size; int loading_value = (loaded_chars / static_cast(total_loading_size)) * 100; - w_lobby->set_loading_value(loading_value); + m_lobby->set_loading_value(loading_value); send_server_packet(new AOPacket("RE#%")); } @@ -313,14 +313,14 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ++loaded_evidence; - w_lobby->set_loading_text("Loading evidence:\n" + QString::number(loaded_evidence) + "/" + + m_lobby->set_loading_text("Loading evidence:\n" + QString::number(loaded_evidence) + "/" + QString::number(evidence_list_size)); - w_courtroom->append_evidence(f_evi); + m_courtroom->append_evidence(f_evi); int total_loading_size = char_list_size + evidence_list_size + music_list_size; int loading_value = ((loaded_chars + loaded_evidence) / static_cast(total_loading_size)) * 100; - w_lobby->set_loading_value(loading_value); + m_lobby->set_loading_value(loading_value); QString next_packet_number = QString::number(loaded_evidence); send_server_packet(new AOPacket("AE#" + next_packet_number + "#%")); @@ -345,12 +345,12 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ++loaded_music; - w_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + + m_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + QString::number(music_list_size)); if (music_turn) { - w_courtroom->append_music(f_music); + m_courtroom->append_music(f_music); } else { @@ -359,12 +359,12 @@ void AOApplication::server_packet_received(AOPacket *p_packet) { music_turn = true; areas--; - w_courtroom->fix_last_area(); - w_courtroom->append_music(f_music); + m_courtroom->fix_last_area(); + m_courtroom->append_music(f_music); } else { - w_courtroom->append_area(f_music); + m_courtroom->append_area(f_music); areas++; } } @@ -373,7 +373,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int total_loading_size = char_list_size + evidence_list_size + music_list_size; int loading_value = ((loaded_chars + loaded_evidence + loaded_music) / static_cast(total_loading_size)) * 100; - w_lobby->set_loading_value(loading_value); + m_lobby->set_loading_value(loading_value); } QString next_packet_number = QString::number(((loaded_music - 1) / 10) + 1); @@ -387,9 +387,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) for (int n_char = 0; n_char < f_contents.size(); ++n_char) { if (f_contents.at(n_char) == "-1") - w_courtroom->set_taken(n_char, true); + m_courtroom->set_taken(n_char, true); else - w_courtroom->set_taken(n_char, false); + m_courtroom->set_taken(n_char, false); } } @@ -412,15 +412,15 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ++loaded_chars; - w_lobby->set_loading_text("Loading chars:\n" + QString::number(loaded_chars) + "/" + + m_lobby->set_loading_text("Loading chars:\n" + QString::number(loaded_chars) + "/" + QString::number(char_list_size)); - w_courtroom->append_char(f_char); + m_courtroom->append_char(f_char); } int total_loading_size = char_list_size + evidence_list_size + music_list_size; int loading_value = (loaded_chars / static_cast(total_loading_size)) * 100; - w_lobby->set_loading_value(loading_value); + m_lobby->set_loading_value(loading_value); send_server_packet(new AOPacket("RM#%")); } @@ -436,12 +436,12 @@ void AOApplication::server_packet_received(AOPacket *p_packet) { ++loaded_music; - w_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + + m_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + QString::number(music_list_size)); if (musics_time) { - w_courtroom->append_music(f_contents.at(n_element)); + m_courtroom->append_music(f_contents.at(n_element)); } else { @@ -450,13 +450,13 @@ void AOApplication::server_packet_received(AOPacket *p_packet) f_contents.at(n_element).endsWith(".opus")) { musics_time = true; - w_courtroom->fix_last_area(); - w_courtroom->append_music(f_contents.at(n_element)); + m_courtroom->fix_last_area(); + m_courtroom->append_music(f_contents.at(n_element)); areas--; } else { - w_courtroom->append_area(f_contents.at(n_element)); + m_courtroom->append_area(f_contents.at(n_element)); areas++; } } @@ -464,7 +464,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) int total_loading_size = char_list_size + evidence_list_size + music_list_size; int loading_value = ((loaded_chars + loaded_evidence + loaded_music) / static_cast(total_loading_size)) * 100; - w_lobby->set_loading_value(loading_value); + m_lobby->set_loading_value(loading_value); } send_server_packet(new AOPacket("RD#%")); @@ -474,8 +474,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (!courtroom_constructed) goto end; - w_courtroom->clear_music(); - w_courtroom->clear_areas(); + m_courtroom->clear_music(); + m_courtroom->clear_areas(); bool musics_time = false; int areas = 0; @@ -484,7 +484,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) { if (musics_time) { - w_courtroom->append_music(f_contents.at(n_element)); + m_courtroom->append_music(f_contents.at(n_element)); } else { @@ -493,27 +493,27 @@ void AOApplication::server_packet_received(AOPacket *p_packet) f_contents.at(n_element).endsWith(".opus")) { musics_time = true; - w_courtroom->fix_last_area(); - w_courtroom->append_music(f_contents.at(n_element)); + m_courtroom->fix_last_area(); + m_courtroom->append_music(f_contents.at(n_element)); areas--; } else { - w_courtroom->append_area(f_contents.at(n_element)); + m_courtroom->append_area(f_contents.at(n_element)); areas++; } } } - w_courtroom->list_music(); - w_courtroom->list_areas(); + m_courtroom->list_music(); + m_courtroom->list_areas(); } else if (header == "DONE") { if (!courtroom_constructed) goto end; - w_courtroom->done_received(); + m_courtroom->done_received(); courtroom_loaded = true; @@ -526,8 +526,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (courtroom_constructed) { - w_courtroom->set_background(f_contents.at(0)); - w_courtroom->set_scene(); + m_courtroom->set_background(f_contents.at(0)); + m_courtroom->set_scene(); } } // server accepting char request(CC) packet @@ -537,34 +537,34 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; if (courtroom_constructed) - w_courtroom->enter_courtroom(f_contents.at(2).toInt()); + m_courtroom->enter_courtroom(f_contents.at(2).toInt()); } else if (header == "MS") { if (courtroom_constructed && courtroom_loaded) - w_courtroom->handle_chatmessage(p_packet->get_contents()); + m_courtroom->handle_chatmessage(p_packet->get_contents()); } else if (header == "ackMS") { if (courtroom_constructed && courtroom_loaded) - w_courtroom->handle_acknowledged_ms(); + m_courtroom->handle_acknowledged_ms(); } else if (header == "MC") { if (courtroom_constructed && courtroom_loaded) - w_courtroom->handle_song(p_packet->get_contents()); + m_courtroom->handle_song(p_packet->get_contents()); } else if (header == "RT") { if (f_contents.size() < 1) goto end; if (courtroom_constructed) - w_courtroom->handle_wtce(f_contents.at(0)); + m_courtroom->handle_wtce(f_contents.at(0)); } 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()); + m_courtroom->set_hp_bar(f_contents.at(0).toInt(), f_contents.at(1).toInt()); } else if (header == "LE") { @@ -587,29 +587,29 @@ void AOApplication::server_packet_received(AOPacket *p_packet) f_evi_list.append(f_evi); } - w_courtroom->set_evidence_list(f_evi_list); + m_courtroom->set_evidence_list(f_evi_list); } } else if (header == "IL") { if (courtroom_constructed && f_contents.size() > 0) - w_courtroom->set_ip_list(f_contents.at(0)); + m_courtroom->set_ip_list(f_contents.at(0)); } else if (header == "MU") { if (courtroom_constructed && f_contents.size() > 0) - w_courtroom->set_mute(true, f_contents.at(0).toInt()); + m_courtroom->set_mute(true, f_contents.at(0).toInt()); } else if (header == "UM") { if (courtroom_constructed && f_contents.size() > 0) - w_courtroom->set_mute(false, f_contents.at(0).toInt()); + m_courtroom->set_mute(false, f_contents.at(0).toInt()); } else if (header == "KK") { if (courtroom_constructed && f_contents.size() > 0) { - int f_cid = w_courtroom->get_cid(); + int f_cid = m_courtroom->get_cid(); int remote_cid = f_contents.at(0).toInt(); if (f_cid != remote_cid && remote_cid != -1) @@ -623,7 +623,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) else if (header == "KB") { if (courtroom_constructed && f_contents.size() > 0) - w_courtroom->set_ban(f_contents.at(0).toInt()); + m_courtroom->set_ban(f_contents.at(0).toInt()); } else if (header == "BD") { @@ -632,28 +632,28 @@ void AOApplication::server_packet_received(AOPacket *p_packet) else if (header == "ZZ") { if (courtroom_constructed && f_contents.size() > 0) - w_courtroom->mod_called(f_contents.at(0)); + m_courtroom->mod_called(f_contents.at(0)); } else if (header == "CL") { qDebug() << f_contents; - w_courtroom->handle_clock(f_contents.at(1)); + m_courtroom->handle_clock(f_contents.at(1)); } else if (header == "GM") { if (f_contents.length() < 1) goto end; - if (config->manual_gamemode_enabled()) + if (ao_config->manual_gamemode_enabled()) goto end; - config->set_gamemode(f_contents.at(0)); + ao_config->set_gamemode(f_contents.at(0)); } else if (header == "TOD") { if (f_contents.length() < 1) goto end; - if (config->manual_timeofday_enabled()) + if (ao_config->manual_timeofday_enabled()) goto end; - config->set_timeofday(f_contents.at(0)); + ao_config->set_timeofday(f_contents.at(0)); } else if (header == "TR") { @@ -663,7 +663,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (!courtroom_constructed) goto end; int timer_id = f_contents.at(0).toInt(); - w_courtroom->resume_timer(timer_id); + m_courtroom->resume_timer(timer_id); } else if (header == "TST") { @@ -674,7 +674,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; int timer_id = f_contents.at(0).toInt(); int new_time = f_contents.at(1).toInt(); - w_courtroom->set_timer_time(timer_id, new_time); + m_courtroom->set_timer_time(timer_id, new_time); } else if (header == "TSS") { @@ -685,7 +685,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; int timer_id = f_contents.at(0).toInt(); int timestep_length = f_contents.at(1).toInt(); - w_courtroom->set_timer_timestep(timer_id, timestep_length); + m_courtroom->set_timer_timestep(timer_id, timestep_length); } else if (header == "TSF") { @@ -696,7 +696,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; int timer_id = f_contents.at(0).toInt(); int firing_interval = f_contents.at(1).toInt(); - w_courtroom->set_timer_firing(timer_id, firing_interval); + m_courtroom->set_timer_firing(timer_id, firing_interval); } else if (header == "TP") { @@ -706,7 +706,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (!courtroom_constructed) goto end; int timer_id = f_contents.at(0).toInt(); - w_courtroom->pause_timer(timer_id); + m_courtroom->pause_timer(timer_id); } else if (header == "SP") { @@ -715,7 +715,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; if (!courtroom_constructed) goto end; - w_courtroom->set_character_position(f_contents.at(0)); + m_courtroom->set_character_position(f_contents.at(0)); } else if (header == "SN") { @@ -728,7 +728,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) const QString l_showname = f_contents.at(0); // By updating now, we prevent the client from sending an SN back when we later on go ahead and modify the // showname box. - config->set_showname(l_showname); + ao_config->set_showname(l_showname); } end: diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 22f9937b3..31bddd8f3 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -51,7 +51,7 @@ QString AOApplication::get_background_path(QString p_file) { if (courtroom_constructed) { - return get_case_sensitive_path(w_courtroom->get_background_path(p_file)); + return get_case_sensitive_path(m_courtroom->get_background_path(p_file)); } // this function being called when the courtroom isn't constructed makes no // sense @@ -201,9 +201,9 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_ext_l QStringList l_path_list; // Only add gamemode and/or time of day if non empty. - const QString l_gamemode = config->gamemode(); - const QString l_timeofday = config->timeofday(); - const QString l_theme_root = get_base_path() + "themes/" + config->theme(); + const QString l_gamemode = ao_config->gamemode(); + const QString l_timeofday = ao_config->timeofday(); + const QString l_theme_root = get_base_path() + "themes/" + ao_config->theme(); if (!l_gamemode.isEmpty()) { diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 6f5d3237e..bd6488a03 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -16,7 +16,7 @@ QStringList AOApplication::get_callwords() #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) return config->callwords().split(" ", QString::SkipEmptyParts); #else - return config->callwords().split(" ", Qt::SkipEmptyParts); + return ao_config->callwords().split(" ", Qt::SkipEmptyParts); #endif } @@ -298,11 +298,11 @@ QMap AOApplication::get_chatmessage_colors() if (path.isEmpty()) { qInfo().noquote() << QString("[color] theme %1 is missing file: %2, using default colors instead") - .arg(config->theme()) + .arg(ao_config->theme()) .arg(file_name); return color_map; } - qInfo().noquote() << QString("[color] loading colors for theme %1").arg(config->theme()); + qInfo().noquote() << QString("[color] loading colors for theme %1").arg(ao_config->theme()); QSettings color_settings(path, QSettings::IniFormat); From 2fd9d5aaa9c034eeb34ee004f192abd5516ab94d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 17 May 2021 19:53:01 +0200 Subject: [PATCH 361/842] More member variable renaming, music parsing improvement, ... * Merged SM and FM packet section into one * Moved more methods from header to source file --- include/aoblipplayer.h | 4 +- include/courtroom.h | 35 ++------- include/lobby.h | 10 +-- src/charselect.cpp | 14 ++-- src/courtroom.cpp | 66 +++++++++++----- src/courtroom_widgets.cpp | 4 +- src/file_functions.cpp | 2 +- src/lobby.cpp | 10 +++ src/packet_distribution.cpp | 146 ++++++------------------------------ 9 files changed, 105 insertions(+), 186 deletions(-) diff --git a/include/aoblipplayer.h b/include/aoblipplayer.h index 197afa5ab..44f2db87d 100644 --- a/include/aoblipplayer.h +++ b/include/aoblipplayer.h @@ -30,6 +30,6 @@ public slots: private: DRAudioStreamFamily::ptr m_family; - std::optional m_blipName; - std::optional m_blipFile; + std::optional m_name; + std::optional m_file; }; diff --git a/include/courtroom.h b/include/courtroom.h index 08524eda9..e1191b57b 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -58,20 +58,8 @@ class Courtroom : public QMainWindow void append_char(char_type p_char); void append_evidence(evi_type p_evi); - void append_music(QString f_music); - void append_area(QString f_area); - void clear_music(); - void clear_areas(); - - void fix_last_area() - { - if (area_list.size() > 0) - { - QString malplaced = area_list.last(); - area_list.removeLast(); - append_music(malplaced); - } - } + void set_area_list(QStringList area_list); + void set_music_list(QStringList music_list); // sets position of widgets based on theme ini files void set_widgets(); @@ -138,14 +126,8 @@ class Courtroom : public QMainWindow QString get_background_path(QString p_file); // cid = character id, returns the cid of the currently selected character - int get_cid() - { - return m_cid; - } - QString get_current_char() - { - return current_char; - } + int get_cid(); + QString get_current_char(); // Set the showname of the client void set_showname(QString p_showname); @@ -257,11 +239,10 @@ class Courtroom : public QMainWindow int m_viewport_width = 256; int m_viewport_height = 192; - QVector char_list; - QVector evidence_list; - QVector music_list; - QVector area_list; - QVector area_names; + QVector m_chr_list; + QVector m_evidence_list; + QStringList m_area_list; + QStringList m_music_list; QVector note_list; QSignalMapper *char_button_mapper = nullptr; diff --git a/include/lobby.h b/include/lobby.h index 12fe6c0d0..df64324d6 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -38,14 +38,8 @@ class Lobby : public QMainWindow void set_fonts(); void set_font(QWidget *widget, QString p_identifier); void set_drtextedit_font(DRTextEdit *widget, QString p_identifier); - void show_loading_overlay() - { - ui_loading_background->show(); - } - void hide_loading_overlay() - { - ui_loading_background->hide(); - } + void show_loading_overlay(); + void hide_loading_overlay(); int get_selected_server(); void set_loading_value(int p_value); diff --git a/src/charselect.cpp b/src/charselect.cpp index 96f49f337..eaf7c18e4 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -123,17 +123,17 @@ void Courtroom::set_char_select_page() button->hide(); } - int total_pages = char_list.size() / max_chars_on_page; + int total_pages = m_chr_list.size() / max_chars_on_page; int chars_on_page = 0; - if (char_list.size() % max_chars_on_page != 0) + if (m_chr_list.size() % max_chars_on_page != 0) { ++total_pages; // i. e. not on the last page if (total_pages > current_char_page + 1) chars_on_page = max_chars_on_page; else - chars_on_page = char_list.size() % max_chars_on_page; + chars_on_page = m_chr_list.size() % max_chars_on_page; } else chars_on_page = max_chars_on_page; @@ -147,16 +147,16 @@ void Courtroom::set_char_select_page() // show all buttons for this page for (int n_button = 0; n_button < chars_on_page; ++n_button) { - if (char_list.length() <= n_button) + if (m_chr_list.length() <= n_button) continue; int n_real_char = n_button + current_char_page * max_chars_on_page; AOCharButton *f_button = ui_char_button_list.at(n_button); - f_button->set_image(char_list.at(n_real_char).name); + f_button->set_image(m_chr_list.at(n_real_char).name); f_button->show(); - if (char_list.at(n_real_char).taken) + if (m_chr_list.at(n_real_char).taken) f_button->set_taken(); } } @@ -165,7 +165,7 @@ void Courtroom::char_clicked(int n_char) { int n_real_char = n_char + current_char_page * max_chars_on_page; - QString char_ini_path = ao_app->get_character_path(char_list.at(n_real_char).name, "char.ini"); + QString char_ini_path = ao_app->get_character_path(m_chr_list.at(n_real_char).name, "char.ini"); qDebug() << "char_ini_path" << char_ini_path; if (!file_exists(char_ini_path)) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index e4fe67346..be683bf76 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -48,6 +48,26 @@ Courtroom::~Courtroom() stop_all_audio(); } +void Courtroom::append_char(char_type p_char) +{ + m_chr_list.append(p_char); +} + +void Courtroom::append_evidence(evi_type p_evi) +{ + m_evidence_list.append(p_evi); +} + +void Courtroom::set_area_list(QStringList area_list) +{ + m_area_list = area_list; +} + +void Courtroom::set_music_list(QStringList music_list) +{ + m_music_list = music_list; +} + void Courtroom::enter_courtroom(int p_cid) { qDebug() << "enter_courtroom"; @@ -127,7 +147,7 @@ void Courtroom::enter_courtroom(int p_cid) } else { - l_chr_name = ao_app->get_char_name(char_list.at(m_cid).name); + l_chr_name = ao_app->get_char_name(m_chr_list.at(m_cid).name); const QString l_ini_showname = ao_app->get_showname(l_chr_name); const QString l_final_showname = l_ini_showname.trimmed().isEmpty() ? l_chr_name : l_ini_showname; ao_app->dr_discord->set_character_name(l_final_showname); @@ -284,19 +304,19 @@ void Courtroom::set_scene() void Courtroom::set_taken(int n_char, bool p_taken) { - if (n_char >= char_list.size()) + if (n_char >= m_chr_list.size()) { qDebug() << "W: set_taken attempted to set an index bigger than char_list size"; return; } char_type f_char; - f_char.name = char_list.at(n_char).name; - f_char.description = char_list.at(n_char).description; + f_char.name = m_chr_list.at(n_char).name; + f_char.description = m_chr_list.at(n_char).description; f_char.taken = p_taken; - f_char.evidence_string = char_list.at(n_char).evidence_string; + f_char.evidence_string = m_chr_list.at(n_char).evidence_string; - char_list.replace(n_char, f_char); + m_chr_list.replace(n_char, f_char); } void Courtroom::set_background(QString p_background) @@ -366,9 +386,9 @@ void Courtroom::list_music() int n_listed_songs = 0; - for (int n_song = 0; n_song < music_list.size(); ++n_song) + for (int n_song = 0; n_song < m_music_list.size(); ++n_song) { - QString i_song = music_list.at(n_song); + QString i_song = m_music_list.at(n_song); if (i_song.toLower().contains(ui_music_search->text().toLower())) { @@ -404,11 +424,11 @@ void Courtroom::list_areas() int n_listed_areas = 0; - for (int n_area = 0; n_area < area_list.size(); ++n_area) + for (int n_area = 0; n_area < m_area_list.size(); ++n_area) { QString i_area = ""; - i_area.append(area_list.at(n_area)); + i_area.append(m_area_list.at(n_area)); if (i_area.toLower().contains(ui_music_search->text().toLower())) { @@ -797,7 +817,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) else is_system_speaking = false; - if (f_char_id < 0 || f_char_id >= char_list.size()) + if (f_char_id < 0 || f_char_id >= m_chr_list.size()) return; if (mute_map.value(m_chatmessage[CMChrId].toInt())) @@ -838,7 +858,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) // Having an empty showname for system is actually what we expect. if (m_chatmessage[CMShowName].isEmpty() && !is_system_speaking) { - f_showname = ao_app->get_showname(char_list.at(f_char_id).name); + f_showname = ao_app->get_showname(m_chr_list.at(f_char_id).name); } else { @@ -907,7 +927,7 @@ void Courtroom::handle_chatmessage_2() // handles IC on_app_reload_theme_requested(); } - QString real_name = char_list.at(m_chatmessage[CMChrId].toInt()).name; + QString real_name = m_chr_list.at(m_chatmessage[CMChrId].toInt()).name; QString f_showname; @@ -1661,6 +1681,16 @@ void Courtroom::set_ban(int p_cid) ao_app->destruct_courtroom(); } +int Courtroom::get_cid() +{ + return m_cid; +} + +QString Courtroom::get_current_char() +{ + return current_char; +} + void Courtroom::handle_song(QStringList p_contents) { if (p_contents.size() < 2) @@ -1680,7 +1710,7 @@ void Courtroom::handle_song(QStringList p_contents) } } - if (l_chr_id < 0 || l_chr_id >= char_list.size()) + if (l_chr_id < 0 || l_chr_id >= m_chr_list.size()) { m_music_player->play(f_song); } @@ -1705,7 +1735,7 @@ void Courtroom::handle_song(QStringList p_contents) QString str_char; if (f_showname.isEmpty()) { - str_char = ao_app->get_showname(char_list.at(l_chr_id).name); + str_char = ao_app->get_showname(m_chr_list.at(l_chr_id).name); } else { @@ -1971,13 +2001,13 @@ void Courtroom::on_mute_list_item_changed(QListWidgetItem *p_item) { int f_cid = -1; - for (int n_char = 0; n_char < char_list.size(); n_char++) + for (int n_char = 0; n_char < m_chr_list.size(); n_char++) { - if (char_list.at(n_char).name == p_item->text()) + if (m_chr_list.at(n_char).name == p_item->text()) f_cid = n_char; } - if (f_cid < 0 || f_cid >= char_list.size()) + if (f_cid < 0 || f_cid >= m_chr_list.size()) { qDebug() << "W: " << p_item->text() << " not present in char_list"; return; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 9353d2812..23d767b9e 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1506,14 +1506,14 @@ void Courtroom::set_mute_list() mute_map.clear(); // maps which characters are muted based on cid, none are muted by default - for (int n_cid = 0; n_cid < char_list.size(); n_cid++) + for (int n_cid = 0; n_cid < m_chr_list.size(); n_cid++) { mute_map.insert(n_cid, false); } QStringList sorted_mute_list; - for (char_type i_char : char_list) + for (char_type i_char : m_chr_list) sorted_mute_list.append(i_char.name); sorted_mute_list.sort(); diff --git a/src/file_functions.cpp b/src/file_functions.cpp index e542a3b6e..bf5544a83 100644 --- a/src/file_functions.cpp +++ b/src/file_functions.cpp @@ -15,7 +15,7 @@ QStringList animated_extensions() QStringList audio_extensions() { - return QStringList{"", ".wav", ".ogg", ".opus", ".mp3"}; + return QStringList{".wav", ".ogg", ".opus", ".mp3"}; } bool file_exists(QString file_path) diff --git a/src/lobby.cpp b/src/lobby.cpp index ee965ba30..3e2c39cba 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -304,6 +304,16 @@ void Lobby::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) widget->set_vertical_alignment(valignment); } +void Lobby::show_loading_overlay() +{ + ui_loading_background->show(); +} + +void Lobby::hide_loading_overlay() +{ + ui_loading_background->hide(); +} + void Lobby::set_loading_text(QString p_text) { ui_loading_text->clear(); diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index f6c1b9d8a..1bfaaecde 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -2,6 +2,7 @@ #include "courtroom.h" #include "debug_functions.h" +#include "file_functions.h" #include "hardware_functions.h" #include "lobby.h" #include "networkmanager.h" @@ -325,60 +326,6 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QString next_packet_number = QString::number(loaded_evidence); send_server_packet(new AOPacket("AE#" + next_packet_number + "#%")); } - else if (header == "EM") - { - if (!courtroom_constructed) - goto end; - - bool music_turn = false; - int areas = 0; - - for (int n_element = 0; n_element < f_contents.size(); n_element += 2) - { - if (f_contents.at(n_element).toInt() != loaded_music) - break; - - if (n_element == f_contents.size() - 1) - break; - - QString f_music = f_contents.at(n_element + 1); - - ++loaded_music; - - m_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + - QString::number(music_list_size)); - - if (music_turn) - { - m_courtroom->append_music(f_music); - } - else - { - if (f_music.endsWith(".wav") || f_music.endsWith(".mp3") || f_music.endsWith(".mp4") || - f_music.endsWith(".ogg") || f_music.endsWith(".opus")) - { - music_turn = true; - areas--; - m_courtroom->fix_last_area(); - m_courtroom->append_music(f_music); - } - else - { - m_courtroom->append_area(f_music); - areas++; - } - } - qDebug() << f_music; - - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = - ((loaded_chars + loaded_evidence + loaded_music) / static_cast(total_loading_size)) * 100; - m_lobby->set_loading_value(loading_value); - } - - QString next_packet_number = QString::number(((loaded_music - 1) / 10) + 1); - send_server_packet(new AOPacket("AM#" + next_packet_number + "#%")); - } else if (header == "CharsCheck") { if (!courtroom_constructed) @@ -424,89 +371,46 @@ void AOApplication::server_packet_received(AOPacket *p_packet) send_server_packet(new AOPacket("RM#%")); } - else if (header == "SM") + else if (header == "SM" || header == "FM") { if (!courtroom_constructed) goto end; - bool musics_time = false; - int areas = 0; + QStringList l_area_list; + QStringList l_music_list; - for (int n_element = 0; n_element < f_contents.size(); ++n_element) + for (int i = 0; i < f_contents.length(); ++i) { - ++loaded_music; + bool l_found_music = false; - m_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + - QString::number(music_list_size)); - - if (musics_time) - { - m_courtroom->append_music(f_contents.at(n_element)); - } - else - { - if (f_contents.at(n_element).endsWith(".wav") || f_contents.at(n_element).endsWith(".mp3") || - f_contents.at(n_element).endsWith(".mp4") || f_contents.at(n_element).endsWith(".ogg") || - f_contents.at(n_element).endsWith(".opus")) + { // look for first song + const QString &i_value = f_contents.at(i); + for (const QString &i_ext : audio_extensions()) { - musics_time = true; - m_courtroom->fix_last_area(); - m_courtroom->append_music(f_contents.at(n_element)); - areas--; - } - else - { - m_courtroom->append_area(f_contents.at(n_element)); - areas++; + if (!i_value.endsWith(i_ext, Qt::CaseInsensitive)) + continue; + l_found_music = true; + break; } } + if (!l_found_music) + continue; + l_area_list = f_contents.mid(0, i - 1); + l_music_list = f_contents.mid(i - 1); + break; + } + m_courtroom->set_area_list(l_area_list); + m_courtroom->set_music_list(l_music_list); + + if (header == "SM") + { int total_loading_size = char_list_size + evidence_list_size + music_list_size; int loading_value = ((loaded_chars + loaded_evidence + loaded_music) / static_cast(total_loading_size)) * 100; m_lobby->set_loading_value(loading_value); + send_server_packet(new AOPacket("RD#%")); } - - send_server_packet(new AOPacket("RD#%")); - } - else if (header == "FM") - { - if (!courtroom_constructed) - goto end; - - m_courtroom->clear_music(); - m_courtroom->clear_areas(); - - bool musics_time = false; - int areas = 0; - - for (int n_element = 0; n_element < f_contents.size(); ++n_element) - { - if (musics_time) - { - m_courtroom->append_music(f_contents.at(n_element)); - } - else - { - if (f_contents.at(n_element).endsWith(".wav") || f_contents.at(n_element).endsWith(".mp3") || - f_contents.at(n_element).endsWith(".mp4") || f_contents.at(n_element).endsWith(".ogg") || - f_contents.at(n_element).endsWith(".opus")) - { - musics_time = true; - m_courtroom->fix_last_area(); - m_courtroom->append_music(f_contents.at(n_element)); - areas--; - } - else - { - m_courtroom->append_area(f_contents.at(n_element)); - areas++; - } - } - } - - m_courtroom->list_music(); - m_courtroom->list_areas(); } else if (header == "DONE") { From 20c50a314b4e97740327604b6a2e5d59c7b48e0b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 17 May 2021 21:07:05 +0200 Subject: [PATCH 362/842] Removed QPointer, optimized for loops, ... --- include/aoapplication.h | 5 ++--- src/aoconfig.cpp | 2 +- src/aopacket.cpp | 2 +- src/draudioengine_p.cpp | 2 +- src/draudiostreamfamily.cpp | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 2276abdab..202dc95b9 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -7,7 +7,6 @@ #include #include -#include #include class NetworkManager; @@ -30,10 +29,10 @@ class AOApplication : public QApplication NetworkManager *net_manager = nullptr; - QPointer m_lobby = nullptr; + Lobby *m_lobby = nullptr; bool lobby_constructed = false; - QPointer m_courtroom = nullptr; + Courtroom *m_courtroom = nullptr; bool courtroom_constructed = false; void construct_lobby(); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 4a13d09df..d117831e1 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -214,7 +214,7 @@ void AOConfigPrivate::save_file() void AOConfigPrivate::invoke_signal(QString p_method_name, QGenericArgument p_arg1) { - for (QObject *i_child : children) + for (QObject *i_child : qAsConst(children)) { QMetaObject::invokeMethod(i_child, p_method_name.toStdString().c_str(), p_arg1); } diff --git a/src/aopacket.cpp b/src/aopacket.cpp index d02da3623..86df4c36b 100644 --- a/src/aopacket.cpp +++ b/src/aopacket.cpp @@ -34,7 +34,7 @@ QString AOPacket::to_string() { QString f_string = m_header; - for (QString i_string : m_contents) + for (const QString &i_string : qAsConst(m_contents)) { f_string += ("#" + i_string); } diff --git a/src/draudioengine_p.cpp b/src/draudioengine_p.cpp index bd8c4101e..fccc068de 100644 --- a/src/draudioengine_p.cpp +++ b/src/draudioengine_p.cpp @@ -26,7 +26,7 @@ DRAudioEnginePrivate::~DRAudioEnginePrivate() void DRAudioEnginePrivate::invoke_signal(QString p_method_name, QGenericArgument p_arg1) { - for (QObject *i_child : children) + for (QObject *i_child : qAsConst(children)) { QMetaObject::invokeMethod(i_child, p_method_name.toStdString().c_str(), p_arg1); } diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp index b5b2b5b10..4b0e72657 100644 --- a/src/draudiostreamfamily.cpp +++ b/src/draudiostreamfamily.cpp @@ -131,7 +131,7 @@ float DRAudioStreamFamily::calculate_volume() void DRAudioStreamFamily::update_device() { - for (DRAudioStream::ptr i_stream : m_stream_list) + for (DRAudioStream::ptr &i_stream : m_stream_list) i_stream->update_device(); } From 63dfe1186a6a3b6fedf2da76440722d2780faa8b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 17 May 2021 21:13:59 +0200 Subject: [PATCH 363/842] Fixed version issue --- src/text_file_functions.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index bd6488a03..7b3c30e20 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -13,11 +13,7 @@ QStringList AOApplication::get_callwords() { -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - return config->callwords().split(" ", QString::SkipEmptyParts); -#else - return ao_config->callwords().split(" ", Qt::SkipEmptyParts); -#endif + return ao_config->callwords().split(" ", DR::SkipEmptyParts); } QString AOApplication::read_note(QString filename) From 552f60f0fc7e4dcda759582af74e7df817526972 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 17 May 2021 22:55:27 +0200 Subject: [PATCH 364/842] Fixed songs not being found --- src/file_functions.cpp | 2 +- src/packet_distribution.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/file_functions.cpp b/src/file_functions.cpp index bf5544a83..e542a3b6e 100644 --- a/src/file_functions.cpp +++ b/src/file_functions.cpp @@ -15,7 +15,7 @@ QStringList animated_extensions() QStringList audio_extensions() { - return QStringList{".wav", ".ogg", ".opus", ".mp3"}; + return QStringList{"", ".wav", ".ogg", ".opus", ".mp3"}; } bool file_exists(QString file_path) diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 1bfaaecde..38d5ae390 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -405,6 +405,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (header == "SM") { + loaded_music = music_list_size; int total_loading_size = char_list_size + evidence_list_size + music_list_size; int loading_value = ((loaded_chars + loaded_evidence + loaded_music) / static_cast(total_loading_size)) * 100; From 269bdfa8fa6a77c2d2897f81e19bc8840c583864 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 17 May 2021 23:13:45 +0200 Subject: [PATCH 365/842] Option for audio extension to ignore lack of suffix --- include/file_functions.h | 2 +- src/file_functions.cpp | 5 +++-- src/packet_distribution.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/file_functions.h b/include/file_functions.h index 77038edd0..599c9008f 100644 --- a/include/file_functions.h +++ b/include/file_functions.h @@ -6,7 +6,7 @@ QStringList animated_or_static_extensions(); QStringList animated_extensions(); -QStringList audio_extensions(); +QStringList audio_extensions(bool no_suffix = false); bool file_exists(QString file_path); bool dir_exists(QString file_path); diff --git a/src/file_functions.cpp b/src/file_functions.cpp index e542a3b6e..3244a1a24 100644 --- a/src/file_functions.cpp +++ b/src/file_functions.cpp @@ -13,9 +13,10 @@ QStringList animated_extensions() return QStringList{".webp", ".apng", ".gif"}; } -QStringList audio_extensions() +QStringList audio_extensions(bool no_suffix) { - return QStringList{"", ".wav", ".ogg", ".opus", ".mp3"}; + static QStringList s_ext_list{"", ".wav", ".ogg", ".opus", ".mp3"}; + return no_suffix ? s_ext_list.mid(1) : s_ext_list; } bool file_exists(QString file_path) diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 38d5ae390..ca5ce765e 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -385,7 +385,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) { // look for first song const QString &i_value = f_contents.at(i); - for (const QString &i_ext : audio_extensions()) + for (const QString &i_ext : audio_extensions(true)) { if (!i_value.endsWith(i_ext, Qt::CaseInsensitive)) continue; From cf882fc5d5bc56ee2a5c42ff07c0271c7a513e3c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 18 May 2021 00:14:48 +0200 Subject: [PATCH 366/842] Restored music loading text --- src/packet_distribution.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index ca5ce765e..bed7be14f 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -406,6 +406,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (header == "SM") { loaded_music = music_list_size; + m_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + + QString::number(music_list_size)); int total_loading_size = char_list_size + evidence_list_size + music_list_size; int loading_value = ((loaded_chars + loaded_evidence + loaded_music) / static_cast(total_loading_size)) * 100; From 4016824594f40f8c3ae86126eb7629a9e36df778 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 19 May 2021 22:11:57 +0200 Subject: [PATCH 367/842] First of #161; added ini_dropdown, name option deprecated, ... --- include/aoconfig.h | 3 ++ include/courtroom.h | 12 ++++---- src/aoapplication.cpp | 2 +- src/aoconfig.cpp | 49 +++++++++++++++++++++++++++++ src/courtroom.cpp | 61 +++++++++++++++++++++++++++++-------- src/courtroom_widgets.cpp | 26 ++++++++++------ src/emotes.cpp | 6 ++++ src/packet_distribution.cpp | 2 +- 8 files changed, 132 insertions(+), 29 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 301474855..119a8d479 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -25,6 +25,7 @@ class AOConfig : public QObject QString username() const; QString showname() const; QString showname_placeholder() const; + QString character_ini(QString base_character) const; QString callwords() const; bool server_alerts_enabled() const; bool discord_presence() const; @@ -71,6 +72,7 @@ public slots: void set_showname(QString p_string); void set_showname_placeholder(QString p_string); void clear_showname_placeholder(); + void set_character_ini(QString base_character, QString target_character); void set_callwords(QString p_string); void set_server_alerts(bool p_enabled); void set_discord_presence(const bool p_enabled); @@ -127,6 +129,7 @@ public slots: void manual_timeofday_changed(bool); void showname_changed(QString); void showname_placeholder_changed(QString); + void character_ini_changed(QString base_character); void always_pre_changed(bool); void chat_tick_interval_changed(int); diff --git a/include/courtroom.h b/include/courtroom.h index e1191b57b..0615badcf 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -126,8 +126,9 @@ class Courtroom : public QMainWindow QString get_background_path(QString p_file); // cid = character id, returns the cid of the currently selected character - int get_cid(); - QString get_current_char(); + int get_character_id(); + QString get_base_character(); + QString get_current_character(); // Set the showname of the client void set_showname(QString p_showname); @@ -330,7 +331,6 @@ class Courtroom : public QMainWindow // character id, which index of the char_list the player is int m_cid = -1; - // if enabled, disable showing our own sprites when we talk in ic bool m_msg_is_first_person = false; @@ -339,9 +339,6 @@ class Courtroom : public QMainWindow bool m_chatbox_message_enable_highlighting = false; QVector m_chatbox_message_highlight_colors; - // cid and this may differ in cases of ini-editing - QString current_char; - QString current_file; // if true, a reload theme order was delayed to be executed *after* a shout @@ -455,6 +452,7 @@ class Courtroom : public QMainWindow AOButton *ui_emote_right = nullptr; QComboBox *ui_emote_dropdown = nullptr; + QComboBox *ui_ini_dropdown = nullptr; QComboBox *ui_pos_dropdown = nullptr; AOImageDisplay *ui_defense_bar = nullptr; @@ -620,6 +618,7 @@ private slots: void on_showname_changed(QString); void on_showname_placeholder_changed(QString); + void on_character_ini_changed(QString); void on_ic_showname_editing_finished(); void on_ic_message_return_pressed(); void on_chat_config_changed(); @@ -644,6 +643,7 @@ private slots: void on_emote_right_clicked(); void on_emote_dropdown_changed(int p_index); + void on_ini_dropdown_changed(int p_index); void on_pos_dropdown_changed(int p_index); void on_evidence_name_edited(); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index d5b427df2..97b5a0a1e 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -166,7 +166,7 @@ QVector &AOApplication::get_favorite_list() QString AOApplication::get_current_char() { if (courtroom_constructed) - return m_courtroom->get_current_char(); + return m_courtroom->get_current_character(); else return ""; } diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index d117831e1..9f622637c 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -58,6 +58,7 @@ private slots: bool manual_timeofday; QString showname; QString showname_placeholder; + QMap ini_map; bool always_pre; int chat_tick_interval; int log_max_lines; @@ -162,6 +163,20 @@ void AOConfigPrivate::read_file() audio_engine->get_family(DRAudio::Family::FBlip)->set_volume(blip_volume); audio_engine->get_family(DRAudio::Family::FBlip)->set_ignore_suppression(effect_ignore_suppression); + { // ini swap + cfg.beginGroup("character_ini"); + + ini_map.clear(); + for (const QString &i_key : cfg.childKeys()) + { + const QString i_value = cfg.value(i_key).toString(); + if (i_key == i_value || i_value.trimmed().isEmpty()) + continue; + ini_map.insert(i_key, i_value); + } + + cfg.endGroup(); + } // audio device update_favorite_device(); } @@ -209,6 +224,17 @@ void AOConfigPrivate::save_file() cfg.setValue("blip_ignore_suppression", blip_ignore_suppression); cfg.setValue("blip_rate", blip_rate); cfg.setValue("blank_blips", blank_blips); + + cfg.remove("character_ini"); + { // ini swap + cfg.beginGroup("character_ini"); + + for (auto it = ini_map.cbegin(); it != ini_map.cend(); ++it) + cfg.setValue(it.key(), it.value()); + + cfg.endGroup(); + } + cfg.sync(); } @@ -296,6 +322,13 @@ QString AOConfig::showname_placeholder() const return d->showname_placeholder; } +QString AOConfig::character_ini(QString p_base_chr) const +{ + if (d->ini_map.contains(p_base_chr)) + return d->ini_map[p_base_chr]; + return p_base_chr; +} + QString AOConfig::callwords() const { return d->callwords; @@ -533,6 +566,22 @@ void AOConfig::clear_showname_placeholder() set_showname_placeholder(nullptr); } +void AOConfig::set_character_ini(QString p_base_chr, QString p_target_chr) +{ + if (d->ini_map.contains(p_base_chr)) + { + if (d->ini_map[p_base_chr] == p_target_chr) + return; + } + else if (p_base_chr == p_target_chr) + return; + if (p_base_chr == p_target_chr) + d->ini_map.remove(p_base_chr); + else + d->ini_map.insert(p_base_chr, p_target_chr); + d->invoke_signal("character_ini_changed", Q_ARG(QString, p_base_chr)); +} + void AOConfig::set_callwords(QString p_string) { if (d->callwords == p_string) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index be683bf76..61cb67978 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -139,7 +139,34 @@ void Courtroom::enter_courtroom(int p_cid) l_current_field = ui_ooc_chat_message; const int l_current_cursor_pos = l_current_field->cursorPosition(); - QString l_chr_name; + const QString l_chr_name = get_current_character(); + { // repopulate ini-swapper + QSignalBlocker b_ini_list(ui_ini_dropdown); + ui_ini_dropdown->clear(); + + QStringList l_name_list{"Default"}; + const QString l_path = ao_app->get_base_path() + "/characters"; + const QFileInfoList &l_info_list = QDir(l_path).entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); + for (const QFileInfo &i_info : l_info_list) + { + const QString l_name = i_info.fileName(); + if (get_base_character() == l_name) + continue; + if (!file_exists(ao_app->get_character_path(l_name, "char.ini"))) + continue; + l_name_list.append(l_name); + } + + for (int i = 0; i < l_name_list.length(); ++i) + { + const QString &i_name = l_name_list.at(i); + const QString l_real_name = i == 0 ? get_base_character() : i_name; + const QString l_icon_file = ao_app->get_character_path(l_real_name, "char_icon.png"); + ui_ini_dropdown->addItem(QIcon(l_icon_file), i_name); + } + ui_ini_dropdown->setCurrentText(get_current_character()); + } + if (is_spectating()) { ao_app->dr_discord->clear_character_name(); @@ -147,9 +174,8 @@ void Courtroom::enter_courtroom(int p_cid) } else { - l_chr_name = ao_app->get_char_name(m_chr_list.at(m_cid).name); - const QString l_ini_showname = ao_app->get_showname(l_chr_name); - const QString l_final_showname = l_ini_showname.trimmed().isEmpty() ? l_chr_name : l_ini_showname; + const QString l_showname = ao_app->get_showname(l_chr_name); + const QString l_final_showname = l_showname.trimmed().isEmpty() ? l_chr_name : l_showname; ao_app->dr_discord->set_character_name(l_final_showname); ao_config->set_showname_placeholder(l_final_showname); @@ -160,11 +186,11 @@ void Courtroom::enter_courtroom(int p_cid) ao_app->send_server_packet(l_packet); } } - const bool l_changed_chr = l_chr_name != current_char; - current_char = l_chr_name; + const bool l_changed_chr = l_chr_name != get_current_character(); + QString l_current_chr = l_chr_name; const int l_prev_emote_count = m_emote_list.count(); - m_emote_list = ao_app->get_emote_list(current_char); + m_emote_list = ao_app->get_emote_list(l_current_chr); const QString l_prev_emote = ui_emote_dropdown->currentText(); set_emote_dropdown(); @@ -195,6 +221,7 @@ void Courtroom::enter_courtroom(int p_cid) ui_emotes->setHidden(is_spectating()); ui_emote_dropdown->setHidden(is_spectating()); + ui_ini_dropdown->setHidden(is_spectating()); ui_ic_chat_message->setEnabled(!is_spectating()); // restore line field focus @@ -654,6 +681,11 @@ void Courtroom::on_showname_placeholder_changed(QString p_showname_placeholder) ui_ic_chat_showname->setToolTip(l_showname); } +void Courtroom::on_character_ini_changed(QString p_base_chr) +{ + enter_courtroom(m_cid); +} + void Courtroom::on_ic_message_return_pressed() { if (ui_ic_chat_message->text() == "" || is_client_muted) @@ -699,7 +731,7 @@ void Courtroom::on_ic_message_return_pressed() packet_contents.append(l_emote.anim); - packet_contents.append(current_char); + packet_contents.append(get_current_character()); if (ui_hidden->isChecked()) packet_contents.append("../../misc/blank"); @@ -708,7 +740,7 @@ void Courtroom::on_ic_message_return_pressed() packet_contents.append(ui_ic_chat_message->text()); - const QString l_side = ao_app->get_char_side(current_char); + const QString l_side = ao_app->get_char_side(get_current_character()); packet_contents.append(l_side); // sfx file @@ -1681,14 +1713,19 @@ void Courtroom::set_ban(int p_cid) ao_app->destruct_courtroom(); } -int Courtroom::get_cid() +int Courtroom::get_character_id() { return m_cid; } -QString Courtroom::get_current_char() +QString Courtroom::get_base_character() +{ + return is_spectating() ? nullptr : m_chr_list.at(m_cid).name; +} + +QString Courtroom::get_current_character() { - return current_char; + return ao_config->character_ini(get_base_character()); } void Courtroom::handle_song(QStringList p_contents) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 23d767b9e..8e5634b15 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -91,6 +91,8 @@ void Courtroom::create_widgets() ui_vp_wtce = new AOMovie(this, ao_app); ui_vp_objection = new AOMovie(this, ao_app); + ui_ini_dropdown = new QComboBox(this); + ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); @@ -248,6 +250,7 @@ void Courtroom::connect_widgets() connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); connect(ui_emote_dropdown, SIGNAL(activated(int)), this, SLOT(on_emote_dropdown_changed(int))); + connect(ui_ini_dropdown, SIGNAL(activated(int)), this, SLOT(on_ini_dropdown_changed(int))); connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); connect(ui_mute_list, SIGNAL(itemChanged(QListWidgetItem *)), this, @@ -256,6 +259,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(showname_changed(QString)), this, SLOT(on_showname_changed(QString))); connect(ao_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); + connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(on_character_ini_changed(QString))); connect(ui_ic_chat_showname, SIGNAL(editingFinished()), this, SLOT(on_ic_showname_editing_finished())); connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ic_message_return_pressed())); @@ -368,6 +372,7 @@ void Courtroom::reset_widget_names() {"emote_left", ui_emote_left}, {"emote_right", ui_emote_right}, {"emote_dropdown", ui_emote_dropdown}, + {"ini_dropdown", ui_ini_dropdown}, {"pos_dropdown", ui_pos_dropdown}, {"defense_bar", ui_defense_bar}, {"prosecution_bar", ui_prosecution_bar}, @@ -677,6 +682,8 @@ void Courtroom::set_widgets() set_size_and_pos(ui_emote_dropdown, "emote_dropdown"); + set_size_and_pos(ui_ini_dropdown, "ini_dropdown"); + set_size_and_pos(ui_pos_dropdown, "pos_dropdown"); set_size_and_pos(ui_defense_bar, "defense_bar"); @@ -1066,8 +1073,8 @@ void Courtroom::check_effects() for (int i = 0; i < ui_effects.size(); ++i) { - QString path = - ao_app->find_asset_path({ao_app->get_character_path(current_char, effect_names.at(i))}, animated_extensions()); + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_current_character(), effect_names.at(i))}, + animated_extensions()); if (path.isEmpty()) path = ao_app->find_theme_asset_path(effect_names.at(i), animated_extensions()); effects_enabled[i] = (!path.isEmpty()); @@ -1085,8 +1092,8 @@ void Courtroom::check_free_blocks() for (int i = 0; i < ui_free_blocks.size(); ++i) { - QString path = ao_app->find_asset_path({ao_app->get_character_path(current_char, free_block_names.at(i))}, - animated_extensions()); + QString path = ao_app->find_asset_path( + {ao_app->get_character_path(get_current_character(), free_block_names.at(i))}, animated_extensions()); if (path.isEmpty()) path = ao_app->find_theme_asset_path(free_block_names.at(i), animated_extensions()); free_blocks_enabled[i] = (!path.isEmpty()); @@ -1104,8 +1111,8 @@ void Courtroom::check_shouts() for (int i = 0; i < ui_shouts.size(); ++i) { - QString path = - ao_app->find_asset_path({ao_app->get_character_path(current_char, shout_names.at(i))}, animated_extensions()); + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_current_character(), shout_names.at(i))}, + animated_extensions()); if (path.isEmpty()) path = ao_app->find_theme_asset_path(shout_names.at(i), animated_extensions()); @@ -1125,8 +1132,8 @@ void Courtroom::check_wtce() for (int i = 0; i < ui_wtce.size(); ++i) { - QString path = - ao_app->find_asset_path({ao_app->get_character_path(current_char, wtce_names.at(i))}, animated_extensions()); + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_current_character(), wtce_names.at(i))}, + animated_extensions()); if (path.isEmpty()) path = ao_app->find_theme_asset_path(wtce_names.at(i), animated_extensions()); wtce_enabled[i] = (!path.isEmpty()); @@ -1396,8 +1403,9 @@ void Courtroom::set_dropdown(QWidget *widget, QString target_tag) void Courtroom::set_dropdowns() { set_dropdown(ui_text_color, "[TEXT COLOR]"); - set_dropdown(ui_pos_dropdown, "[POS DROPDOWN]"); set_dropdown(ui_emote_dropdown, "[EMOTE DROPDOWN]"); + set_dropdown(ui_ini_dropdown, "[INI DROPDOWN]"); + set_dropdown(ui_pos_dropdown, "[POS DROPDOWN]"); set_dropdown(ui_mute_list, "[MUTE LIST]"); set_dropdown(ui_ic_chat_message, "[IC LINE]"); set_dropdown(ui_ooc_chat_message, "[OOC LINE]"); diff --git a/src/emotes.cpp b/src/emotes.cpp index 564258774..8160cf4ca 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -208,3 +208,9 @@ void Courtroom::on_emote_dropdown_changed(int p_index) { select_emote(p_index); } + +void Courtroom::on_ini_dropdown_changed(int p_index) +{ + ao_config->set_character_ini(get_base_character(), + p_index == 0 ? get_base_character() : ui_ini_dropdown->itemText(p_index)); +} diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index bed7be14f..92eef62f6 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -516,7 +516,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) { if (courtroom_constructed && f_contents.size() > 0) { - int f_cid = m_courtroom->get_cid(); + int f_cid = m_courtroom->get_character_id(); int remote_cid = f_contents.at(0).toInt(); if (f_cid != remote_cid && remote_cid != -1) From 0094ea2657e9761d10091ff7baf6d1905c5d3ecc Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 19 May 2021 23:20:37 +0200 Subject: [PATCH 368/842] Reintroduce blank-icon --- src/courtroom.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 61cb67978..8866cf0c2 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -157,12 +157,14 @@ void Courtroom::enter_courtroom(int p_cid) l_name_list.append(l_name); } + QPixmap l_blank_image(64, 64); + l_blank_image.fill(Qt::transparent); for (int i = 0; i < l_name_list.length(); ++i) { const QString &i_name = l_name_list.at(i); const QString l_real_name = i == 0 ? get_base_character() : i_name; const QString l_icon_file = ao_app->get_character_path(l_real_name, "char_icon.png"); - ui_ini_dropdown->addItem(QIcon(l_icon_file), i_name); + ui_ini_dropdown->addItem(file_exists(l_icon_file) ? QIcon(l_icon_file) : QIcon(l_blank_image), i_name); } ui_ini_dropdown->setCurrentText(get_current_character()); } @@ -683,6 +685,7 @@ void Courtroom::on_showname_placeholder_changed(QString p_showname_placeholder) void Courtroom::on_character_ini_changed(QString p_base_chr) { + Q_UNUSED(p_base_chr) enter_courtroom(m_cid); } From 74a9fc2ce009215fe88a1a199f71ad568f559581 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 20 May 2021 02:03:15 +0200 Subject: [PATCH 369/842] Added MacOS/iOS workaround for looking up inside char.ini variables --- src/text_file_functions.cpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 7b3c30e20..c6dae1c7b 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -500,16 +500,24 @@ QStringList AOApplication::get_sfx_list() return r_sfx_list; } +QString drLookupKey(const QStringList &keyList, const QString &targetKey) +{ + const QString finalTargetKey = targetKey.toLower(); + for (const QString &i_key : qAsConst(keyList)) + if (i_key.toLower() == finalTargetKey) + return i_key; + return targetKey; +} + // returns whatever is to the right of "search_line =" within target_tag and // terminator_tag, trimmed returns the empty string if the search line couldnt // be found - QVariant AOApplication::read_char_ini(QString p_chr, QString p_group, QString p_key, QVariant p_def) { QSettings s(get_character_path(p_chr, "char.ini"), QSettings::IniFormat); s.setIniCodec("UTF-8"); - s.beginGroup(p_group); - return s.value(p_key, p_def); + s.beginGroup(drLookupKey(s.childGroups(), p_group)); + return s.value(drLookupKey(s.childKeys(), p_key), p_def); } QVariant AOApplication::read_char_ini(QString p_chr, QString p_group, QString p_key) @@ -596,13 +604,14 @@ QVector AOApplication::get_emote_list(QString p_chr) QStringList l_keys; { // recover all numbered keys, ignore words - l_chrini.beginGroup("emotions"); + const QStringList l_group_list = l_chrini.childGroups(); + l_chrini.beginGroup(drLookupKey(l_group_list, "emotions")); l_keys = l_chrini.childKeys(); l_chrini.endGroup(); // remove keywords - l_keys.removeAll("firstmode"); - l_keys.removeAll("number"); + l_keys.removeAll(drLookupKey(l_keys, "firstmode")); + l_keys.removeAll(drLookupKey(l_keys, "number")); // remove all negative and non-numbers for (int i = 0; i < l_keys.length(); ++i) @@ -625,7 +634,8 @@ QVector AOApplication::get_emote_list(QString p_chr) for (const QString &i_key : qAsConst(l_keys)) { - l_chrini.beginGroup("emotions"); + const QStringList l_group_list = l_chrini.childGroups(); + l_chrini.beginGroup(drLookupKey(l_group_list, "emotions")); const QStringList l_emotions = l_chrini.value(i_key).toString().split("#", DR::KeepEmptyParts); l_chrini.endGroup(); @@ -653,11 +663,11 @@ QVector AOApplication::get_emote_list(QString p_chr) if (DeskModifier < l_emotions.length()) l_emote.desk_modifier = l_emotions.at(DeskModifier).toInt(); - l_chrini.beginGroup("soundn"); + l_chrini.beginGroup(drLookupKey(l_group_list, "soundn")); l_emote.sound_file = l_chrini.value(i_key).toString(); l_chrini.endGroup(); - l_chrini.beginGroup("soundt"); + l_chrini.beginGroup(drLookupKey(l_group_list, "soundt")); l_emote.sound_delay = qMax(l_chrini.value(i_key).toInt(), 0); l_chrini.endGroup(); From 789501a2a9f25a026509b83383b2445389aa27eb Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 25 May 2021 00:31:11 -0400 Subject: [PATCH 370/842] Create build.yml --- build.yml | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 build.yml diff --git a/build.yml b/build.yml new file mode 100644 index 000000000..acb1687d2 --- /dev/null +++ b/build.yml @@ -0,0 +1,136 @@ +# Original code from skyedeving and oldmud0 +# at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml +name: build + +on: + push: + branches: [ actions ] + pull_request: + branches: [ master ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup workspace + shell: bash + working-directory: ${{github.workspace}} + run: | + echo "parentworkspace=D:\\a\\DRO-Client\\" >> $GITHUB_ENV + mkdir "3rd" + mkdir "3rd/include" + mkdir "3rd/x86" + echo $PATH + + - name: Fetch Discord external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o discord_rpc_win.zip + unzip discord_rpc_win.zip + cp ./discord-rpc/win32-dynamic/include/discord_register.h ./DRO-Client/3rd/include + cp ./discord-rpc/win32-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include + cp ./discord-rpc/win32-dynamic/bin/discord-rpc.dll ./DRO-Client/3rd/x86 + cp ./discord-rpc/win32-dynamic/lib/discord-rpc.lib ./DRO-Client/3rd/x86 + + - name: Fetch Bass external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bass24.zip -o bass.zip + unzip bass.zip + cp ./c/bass.h ./DRO-Client/3rd/include + cp ./bass.dll ./DRO-Client/3rd/x86 + cp ./c/bass.lib ./DRO-Client/3rd/x86 + ls ./DRO-Client/3rd -R + + - name: Fetch QtApng external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-mingw73_32-5.14.1.zip -o apng.zip + unzip apng.zip + + - name: Cache Qt + id: cache-qt + uses: actions/cache@v1 + with: + path: ../Qt + key: ${{ runner.os }}-qt5 + + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: '5.14.2' + cached: ${{steps.cache-qt.outputs.cache-hit}} + arch: 'win32_mingw73' + + - name: Install AQt + shell: bash + working-directory: ${{env.parentworkspace}} + run: pip install aqtinstall + + - name: Install MinGW + shell: bash + working-directory: ${{env.parentworkspace}} + run: aqt tool -O ./Qt windows tools_mingw 7.3.0-1-202004170606 qt.tools.win32_mingw730 + + - name: Update PATH + shell: bash + working-directory: ${{env.parentworkspace}} + # For whatever reason, qmake insists on using the MinGW64 installation that came with the host machine rather than the newly installed MinGW32 + # I could not find any way of making it use that MinGW32 folder, it always insisted on using MinGW64. + # Therefore, I just brought the MinGW32 folder to the MinGW64 folder. + # Tom Scott would be proud of this bodge + run: | + export PATH=/d/a/DRO-Client/Qt/Tools/mingw730_32/bin:$PATH + rm -r /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ + cp -r /d/a/DRO-Client/Qt/Tools/mingw730_32/ /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ + + - name: Run qmake + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + ./Qt/5.14.2/mingw73_32/bin/qmake.exe -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec win32-g++ "CONFIG+=qtquickcompiler" + + - name: Run MinGW + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + ./Qt/Tools/mingw730_32/bin/mingw32-make.exe -f ./Makefile qmake_all + ./Qt/Tools/mingw730_32/bin/mingw32-make.exe -j8 + + - name: Set up deploy folder + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + mkdir "Danganronpa_Online" + cp ./release/dro-client.exe "./Danganronpa_Online/Danganronpa_Online.exe" + + - name: Deploy + working-directory: ${{env.parentworkspace}}/Danganronpa_Online + shell: bash + run: | + windeployqt.exe --quick . + cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . + cp ../bass.dll . + cp ../mingw73_32/plugins/imageformats/qapng.dll ./imageformats/ + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: Danganronpa_Online + path: ${{env.parentworkspace}}/Danganronpa_Online + + - name: Upload Artifact (just .exe) + uses: actions/upload-artifact@v2 + with: + name: dro-client.exe + path: ${{env.parentworkspace}}/Danganronpa_Online/Danganronpa_Online.exe From 49d9aacbd603cef58e6b11da4c2ee62b9854bbcb Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 25 May 2021 00:33:25 -0400 Subject: [PATCH 371/842] Update build.yml --- build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.yml b/build.yml index acb1687d2..78605e66b 100644 --- a/build.yml +++ b/build.yml @@ -82,7 +82,7 @@ jobs: working-directory: ${{env.parentworkspace}} run: aqt tool -O ./Qt windows tools_mingw 7.3.0-1-202004170606 qt.tools.win32_mingw730 - - name: Update PATH + - name: Setup MinGW shell: bash working-directory: ${{env.parentworkspace}} # For whatever reason, qmake insists on using the MinGW64 installation that came with the host machine rather than the newly installed MinGW32 From 63df8236eea0bb6b354206de75c28ca5b8a89fc8 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 25 May 2021 00:34:45 -0400 Subject: [PATCH 372/842] Update and rename build.yml to build-windows.yml --- build.yml => build-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename build.yml => build-windows.yml (99%) diff --git a/build.yml b/build-windows.yml similarity index 99% rename from build.yml rename to build-windows.yml index 78605e66b..7dcf9e093 100644 --- a/build.yml +++ b/build-windows.yml @@ -1,6 +1,6 @@ # Original code from skyedeving and oldmud0 # at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml -name: build +name: build-windows on: push: From a85feabaac4cb40a71aa4f34e871e4267f70162e Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 25 May 2021 00:38:20 -0400 Subject: [PATCH 373/842] Update build-windows.yml --- build-windows.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/build-windows.yml b/build-windows.yml index 7dcf9e093..c4a122ea4 100644 --- a/build-windows.yml +++ b/build-windows.yml @@ -2,11 +2,7 @@ # at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml name: build-windows -on: - push: - branches: [ actions ] - pull_request: - branches: [ master ] +on: [push, pull_request] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) From a3b75de146f5c25ba2b8254df544339de5cad18a Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 25 May 2021 00:44:26 -0400 Subject: [PATCH 374/842] Move to workflows folder --- build-windows.yml => .github/workflows/build-windows.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename build-windows.yml => .github/workflows/build-windows.yml (100%) diff --git a/build-windows.yml b/.github/workflows/build-windows.yml similarity index 100% rename from build-windows.yml rename to .github/workflows/build-windows.yml From ea9eebaa5f1cf47f1cb60c96cedc7f09bd7a1035 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 25 May 2021 00:47:21 -0400 Subject: [PATCH 375/842] Make action run only on all pushes --- .github/workflows/build-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index c4a122ea4..366a40be5 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -2,7 +2,7 @@ # at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml name: build-windows -on: [push, pull_request] +on: push env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) From f77943edcc3bedbcf2ce7a7b4c0682598d812738 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 25 May 2021 11:54:16 -0400 Subject: [PATCH 376/842] Update build-windows.yml --- .github/workflows/build-windows.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 366a40be5..3c5c91b70 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -23,7 +23,6 @@ jobs: mkdir "3rd" mkdir "3rd/include" mkdir "3rd/x86" - echo $PATH - name: Fetch Discord external library shell: bash @@ -96,7 +95,7 @@ jobs: run: | ./Qt/5.14.2/mingw73_32/bin/qmake.exe -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec win32-g++ "CONFIG+=qtquickcompiler" - - name: Run MinGW + - name: Run make shell: bash working-directory: ${{env.parentworkspace}} run: | From 3d827fcd9492e16f53551074b1b01c5476061ce9 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 25 May 2021 11:59:16 -0400 Subject: [PATCH 377/842] Support GitHub Actions for MacOS --- .github/workflows/build-mac | 96 +++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 .github/workflows/build-mac diff --git a/.github/workflows/build-mac b/.github/workflows/build-mac new file mode 100644 index 000000000..df1f5736f --- /dev/null +++ b/.github/workflows/build-mac @@ -0,0 +1,96 @@ +# Original code from skyedeving and oldmud0 +# at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml +name: build-mac + +on: push + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + macos: + runs-on: macos-10.15 + + steps: + - uses: actions/checkout@v2 + + - name: Setup workspace + shell: bash + working-directory: ${{github.workspace}} + run: | + echo "parentworkspace=/Users/runner/work/DRO-Client/" >> $GITHUB_ENV + mkdir "3rd" + mkdir "3rd/include" + mkdir "3rd/x86_64" + + - name: Fetch Discord external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip -o discord_rpc_osx.zip + unzip discord_rpc_osx.zip + cp ./discord-rpc/osx-dynamic/include/discord_register.h ./DRO-Client/3rd/include + cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include + cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./DRO-Client/3rd/x86_64 + + - name: Fetch Bass external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bass24-osx.zip -o bass.zip + unzip bass.zip + cp ./bass.h ./DRO-Client/3rd/include + cp ./libbass.dylib ./DRO-Client/3rd/x86_64 + + - name: Fetch QtApng external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-clang_64-5.14.1.tar.xz -o qtapng.tar.xz + tar -xvf qtapng.tar.xz + + - name: Cache Qt + id: cache-qt + uses: actions/cache@v1 + with: + path: ../Qt + key: ${{ runner.os }}-qt5 + + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: '5.14.2' + cached: ${{steps.cache-qt.outputs.cache-hit}} + + - name: Run qmake + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + ./Qt/5.14.2/clang_64/bin/qmake -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec macx-clang "CONFIG+=x86_64" "CONFIG+=qtquickcompiler" + + - name: Run Make + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + /usr/bin/make -f ./Makefile qmake_all + /usr/bin/make -j12 + + - name: Set up deploy folder + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + mkdir "Danganronpa_Online" + cp -R ./dro-client.app "./Danganronpa_Online" + mv "./Danganronpa_Online/dro-client.app" "./Danganronpa_Online/Danganronpa_Online.app" + + - name: Deploy + working-directory: ${{env.parentworkspace}}/Danganronpa_Online + shell: bash + run: | + ../Qt/5.14.2/clang_64/bin/macdeployqt Danganronpa_Online.app + cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./Danganronpa_Online.app/Contents/Frameworks/ + cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./Danganronpa_Online.app/Contents/MacOS/ + cp ../libbass.dylib ./Danganronpa_Online.app/Contents/Frameworks/ + cp ../libbass.dylib ./Danganronpa_Online.app/Contents/MacOS/ + cp -R ../clang_64/plugins/imageformats ./Danganronpa_Online.app/Contents/MacOS/ From ac92c1645503cc6617c72e55bdcffc6fcd59ede1 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 25 May 2021 12:03:20 -0400 Subject: [PATCH 378/842] Fix typos --- .../workflows/{build-mac => build-mac.yml} | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) rename .github/workflows/{build-mac => build-mac.yml} (67%) diff --git a/.github/workflows/build-mac b/.github/workflows/build-mac.yml similarity index 67% rename from .github/workflows/build-mac rename to .github/workflows/build-mac.yml index df1f5736f..4aa257319 100644 --- a/.github/workflows/build-mac +++ b/.github/workflows/build-mac.yml @@ -46,9 +46,11 @@ jobs: - name: Fetch QtApng external library shell: bash working-directory: ${{env.parentworkspace}} + # ./clang_64/plugins/imageformats/libqapng.dylib run: | curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-clang_64-5.14.1.tar.xz -o qtapng.tar.xz tar -xvf qtapng.tar.xz + ls -R - name: Cache Qt id: cache-qt @@ -80,17 +82,30 @@ jobs: shell: bash working-directory: ${{env.parentworkspace}} run: | - mkdir "Danganronpa_Online" - cp -R ./dro-client.app "./Danganronpa_Online" - mv "./Danganronpa_Online/dro-client.app" "./Danganronpa_Online/Danganronpa_Online.app" + mkdir "Danganronpa Online" + cp -R ./dro-client.app "./Danganronpa Online" + mv "./Danganronpa Online/dro-client.app" "./Danganronpa Online/Danganronpa Online.app" - name: Deploy - working-directory: ${{env.parentworkspace}}/Danganronpa_Online + working-directory: "${{env.parentworkspace}}/Danganronpa Online" shell: bash run: | - ../Qt/5.14.2/clang_64/bin/macdeployqt Danganronpa_Online.app - cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./Danganronpa_Online.app/Contents/Frameworks/ - cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./Danganronpa_Online.app/Contents/MacOS/ - cp ../libbass.dylib ./Danganronpa_Online.app/Contents/Frameworks/ - cp ../libbass.dylib ./Danganronpa_Online.app/Contents/MacOS/ - cp -R ../clang_64/plugins/imageformats ./Danganronpa_Online.app/Contents/MacOS/ + ls -R + ../Qt/5.14.2/clang_64/bin/macdeployqt "Danganronpa Online.app" + cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/Frameworks/" + cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/MacOS/" + cp ../libbass.dylib "./Danganronpa Online.app/Contents/Frameworks/" + cp ../libbass.dylib "./Danganronpa Online.app/Contents/MacOS/" + cp -R ../clang_64/plugins/imageformats "./Danganronpa Online.app/Contents/MacOS/" + ls -R + + - name: Make DMG + working-directory: ${{env.parentworkspace}} + shell: bash + run: hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online" -ov -format UDZO "Danganronpa Online.dmg" + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: "Danganronpa Online.dmg" + path: "${{env.parentworkspace}}/Danganronpa Online.dmg" From 084081a9ab8cff8fc881d5846b77fc0e69478b96 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 25 May 2021 18:41:43 -0400 Subject: [PATCH 379/842] Remove Qt cache --- .github/workflows/build-windows.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 3c5c91b70..ce7b4f02e 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -52,19 +52,11 @@ jobs: run: | curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-mingw73_32-5.14.1.zip -o apng.zip unzip apng.zip - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: ${{ runner.os }}-qt5 - name: Install Qt uses: jurplel/install-qt-action@v2 with: version: '5.14.2' - cached: ${{steps.cache-qt.outputs.cache-hit}} arch: 'win32_mingw73' - name: Install AQt From fb1bd403bcaab0a208c61eb1fde721ae5f71897b Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 25 May 2021 19:13:14 -0400 Subject: [PATCH 380/842] Remove leftover code+Remove QT Cache --- .github/workflows/build-mac.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/build-mac.yml b/.github/workflows/build-mac.yml index 4aa257319..b023ff720 100644 --- a/.github/workflows/build-mac.yml +++ b/.github/workflows/build-mac.yml @@ -46,24 +46,15 @@ jobs: - name: Fetch QtApng external library shell: bash working-directory: ${{env.parentworkspace}} - # ./clang_64/plugins/imageformats/libqapng.dylib run: | curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-clang_64-5.14.1.tar.xz -o qtapng.tar.xz tar -xvf qtapng.tar.xz ls -R - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: ${{ runner.os }}-qt5 - name: Install Qt uses: jurplel/install-qt-action@v2 with: version: '5.14.2' - cached: ${{steps.cache-qt.outputs.cache-hit}} - name: Run qmake shell: bash @@ -90,14 +81,12 @@ jobs: working-directory: "${{env.parentworkspace}}/Danganronpa Online" shell: bash run: | - ls -R ../Qt/5.14.2/clang_64/bin/macdeployqt "Danganronpa Online.app" cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/Frameworks/" cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/MacOS/" cp ../libbass.dylib "./Danganronpa Online.app/Contents/Frameworks/" cp ../libbass.dylib "./Danganronpa Online.app/Contents/MacOS/" cp -R ../clang_64/plugins/imageformats "./Danganronpa Online.app/Contents/MacOS/" - ls -R - name: Make DMG working-directory: ${{env.parentworkspace}} From 621100f33d26f9ad038bd88b5a352a8bdd82d63a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 27 May 2021 06:42:47 +0200 Subject: [PATCH 381/842] Start of #175 --- include/aoapplication.h | 1 + include/courtroom.h | 8 +- res/ui/config_panel.ui | 2 +- src/courtroom.cpp | 241 ++++++++++++++++++++---------------- src/courtroom_widgets.cpp | 3 +- src/packet_distribution.cpp | 13 +- 6 files changed, 156 insertions(+), 112 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 202dc95b9..21874f036 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -53,6 +53,7 @@ class AOApplication : public QApplication #endif bool m_FL_showname_enabled = false; bool m_FL_chrini_enabled = false; + bool m_FL_chat_speed = false; ///////////////loading info/////////////////// diff --git a/include/courtroom.h b/include/courtroom.h index 0615badcf..1c8b465a2 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -89,6 +89,8 @@ class Courtroom : public QMainWindow // it's a legacy bg void set_background(QString p_background); + void set_tick_rate(const std::optional &tick_rate); + // sets the evidence list member variable to argument void set_evidence_list(QVector &p_evi_list); @@ -253,6 +255,8 @@ class Courtroom : public QMainWindow // maintains a timer for how fast messages tick onto screen QTimer *chat_tick_timer = nullptr; + std::optional m_server_chat_tick_rate; + int m_chat_tick_speed = 0; // which tick position(character in chat message) we are at int tick_pos = 0; // used to determine how often blips sound @@ -612,7 +616,9 @@ private slots: void setup_chat(); void play_sfx(); - void chat_tick(); + void start_chat_timer(); + void next_chat_letter(); + void post_chat(); void on_mute_list_item_changed(QListWidgetItem *p_item); diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index e3d8aa682..fadfa4e41 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -276,7 +276,7 @@ ms - 10 + 0 999 diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 8866cf0c2..1b8311d97 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -354,6 +354,11 @@ void Courtroom::set_background(QString p_background) current_background = p_background; } +void Courtroom::set_tick_rate(const std::optional &tick_rate) +{ + m_server_chat_tick_rate = tick_rate; +} + void Courtroom::handle_music_anim() { QString file_a = design_ini; @@ -858,7 +863,8 @@ void Courtroom::handle_chatmessage(QStringList p_contents) if (mute_map.value(m_chatmessage[CMChrId].toInt())) return; - chatmessage_is_empty = m_chatmessage[CMMessage] == " " || m_chatmessage[CMMessage] == ""; + const QString l_message = QString(m_chatmessage[CMMessage]).remove(Qt::Key_BraceLeft).remove(Qt::Key_BraceRight); + chatmessage_is_empty = l_message.trimmed().isEmpty(); m_msg_is_first_person = false; // reset our ui state if client just spoke @@ -912,13 +918,13 @@ void Courtroom::handle_chatmessage(QStringList p_contents) ui_vp_effect->stop(); if (is_system_speaking) - append_system_text(f_showname, m_chatmessage[CMMessage]); + append_system_text(f_showname, l_message); else - append_ic_text(f_showname, m_chatmessage[CMMessage], false, false, f_char_id == m_cid); + append_ic_text(f_showname, l_message, false, false, f_char_id == m_cid); if (ao_config->log_is_recording_enabled() && (!chatmessage_is_empty || !is_system_speaking)) { - save_textlog(f_showname + ": " + m_chatmessage[CMMessage]); + save_textlog(f_showname + ": " + l_message); } int objection_mod = m_chatmessage[CMShoutModifier].toInt(); @@ -1176,7 +1182,7 @@ void Courtroom::handle_chatmessage_3() } } - chat_tick_timer->start(ao_config->chat_tick_interval()); + start_chat_timer(); } void Courtroom::on_chat_config_changed() @@ -1480,6 +1486,7 @@ void Courtroom::setup_chat() ui_vp_chatbox->show(); + m_chat_tick_speed = 0; tick_pos = 0; blip_pos = 0; @@ -1497,8 +1504,25 @@ void Courtroom::setup_chat() text_state = 1; } -void Courtroom::chat_tick() +void Courtroom::start_chat_timer() { + float l_tick_rate = ao_config->chat_tick_interval(); + if (m_server_chat_tick_rate.has_value()) + l_tick_rate = qMax(m_server_chat_tick_rate.value(), 0); + l_tick_rate = qBound(l_tick_rate * (1.0f - qBound(0.4f * m_chat_tick_speed, -1.0f, 1.0f)), 0.0f, l_tick_rate * 2.0f); + qDebug() << "start_chat_timer" << l_tick_rate; + chat_tick_timer->start(l_tick_rate); +} + +void Courtroom::next_chat_letter() +{ + const QString &f_message = m_chatmessage[CMMessage]; + if (tick_pos >= f_message.length()) + { + post_chat(); + return; + } + // note: this is called fairly often(every 60 ms when char is talking) // do not perform heavy operations here QTextCharFormat vp_message_format = ui_vp_message->currentCharFormat(); @@ -1507,137 +1531,142 @@ void Courtroom::chat_tick() else vp_message_format.setTextOutline(Qt::NoPen); - QString f_message = m_chatmessage[CMMessage]; - - if (tick_pos >= f_message.size()) + const QChar f_character = f_message.at(tick_pos); + if (f_character == Qt::Key_BraceLeft || f_character == Qt::Key_BraceRight) // { or } { - text_state = 2; - chat_tick_timer->stop(); - anim_state = 3; + ++tick_pos; + const bool is_positive = f_character == Qt::Key_BraceRight; + m_chat_tick_speed = qBound(m_chat_tick_speed + (is_positive ? 1 : -1), -3, 3); + next_chat_letter(); + return; + } + else if (f_character == Qt::Key_Space) + { + ui_vp_message->insertPlainText(f_character); + } + else if (m_chatmessage[CMTextColor].toInt() == DR::CRainbow) + { + QString html_color; - if (m_msg_is_first_person == false) + switch (rainbow_counter) { - ui_vp_player_char->play_idle(m_chatmessage[CMChrName], m_chatmessage[CMEmote]); + case 0: + html_color = "#BA1518"; + break; + case 1: + html_color = "#D55900"; + break; + case 2: + html_color = "#E7CE4E"; + break; + case 3: + html_color = "#65C856"; + break; + default: + html_color = "#1596C8"; + rainbow_counter = -1; } - m_string_color = ""; - m_color_stack.clear(); - } + ++rainbow_counter; + // Apply color to the next character + QColor text_color; + text_color.setNamedColor(html_color); + vp_message_format.setForeground(text_color); - else + ui_vp_message->textCursor().insertText(f_character, vp_message_format); + } + else if (m_chatbox_message_enable_highlighting) { - QString f_character = f_message.at(tick_pos); + bool highlight_found = false; + bool render_character = true; + // render_character should only be false if the character is a highlight + // character specifically marked as a character that should not be + // rendered. + if (m_color_stack.isEmpty()) + m_color_stack.push(""); - if (f_character == " ") - ui_vp_message->insertPlainText(" "); - else if (m_chatmessage[CMTextColor].toInt() == DR::CRainbow) + for (const auto &col : qAsConst(m_chatbox_message_highlight_colors)) { - QString html_color; - - switch (rainbow_counter) + if (f_character == col[0][0] && m_string_color != col[1]) { - case 0: - html_color = "#BA1518"; - break; - case 1: - html_color = "#D55900"; - break; - case 2: - html_color = "#E7CE4E"; + m_color_stack.push(col[1]); + m_string_color = m_color_stack.top(); + highlight_found = true; + render_character = (col[2] != "0"); break; - case 3: - html_color = "#65C856"; - break; - default: - html_color = "#1596C8"; - rainbow_counter = -1; } + } - ++rainbow_counter; - // Apply color to the next character - QColor text_color; - text_color.setNamedColor(html_color); - vp_message_format.setForeground(text_color); - - ui_vp_message->textCursor().insertText(f_character, vp_message_format); + // Apply color to the next character + if (m_string_color.isEmpty()) + vp_message_format.setForeground(m_base_string_color); + else + { + QColor textColor; + textColor.setNamedColor(m_string_color); + vp_message_format.setForeground(textColor); } - else if (m_chatbox_message_enable_highlighting) + + QString m_future_string_color = m_string_color; + + for (const auto &col : qAsConst(m_chatbox_message_highlight_colors)) { - bool highlight_found = false; - bool render_character = true; - // render_character should only be false if the character is a highlight - // character specifically marked as a character that should not be - // rendered. - if (m_color_stack.isEmpty()) - m_color_stack.push(""); - - for (const auto &col : m_chatbox_message_highlight_colors) + if (f_character == col[0][1] && !highlight_found) { - if (f_character == col[0][0] && m_string_color != col[1]) - { - m_color_stack.push(col[1]); - m_string_color = m_color_stack.top(); - highlight_found = true; - render_character = (col[2] != "0"); - break; - } + if (m_color_stack.size() > 1) + m_color_stack.pop(); + m_future_string_color = m_color_stack.top(); + render_character = (col[2] != "0"); + break; } + } - // Apply color to the next character - if (m_string_color.isEmpty()) - vp_message_format.setForeground(m_base_string_color); - else - { - QColor textColor; - textColor.setNamedColor(m_string_color); - vp_message_format.setForeground(textColor); - } + if (render_character) + ui_vp_message->textCursor().insertText(f_character, vp_message_format); - QString m_future_string_color = m_string_color; + m_string_color = m_future_string_color; + } + else + { + ui_vp_message->textCursor().insertText(f_character, vp_message_format); + } - for (const auto &col : m_chatbox_message_highlight_colors) - { - if (f_character == col[0][1] && !highlight_found) - { - if (m_color_stack.size() > 1) - m_color_stack.pop(); - m_future_string_color = m_color_stack.top(); - render_character = (col[2] != "0"); - break; - } - } + QScrollBar *scroll = ui_vp_message->verticalScrollBar(); + scroll->setValue(scroll->maximum()); - if (render_character) - ui_vp_message->textCursor().insertText(f_character, vp_message_format); + if ((f_message.at(tick_pos) != ' ' || ao_config->blank_blips_enabled())) + { - m_string_color = m_future_string_color; - } - else + if (blip_pos % ao_config->blip_rate() == 0) { - ui_vp_message->textCursor().insertText(f_character, vp_message_format); - } + blip_pos = 0; - QScrollBar *scroll = ui_vp_message->verticalScrollBar(); - scroll->setValue(scroll->maximum()); + // play blip + m_blips_player->blip_tick(); + } - if ((f_message.at(tick_pos) != ' ' || ao_config->blank_blips_enabled())) - { + ++blip_pos; + } - if (blip_pos % ao_config->blip_rate() == 0) - { - blip_pos = 0; + ui_vp_message->repaint(); - // play blip - m_blips_player->blip_tick(); - } + ++tick_pos; + start_chat_timer(); +} - ++blip_pos; - } +void Courtroom::post_chat() +{ + text_state = 2; + chat_tick_timer->stop(); + anim_state = 3; - ++tick_pos; + if (m_msg_is_first_person == false) + { + ui_vp_player_char->play_idle(m_chatmessage[CMChrName], m_chatmessage[CMEmote]); } - ui_vp_message->repaint(); + m_string_color = ""; + m_color_stack.clear(); } void Courtroom::show_testimony() diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 8e5634b15..50365e9f9 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -25,6 +25,7 @@ void Courtroom::create_widgets() keepalive_timer->start(60000); chat_tick_timer = new QTimer(this); + chat_tick_timer->setSingleShot(true); chat_tick_timer->setTimerType(Qt::PreciseTimer); // Prevents drift sfx_delay_timer = new QTimer(this); @@ -239,7 +240,7 @@ void Courtroom::connect_widgets() connect(sfx_delay_timer, SIGNAL(timeout()), this, SLOT(play_sfx())); - connect(chat_tick_timer, SIGNAL(timeout()), this, SLOT(chat_tick())); + connect(chat_tick_timer, SIGNAL(timeout()), this, SLOT(next_chat_letter())); connect(realization_timer, SIGNAL(timeout()), this, SLOT(realization_done())); diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 92eef62f6..f3463308e 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -138,6 +138,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) #endif m_FL_showname_enabled = false; m_FL_chrini_enabled = false; + m_FL_chat_speed = false; AOPacket *hi_packet = new AOPacket("HI#" + f_hdid + "#%"); send_server_packet(hi_packet); @@ -163,10 +164,11 @@ void AOApplication::server_packet_received(AOPacket *p_packet) else if (header == "FL") { #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - m_FL_ackMS_enabled = f_packet.contains("ackMS", Qt::CaseInsensitive); - m_FL_showname_enabled = f_packet.contains("showname", Qt::CaseInsensitive); - m_FL_chrini_enabled = f_packet.contains("chrini", Qt::CaseInsensitive); + m_FL_ackMS_enabled = f_contents.contains("ackMS", Qt::CaseInsensitive); #endif + m_FL_showname_enabled = f_contents.contains("showname", Qt::CaseInsensitive); + m_FL_chrini_enabled = f_contents.contains("chrini", Qt::CaseInsensitive); + m_FL_chat_speed = f_contents.contains("chat_speed", Qt::CaseInsensitive); } else if (header == "PN") { @@ -437,6 +439,11 @@ void AOApplication::server_packet_received(AOPacket *p_packet) m_courtroom->set_scene(); } } + else if (header == "chat_tick_rate") + { + if (courtroom_constructed) + m_courtroom->set_tick_rate(f_contents.isEmpty() ? std::nullopt : std::optional(f_contents.at(0).toInt())); + } // server accepting char request(CC) packet else if (header == "PV") { From 61cc9a00a1bc668cfd8f27bc8cb23adcdaab94c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Thu, 27 May 2021 19:10:26 +0200 Subject: [PATCH 382/842] Removed debug message --- src/courtroom.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 1b8311d97..7e5164908 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1506,11 +1506,10 @@ void Courtroom::setup_chat() void Courtroom::start_chat_timer() { - float l_tick_rate = ao_config->chat_tick_interval(); + double l_tick_rate = ao_config->chat_tick_interval(); if (m_server_chat_tick_rate.has_value()) l_tick_rate = qMax(m_server_chat_tick_rate.value(), 0); - l_tick_rate = qBound(l_tick_rate * (1.0f - qBound(0.4f * m_chat_tick_speed, -1.0f, 1.0f)), 0.0f, l_tick_rate * 2.0f); - qDebug() << "start_chat_timer" << l_tick_rate; + l_tick_rate = qBound(l_tick_rate * (1.0 - qBound(0.4 * m_chat_tick_speed, -1.0, 1.0)), 0.0, l_tick_rate * 2.0); chat_tick_timer->start(l_tick_rate); } From 33462bcf45d332c91eca05837876dd2d754cd6ba Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 28 May 2021 08:53:51 +0200 Subject: [PATCH 383/842] Added ignore-character keyword for speed modifiers --- include/courtroom.h | 1 + src/courtroom.cpp | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 1c8b465a2..7cb72cbd8 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -259,6 +259,7 @@ class Courtroom : public QMainWindow int m_chat_tick_speed = 0; // which tick position(character in chat message) we are at int tick_pos = 0; + bool ignore_next_character = false; // used to determine how often blips sound int blip_pos = 0; int rainbow_counter = 0; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 7e5164908..adf0bc81a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -863,7 +864,9 @@ void Courtroom::handle_chatmessage(QStringList p_contents) if (mute_map.value(m_chatmessage[CMChrId].toInt())) return; - const QString l_message = QString(m_chatmessage[CMMessage]).remove(Qt::Key_BraceLeft).remove(Qt::Key_BraceRight); + const QString l_message = QString(m_chatmessage[CMMessage]) + .remove(QRegularExpression("(?repaint(); ++tick_pos; + ignore_next_character = false; start_chat_timer(); } From 53c257c36becf670938cc3e1f9817fb8ab5593ec Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 01:41:41 +0200 Subject: [PATCH 384/842] #177 start Refactored all headers minus courtroom.h --- include/aoapplication.h | 123 +++++++++++++++++++++----------- include/aobutton.h | 3 + include/aocharbutton.h | 8 +-- include/aocharmovie.h | 2 - include/aoconfigpanel.h | 13 +--- include/aoevidencebutton.h | 25 +++---- include/aoevidencedescription.h | 6 +- include/aoimagedisplay.h | 2 + include/aomovie.h | 11 --- include/aonotearea.h | 5 +- include/aotextarea.h | 2 +- include/aotimer.h | 31 ++++---- include/draudioengine.h | 6 +- include/draudioengine_p.h | 13 ++-- include/draudiostream.h | 6 +- include/draudiostreamfamily.h | 12 ++-- include/lobby.h | 17 ++--- include/networkmanager.h | 36 +++++----- src/aobutton.cpp | 5 ++ src/aoevidencebutton.cpp | 5 ++ src/aoimagedisplay.cpp | 5 ++ src/aomovie.cpp | 9 +++ src/aotextarea.cpp | 2 + src/charselect.cpp | 2 +- src/courtroom.cpp | 16 ++--- src/courtroom_widgets.cpp | 14 ++-- src/drdiscord.cpp | 1 - src/lobby.cpp | 2 +- src/main.cpp | 2 +- src/networkmanager.cpp | 7 ++ src/packet_distribution.cpp | 16 ++--- 31 files changed, 227 insertions(+), 180 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 21874f036..2bee38eae 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -23,54 +23,62 @@ class AOApplication : public QApplication AOApplication(int &argc, char **argv); ~AOApplication(); - AOConfig *ao_config = nullptr; - AOConfigPanel *ao_config_panel = nullptr; - DRDiscord *dr_discord = nullptr; - - NetworkManager *net_manager = nullptr; - - Lobby *m_lobby = nullptr; - bool lobby_constructed = false; - - Courtroom *m_courtroom = nullptr; - bool courtroom_constructed = false; - + int get_client_id() const + { + return s_pv; + } + void set_client_id(int id) + { + s_pv = id; + } + + Lobby *get_lobby() const + { + return m_lobby; + } void construct_lobby(); void destruct_lobby(); + Courtroom *get_courtroom() const + { + return m_courtroom; + } void construct_courtroom(); void destruct_courtroom(); + DRDiscord *get_discord() const + { + return dr_discord; + } + + NetworkManager *get_network_manager() + { + return net_manager; + } + + bool has_message_acknowledgement_feature() const + { + return feature_ackMS; + } + bool has_character_declaration_feature() const + { + return feature_chrini; + } + bool has_showname_declaration_feature() const + { + return feature_showname; + } + bool has_chat_speed_feature() const + { + return feature_chat_speed; + } + void ms_packet_received(AOPacket *p_packet); void server_packet_received(AOPacket *p_packet); void send_ms_packet(AOPacket *p_packet); void send_server_packet(AOPacket *p_packet, bool encoded = true); - ///////////////server metadata//////////////// -#ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - bool m_FL_ackMS_enabled = false; -#endif - bool m_FL_showname_enabled = false; - bool m_FL_chrini_enabled = false; - bool m_FL_chat_speed = false; - - ///////////////loading info/////////////////// - - // player number, it's hardly used but might be needed for some old servers - int s_pv = 0; - - QString server_software; - - int char_list_size = 0; - int loaded_chars = 0; - int evidence_list_size = 0; - int loaded_evidence = 0; - int music_list_size = 0; - int loaded_music = 0; - - bool courtroom_loaded = false; - /////////////////////////////////////////// void set_favorite_list(); @@ -209,10 +217,49 @@ class AOApplication : public QApplication // Returns overlay at p_effect in char_path/overlay QStringList get_overlay(QString p_char, int p_effect); +public slots: + void server_disconnected(); + void loading_cancelled(); + signals: void reload_theme(); private: + AOConfig *ao_config = nullptr; + AOConfigPanel *ao_config_panel = nullptr; + DRDiscord *dr_discord = nullptr; + + NetworkManager *net_manager = nullptr; + + Lobby *m_lobby = nullptr; + bool lobby_constructed = false; + + Courtroom *m_courtroom = nullptr; + bool courtroom_constructed = false; + + ///////////////server metadata//////////////// +#ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release + bool feature_ackMS = false; +#endif + bool feature_showname = false; + bool feature_chrini = false; + bool feature_chat_speed = false; + + ///////////////loading info/////////////////// + // player number, it's hardly used but might be needed for some old servers + int s_pv = 0; + + QString server_software; + + int char_list_size = 0; + int loaded_chars = 0; + int evidence_list_size = 0; + int loaded_evidence = 0; + int music_list_size = 0; + int loaded_music = 0; + + bool courtroom_loaded = false; + QVector server_list; QVector favorite_list; @@ -224,10 +271,6 @@ private slots: void on_config_reload_theme_requested(); void on_config_gamemode_changed(); void on_config_timeofday_changed(); - -public slots: - void server_disconnected(); - void loading_cancelled(); }; #endif // AOAPPLICATION_H diff --git a/include/aobutton.h b/include/aobutton.h index cd0000158..54dcd64c9 100644 --- a/include/aobutton.h +++ b/include/aobutton.h @@ -11,8 +11,11 @@ class AOButton : public QPushButton public: AOButton(QWidget *parent, AOApplication *p_ao_app); + + QString get_image(); void set_image(QString p_image); +private: AOApplication *ao_app = nullptr; QString image_path; }; diff --git a/include/aocharbutton.h b/include/aocharbutton.h index a2b26eace..f19a8499f 100644 --- a/include/aocharbutton.h +++ b/include/aocharbutton.h @@ -14,7 +14,6 @@ class AOCharButton : public QPushButton public: AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos); - AOApplication *ao_app = nullptr; void reset(); void set_taken(); @@ -24,12 +23,13 @@ class AOCharButton : public QPushButton void mouse_entered(AOCharButton *p_caller); void mouse_left(); -private: - AOImageDisplay *ui_taken = nullptr; - protected: void enterEvent(QEvent *e); void leaveEvent(QEvent *e); + +private: + AOApplication *ao_app = nullptr; + AOImageDisplay *ui_taken = nullptr; }; #endif // AOCHARBUTTON_H diff --git a/include/aocharmovie.h b/include/aocharmovie.h index ad2f0ff47..363109de4 100644 --- a/include/aocharmovie.h +++ b/include/aocharmovie.h @@ -30,11 +30,9 @@ class AOCharMovie : public QLabel private: AOApplication *ao_app = nullptr; - QMovie *m_movie = nullptr; QTimer *m_frame_timer = nullptr; QImage m_current_frame; - bool m_mirror = false; bool m_play_once = false; diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 60ef31fae..31afaa82f 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -29,12 +29,12 @@ class AOConfigPanel : public QWidget public slots: void on_config_reload_theme_requested(); -protected: - void showEvent(QShowEvent *event); - signals: void reload_theme(); +protected: + void showEvent(QShowEvent *event) override; + private: void refresh_theme_list(); void refresh_gamemode_list(); @@ -60,25 +60,20 @@ private slots: private: // FIXME This dependency shouldn't have come to exist. AOApplication *ao_app = nullptr; - // driver AOConfig *m_config = nullptr; DRAudioEngine *m_engine = nullptr; - // behaviour QPushButton *w_save = nullptr; QPushButton *w_close = nullptr; QCheckBox *w_autosave = nullptr; - // general QLineEdit *w_username = nullptr; QLineEdit *w_callwords = nullptr; QCheckBox *w_server_alerts = nullptr; - QGroupBox *w_discord_presence = nullptr; QCheckBox *w_discord_hide_server = nullptr; QCheckBox *w_discord_hide_character = nullptr; - // game QComboBox *w_theme = nullptr; QPushButton *w_reload_theme = nullptr; @@ -89,7 +84,6 @@ private slots: QLineEdit *w_showname = nullptr; QCheckBox *w_always_pre = nullptr; QSpinBox *w_chat_tick_interval = nullptr; - // IC Chatlog QSpinBox *w_log_max_lines = nullptr; QCheckBox *w_log_display_timestamp = nullptr; @@ -100,7 +94,6 @@ private slots: QRadioButton *w_log_orientation_top_down = nullptr; QRadioButton *w_log_orientation_bottom_up = nullptr; QCheckBox *w_log_is_recording = nullptr; - // audio QComboBox *w_device = nullptr; QCheckBox *w_favorite_device = nullptr; diff --git a/include/aoevidencebutton.h b/include/aoevidencebutton.h index 5e06bb891..6e9aa770c 100644 --- a/include/aoevidencebutton.h +++ b/include/aoevidencebutton.h @@ -17,13 +17,20 @@ class AOEvidenceButton : public QPushButton void reset(); void set_image(QString p_image); void set_theme_image(QString p_image); - void set_id(int p_id) - { - m_id = p_id; - } + void set_id(int p_id); void set_selected(bool p_selected); +signals: + void evidence_clicked(int p_id); + void evidence_double_clicked(int p_id); + void on_hover(int p_id, bool p_state); + +protected: + void enterEvent(QEvent *e); + void leaveEvent(QEvent *e); + void mouseDoubleClickEvent(QMouseEvent *e); + private: AOApplication *ao_app = nullptr; @@ -32,16 +39,6 @@ class AOEvidenceButton : public QPushButton int m_id = 0; -protected: - void enterEvent(QEvent *e); - void leaveEvent(QEvent *e); - void mouseDoubleClickEvent(QMouseEvent *e); - -signals: - void evidence_clicked(int p_id); - void evidence_double_clicked(int p_id); - void on_hover(int p_id, bool p_state); - private slots: void on_clicked(); }; diff --git a/include/aoevidencedescription.h b/include/aoevidencedescription.h index 23666a6c0..343bfd7b7 100644 --- a/include/aoevidencedescription.h +++ b/include/aoevidencedescription.h @@ -10,12 +10,12 @@ class AOEvidenceDescription : public QPlainTextEdit public: AOEvidenceDescription(QWidget *parent); -protected: - void mouseDoubleClickEvent(QMouseEvent *e); - signals: void double_clicked(); +protected: + void mouseDoubleClickEvent(QMouseEvent *e); + private slots: void on_enter_pressed(); }; diff --git a/include/aoimagedisplay.h b/include/aoimagedisplay.h index 2a9e3eaff..0d2c13082 100644 --- a/include/aoimagedisplay.h +++ b/include/aoimagedisplay.h @@ -11,9 +11,11 @@ class AOImageDisplay : public QLabel public: AOImageDisplay(QWidget *parent, AOApplication *p_ao_app); + QString get_image(); void set_image(QString p_image); void set_image_from_path(QString p_path); +private: AOApplication *ao_app = nullptr; QString image_path; }; diff --git a/include/aomovie.h b/include/aomovie.h index e194019de..9a8ad13a2 100644 --- a/include/aomovie.h +++ b/include/aomovie.h @@ -17,19 +17,8 @@ class AOMovie : public QLabel void set_play_once(bool p_play_once); void play(QString p_file, QString p_char = ""); - - /// - /// \brief Searches and play the first interjection file it can find based on - /// the provided character name and interjection name. - /// void play_interjection(QString p_char_name, QString p_interjection_name); void combo_resize(int w, int h); - - /* - * @brief Returns the state of the current movie. Refer to QMovie::state() - * for more details. - * @returns Current movie status - */ QMovie::MovieState state(); void stop(); diff --git a/include/aonotearea.h b/include/aonotearea.h index e29bf0c8c..b4b506414 100644 --- a/include/aonotearea.h +++ b/include/aonotearea.h @@ -17,13 +17,14 @@ class AONoteArea : public AOImageDisplay Q_OBJECT public: - AONoteArea(QWidget *p_parent, AOApplication *p_ao_app); - AOButton *add_button = nullptr; QVBoxLayout *m_layout = nullptr; + AONoteArea(QWidget *p_parent, AOApplication *p_ao_app); + private: AOApplication *ao_app = nullptr; + void set_layout(); }; diff --git a/include/aotextarea.h b/include/aotextarea.h index 32635fdb0..033e8b03b 100644 --- a/include/aotextarea.h +++ b/include/aotextarea.h @@ -12,7 +12,7 @@ class AOTextArea : public QTextBrowser void append_error(QString p_message); private: - const QRegExp omnis_dank_url_regex = QRegExp("\\b(https?://\\S+\\.\\S+)\\b"); + static const QRegExp omnis_dank_url_regex; void auto_scroll(QTextCursor old_cursor, int scrollbar_value, bool is_scrolled_down); }; diff --git a/include/aotimer.h b/include/aotimer.h index c3a6b76e2..3296c8b64 100644 --- a/include/aotimer.h +++ b/include/aotimer.h @@ -12,9 +12,6 @@ class ManualTimer { - QTime current_time; - int timestep_length; - public: QTime get_time() { @@ -37,6 +34,10 @@ class ManualTimer { current_time = current_time.addMSecs(timestep_length); } + +private: + QTime current_time; + int timestep_length; }; class AOTimer : public DRTextEdit @@ -46,19 +47,6 @@ class AOTimer : public DRTextEdit public: AOTimer(QWidget *p_parent); -private: - ManualTimer old_manual_timer; // Pre-update manual timer - ManualTimer manual_timer; - QTimer firing_timer; - - QTime start_time = QTime(0, 0); - // All of this is in miliseconds - int manual_timer_timestep_length = -12; - int firing_timer_length = 12; - int time_spent_in_timestep = 0; - - bool paused; - public slots: void update_time(); void set(); @@ -71,6 +59,17 @@ public slots: void set_concentrate_mode(); void set_normal_mode(); void set_fast_forward_mode(); + +private: + ManualTimer old_manual_timer; // Pre-update manual timer + ManualTimer manual_timer; + QTimer firing_timer; + QTime start_time = QTime(0, 0); + // All of this is in miliseconds + int manual_timer_timestep_length = -12; + int firing_timer_length = 12; + int time_spent_in_timestep = 0; + bool paused; }; #endif // AOTIMER_H diff --git a/include/draudioengine.h b/include/draudioengine.h index 1c9990009..62bbac66d 100644 --- a/include/draudioengine.h +++ b/include/draudioengine.h @@ -7,9 +7,6 @@ class DRAudioEngine : public QObject Q_OBJECT public: - DRAudioEngine(QObject *p_parent = nullptr); - ~DRAudioEngine(); - static std::optional get_device(); static std::optional get_favorite_device(); static QList get_device_list(); @@ -22,6 +19,9 @@ class DRAudioEngine : public QObject static bool is_suppressed(); static bool is_suppress_background_audio(); + DRAudioEngine(QObject *p_parent = nullptr); + ~DRAudioEngine(); + public slots: void set_device(DRAudioDevice p_device); void set_favorite_device(DRAudioDevice p_device); diff --git a/include/draudioengine_p.h b/include/draudioengine_p.h index 12a974f67..4755db18d 100644 --- a/include/draudioengine_p.h +++ b/include/draudioengine_p.h @@ -30,13 +30,6 @@ class DRAudioEnginePrivate : public QObject friend class DRAudioEngine; friend class DRAudioEngineData; - void update_device(); - void update_device_list(); - void update_options(); - void update_volume(); - - void check_stream_error(); - QObjectList children; QPointer engine; @@ -53,6 +46,12 @@ class DRAudioEnginePrivate : public QObject QMap device_map; QMap family_map; + void update_device(); + void update_device_list(); + void update_options(); + void update_volume(); + void check_stream_error(); + private slots: void on_event_tick(); }; diff --git a/include/draudiostream.h b/include/draudiostream.h index f302e388f..43a872376 100644 --- a/include/draudiostream.h +++ b/include/draudiostream.h @@ -59,9 +59,6 @@ public slots: friend class DRAudioEnginePrivate; friend class DRAudioStreamFamily; - void cache_position(); - void update_device(); - // static method DRAudio::Family m_family; std::optional m_file; @@ -72,6 +69,9 @@ public slots: QStack m_hsync_stack; std::optional m_position; + void cache_position(); + void update_device(); + private slots: void on_device_error(); diff --git a/include/draudiostreamfamily.h b/include/draudiostreamfamily.h index f88cc8202..e228d481e 100644 --- a/include/draudiostreamfamily.h +++ b/include/draudiostreamfamily.h @@ -51,6 +51,12 @@ public slots: friend class DRAudioEngineData; friend class DRAudioEnginePrivate; + DRAudio::Family m_family; + int32_t m_volume = 0; + int32_t m_capacity = 0; + DRAudio::Options m_options; + stream_list m_stream_list; + DRAudioStreamFamily(DRAudio::Family p_family); float calculate_volume(); @@ -60,12 +66,6 @@ public slots: void update_options(); void update_volume(); - DRAudio::Family m_family; - int32_t m_volume = 0; - int32_t m_capacity = 0; - DRAudio::Options m_options; - stream_list m_stream_list; - private slots: void on_stream_finished(); }; diff --git a/include/lobby.h b/include/lobby.h index df64324d6..c94d06c44 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -41,44 +41,35 @@ class Lobby : public QMainWindow void show_loading_overlay(); void hide_loading_overlay(); int get_selected_server(); - void set_loading_value(int p_value); - bool public_servers_selected = true; - private: AOApplication *ao_app = nullptr; AOConfig *ao_config = nullptr; - AOImageDisplay *ui_background = nullptr; + server_type f_last_server; + bool public_servers_selected = true; + // ui + AOImageDisplay *ui_background = nullptr; AOButton *ui_public_servers = nullptr; AOButton *ui_favorites = nullptr; - AOButton *ui_refresh = nullptr; AOButton *ui_add_to_fav = nullptr; AOButton *ui_connect = nullptr; - DRTextEdit *ui_version = nullptr; AOButton *ui_about = nullptr; - QListWidget *ui_server_list = nullptr; - DRTextEdit *ui_player_count = nullptr; AOTextArea *ui_description = nullptr; - AOTextArea *ui_chatbox = nullptr; - QLineEdit *ui_chatname = nullptr; QLineEdit *ui_chatmessage = nullptr; - AOImageDisplay *ui_loading_background = nullptr; DRTextEdit *ui_loading_text = nullptr; QProgressBar *ui_progress_bar = nullptr; AOButton *ui_cancel = nullptr; - server_type f_last_server; - void set_size_and_pos(QWidget *p_widget, QString p_identifier); private slots: diff --git a/include/networkmanager.h b/include/networkmanager.h index e4a2af7ca..28934eeb7 100644 --- a/include/networkmanager.h +++ b/include/networkmanager.h @@ -14,27 +14,15 @@ class NetworkManager : public QObject Q_OBJECT public: + static const QString ms_srv_hostname; + static const QString ms_nosrv_hostname; + static const int ms_port; + static const int timeout_milliseconds; + static const int ms_reconnect_delay_ms; + NetworkManager(AOApplication *parent); ~NetworkManager(); - AOApplication *ao_app = nullptr; - QTcpSocket *ms_socket = nullptr; - QTcpSocket *server_socket = nullptr; - QTimer *ms_reconnect_timer = nullptr; - - const QString ms_srv_hostname = "_aoms._tcp.aceattorneyonline.com"; - const QString ms_nosrv_hostname = "master.aceattorneyonline.com"; - - static const int ms_port = 27016; - static const int timeout_milliseconds = 2000; - static const int ms_reconnect_delay_ms = 7000; - - bool ms_partial_packet = false; - QString ms_temp_packet; - - bool partial_packet = false; - QString temp_packet; - void connect_to_master(); void connect_to_master_nosrv(); void connect_to_server(server_type p_server); @@ -47,6 +35,18 @@ public slots: signals: void ms_connect_finished(bool success, bool will_retry); +private: + AOApplication *ao_app = nullptr; + QTcpSocket *ms_socket = nullptr; + QTcpSocket *server_socket = nullptr; + QTimer *ms_reconnect_timer = nullptr; + + bool ms_partial_packet = false; + QString ms_temp_packet; + + bool partial_packet = false; + QString temp_packet; + private slots: void handle_ms_packet(); void handle_server_packet(); diff --git a/src/aobutton.cpp b/src/aobutton.cpp index c2d3b26b3..3224e4073 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -10,6 +10,11 @@ AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) : QPushButton(paren ao_app = p_ao_app; } +QString AOButton::get_image() +{ + return image_path; +} + void AOButton::set_image(QString p_image) { image_path = ao_app->find_theme_asset_path(p_image); diff --git a/src/aoevidencebutton.cpp b/src/aoevidencebutton.cpp index f15988d27..f2698ab7f 100644 --- a/src/aoevidencebutton.cpp +++ b/src/aoevidencebutton.cpp @@ -58,6 +58,11 @@ void AOEvidenceButton::set_theme_image(QString p_image) this->setStyleSheet("border-image:url(\"" + path + "\")"); } +void AOEvidenceButton::set_id(int p_id) +{ + m_id = p_id; +} + void AOEvidenceButton::set_selected(bool p_selected) { if (p_selected) diff --git a/src/aoimagedisplay.cpp b/src/aoimagedisplay.cpp index 1110aba5d..6d6c2dd8c 100644 --- a/src/aoimagedisplay.cpp +++ b/src/aoimagedisplay.cpp @@ -13,6 +13,11 @@ AOImageDisplay::AOImageDisplay(QWidget *parent, AOApplication *p_ao_app) : QLabe ao_app = p_ao_app; } +QString AOImageDisplay::get_image() +{ + return image_path; +} + void AOImageDisplay::set_image(QString p_image) { QString f_path = ao_app->find_theme_asset_path(p_image); diff --git a/src/aomovie.cpp b/src/aomovie.cpp index cb21adeca..193dbd09d 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -76,6 +76,10 @@ void AOMovie::play(QString p_file, QString p_char) m_movie->start(); } +/// +/// \brief Searches and play the first interjection file it can find based on +/// the provided character name and interjection name. +/// void AOMovie::play_interjection(QString p_char_name, QString p_interjection_name) { m_movie->stop(); @@ -144,6 +148,11 @@ void AOMovie::combo_resize(int w, int h) m_movie->setScaledSize(f_size); } +/* + * @brief Returns the state of the current movie. Refer to QMovie::state() + * for more details. + * @returns Current movie status + */ QMovie::MovieState AOMovie::state() { return m_movie->state(); diff --git a/src/aotextarea.cpp b/src/aotextarea.cpp index 95aaad9d2..640a07a97 100644 --- a/src/aotextarea.cpp +++ b/src/aotextarea.cpp @@ -6,6 +6,8 @@ #include #include +const QRegExp AOTextArea::omnis_dank_url_regex = QRegExp("\\b(https?://\\S+\\.\\S+)\\b"); + AOTextArea::AOTextArea(QWidget *p_parent) : QTextBrowser(p_parent) {} diff --git a/src/charselect.cpp b/src/charselect.cpp index eaf7c18e4..0f2729cc0 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -182,7 +182,7 @@ void Courtroom::char_clicked(int n_char) else { QString content = - "CC#" + QString::number(ao_app->s_pv) + "#" + QString::number(n_real_char) + "#" + get_hdid() + "#%"; + "CC#" + QString::number(ao_app->get_client_id()) + "#" + QString::number(n_real_char) + "#" + get_hdid() + "#%"; ao_app->send_server_packet(new AOPacket(content)); } } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index adf0bc81a..5f0f4a9c9 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -172,17 +172,17 @@ void Courtroom::enter_courtroom(int p_cid) if (is_spectating()) { - ao_app->dr_discord->clear_character_name(); + ao_app->get_discord()->clear_character_name(); ao_config->clear_showname_placeholder(); } else { const QString l_showname = ao_app->get_showname(l_chr_name); const QString l_final_showname = l_showname.trimmed().isEmpty() ? l_chr_name : l_showname; - ao_app->dr_discord->set_character_name(l_final_showname); + ao_app->get_discord()->set_character_name(l_final_showname); ao_config->set_showname_placeholder(l_final_showname); - if (ao_app->m_FL_chrini_enabled) + if (ao_app->has_character_declaration_feature()) { QStringList l_content{l_chr_name, l_final_showname}; AOPacket *l_packet = new AOPacket("chrini", l_content); @@ -666,7 +666,7 @@ void Courtroom::on_showname_changed(QString p_showname) */ void Courtroom::send_showname_packet(QString p_showname) { - if (ao_app->m_FL_showname_enabled) + if (ao_app->has_showname_declaration_feature()) { QStringList l_content = {p_showname}; ao_app->send_server_packet(new AOPacket("SN", l_content)); @@ -880,7 +880,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) // possible the server crafted a message with the same char_id // as the client, but the client did not send that message, but it is // the best we can do. - if (!ao_app->m_FL_ackMS_enabled) + if (!ao_app->has_message_acknowledgement_feature()) { handle_acknowledged_ms(); } @@ -2462,8 +2462,8 @@ void Courtroom::on_back_to_lobby_clicked() hide(); ao_app->construct_lobby(); - ao_app->m_lobby->list_servers(); - ao_app->m_lobby->set_choose_a_server(); + ao_app->get_lobby()->list_servers(); + ao_app->get_lobby()->set_choose_a_server(); ao_app->destruct_courtroom(); } @@ -2481,7 +2481,7 @@ void Courtroom::on_char_select_right_clicked() void Courtroom::on_spectator_clicked() { - QString content = "CC#" + QString::number(ao_app->s_pv) + "#-1#" + get_hdid() + "#%"; + QString content = "CC#" + QString::number(ao_app->get_client_id()) + "#-1#" + get_hdid() + "#%"; ao_app->send_server_packet(new AOPacket(content)); enter_courtroom(-1); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 50365e9f9..da259709c 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -796,23 +796,23 @@ void Courtroom::set_widgets() // set_image first tries the gamemode-timeofday folder, then the theme // folder, then falls back to the default theme ui_change_character->set_image("changecharacter.png"); - if (ui_change_character->image_path.isEmpty()) + if (ui_change_character->get_image().isEmpty()) ui_change_character->setText("Change Character"); ui_call_mod->set_image("callmod.png"); - if (ui_call_mod->image_path.isEmpty()) + if (ui_call_mod->get_image().isEmpty()) ui_call_mod->setText("Call Mod"); ui_switch_area_music->set_image("switch_area_music.png"); - if (ui_switch_area_music->image_path.isEmpty()) + if (ui_switch_area_music->get_image().isEmpty()) ui_switch_area_music->setText("A/M"); ui_config_panel->set_image("config_panel.png"); - if (ui_config_panel->image_path.isEmpty()) + if (ui_config_panel->get_image().isEmpty()) ui_config_panel->setText("Config"); ui_note_button->set_image("notebutton.png"); - if (ui_note_button->image_path.isEmpty()) + if (ui_note_button->get_image().isEmpty()) ui_note_button->setText("Notes"); } @@ -850,7 +850,7 @@ void Courtroom::set_widgets() QString image = label_images[i].toLower() + ".png"; ui_label_images[i]->set_image(image); - if (!ui_label_images[i]->image_path.isEmpty()) + if (!ui_label_images[i]->get_image().isEmpty()) ui_checks[i]->setText(""); else ui_checks[i]->setText(label_images[i]); @@ -862,7 +862,7 @@ void Courtroom::set_widgets() QString image = label_images[j].toLower() + ".png"; ui_label_images[j]->set_image(image); - if (!ui_label_images[j]->image_path.isEmpty()) + if (!ui_label_images[j]->get_image().isEmpty()) ui_labels[i]->setText(""); else ui_labels[i]->setText(label_images[j]); diff --git a/src/drdiscord.cpp b/src/drdiscord.cpp index 1eb404d78..b83ebbe0d 100644 --- a/src/drdiscord.cpp +++ b/src/drdiscord.cpp @@ -46,7 +46,6 @@ DRDiscord::DRDiscord(QObject *f_parent) : QObject(f_parent) DRDiscord::~DRDiscord() { this->disconnect(m_waiter); - stop(); } diff --git a/src/lobby.cpp b/src/lobby.cpp index 3e2c39cba..de1c0b0d9 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -455,7 +455,7 @@ void Lobby::on_server_list_clicked(QModelIndex p_model) ui_description->append(f_last_server.desc); ui_description->ensureCursorVisible(); - ao_app->net_manager->connect_to_server(f_last_server); + ao_app->get_network_manager()->connect_to_server(f_last_server); } void Lobby::on_chatfield_return_pressed() diff --git a/src/main.cpp b/src/main.cpp index ac410b49c..e2bb33d90 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,7 +24,7 @@ int main(int argc, char *argv[]) #ifdef QT_NO_DEBUG app.net_manager->connect_to_master(); #endif - app.m_lobby->show(); + app.get_lobby()->show(); return app.exec(); } diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp index 8adf7611a..ccb8b9027 100644 --- a/src/networkmanager.cpp +++ b/src/networkmanager.cpp @@ -6,6 +6,13 @@ #include +const QString NetworkManager::ms_srv_hostname = "_aoms._tcp.aceattorneyonline.com"; +const QString NetworkManager::ms_nosrv_hostname = "master.aceattorneyonline.com"; + +const int NetworkManager::ms_port = 27016; +const int NetworkManager::timeout_milliseconds = 2000; +const int NetworkManager::ms_reconnect_delay_ms = 7000; + NetworkManager::NetworkManager(AOApplication *parent) : QObject(parent) { ao_app = parent; diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index f3463308e..49fcb05f2 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -134,11 +134,11 @@ void AOApplication::server_packet_received(AOPacket *p_packet) f_hdid = get_hdid(); #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - m_FL_ackMS_enabled = false; + feature_ackMS = false; #endif - m_FL_showname_enabled = false; - m_FL_chrini_enabled = false; - m_FL_chat_speed = false; + feature_showname = false; + feature_chrini = false; + feature_chat_speed = false; AOPacket *hi_packet = new AOPacket("HI#" + f_hdid + "#%"); send_server_packet(hi_packet); @@ -164,11 +164,11 @@ void AOApplication::server_packet_received(AOPacket *p_packet) else if (header == "FL") { #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - m_FL_ackMS_enabled = f_contents.contains("ackMS", Qt::CaseInsensitive); + feature_ackMS = f_contents.contains("ackMS", Qt::CaseInsensitive); #endif - m_FL_showname_enabled = f_contents.contains("showname", Qt::CaseInsensitive); - m_FL_chrini_enabled = f_contents.contains("chrini", Qt::CaseInsensitive); - m_FL_chat_speed = f_contents.contains("chat_speed", Qt::CaseInsensitive); + feature_showname = f_contents.contains("showname", Qt::CaseInsensitive); + feature_chrini = f_contents.contains("chrini", Qt::CaseInsensitive); + feature_chat_speed = f_contents.contains("chat_speed", Qt::CaseInsensitive); } else if (header == "PN") { From 528417c266aedd76109a7b3322bdb02004267dcb Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 04:31:26 +0200 Subject: [PATCH 385/842] #178 begin implementation --- dronline-client.pro | 2 + include/courtroom.h | 15 +- include/lobby.h | 7 +- include/theme.h | 16 ++ src/charselect.cpp | 3 +- src/courtroom.cpp | 39 ++--- src/courtroom_widgets.cpp | 307 ++++++++++++++------------------------ src/emotes.cpp | 3 +- src/evidence.cpp | 6 +- src/lobby.cpp | 155 ++++--------------- src/theme.cpp | 121 +++++++++++++++ 11 files changed, 315 insertions(+), 359 deletions(-) create mode 100644 include/theme.h create mode 100644 src/theme.cpp diff --git a/dronline-client.pro b/dronline-client.pro index a02477f4f..06d90ce5c 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -62,6 +62,7 @@ HEADERS += \ include/lobby.h \ include/misc_functions.h \ include/networkmanager.h \ + include/theme.h \ include/version.h SOURCES += \ @@ -121,6 +122,7 @@ SOURCES += \ src/packet_distribution.cpp \ src/path_functions.cpp \ src/text_file_functions.cpp \ + src/theme.cpp \ src/version.cpp # 1. You need to get BASS and put the x86 bass DLL/headers in the project root folder diff --git a/include/courtroom.h b/include/courtroom.h index 7cb72cbd8..66e5b1f99 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -63,10 +63,6 @@ class Courtroom : public QMainWindow // sets position of widgets based on theme ini files void set_widgets(); - // sets font properties based on theme ini files - void set_font(QWidget *widget, QString p_identifier); - // sets font properties for DRTextEdit (same as above but also text outline, and alignments) - void set_drtextedit_font(DRTextEdit *widget, QString p_identifier); // helper function that calls above function on the relevant widgets void set_fonts(); @@ -78,9 +74,6 @@ class Courtroom : public QMainWindow void set_window_title(QString p_title); - // reads theme inis and sets size and pos based on the identifier - void set_size_and_pos(QWidget *p_widget, QString p_identifier); - // sets status as taken on character with cid n_char and places proper shading // on charselect void set_taken(int n_char, bool p_taken); @@ -293,10 +286,10 @@ class Courtroom : public QMainWindow QString shownames_ini = "configs/shownames.ini"; // theme files locations - QString design_ini = "courtroom_design.ini"; - QString fonts_ini = "courtroom_fonts.ini"; - QString cc_config_ini = "courtroom_config.ini"; - QString cc_sounds_ini = "courtroom_sounds.ini"; + static const QString INI_DESIGN; + static const QString INI_FONTS; + static const QString INI_CONFIG; + static const QString INI_SOUNDS; // every time point in char.inis times this equals the final time const int time_mod = 40; diff --git a/include/lobby.h b/include/lobby.h index df64324d6..25bfa8531 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -36,8 +36,6 @@ class Lobby : public QMainWindow void set_stylesheet(QWidget *widget, QString target_tag); void set_stylesheets(); void set_fonts(); - void set_font(QWidget *widget, QString p_identifier); - void set_drtextedit_font(DRTextEdit *widget, QString p_identifier); void show_loading_overlay(); void hide_loading_overlay(); int get_selected_server(); @@ -47,6 +45,9 @@ class Lobby : public QMainWindow bool public_servers_selected = true; private: + static const QString INI_DESIGN; + static const QString INI_FONTS; + AOApplication *ao_app = nullptr; AOConfig *ao_config = nullptr; @@ -79,8 +80,6 @@ class Lobby : public QMainWindow server_type f_last_server; - void set_size_and_pos(QWidget *p_widget, QString p_identifier); - private slots: void on_public_servers_clicked(); void on_favorites_clicked(); diff --git a/include/theme.h b/include/theme.h new file mode 100644 index 000000000..2b1479aad --- /dev/null +++ b/include/theme.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +// src +class AOApplication; +class DRTextEdit; + +// qt +class QLineEdit; +class QWidget; + +void set_size_and_pos(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); +void set_text_alignment(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); +void set_font(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); +void set_drtextedit_font(DRTextEdit *widget, QString identifier, QString ini_file, AOApplication *ao_app); diff --git a/src/charselect.cpp b/src/charselect.cpp index eaf7c18e4..9fc9f6191 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -3,6 +3,7 @@ #include "debug_functions.h" #include "file_functions.h" #include "hardware_functions.h" +#include "theme.h" #include @@ -50,7 +51,7 @@ void Courtroom::reconstruct_char_select() int y_spacing = f_spacing.y(); int y_mod_count = 0; - set_size_and_pos(ui_char_buttons, "char_buttons"); + set_size_and_pos(ui_char_buttons, "char_buttons", INI_DESIGN, ao_app); char_columns = ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + 1; char_rows = ((ui_char_buttons->height() - button_height) / (y_spacing + button_height)) + 1; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index adf0bc81a..8279a034c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -23,6 +23,11 @@ #include "networkmanager.h" +const QString Courtroom::INI_DESIGN = "courtroom_design.ini"; +const QString Courtroom::INI_FONTS = "courtroom_fonts.ini"; +const QString Courtroom::INI_CONFIG = "courtroom_config.ini"; +const QString Courtroom::INI_SOUNDS = "courtroom_sounds.ini"; + Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; @@ -362,8 +367,8 @@ void Courtroom::set_tick_rate(const std::optional &tick_rate) void Courtroom::handle_music_anim() { - QString file_a = design_ini; - QString file_b = fonts_ini; + QString file_a = INI_DESIGN; + QString file_b = INI_FONTS; pos_size_type res_a = ao_app->get_element_dimensions("music_name", file_a); pos_size_type res_b = ao_app->get_element_dimensions("music_area", file_a); float speed = static_cast(ao_app->get_font_property("music_name_speed", file_b)); @@ -371,7 +376,7 @@ void Courtroom::handle_music_anim() QFont f_font = ui_vp_music_name->font(); QFontMetrics fm(f_font); int dist; - if (ao_app->read_theme_ini_bool("enable_const_music_speed", cc_config_ini)) + if (ao_app->read_theme_ini_bool("enable_const_music_speed", INI_CONFIG)) dist = res_b.width; else dist = fm.horizontalAdvance(ui_vp_music_name->toPlainText()); @@ -414,7 +419,7 @@ void Courtroom::list_music() { ui_music_list->clear(); - QString f_file = design_ini; + QString f_file = INI_DESIGN; QBrush found_brush(ao_app->get_color("found_song_color", f_file)); QBrush missing_brush(ao_app->get_color("missing_song_color", f_file)); @@ -489,8 +494,8 @@ QString Courtroom::current_sfx_file() void Courtroom::update_sfx_list() { // colors - m_sfx_color_found = ao_app->get_color("found_song_color", design_ini); - m_sfx_color_missing = ao_app->get_color("missing_song_color", design_ini); + m_sfx_color_found = ao_app->get_color("found_song_color", INI_DESIGN); + m_sfx_color_missing = ao_app->get_color("missing_song_color", INI_DESIGN); // items m_sfx_list.clear(); @@ -1089,7 +1094,7 @@ void Courtroom::handle_chatmessage_3() const bool l_hide_emote = (f_emote == "../../misc/blank"); QString path; - if (!chatmessage_is_empty && ao_app->read_theme_ini_bool("enable_showname_image", cc_config_ini)) + if (!chatmessage_is_empty && ao_app->read_theme_ini_bool("enable_showname_image", INI_CONFIG)) { // Asset lookup order // 1. In the theme folder (gamemode-timeofday/main/default), in the character @@ -1218,16 +1223,16 @@ void Courtroom::update_ic_log(bool p_reset_log) // prepare the formats we need // default color - QColor default_color = ao_app->get_color("ic_chatlog_color", fonts_ini); + QColor default_color = ao_app->get_color("ic_chatlog_color", INI_FONTS); QColor not_found_color = QColor(255, 255, 255); QTextCharFormat name_format = ui_ic_chatlog->currentCharFormat(); - if (ao_app->get_font_property("ic_chatlog_bold", fonts_ini)) + if (ao_app->get_font_property("ic_chatlog_bold", INI_FONTS)) name_format.setFontWeight(QFont::Bold); else name_format.setFontWeight(QFont::Normal); - QColor showname_color = ao_app->get_color("ic_chatlog_showname_color", fonts_ini); + QColor showname_color = ao_app->get_color("ic_chatlog_showname_color", INI_FONTS); if (showname_color == not_found_color) showname_color = default_color; name_format.setForeground(showname_color); @@ -1236,7 +1241,7 @@ void Courtroom::update_ic_log(bool p_reset_log) if (ao_config->log_display_self_highlight_enabled()) { - QColor selfname_color = ao_app->get_color("ic_chatlog_selfname_color", fonts_ini); + QColor selfname_color = ao_app->get_color("ic_chatlog_selfname_color", INI_FONTS); if (selfname_color == not_found_color) selfname_color = showname_color; selfname_format.setForeground(selfname_color); @@ -1244,14 +1249,14 @@ void Courtroom::update_ic_log(bool p_reset_log) QTextCharFormat line_format = ui_ic_chatlog->currentCharFormat(); line_format.setFontWeight(QFont::Normal); - QColor message_color = ao_app->get_color("ic_chatlog_message_color", fonts_ini); + QColor message_color = ao_app->get_color("ic_chatlog_message_color", INI_FONTS); if (message_color == not_found_color) message_color = default_color; line_format.setForeground(message_color); QTextCharFormat system_format = ui_ic_chatlog->currentCharFormat(); system_format.setFontWeight(QFont::Normal); - QColor system_color = ao_app->get_color("ic_chatlog_system_color", fonts_ini); + QColor system_color = ao_app->get_color("ic_chatlog_system_color", INI_FONTS); if (system_color == not_found_color) system_color = not_found_color; system_format.setForeground(system_color); @@ -1495,8 +1500,8 @@ void Courtroom::setup_chat() blip_pos = 0; // Cache these so chat_tick performs better - m_chatbox_message_outline = (ao_app->get_font_property("message_outline", fonts_ini) == 1); - m_chatbox_message_enable_highlighting = (ao_app->read_theme_ini_bool("enable_highlighting", cc_config_ini)); + m_chatbox_message_outline = (ao_app->get_font_property("message_outline", INI_FONTS) == 1); + m_chatbox_message_enable_highlighting = (ao_app->read_theme_ini_bool("enable_highlighting", INI_CONFIG)); m_chatbox_message_highlight_colors = ao_app->get_highlight_colors(); QString f_gender = ao_app->get_gender(m_chatmessage[CMChrName]); @@ -1841,7 +1846,7 @@ void Courtroom::handle_song(QStringList p_contents) void Courtroom::handle_wtce(QString p_wtce) { - QString sfx_file = cc_sounds_ini; + QString sfx_file = INI_SOUNDS; int index = p_wtce.at(p_wtce.size() - 1).digitValue(); if (index > 0 && index < wtce_names.size() + 1 && wtce_names.size() > 0) // check to prevent crash @@ -2216,7 +2221,7 @@ void Courtroom::on_cycle_clicked() break; } - if (ao_app->read_theme_ini_bool("enable_cycle_ding", cc_config_ini)) + if (ao_app->read_theme_ini_bool("enable_cycle_ding", INI_CONFIG)) m_system_player->play(ao_app->get_sfx("cycle")); set_shouts(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 50365e9f9..acb7b5919 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -6,6 +6,7 @@ #include "file_functions.h" #include "hardware_functions.h" #include "lobby.h" +#include "theme.h" #include #include @@ -544,7 +545,7 @@ void Courtroom::set_widget_layers() void Courtroom::set_widgets() { - QString filename = design_ini; + QString filename = INI_DESIGN; pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", filename); if (f_courtroom.width < 0 || f_courtroom.height < 0) @@ -565,7 +566,7 @@ void Courtroom::set_widgets() ui_background->resize(m_courtroom_width, m_courtroom_height); ui_background->set_image("courtroombackground.png"); - set_size_and_pos(ui_viewport, "viewport"); + set_size_and_pos(ui_viewport, "viewport", INI_DESIGN, ao_app); ui_vp_background->move(0, 0); ui_vp_background->combo_resize(ui_viewport->size()); @@ -583,19 +584,19 @@ void Courtroom::set_widgets() ui_vp_evidence_display->move(0, 0); ui_vp_evidence_display->resize(ui_viewport->width(), ui_viewport->height()); - set_size_and_pos(ui_vp_notepad_image, "notepad_image"); + set_size_and_pos(ui_vp_notepad_image, "notepad_image", INI_DESIGN, ao_app); ui_vp_notepad_image->set_image("notepad_image.png"); ui_vp_notepad_image->hide(); - set_size_and_pos(ui_vp_notepad, "notepad"); + set_size_and_pos(ui_vp_notepad, "notepad", INI_DESIGN, ao_app); ui_vp_notepad->hide(); - set_size_and_pos(ui_vp_showname, "showname"); + set_size_and_pos(ui_vp_showname, "showname", INI_DESIGN, ao_app); - set_size_and_pos(ui_vp_showname_image, "showname_image"); + set_size_and_pos(ui_vp_showname_image, "showname_image", INI_DESIGN, ao_app); ui_vp_showname_image->hide(); - set_size_and_pos(ui_vp_message, "message"); + set_size_and_pos(ui_vp_message, "message", INI_DESIGN, ao_app); ui_vp_message->setTextInteractionFlags(Qt::NoTextInteraction); ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "color: white"); @@ -615,41 +616,43 @@ void Courtroom::set_widgets() ui_vp_objection->move(ui_viewport->x(), ui_viewport->y()); ui_vp_objection->combo_resize(ui_viewport->width(), ui_viewport->height()); - set_size_and_pos(ui_ic_chatlog, "ic_chatlog"); + set_size_and_pos(ui_ic_chatlog, "ic_chatlog", INI_DESIGN, ao_app); - set_size_and_pos(ui_server_chatlog, "server_chatlog"); + set_size_and_pos(ui_server_chatlog, "server_chatlog", INI_DESIGN, ao_app); - set_size_and_pos(ui_mute_list, "mute_list"); + set_size_and_pos(ui_mute_list, "mute_list", INI_DESIGN, ao_app); ui_mute_list->hide(); - set_size_and_pos(ui_music_list, "music_list"); - set_size_and_pos(ui_area_list, "area_list"); + set_size_and_pos(ui_music_list, "music_list", INI_DESIGN, ao_app); + set_size_and_pos(ui_area_list, "area_list", INI_DESIGN, ao_app); if (ui_music_list->isVisible()) ui_area_list->hide(); // ui_area_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - set_size_and_pos(ui_sfx_list, "sfx_list"); + set_size_and_pos(ui_sfx_list, "sfx_list", INI_DESIGN, ao_app); - set_size_and_pos(ui_ic_chat_showname, "ic_chat_name"); + set_size_and_pos(ui_ic_chat_showname, "ic_chat_name", INI_DESIGN, ao_app); + set_text_alignment(ui_ic_chat_showname, "ic_chat_name", INI_FONTS, ao_app); ui_ic_chat_showname->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); - set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message"); + set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message", INI_DESIGN, ao_app); + set_text_alignment(ui_ic_chat_message, "ao2_ic_chat_message", INI_FONTS, ao_app); ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); - set_size_and_pos(ui_vp_chatbox, "ao2_chatbox"); + set_size_and_pos(ui_vp_chatbox, "ao2_chatbox", INI_DESIGN, ao_app); - set_size_and_pos(ui_vp_music_area, "music_area"); + set_size_and_pos(ui_vp_music_area, "music_area", INI_DESIGN, ao_app); ui_vp_music_area->show(); - set_size_and_pos(ui_vp_music_name, "music_name"); + set_size_and_pos(ui_vp_music_name, "music_name", INI_DESIGN, ao_app); - set_size_and_pos(ui_vp_music_display_a, "music_display_a"); + set_size_and_pos(ui_vp_music_display_a, "music_display_a", INI_DESIGN, ao_app); ui_vp_music_display_a->set_image("music_display_a.png"); ui_vp_music_display_a->show(); - set_size_and_pos(ui_vp_music_display_b, "music_display_b"); + set_size_and_pos(ui_vp_music_display_b, "music_display_b", INI_DESIGN, ao_app); ui_vp_music_display_b->set_image("music_display_b.png"); ui_vp_music_display_b->show(); - set_size_and_pos(ui_vp_clock, "clock"); + set_size_and_pos(ui_vp_clock, "clock", INI_DESIGN, ao_app); ui_vp_clock->hide(); ui_vp_chatbox->set_image("chatmed.png"); @@ -658,56 +661,60 @@ void Courtroom::set_widgets() ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); ui_muted->set_image("muted.png"); - set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message"); + set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message", INI_DESIGN, ao_app); + set_text_alignment(ui_ooc_chat_message, "ooc_chat_message", INI_FONTS, ao_app); ui_ooc_chat_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name"); + set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name", INI_DESIGN, ao_app); + set_text_alignment(ui_ooc_chat_name, "ooc_chat_name", INI_FONTS, ao_app); ui_ooc_chat_name->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - set_size_and_pos(ui_music_search, "music_search"); + set_size_and_pos(ui_music_search, "music_search", INI_DESIGN, ao_app); + set_text_alignment(ui_music_search, "music_search", INI_FONTS, ao_app); - set_size_and_pos(ui_sfx_search, "sfx_search"); + set_size_and_pos(ui_sfx_search, "sfx_search", INI_DESIGN, ao_app); + set_text_alignment(ui_sfx_search, "sfx_search", INI_FONTS, ao_app); // char select reconstruct_char_select(); // emotes - set_size_and_pos(ui_emotes, "emotes"); + set_size_and_pos(ui_emotes, "emotes", INI_DESIGN, ao_app); reconstruct_emotes(); - set_size_and_pos(ui_emote_left, "emote_left"); + set_size_and_pos(ui_emote_left, "emote_left", INI_DESIGN, ao_app); ui_emote_left->set_image("arrow_left.png"); - set_size_and_pos(ui_emote_right, "emote_right"); + set_size_and_pos(ui_emote_right, "emote_right", INI_DESIGN, ao_app); ui_emote_right->set_image("arrow_right.png"); - set_size_and_pos(ui_emote_dropdown, "emote_dropdown"); + set_size_and_pos(ui_emote_dropdown, "emote_dropdown", INI_DESIGN, ao_app); - set_size_and_pos(ui_ini_dropdown, "ini_dropdown"); + set_size_and_pos(ui_ini_dropdown, "ini_dropdown", INI_DESIGN, ao_app); - set_size_and_pos(ui_pos_dropdown, "pos_dropdown"); + set_size_and_pos(ui_pos_dropdown, "pos_dropdown", INI_DESIGN, ao_app); - set_size_and_pos(ui_defense_bar, "defense_bar"); + set_size_and_pos(ui_defense_bar, "defense_bar", INI_DESIGN, ao_app); ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + ".png"); - set_size_and_pos(ui_prosecution_bar, "prosecution_bar"); + set_size_and_pos(ui_prosecution_bar, "prosecution_bar", INI_DESIGN, ao_app); ui_prosecution_bar->set_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); for (int i = 0; i < shout_names.size(); ++i) { - set_size_and_pos(ui_shouts[i], shout_names[i]); + set_size_and_pos(ui_shouts[i], shout_names[i], INI_DESIGN, ao_app); } reset_shout_buttons(); - set_size_and_pos(ui_shout_up, "shout_up"); + set_size_and_pos(ui_shout_up, "shout_up", INI_DESIGN, ao_app); ui_shout_up->set_image("shoutup.png"); ui_shout_up->hide(); - set_size_and_pos(ui_shout_down, "shout_down"); + set_size_and_pos(ui_shout_down, "shout_down", INI_DESIGN, ao_app); ui_shout_down->set_image("shoutdown.png"); ui_shout_down->hide(); // courtroom_config.ini necessary + check for crash - if (ao_app->read_theme_ini_bool("enable_single_shout", cc_config_ini) && ui_shouts.size() > 0) + if (ao_app->read_theme_ini_bool("enable_single_shout", INI_CONFIG) && ui_shouts.size() > 0) { for (auto &shout : ui_shouts) move_widget(shout, "bullet"); @@ -720,18 +727,18 @@ void Courtroom::set_widgets() for (int i = 0; i < effect_names.size(); ++i) { - set_size_and_pos(ui_effects[i], effect_names[i]); + set_size_and_pos(ui_effects[i], effect_names[i], INI_DESIGN, ao_app); } reset_effect_buttons(); - set_size_and_pos(ui_effect_up, "effect_up"); + set_size_and_pos(ui_effect_up, "effect_up", INI_DESIGN, ao_app); ui_effect_up->set_image("effectup.png"); ui_effect_up->hide(); - set_size_and_pos(ui_effect_down, "effect_down"); + set_size_and_pos(ui_effect_down, "effect_down", INI_DESIGN, ao_app); ui_effect_down->set_image("effectdown.png"); ui_effect_down->hide(); - if (ao_app->read_theme_ini_bool("enable_single_effect", cc_config_ini) && + if (ao_app->read_theme_ini_bool("enable_single_effect", INI_CONFIG) && ui_effects.size() > 0) // check to prevent crashing { for (auto &effect : ui_effects) @@ -743,19 +750,19 @@ void Courtroom::set_widgets() ui_effect_down->show(); } - set_size_and_pos(ui_wtce_up, "wtce_up"); + set_size_and_pos(ui_wtce_up, "wtce_up", INI_DESIGN, ao_app); ui_wtce_up->set_image("wtceup.png"); ui_wtce_up->hide(); - set_size_and_pos(ui_wtce_down, "wtce_down"); + set_size_and_pos(ui_wtce_down, "wtce_down", INI_DESIGN, ao_app); ui_wtce_down->set_image("wtcedown.png"); ui_wtce_down->hide(); for (int i = 0; i < wtce_names.size(); ++i) { - set_size_and_pos(ui_wtce[i], wtce_names[i]); + set_size_and_pos(ui_wtce[i], wtce_names[i], INI_DESIGN, ao_app); } - if (ao_app->read_theme_ini_bool("enable_single_wtce", cc_config_ini)) // courtroom_config.ini necessary + if (ao_app->read_theme_ini_bool("enable_single_wtce", INI_CONFIG)) // courtroom_config.ini necessary { for (auto &wtce : ui_wtce) move_widget(wtce, "wtce"); @@ -766,17 +773,17 @@ void Courtroom::set_widgets() for (int i = 0; i < free_block_names.size(); ++i) { - set_size_and_pos(ui_free_blocks[i], free_block_names[i]); + set_size_and_pos(ui_free_blocks[i], free_block_names[i], INI_DESIGN, ao_app); } set_free_blocks(); // Set the default values for the buttons, then determine if they should be // replaced by images - set_size_and_pos(ui_change_character, "change_character"); - set_size_and_pos(ui_call_mod, "call_mod"); - set_size_and_pos(ui_note_button, "note_button"); - set_size_and_pos(ui_switch_area_music, "switch_area_music"); - set_size_and_pos(ui_config_panel, "config_panel"); + set_size_and_pos(ui_change_character, "change_character", INI_DESIGN, ao_app); + set_size_and_pos(ui_call_mod, "call_mod", INI_DESIGN, ao_app); + set_size_and_pos(ui_note_button, "note_button", INI_DESIGN, ao_app); + set_size_and_pos(ui_switch_area_music, "switch_area_music", INI_DESIGN, ao_app); + set_size_and_pos(ui_config_panel, "config_panel", INI_DESIGN, ao_app); ui_change_character->setText(""); ui_call_mod->setText(""); @@ -790,7 +797,7 @@ void Courtroom::set_widgets() ui_config_panel->setStyleSheet(""); ui_note_button->setStyleSheet(""); - if (ao_app->read_theme_ini_bool("enable_button_images", cc_config_ini)) + if (ao_app->read_theme_ini_bool("enable_button_images", INI_CONFIG)) { // Set files, ask questions later // set_image first tries the gamemode-timeofday folder, then the theme @@ -831,19 +838,19 @@ void Courtroom::set_widgets() ui_config_panel->resize(64, 64); } - set_size_and_pos(ui_pre, "pre"); + set_size_and_pos(ui_pre, "pre", INI_DESIGN, ao_app); ui_pre->setText("Pre"); - set_size_and_pos(ui_flip, "flip"); + set_size_and_pos(ui_flip, "flip", INI_DESIGN, ao_app); - set_size_and_pos(ui_hidden, "hidden"); + set_size_and_pos(ui_hidden, "hidden", INI_DESIGN, ao_app); for (int i = 0; i < ui_label_images.size(); ++i) { - set_size_and_pos(ui_label_images[i], label_images[i].toLower() + "_image"); + set_size_and_pos(ui_label_images[i], label_images[i].toLower() + "_image", INI_DESIGN, ao_app); } - if (ao_app->read_theme_ini_bool("enable_label_images", cc_config_ini)) + if (ao_app->read_theme_ini_bool("enable_label_images", INI_CONFIG)) { for (int i = 0; i < ui_checks.size(); ++i) // loop through checks { @@ -884,80 +891,80 @@ void Courtroom::set_widgets() } } - set_size_and_pos(ui_mute, "mute_button"); + set_size_and_pos(ui_mute, "mute_button", INI_DESIGN, ao_app); ui_mute->set_image("mute.png"); - set_size_and_pos(ui_defense_plus, "defense_plus"); + set_size_and_pos(ui_defense_plus, "defense_plus", INI_DESIGN, ao_app); ui_defense_plus->set_image("defplus.png"); - set_size_and_pos(ui_defense_minus, "defense_minus"); + set_size_and_pos(ui_defense_minus, "defense_minus", INI_DESIGN, ao_app); ui_defense_minus->set_image("defminus.png"); - set_size_and_pos(ui_prosecution_plus, "prosecution_plus"); + set_size_and_pos(ui_prosecution_plus, "prosecution_plus", INI_DESIGN, ao_app); ui_prosecution_plus->set_image("proplus.png"); - set_size_and_pos(ui_prosecution_minus, "prosecution_minus"); + set_size_and_pos(ui_prosecution_minus, "prosecution_minus", INI_DESIGN, ao_app); ui_prosecution_minus->set_image("prominus.png"); - set_size_and_pos(ui_text_color, "text_color"); + set_size_and_pos(ui_text_color, "text_color", INI_DESIGN, ao_app); - set_size_and_pos(ui_evidence_button, "evidence_button"); + set_size_and_pos(ui_evidence_button, "evidence_button", INI_DESIGN, ao_app); ui_evidence_button->set_image("evidencebutton.png"); - set_size_and_pos(ui_evidence, "evidence_background"); + set_size_and_pos(ui_evidence, "evidence_background", INI_DESIGN, ao_app); ui_evidence->set_image("evidencebackground.png"); - set_size_and_pos(ui_evidence_name, "evidence_name"); + set_size_and_pos(ui_evidence_name, "evidence_name", INI_DESIGN, ao_app); - set_size_and_pos(ui_evidence_buttons, "evidence_buttons"); + set_size_and_pos(ui_evidence_buttons, "evidence_buttons", INI_DESIGN, ao_app); - set_size_and_pos(ui_evidence_left, "evidence_left"); + set_size_and_pos(ui_evidence_left, "evidence_left", INI_DESIGN, ao_app); ui_evidence_left->set_image("arrow_left.png"); - set_size_and_pos(ui_evidence_right, "evidence_right"); + set_size_and_pos(ui_evidence_right, "evidence_right", INI_DESIGN, ao_app); ui_evidence_right->set_image("arrow_right.png"); - set_size_and_pos(ui_evidence_present, "evidence_present"); + set_size_and_pos(ui_evidence_present, "evidence_present", INI_DESIGN, ao_app); ui_evidence_present->set_image("present_disabled.png"); - set_size_and_pos(ui_evidence_overlay, "evidence_overlay"); + set_size_and_pos(ui_evidence_overlay, "evidence_overlay", INI_DESIGN, ao_app); ui_evidence_overlay->set_image("evidenceoverlay.png"); - set_size_and_pos(ui_evidence_delete, "evidence_delete"); + set_size_and_pos(ui_evidence_delete, "evidence_delete", INI_DESIGN, ao_app); ui_evidence_delete->set_image("deleteevidence.png"); - set_size_and_pos(ui_evidence_image_name, "evidence_image_name"); + set_size_and_pos(ui_evidence_image_name, "evidence_image_name", INI_DESIGN, ao_app); - set_size_and_pos(ui_evidence_image_button, "evidence_image_button"); + set_size_and_pos(ui_evidence_image_button, "evidence_image_button", INI_DESIGN, ao_app); - set_size_and_pos(ui_evidence_x, "evidence_x"); + set_size_and_pos(ui_evidence_x, "evidence_x", INI_DESIGN, ao_app); ui_evidence_x->set_image("evidencex.png"); - set_size_and_pos(ui_evidence_description, "evidence_description"); + set_size_and_pos(ui_evidence_description, "evidence_description", INI_DESIGN, ao_app); ui_char_button_selector->set_image("char_selector.png"); ui_char_button_selector->hide(); - set_size_and_pos(ui_back_to_lobby, "back_to_lobby"); + set_size_and_pos(ui_back_to_lobby, "back_to_lobby", INI_DESIGN, ao_app); ui_back_to_lobby->setText("Back to Lobby"); - set_size_and_pos(ui_char_buttons, "char_buttons"); + set_size_and_pos(ui_char_buttons, "char_buttons", INI_DESIGN, ao_app); - set_size_and_pos(ui_char_select_left, "char_select_left"); + set_size_and_pos(ui_char_select_left, "char_select_left", INI_DESIGN, ao_app); ui_char_select_left->set_image("arrow_left.png"); - set_size_and_pos(ui_char_select_right, "char_select_right"); + set_size_and_pos(ui_char_select_right, "char_select_right", INI_DESIGN, ao_app); ui_char_select_right->set_image("arrow_right.png"); - set_size_and_pos(ui_spectator, "spectator"); + set_size_and_pos(ui_spectator, "spectator", INI_DESIGN, ao_app); handle_music_anim(); - set_size_and_pos(ui_set_notes, "set_notes_button"); + set_size_and_pos(ui_set_notes, "set_notes_button", INI_DESIGN, ao_app); ui_set_notes->set_image("set_notes.png"); ui_note_area->m_layout->setSpacing(10); - set_size_and_pos(ui_note_area, "note_area"); - set_size_and_pos(note_scroll_area, "note_area"); + set_size_and_pos(ui_note_area, "note_area", INI_DESIGN, ao_app); + set_size_and_pos(note_scroll_area, "note_area", INI_DESIGN, ao_app); note_scroll_area->setWidget(ui_note_area); ui_note_area->set_image("note_area.png"); ui_note_area->add_button->set_image("add_button.png"); @@ -979,27 +986,9 @@ void Courtroom::set_widgets() set_fonts(); } -void Courtroom::set_size_and_pos(QWidget *p_widget, QString p_identifier) -{ - QString filename = design_ini; - - pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - - 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::move_widget(QWidget *p_widget, QString p_identifier) { - QString filename = design_ini; + QString filename = INI_DESIGN; pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); @@ -1020,7 +1009,7 @@ int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_it // &item_vector must be a vector of size at least 1! // Redraw the new correct number of items. - int new_item_number = ao_app->read_theme_ini_int(config_item_number, cc_config_ini); + int new_item_number = ao_app->read_theme_ini_int(config_item_number, INI_CONFIG); int current_item_number = item_vector.size(); // Note we use the fact that, if config_item_number is not there, // read_theme_ini returns an empty string, which .toInt() would fail to @@ -1054,7 +1043,7 @@ int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_it for (int i = 0; i < new_item_number; i++) { item_vector[i]->show(); - set_size_and_pos(item_vector[i], item_name + "_" + QString::number(i)); + set_size_and_pos(item_vector[i], item_name + "_" + QString::number(i), INI_DESIGN, ao_app); // Note that show is deliberately placed before set_size_and_pos // That is because we want to retain the functionality set_size_and_pos // includes where hides a widget if it is unable to find a position for it, @@ -1168,7 +1157,7 @@ void Courtroom::load_effects() delete_widget(widget); // And create new effects - int effect_number = ao_app->read_theme_ini_int("effect_number", cc_config_ini); + int effect_number = ao_app->read_theme_ini_int("effect_number", INI_CONFIG); effects_enabled.resize(effect_number); ui_effects.resize(effect_number); @@ -1207,7 +1196,7 @@ void Courtroom::load_free_blocks() delete_widget(widget); // And create new free block buttons - int free_block_number = ao_app->read_theme_ini_int("free_block_number", cc_config_ini); + int free_block_number = ao_app->read_theme_ini_int("free_block_number", INI_CONFIG); free_blocks_enabled.resize(free_block_number); ui_free_blocks.resize(free_block_number); @@ -1239,7 +1228,7 @@ void Courtroom::load_shouts() delete_widget(widget); // And create new shouts - int shout_number = ao_app->read_theme_ini_int("shout_number", cc_config_ini); + int shout_number = ao_app->read_theme_ini_int("shout_number", INI_CONFIG); shouts_enabled.resize(shout_number); ui_shouts.resize(shout_number); @@ -1281,7 +1270,7 @@ void Courtroom::load_wtce() delete_widget(widget); // And create new wtce buttons - const int l_wtce_count = ao_app->read_theme_ini_int("wtce_number", cc_config_ini); + const int l_wtce_count = ao_app->read_theme_ini_int("wtce_number", INI_CONFIG); wtce_enabled.resize(l_wtce_count); ui_wtce.clear(); @@ -1359,7 +1348,7 @@ void Courtroom::set_judge_wtce() wtce->hide(); // check if we use a single wtce or multiple - const bool is_single_wtce = ao_app->read_theme_ini_bool("enable_single_wtce", cc_config_ini); + const bool is_single_wtce = ao_app->read_theme_ini_bool("enable_single_wtce", INI_CONFIG); // update visibility for next/previous ui_wtce_up->setVisible(is_judge && is_single_wtce); @@ -1412,101 +1401,23 @@ void Courtroom::set_dropdowns() set_dropdown(ui_ooc_chat_message, "[OOC LINE]"); } -void Courtroom::set_font(QWidget *widget, QString p_identifier) -{ - QString design_file = fonts_ini; - QString class_name = widget->metaObject()->className(); - - int f_weight = ao_app->get_font_property(p_identifier, design_file); - - // Font priority - // 1. "font_" + p_identifier - // 2. "font_default" - // 3. System font - QFontDatabase font_database; - QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); - if (!font_database.families().contains(font_name)) - { - font_name = ao_app->get_font_name("font_default", "courtroom_fonts.ini"); - } - widget->setFont(QFont(font_name, f_weight)); - - QString font_color = ao_app->read_theme_ini(p_identifier + "_color", "courtroom_fonts.ini"); - if (font_color.isEmpty()) - font_color = "255, 255, 255"; - QString color = "rgba(" + font_color + ", 255)"; - - int bold = ao_app->get_font_property(p_identifier + "_bold", design_file); - QString is_bold = (bold == 1 ? "bold" : ""); - - QString style_sheet_string = class_name + " { " + "background-color: rgba(0, 0, 0, 0);\n" + "color: " + color + - ";\n" + "font: " + is_bold + ";" + " }"; - widget->setStyleSheet(style_sheet_string); -} - -void Courtroom::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) -{ - set_font(widget, p_identifier); - // Do outlines - bool outline = (ao_app->get_font_property(p_identifier + "_outline", fonts_ini) == 1); - widget->set_outline(outline); - - // Do horizontal alignments - int raw_halign = ao_app->get_font_property(p_identifier + "_halign", fonts_ini); - switch (raw_halign) - { - default: - qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; - [[fallthrough]]; - case DR::Left: - widget->set_horizontal_alignment(Qt::AlignLeft); - break; - case DR::Middle: - widget->set_horizontal_alignment(Qt::AlignHCenter); - break; - case DR::Right: - widget->set_horizontal_alignment(Qt::AlignRight); - break; - } - - // Do vertical alignments - int raw_valign = ao_app->get_font_property(p_identifier + "_valign", fonts_ini); - Qt::Alignment valignment; - switch (raw_valign) - { - default: - qWarning() << "Unknown vertical alignment for" << p_identifier << ":" << raw_valign << "Assuming Top."; - [[fallthrough]]; - case DR::Top: - valignment = Qt::AlignTop; - break; - case DR::Middle: - valignment = Qt::AlignVCenter; - break; - case DR::Bottom: - valignment = Qt::AlignBottom; - break; - } - widget->set_vertical_alignment(valignment); -} - void Courtroom::set_fonts() { - set_drtextedit_font(ui_vp_showname, "showname"); - set_drtextedit_font(ui_vp_message, "message"); - set_drtextedit_font(ui_ic_chatlog, "ic_chatlog"); + set_drtextedit_font(ui_vp_showname, "showname", INI_FONTS, ao_app); + set_drtextedit_font(ui_vp_message, "message", INI_FONTS, ao_app); + set_drtextedit_font(ui_ic_chatlog, "ic_chatlog", INI_FONTS, ao_app); // Chatlog does not support drtextedit because html - set_font(ui_server_chatlog, "server_chatlog"); - set_font(ui_music_list, "music_list"); - set_font(ui_area_list, "area_list"); - set_font(ui_sfx_list, "sfx_list"); - set_drtextedit_font(ui_vp_music_name, "music_name"); - set_drtextedit_font(ui_vp_notepad, "notepad"); + set_font(ui_server_chatlog, "server_chatlog", INI_FONTS, ao_app); + set_font(ui_music_list, "music_list", INI_FONTS, ao_app); + set_font(ui_area_list, "area_list", INI_FONTS, ao_app); + set_font(ui_sfx_list, "sfx_list", INI_FONTS, ao_app); + set_drtextedit_font(ui_vp_music_name, "music_name", INI_FONTS, ao_app); + set_drtextedit_font(ui_vp_notepad, "notepad", INI_FONTS, ao_app); for (int i = 0; i < ui_timers.length(); ++i) { AOTimer *i_timer = ui_timers.at(i); - set_drtextedit_font(i_timer, QString("timer_%1").arg(i)); + set_drtextedit_font(i_timer, QString("timer_%1").arg(i), INI_FONTS, ao_app); } } diff --git a/src/emotes.cpp b/src/emotes.cpp index 8160cf4ca..490223c2c 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -1,6 +1,7 @@ #include "courtroom.h" #include "aoemotebutton.h" +#include "theme.h" #include @@ -30,7 +31,7 @@ void Courtroom::reconstruct_emotes() delete ui_emote_list.takeLast(); // resize and move - set_size_and_pos(ui_emotes, "emotes"); + set_size_and_pos(ui_emotes, "emotes", INI_DESIGN, ao_app); QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", "courtroom_design.ini"); diff --git a/src/evidence.cpp b/src/evidence.cpp index 7c4ddb3d7..1c5dad5aa 100644 --- a/src/evidence.cpp +++ b/src/evidence.cpp @@ -1,5 +1,7 @@ #include "courtroom.h" +#include "theme.h" + #include #include @@ -32,8 +34,8 @@ void Courtroom::construct_evidence() ui_evidence_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "color: white;"); - set_size_and_pos(ui_evidence, "evidence_background"); - set_size_and_pos(ui_evidence_buttons, "evidence_buttons"); + set_size_and_pos(ui_evidence, "evidence_background", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_buttons, "evidence_buttons", INI_DESIGN, ao_app); QPoint f_spacing = ao_app->get_button_spacing("evidence_button_spacing", "courtroom_design.ini"); diff --git a/src/lobby.cpp b/src/lobby.cpp index 3e2c39cba..f8d72a1af 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -6,6 +6,7 @@ #include "drpather.h" #include "drtextedit.h" #include "networkmanager.h" +#include "theme.h" #include "version.h" #include @@ -13,6 +14,9 @@ #include #include +const QString Lobby::INI_DESIGN = "lobby_design.ini"; +const QString Lobby::INI_FONTS = "lobby_fonts.ini"; + Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; @@ -100,60 +104,62 @@ void Lobby::set_widgets() this->resize(f_lobby.width, f_lobby.height); } - set_size_and_pos(ui_background, "lobby"); + set_size_and_pos(ui_background, "lobby", INI_DESIGN, ao_app); ui_background->set_image("lobbybackground.png"); - set_size_and_pos(ui_public_servers, "public_servers"); + set_size_and_pos(ui_public_servers, "public_servers", INI_DESIGN, ao_app); ui_public_servers->set_image("publicservers_selected.png"); - set_size_and_pos(ui_favorites, "favorites"); + set_size_and_pos(ui_favorites, "favorites", INI_DESIGN, ao_app); ui_favorites->set_image("favorites.png"); - set_size_and_pos(ui_refresh, "refresh"); + set_size_and_pos(ui_refresh, "refresh", INI_DESIGN, ao_app); ui_refresh->set_image("refresh.png"); - set_size_and_pos(ui_add_to_fav, "add_to_fav"); + set_size_and_pos(ui_add_to_fav, "add_to_fav", INI_DESIGN, ao_app); ui_add_to_fav->set_image("addtofav.png"); - set_size_and_pos(ui_connect, "connect"); + set_size_and_pos(ui_connect, "connect", INI_DESIGN, ao_app); ui_connect->set_image("connect.png"); - set_size_and_pos(ui_version, "version"); + set_size_and_pos(ui_version, "version", INI_DESIGN, ao_app); ui_version->setText("Version: " + get_version_string()); - set_size_and_pos(ui_about, "about"); + set_size_and_pos(ui_about, "about", INI_DESIGN, ao_app); ui_about->set_image("about.png"); - set_size_and_pos(ui_server_list, "server_list"); + set_size_and_pos(ui_server_list, "server_list", INI_DESIGN, ao_app); ui_server_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "font: bold;"); - set_size_and_pos(ui_player_count, "player_count"); + set_size_and_pos(ui_player_count, "player_count", INI_DESIGN, ao_app); ui_player_count->setStyleSheet("font: bold;" "color: white;" "qproperty-alignment: AlignCenter;"); - set_size_and_pos(ui_description, "description"); + set_size_and_pos(ui_description, "description", INI_DESIGN, ao_app); ui_description->setReadOnly(true); ui_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "color: white;"); - set_size_and_pos(ui_chatbox, "chatbox"); + set_size_and_pos(ui_chatbox, "chatbox", INI_DESIGN, ao_app); ui_chatbox->setReadOnly(true); ui_chatbox->setStyleSheet("QTextBrowser{background-color: rgba(0, 0, 0, 0);}"); - set_size_and_pos(ui_chatname, "chatname"); + set_size_and_pos(ui_chatname, "chatname", INI_DESIGN, ao_app); + set_text_alignment(ui_chatname, "chatname", INI_FONTS, ao_app); ui_chatname->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "selection-background-color: rgba(0, 0, 0, 0);"); - set_size_and_pos(ui_chatmessage, "chatmessage"); + set_size_and_pos(ui_chatmessage, "chatmessage", INI_DESIGN, ao_app); + set_text_alignment(ui_chatmessage, "chatmessage", INI_FONTS, ao_app); ui_chatmessage->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "selection-background-color: rgba(0, 0, 0, 0);"); ui_loading_background->resize(this->width(), this->height()); ui_loading_background->set_image("loadingbackground.png"); - set_size_and_pos(ui_loading_text, "loading_label"); + set_size_and_pos(ui_loading_text, "loading_label", INI_DESIGN, ao_app); ui_loading_text->setFont(QFont("Arial", 20, QFont::Bold)); ui_loading_text->setReadOnly(true); ui_loading_text->setAlignment(Qt::AlignCenter); @@ -162,8 +168,8 @@ void Lobby::set_widgets() "color: rgba(255, 128, 0, 255);"); ui_loading_text->append("Loading"); - set_size_and_pos(ui_progress_bar, "progress_bar"); - set_size_and_pos(ui_cancel, "cancel"); + set_size_and_pos(ui_progress_bar, "progress_bar", INI_DESIGN, ao_app); + set_size_and_pos(ui_cancel, "cancel", INI_DESIGN, ao_app); ui_cancel->setText("Cancel"); ui_loading_background->hide(); @@ -173,33 +179,15 @@ void Lobby::set_widgets() set_choose_a_server(); } -void Lobby::set_size_and_pos(QWidget *p_widget, QString p_identifier) -{ - QString filename = "lobby_design.ini"; - - pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - - 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 Lobby::set_fonts() { - set_drtextedit_font(ui_player_count, "player_count"); - set_font(ui_description, "description"); - set_font(ui_chatbox, "chatbox"); - set_font(ui_chatname, "chatname"); - set_font(ui_chatmessage, "chatmessage"); - set_drtextedit_font(ui_loading_text, "loading_text"); - set_font(ui_server_list, "server_list"); + set_drtextedit_font(ui_player_count, "player_count", INI_FONTS, ao_app); + set_font(ui_description, "description", INI_FONTS, ao_app); + set_font(ui_chatbox, "chatbox", INI_FONTS, ao_app); + set_font(ui_chatname, "chatname", INI_FONTS, ao_app); + set_font(ui_chatmessage, "chatmessage", INI_FONTS, ao_app); + set_drtextedit_font(ui_loading_text, "loading_text", INI_FONTS, ao_app); + set_font(ui_server_list, "server_list", INI_FONTS, ao_app); } void Lobby::set_stylesheet(QWidget *widget, QString target_tag) @@ -221,89 +209,6 @@ void Lobby::set_stylesheets() set_stylesheet(ui_server_list, "[SERVER LIST]"); } -void Lobby::set_font(QWidget *widget, QString p_identifier) -{ - QString design_file = "lobby_fonts.ini"; - - if (!(bool)ao_app->get_font_property("use_custom_fonts", design_file)) - return; - - int f_weight = ao_app->get_font_property(p_identifier, design_file); - - // Font priority - // 1. "font_" + p_identifier - // 2. "font_default" - // 3. System font - QFontDatabase font_database; - QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); - if (!font_database.families().contains(font_name)) - font_name = ao_app->get_font_name("font_default", "lobby_fonts.ini"); - QFont font(font_name, f_weight); - widget->setFont(font); - - QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); - - bool bold = (bool)ao_app->get_font_property(p_identifier + "_bold", design_file); - QString is_bold = ""; - if (bold) - is_bold = "bold"; - - QString class_name = widget->metaObject()->className(); - QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + "color: rgba(" + - QString::number(f_color.red()) + ", " + QString::number(f_color.green()) + ", " + - QString::number(f_color.blue()) + ", 255);\n" + "font: " + is_bold + "; }"; - - widget->setStyleSheet(style_sheet_string); -} - -void Lobby::set_drtextedit_font(DRTextEdit *widget, QString p_identifier) -{ - set_font(widget, p_identifier); - - QString fonts_ini = "lobby_fonts.ini"; - // Do outlines - bool outline = (ao_app->get_font_property(p_identifier + "_outline", fonts_ini) == 1); - widget->set_outline(outline); - - // Do horizontal alignments - int raw_halign = ao_app->get_font_property(p_identifier + "_halign", fonts_ini); - switch (raw_halign) - { - default: - qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; - [[fallthrough]]; - case DR::Left: - widget->set_horizontal_alignment(Qt::AlignLeft); - break; - case DR::Middle: - widget->set_horizontal_alignment(Qt::AlignHCenter); - break; - case DR::Right: - widget->set_horizontal_alignment(Qt::AlignRight); - break; - } - - // Do vertical alignments - int raw_valign = ao_app->get_font_property(p_identifier + "_valign", fonts_ini); - Qt::Alignment valignment; - switch (raw_valign) - { - default: - qWarning() << "Unknown vertical alignment for" << p_identifier << ":" << raw_valign << "Assuming Top."; - [[fallthrough]]; - case DR::Top: - valignment = Qt::AlignTop; - break; - case DR::Middle: - valignment = Qt::AlignVCenter; - break; - case DR::Bottom: - valignment = Qt::AlignBottom; - break; - } - widget->set_vertical_alignment(valignment); -} - void Lobby::show_loading_overlay() { ui_loading_background->show(); diff --git a/src/theme.cpp b/src/theme.cpp new file mode 100644 index 000000000..ab110e286 --- /dev/null +++ b/src/theme.cpp @@ -0,0 +1,121 @@ +#include "theme.h" + +// src +#include "aoapplication.h" +#include "datatypes.h" +#include "drtextedit.h" + +// qt +#include +#include +#include +#include +#include +#include + +void set_size_and_pos(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) +{ + pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, p_ini_file); + + if (design_ini_result.width < 0 || design_ini_result.height < 0) + { + qDebug() << "W: could not find \"" << p_identifier << "\" in " << p_ini_file; + 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 set_text_alignment(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) +{ + const QStringList l_values = + ao_app->read_theme_ini(p_identifier + "_align", p_ini_file).split(",", DR::SkipEmptyParts); + p_widget->setProperty( + "alignment", QVariant(QHash{ + {"left", Qt::AlignHCenter}, {"center", Qt::AlignHCenter}, {"right", Qt::AlignRight}} + .value(l_values.value(0).toLower(), Qt::AlignLeft) | + QHash{ + {"top", Qt::AlignTop}, {"center", Qt::AlignVCenter}, {"bottom", Qt::AlignBottom}} + .value(l_values.value(1).toLower(), Qt::AlignVCenter))); +} + +void set_font(QWidget *p_widget, QString p_identifier, QString ini_file, AOApplication *ao_app) +{ + QString class_name = p_widget->metaObject()->className(); + + int f_weight = ao_app->get_font_property(p_identifier, ini_file); + + // Font priority + // 1. "font_" + p_identifier + // 2. "font_default" + // 3. System font + QFontDatabase font_database; + QString font_name = ao_app->get_font_name("font_" + p_identifier, ini_file); + if (!font_database.families().contains(font_name)) + { + font_name = ao_app->get_font_name("font_default", ini_file); + } + p_widget->setFont(QFont(font_name, f_weight)); + + QString font_color = ao_app->read_theme_ini(p_identifier + "_color", ini_file); + if (font_color.isEmpty()) + font_color = "255, 255, 255"; + QString color = "rgba(" + font_color + ", 255)"; + + int bold = ao_app->get_font_property(p_identifier + "_bold", ini_file); + QString is_bold = (bold == 1 ? "bold" : ""); + + QString style_sheet_string = class_name + " { " + "background-color: rgba(0, 0, 0, 0);\n" + "color: " + color + + ";\n" + "font: " + is_bold + ";" + " }"; + p_widget->setStyleSheet(style_sheet_string); +} + +void set_drtextedit_font(DRTextEdit *p_widget, QString p_identifier, QString ini_file, AOApplication *ao_app) +{ + set_font(p_widget, p_identifier, ini_file, ao_app); + + // Do outlines + bool outline = (ao_app->get_font_property(p_identifier + "_outline", ini_file) == 1); + p_widget->set_outline(outline); + + // Do horizontal alignments + int raw_halign = ao_app->get_font_property(p_identifier + "_halign", ini_file); + switch (raw_halign) + { + default: + qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; + [[fallthrough]]; + case DR::Left: + p_widget->set_horizontal_alignment(Qt::AlignLeft); + break; + case DR::Middle: + p_widget->set_horizontal_alignment(Qt::AlignHCenter); + break; + case DR::Right: + p_widget->set_horizontal_alignment(Qt::AlignRight); + break; + } + + // Do vertical alignments + int raw_valign = ao_app->get_font_property(p_identifier + "_valign", ini_file); + Qt::Alignment valignment; + switch (raw_valign) + { + default: + qWarning() << "Unknown vertical alignment for" << p_identifier << ":" << raw_valign << "Assuming Top."; + [[fallthrough]]; + case DR::Top: + valignment = Qt::AlignTop; + break; + case DR::Middle: + valignment = Qt::AlignVCenter; + break; + case DR::Bottom: + valignment = Qt::AlignBottom; + break; + } + p_widget->set_vertical_alignment(valignment); +} From bba9d0cd02c735eb256538243a97394f4ac358ca Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 05:18:38 +0200 Subject: [PATCH 386/842] Removed extra headers --- include/drtextedit.h | 1 - include/theme.h | 3 +-- src/theme.cpp | 4 ---- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/include/drtextedit.h b/include/drtextedit.h index 1390e1ef2..e58e3184a 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -2,7 +2,6 @@ #define DRTEXTEDIT_H #include -#include class DRTextEdit : public QTextEdit { diff --git a/include/theme.h b/include/theme.h index 2b1479aad..0efd9b4ca 100644 --- a/include/theme.h +++ b/include/theme.h @@ -1,13 +1,12 @@ #pragma once -#include - // src class AOApplication; class DRTextEdit; // qt class QLineEdit; +class QString; class QWidget; void set_size_and_pos(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); diff --git a/src/theme.cpp b/src/theme.cpp index ab110e286..b5558bde7 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -8,10 +8,6 @@ // qt #include #include -#include -#include -#include -#include void set_size_and_pos(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) { From 2caec7d7716deec8f6cbd3417cfc899439eed831 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 08:25:25 +0200 Subject: [PATCH 387/842] Cleaned up header inclusions --- include/aoapplication.h | 72 ++++++++++-------------------------- include/aoblipplayer.h | 8 ++-- include/aobutton.h | 2 +- include/aocharbutton.h | 5 +-- include/aocharmovie.h | 7 ++-- include/aoobject.h | 4 +- include/aopixmap.h | 2 +- include/aoscene.h | 6 +-- include/aotimer.h | 8 +--- include/datatypes.h | 5 --- include/debug_functions.h | 2 +- include/draudio.h | 1 - include/draudiodevice.h | 2 - include/draudioengine_p.h | 1 - include/drdiscord.h | 11 +----- include/drpather.h | 2 +- include/drtextedit.h | 1 - include/file_functions.h | 4 +- include/hardware_functions.h | 2 +- include/lobby.h | 27 +++++++------- include/networkmanager.h | 14 ++++--- include/version.h | 2 +- src/aoapplication.cpp | 51 +++++++++++++++++++++++++ src/aoblipplayer.cpp | 4 ++ src/aobutton.cpp | 1 + src/aomusicplayer.cpp | 2 +- src/aosfxplayer.cpp | 3 +- src/aoshoutplayer.cpp | 3 +- src/aosystemplayer.cpp | 5 +-- src/aotimer.cpp | 1 + src/courtroom.cpp | 4 +- src/datatypes.cpp | 2 + src/drdiscord.cpp | 2 + src/lobby.cpp | 16 +++++++- src/networkmanager.cpp | 8 ++-- src/packet_distribution.cpp | 3 +- src/text_file_functions.cpp | 6 +-- src/version.cpp | 2 + 38 files changed, 161 insertions(+), 140 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 2bee38eae..4efb97370 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -1,19 +1,17 @@ #ifndef AOAPPLICATION_H #define AOAPPLICATION_H -#include "aopacket.h" #include "datatypes.h" -#include "drdiscord.h" -#include -#include -#include - -class NetworkManager; -class Lobby; -class Courtroom; class AOConfig; class AOConfigPanel; +class AOPacket; +class Courtroom; +class DRDiscord; +class Lobby; +class NetworkManager; + +#include class AOApplication : public QApplication { @@ -23,55 +21,25 @@ class AOApplication : public QApplication AOApplication(int &argc, char **argv); ~AOApplication(); - int get_client_id() const - { - return s_pv; - } - void set_client_id(int id) - { - s_pv = id; - } - - Lobby *get_lobby() const - { - return m_lobby; - } + int get_client_id() const; + void set_client_id(int id); + + Lobby *get_lobby() const; void construct_lobby(); void destruct_lobby(); - Courtroom *get_courtroom() const - { - return m_courtroom; - } + Courtroom *get_courtroom() const; void construct_courtroom(); void destruct_courtroom(); - DRDiscord *get_discord() const - { - return dr_discord; - } - - NetworkManager *get_network_manager() - { - return net_manager; - } - - bool has_message_acknowledgement_feature() const - { - return feature_ackMS; - } - bool has_character_declaration_feature() const - { - return feature_chrini; - } - bool has_showname_declaration_feature() const - { - return feature_showname; - } - bool has_chat_speed_feature() const - { - return feature_chat_speed; - } + DRDiscord *get_discord() const; + + NetworkManager *get_network_manager(); + + bool has_message_acknowledgement_feature() const; + bool has_character_declaration_feature() const; + bool has_showname_declaration_feature() const; + bool has_chat_speed_feature() const; void ms_packet_received(AOPacket *p_packet); void server_packet_received(AOPacket *p_packet); diff --git a/include/aoblipplayer.h b/include/aoblipplayer.h index 44f2db87d..af69bcdc0 100644 --- a/include/aoblipplayer.h +++ b/include/aoblipplayer.h @@ -1,10 +1,10 @@ #pragma once -// pdir -#include "aoapplication.h" #include "aoobject.h" #include "draudioengine.h" +class AOApplication; + // 3rd #include @@ -15,13 +15,13 @@ // std #include -const int BLIP_COUNT = 5; - class AOBlipPlayer : public AOObject { Q_OBJECT public: + static const int BLIP_COUNT; + AOBlipPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); public slots: diff --git a/include/aobutton.h b/include/aobutton.h index 54dcd64c9..41fd22eb3 100644 --- a/include/aobutton.h +++ b/include/aobutton.h @@ -1,7 +1,7 @@ #ifndef AOBUTTON_H #define AOBUTTON_H -#include "aoapplication.h" +class AOApplication; #include diff --git a/include/aocharbutton.h b/include/aocharbutton.h index f19a8499f..bff80c26d 100644 --- a/include/aocharbutton.h +++ b/include/aocharbutton.h @@ -1,12 +1,11 @@ #ifndef AOCHARBUTTON_H #define AOCHARBUTTON_H -#include "aoapplication.h" #include "aoimagedisplay.h" +class AOApplication; + #include -#include -#include class AOCharButton : public QPushButton { diff --git a/include/aocharmovie.h b/include/aocharmovie.h index 363109de4..1a600cff6 100644 --- a/include/aocharmovie.h +++ b/include/aocharmovie.h @@ -3,11 +3,12 @@ #include "aopixmap.h" +class AOApplication; + #include -#include -#include -class AOApplication; +class QMovie; +class QTimer; class AOCharMovie : public QLabel { diff --git a/include/aoobject.h b/include/aoobject.h index a95421514..9437549c5 100644 --- a/include/aoobject.h +++ b/include/aoobject.h @@ -1,6 +1,8 @@ #pragma once -#include "aoapplication.h" +class AOApplication; + +#include class AOObject : public QObject { diff --git a/include/aopixmap.h b/include/aopixmap.h index c9fa24e74..67f0a4427 100644 --- a/include/aopixmap.h +++ b/include/aopixmap.h @@ -1,6 +1,6 @@ #ifndef AOPIXMAP_H #define AOPIXMAP_H -// qt + #include class AOPixmap diff --git a/include/aoscene.h b/include/aoscene.h index ac726120d..9e69f477a 100644 --- a/include/aoscene.h +++ b/include/aoscene.h @@ -1,10 +1,10 @@ #ifndef AOSCENE_H #define AOSCENE_H -#include - -class Courtroom; class AOApplication; +class Courtroom; + +#include class AOScene : public QLabel { diff --git a/include/aotimer.h b/include/aotimer.h index 3296c8b64..e9b4ca180 100644 --- a/include/aotimer.h +++ b/include/aotimer.h @@ -1,14 +1,10 @@ #ifndef AOTIMER_H #define AOTIMER_H -#include +#include "drtextedit.h" + #include #include -#include - -#include "aobutton.h" -#include "aolabel.h" -#include "drtextedit.h" class ManualTimer { diff --git a/include/datatypes.h b/include/datatypes.h index 7388fbb03..0a8550fb6 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -2,11 +2,6 @@ #define DATATYPES_H #include -#include -#include -#include - -#include class DREmote { diff --git a/include/debug_functions.h b/include/debug_functions.h index 6feaf90a5..35717a101 100644 --- a/include/debug_functions.h +++ b/include/debug_functions.h @@ -1,7 +1,7 @@ #ifndef DEBUG_FUNCTIONS_H #define DEBUG_FUNCTIONS_H -#include +class QString; void call_error(QString message); void call_notice(QString message); diff --git a/include/draudio.h b/include/draudio.h index ec19fc931..4c60b3389 100644 --- a/include/draudio.h +++ b/include/draudio.h @@ -1,7 +1,6 @@ #pragma once #include -#include namespace DRAudio { diff --git a/include/draudiodevice.h b/include/draudiodevice.h index c142f8d60..aa49ad75a 100644 --- a/include/draudiodevice.h +++ b/include/draudiodevice.h @@ -2,9 +2,7 @@ #include -#include #include -#include #include diff --git a/include/draudioengine_p.h b/include/draudioengine_p.h index 4755db18d..d27c6c087 100644 --- a/include/draudioengine_p.h +++ b/include/draudioengine_p.h @@ -3,7 +3,6 @@ #include "draudio.h" #include "draudiostreamfamily.h" -#include #include #include #include diff --git a/include/drdiscord.h b/include/drdiscord.h index 198b5a7cd..c73caa620 100644 --- a/include/drdiscord.h +++ b/include/drdiscord.h @@ -2,12 +2,10 @@ #include -#include -#include #include #include -#include -#include + +class QTimer; #include @@ -83,9 +81,4 @@ public slots: private slots: void on_update_queued(); - - /* -signals: - void presence_changed(QPrivateSignal); - */ }; diff --git a/include/drpather.h b/include/drpather.h index ff7be17a9..a4003716d 100644 --- a/include/drpather.h +++ b/include/drpather.h @@ -1,6 +1,6 @@ #pragma once -#include +class QString; class DRPather { diff --git a/include/drtextedit.h b/include/drtextedit.h index 1390e1ef2..e58e3184a 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -2,7 +2,6 @@ #define DRTEXTEDIT_H #include -#include class DRTextEdit : public QTextEdit { diff --git a/include/file_functions.h b/include/file_functions.h index 599c9008f..4c057f302 100644 --- a/include/file_functions.h +++ b/include/file_functions.h @@ -1,8 +1,8 @@ #ifndef FILE_FUNCTIONS_H #define FILE_FUNCTIONS_H -#include -#include +class QString; +class QStringList; QStringList animated_or_static_extensions(); QStringList animated_extensions(); diff --git a/include/hardware_functions.h b/include/hardware_functions.h index 5aa8473c5..d3cf5a17d 100644 --- a/include/hardware_functions.h +++ b/include/hardware_functions.h @@ -1,7 +1,7 @@ #ifndef HARDWARE_FUNCTIONS_H #define HARDWARE_FUNCTIONS_H -#include +class QString; QString get_hdid(); diff --git a/include/lobby.h b/include/lobby.h index c94d06c44..631bb5bc3 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -1,22 +1,21 @@ #ifndef LOBBY_H #define LOBBY_H -#include "aobutton.h" -#include "aoconfig.h" -#include "aoimagedisplay.h" -#include "aopacket.h" -#include "aotextarea.h" -#include "drtextedit.h" +#include "datatypes.h" + +class AOApplication; +class AOButton; +class AOConfig; +class AOImageDisplay; +class AOTextArea; +class DRTextEdit; -#include -#include -#include #include -#include -#include -#include +#include -class AOApplication; +class QListWidget; +class QLineEdit; +class QProgressBar; class Lobby : public QMainWindow { @@ -25,6 +24,8 @@ class Lobby : public QMainWindow public: Lobby(AOApplication *p_ao_app); + bool is_public_server() const; + void set_widgets(); void list_servers(); void list_favorites(); diff --git a/include/networkmanager.h b/include/networkmanager.h index 28934eeb7..52171598b 100644 --- a/include/networkmanager.h +++ b/include/networkmanager.h @@ -1,13 +1,15 @@ #ifndef NETWORKMANAGER_H #define NETWORKMANAGER_H -#include "aoapplication.h" -#include "aopacket.h" +#include "datatypes.h" -#include -#include -#include -#include +class AOApplication; + +#include +#include + +class QTcpSocket; +class QTimer; class NetworkManager : public QObject { diff --git a/include/version.h b/include/version.h index 0c0758f67..7da7f84b9 100644 --- a/include/version.h +++ b/include/version.h @@ -1,6 +1,6 @@ #pragma once -#include +class QString; int get_release_version(); int get_major_version(); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 97b5a0a1e..bd88ff443 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -2,6 +2,7 @@ #include "courtroom.h" #include "debug_functions.h" +#include "drdiscord.h" #include "lobby.h" #include "networkmanager.h" @@ -49,6 +50,21 @@ AOApplication::~AOApplication() destruct_courtroom(); } +int AOApplication::get_client_id() const +{ + return s_pv; +} + +void AOApplication::set_client_id(int id) +{ + s_pv = id; +} + +Lobby *AOApplication::get_lobby() const +{ + return m_lobby; +} + void AOApplication::construct_lobby() { if (lobby_constructed) @@ -90,6 +106,11 @@ void AOApplication::destruct_lobby() lobby_constructed = false; } +Courtroom *AOApplication::get_courtroom() const +{ + return m_courtroom; +} + void AOApplication::construct_courtroom() { if (courtroom_constructed) @@ -133,6 +154,36 @@ void AOApplication::destruct_courtroom() net_manager->disconnect_from_server(); } +DRDiscord *AOApplication::get_discord() const +{ + return dr_discord; +} + +NetworkManager *AOApplication::get_network_manager() +{ + return net_manager; +} + +bool AOApplication::has_message_acknowledgement_feature() const +{ + return feature_ackMS; +} + +bool AOApplication::has_character_declaration_feature() const +{ + return feature_chrini; +} + +bool AOApplication::has_showname_declaration_feature() const +{ + return feature_showname; +} + +bool AOApplication::has_chat_speed_feature() const +{ + return feature_chat_speed; +} + void AOApplication::on_config_theme_changed() { Q_EMIT reload_theme(); diff --git a/src/aoblipplayer.cpp b/src/aoblipplayer.cpp index 8f81e387f..5385b29ad 100644 --- a/src/aoblipplayer.cpp +++ b/src/aoblipplayer.cpp @@ -1,5 +1,9 @@ #include "aoblipplayer.h" +#include "aoapplication.h" + +const int AOBlipPlayer::BLIP_COUNT = 5; + AOBlipPlayer::AOBlipPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) { m_family = DRAudioEngine::get_family(DRAudio::Family::FBlip); diff --git a/src/aobutton.cpp b/src/aobutton.cpp index 3224e4073..f92328c98 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -1,5 +1,6 @@ #include "aobutton.h" +#include "aoapplication.h" #include "debug_functions.h" #include "file_functions.h" diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index ab1f27d8f..6250021dd 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -1,6 +1,6 @@ #include "aomusicplayer.h" -#include +#include "aoapplication.h" AOMusicPlayer::AOMusicPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) { diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index 27ae215d6..114b2be2b 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -1,10 +1,9 @@ #include "aosfxplayer.h" +#include "aoapplication.h" #include "draudioengine.h" #include "file_functions.h" -#include - #include AOSfxPlayer::AOSfxPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) diff --git a/src/aoshoutplayer.cpp b/src/aoshoutplayer.cpp index 4768636c6..28e119492 100644 --- a/src/aoshoutplayer.cpp +++ b/src/aoshoutplayer.cpp @@ -1,10 +1,9 @@ #include "aoshoutplayer.h" +#include "aoapplication.h" #include "draudioengine.h" #include "file_functions.h" -#include - AOShoutPlayer::AOShoutPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) {} diff --git a/src/aosystemplayer.cpp b/src/aosystemplayer.cpp index cf6c971bf..e8323b9b2 100644 --- a/src/aosystemplayer.cpp +++ b/src/aosystemplayer.cpp @@ -1,12 +1,9 @@ #include "aosystemplayer.h" +#include "aoapplication.h" #include "draudioengine.h" #include "file_functions.h" -#include - -#include - AOSystemPlayer::AOSystemPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) {} diff --git a/src/aotimer.cpp b/src/aotimer.cpp index 968de2129..cd53edf79 100644 --- a/src/aotimer.cpp +++ b/src/aotimer.cpp @@ -1,4 +1,5 @@ #include "aotimer.h" + #include AOTimer::AOTimer(QWidget *p_parent) : DRTextEdit(p_parent) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 5f0f4a9c9..e6660f64f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -3,9 +3,11 @@ #include "aoapplication.h" #include "datatypes.h" #include "debug_functions.h" +#include "drdiscord.h" #include "file_functions.h" #include "hardware_functions.h" #include "lobby.h" +#include "networkmanager.h" #include #include @@ -21,8 +23,6 @@ #include #include -#include "networkmanager.h" - Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; diff --git a/src/datatypes.cpp b/src/datatypes.cpp index e9e48a65d..9e87853ea 100644 --- a/src/datatypes.cpp +++ b/src/datatypes.cpp @@ -1,5 +1,7 @@ #include "datatypes.h" +#include + QMap DR::get_default_color_map() { QMap default_color_map; diff --git a/src/drdiscord.cpp b/src/drdiscord.cpp index b83ebbe0d..19b13f6eb 100644 --- a/src/drdiscord.cpp +++ b/src/drdiscord.cpp @@ -1,7 +1,9 @@ #include "drdiscord.h" +#include #include #include +#include #include diff --git a/src/lobby.cpp b/src/lobby.cpp index de1c0b0d9..c4fa9ced0 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -1,7 +1,11 @@ #include "lobby.h" #include "aoapplication.h" -#include "aosfxplayer.h" +#include "aobutton.h" +#include "aoconfig.h" +#include "aoimagedisplay.h" +#include "aopacket.h" +#include "aotextarea.h" #include "debug_functions.h" #include "drpather.h" #include "drtextedit.h" @@ -9,9 +13,12 @@ #include "version.h" #include +#include #include +#include +#include #include -#include +#include Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() { @@ -68,6 +75,11 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() set_widgets(); } +bool Lobby::is_public_server() const +{ + return public_servers_selected; +} + // sets images, position and size void Lobby::set_widgets() { diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp index ccb8b9027..a6e6e4962 100644 --- a/src/networkmanager.cpp +++ b/src/networkmanager.cpp @@ -1,10 +1,10 @@ #include "networkmanager.h" -#include "datatypes.h" -#include "debug_functions.h" -#include "lobby.h" +#include "aoapplication.h" +#include "aopacket.h" -#include +#include +#include const QString NetworkManager::ms_srv_hostname = "_aoms._tcp.aceattorneyonline.com"; const QString NetworkManager::ms_nosrv_hostname = "master.aceattorneyonline.com"; diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 49fcb05f2..3e33af32f 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -2,6 +2,7 @@ #include "courtroom.h" #include "debug_functions.h" +#include "drdiscord.h" #include "file_functions.h" #include "hardware_functions.h" #include "lobby.h" @@ -202,7 +203,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QString server_name, server_address; bool is_favorite = false; - if (m_lobby->public_servers_selected) + if (m_lobby->is_public_server()) { if (selected_server >= 0 && selected_server < server_list.size()) { diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index c6dae1c7b..5373b131e 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -1,15 +1,13 @@ #include "aoapplication.h" -#include "file_functions.h" - #include "aoconfig.h" +#include "file_functions.h" #include #include +#include #include -#include #include -#include QStringList AOApplication::get_callwords() { diff --git a/src/version.cpp b/src/version.cpp index 82fadba75..58f2d3b2b 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -1,5 +1,7 @@ #include "version.h" +#include + int get_release_version() { return 1; From 2833a60c20ef120f277cf09377809e74ea4ae8aa Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 09:05:40 +0200 Subject: [PATCH 388/842] Cleaned up source #include --- include/aonotearea.h | 16 +++----- include/courtroom.h | 77 ++++++++++++++++++------------------- src/aoapplication.cpp | 11 ++---- src/aocharmovie.cpp | 6 +-- src/aomovie.cpp | 2 +- src/aonotearea.cpp | 7 +++- src/aonotepicker.cpp | 2 +- src/aoscene.cpp | 7 ++-- src/audio_functions.cpp | 2 + src/charselect.cpp | 5 +++ src/courtroom.cpp | 34 +++++++++++----- src/courtroom_widgets.cpp | 43 +++++++++++++-------- src/emotes.cpp | 6 +++ src/evidence.cpp | 8 +++- src/packet_distribution.cpp | 3 +- src/path_functions.cpp | 5 ++- 16 files changed, 135 insertions(+), 99 deletions(-) diff --git a/include/aonotearea.h b/include/aonotearea.h index b4b506414..de1971199 100644 --- a/include/aonotearea.h +++ b/include/aonotearea.h @@ -1,16 +1,12 @@ #ifndef AONOTEAREA_HPP #define AONOTEAREA_HPP -#include -#include -#include -#include -#include -#include -#include -#include - -#include "aoapplication.h" +#include "aoimagedisplay.h" + +class AOApplication; +class AOButton; + +class QVBoxLayout; class AONoteArea : public AOImageDisplay { diff --git a/include/courtroom.h b/include/courtroom.h index 7cb72cbd8..f04fb53a7 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -1,52 +1,49 @@ #ifndef COURTROOM_H #define COURTROOM_H -#include "aoblipplayer.h" -#include "aobutton.h" -#include "aocharbutton.h" -#include "aocharmovie.h" -#include "aoconfig.h" -#include "aoconfigpanel.h" -#include "aoemotebutton.h" -#include "aoevidencebutton.h" -#include "aoevidencedescription.h" -#include "aoevidencedisplay.h" -#include "aoimagedisplay.h" -#include "aolabel.h" -#include "aolineedit.h" -#include "aomovie.h" -#include "aomusicplayer.h" -#include "aonotearea.h" -#include "aonotepad.h" -#include "aopacket.h" -#include "aoscene.h" -#include "aosfxplayer.h" -#include "aoshoutplayer.h" -#include "aosystemplayer.h" -#include "aotextarea.h" -#include "aotimer.h" #include "datatypes.h" -#include "draudioengine.h" - -#include -#include -#include -#include -#include -#include + +class AOApplication; +class AOBlipPlayer; +class AOButton; +class AOCharButton; +class AOCharMovie; +class AOConfig; +class AOEmoteButton; +class AOEvidenceButton; +class AOEvidenceDescription; +class AOEvidenceDisplay; +class AOImageDisplay; +class AOLabel; +class AOLineEdit; +class AOMovie; +class AOMusicPlayer; +class AONoteArea; +class AONotepad; +class AOScene; +class AOSfxPlayer; +class AOShoutPlayer; +class AOSystemPlayer; +class AOTextArea; +class AOTimer; +class DRTextEdit; + #include #include -#include -#include +#include #include -#include -#include -#include #include -#include -#include -class AOApplication; +class QCheckBox; +class QComboBox; +class QLineEdit; +class QListWidget; +class QListWidgetItem; +class QPropertyAnimation; +class QScrollArea; +class QSignalMapper; + +#include class Courtroom : public QMainWindow { diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index bd88ff443..aff01b91e 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -1,19 +1,14 @@ #include "aoapplication.h" +#include "aoconfig.h" +#include "aoconfigpanel.h" +#include "aopacket.h" #include "courtroom.h" #include "debug_functions.h" #include "drdiscord.h" #include "lobby.h" #include "networkmanager.h" -#include "aoconfig.h" -#include "aoconfigpanel.h" - -#include -#include -#include -#include - #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) #include #else diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 88937fe2d..e33ebdb11 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -1,12 +1,10 @@ #include "aocharmovie.h" #include "aoapplication.h" -#include "courtroom.h" #include "file_functions.h" -#include "misc_functions.h" -#include -#include +#include +#include AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) { diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 193dbd09d..9d4346815 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -1,6 +1,6 @@ #include "aomovie.h" -#include "courtroom.h" +#include "aoapplication.h" #include "file_functions.h" #include "misc_functions.h" diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp index d5557a02c..d901513c3 100644 --- a/src/aonotearea.cpp +++ b/src/aonotearea.cpp @@ -1,9 +1,14 @@ #include "aonotearea.h" -#include "aonotepicker.h" +#include "aobutton.h" +#include "aonotepicker.h" #include "courtroom.h" #include +#include +#include +#include +#include AONoteArea::AONoteArea(QWidget *p_parent, AOApplication *p_ao_app) : AOImageDisplay(p_parent, p_ao_app) { diff --git a/src/aonotepicker.cpp b/src/aonotepicker.cpp index 0d7b61807..7c10bed01 100644 --- a/src/aonotepicker.cpp +++ b/src/aonotepicker.cpp @@ -1,10 +1,10 @@ #include "aonotepicker.h" +#include "aonotearea.h" #include "courtroom.h" #include "debug_functions.h" #include "drpather.h" -#include #include AONotePicker::AONotePicker(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) diff --git a/src/aoscene.cpp b/src/aoscene.cpp index 23c925378..3d4991b6e 100644 --- a/src/aoscene.cpp +++ b/src/aoscene.cpp @@ -1,9 +1,8 @@ #include "aoscene.h" -// src -#include "courtroom.h" + +#include "aoapplication.h" #include "file_functions.h" -// qt -#include + #include AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent), ao_app(p_ao_app) diff --git a/src/audio_functions.cpp b/src/audio_functions.cpp index 34bf0fadb..d852e6bea 100644 --- a/src/audio_functions.cpp +++ b/src/audio_functions.cpp @@ -1,5 +1,7 @@ #include "courtroom.h" +#include "draudioengine.h" + bool Courtroom::is_audio_suppressed() const { return m_audio_mute; diff --git a/src/charselect.cpp b/src/charselect.cpp index 0f2729cc0..e70154232 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -1,10 +1,15 @@ #include "courtroom.h" +#include "aobutton.h" +#include "aocharbutton.h" +#include "aoimagedisplay.h" +#include "aopacket.h" #include "debug_functions.h" #include "file_functions.h" #include "hardware_functions.h" #include +#include void Courtroom::construct_char_select() { diff --git a/src/courtroom.cpp b/src/courtroom.cpp index e6660f64f..761edb2bd 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1,27 +1,41 @@ #include "courtroom.h" #include "aoapplication.h" -#include "datatypes.h" +#include "aoblipplayer.h" +#include "aobutton.h" +#include "aocharmovie.h" +#include "aoconfig.h" +#include "aoevidencedisplay.h" +#include "aoimagedisplay.h" +#include "aomovie.h" +#include "aomusicplayer.h" +#include "aonotearea.h" +#include "aonotepicker.h" +#include "aopacket.h" +#include "aoscene.h" +#include "aosfxplayer.h" +#include "aoshoutplayer.h" +#include "aosystemplayer.h" +#include "aotextarea.h" +#include "aotimer.h" #include "debug_functions.h" #include "drdiscord.h" #include "file_functions.h" #include "hardware_functions.h" #include "lobby.h" -#include "networkmanager.h" -#include +#include +#include #include -#include -#include -#include +#include #include +#include +#include #include #include -#include -#include +#include #include -#include -#include +#include Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index da259709c..a100447ec 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1,23 +1,34 @@ #include "courtroom.h" -#include "aoapplication.h" -#include "datatypes.h" -#include "debug_functions.h" +#include "aoblipplayer.h" +#include "aobutton.h" +#include "aocharmovie.h" +#include "aoconfig.h" +#include "aoevidencedescription.h" +#include "aoevidencedisplay.h" +#include "aoimagedisplay.h" +#include "aolabel.h" +#include "aolineedit.h" +#include "aomovie.h" +#include "aomusicplayer.h" +#include "aonotearea.h" +#include "aoscene.h" +#include "aosfxplayer.h" +#include "aoshoutplayer.h" +#include "aosystemplayer.h" +#include "aotextarea.h" +#include "aotimer.h" +#include "drtextedit.h" #include "file_functions.h" -#include "hardware_functions.h" -#include "lobby.h" - -#include -#include -#include -#include -#include -#include + +#include +#include +#include +#include #include -#include -#include -#include -#include +#include +#include +#include void Courtroom::create_widgets() { diff --git a/src/emotes.cpp b/src/emotes.cpp index 8160cf4ca..14a0e5980 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -1,8 +1,14 @@ #include "courtroom.h" +#include "aoapplication.h" +#include "aobutton.h" +#include "aoconfig.h" #include "aoemotebutton.h" +#include +#include #include +#include void Courtroom::construct_emotes() { diff --git a/src/evidence.cpp b/src/evidence.cpp index 7c4ddb3d7..92a0b7c16 100644 --- a/src/evidence.cpp +++ b/src/evidence.cpp @@ -1,6 +1,12 @@ #include "courtroom.h" -#include +#include "aobutton.h" +#include "aoevidencebutton.h" +#include "aoevidencedescription.h" +#include "aoimagedisplay.h" +#include "aolineedit.h" +#include "aopacket.h" + #include void Courtroom::construct_evidence() diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 3e33af32f..5942cf3f7 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -1,5 +1,7 @@ #include "aoapplication.h" +#include "aoconfig.h" +#include "aopacket.h" #include "courtroom.h" #include "debug_functions.h" #include "drdiscord.h" @@ -9,7 +11,6 @@ #include "networkmanager.h" #include "version.h" -#include #include void AOApplication::ms_packet_received(AOPacket *p_packet) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 31bddd8f3..92f1e9632 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -1,12 +1,13 @@ #include "aoapplication.h" +#include "aoconfig.h" #include "courtroom.h" #include "drpather.h" #include "file_functions.h" -#include #include -#include +#include +#include // Copied over from Vanilla. // As said in the comments there, this is a *super broad* definition. From 22303be3ce63ac585ceb1827753d88e54d69cb70 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 09:09:11 +0200 Subject: [PATCH 389/842] More #include changes --- include/aotimer.h | 5 +++-- src/aotimer.cpp | 30 ++++++++++++++++-------------- src/courtroom.cpp | 1 + src/courtroom_widgets.cpp | 1 + 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/include/aotimer.h b/include/aotimer.h index e9b4ca180..96c71d5d3 100644 --- a/include/aotimer.h +++ b/include/aotimer.h @@ -4,7 +4,8 @@ #include "drtextedit.h" #include -#include + +class QTimer; class ManualTimer { @@ -59,7 +60,7 @@ public slots: private: ManualTimer old_manual_timer; // Pre-update manual timer ManualTimer manual_timer; - QTimer firing_timer; + QTimer *firing_timer = nullptr; QTime start_time = QTime(0, 0); // All of this is in miliseconds int manual_timer_timestep_length = -12; diff --git a/src/aotimer.cpp b/src/aotimer.cpp index cd53edf79..31ca10eab 100644 --- a/src/aotimer.cpp +++ b/src/aotimer.cpp @@ -1,6 +1,7 @@ #include "aotimer.h" #include +#include AOTimer::AOTimer(QWidget *p_parent) : DRTextEdit(p_parent) { @@ -12,9 +13,10 @@ AOTimer::AOTimer(QWidget *p_parent) : DRTextEdit(p_parent) setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setReadOnly(true); - firing_timer.setTimerType(Qt::PreciseTimer); - firing_timer.setInterval(firing_timer_length); - connect(&firing_timer, SIGNAL(timeout()), this, SLOT(update_time())); + firing_timer = new QTimer(this); + firing_timer->setTimerType(Qt::PreciseTimer); + firing_timer->setInterval(firing_timer_length); + connect(firing_timer, SIGNAL(timeout()), this, SLOT(update_time())); set_time(start_time); old_manual_timer.set_time(start_time); @@ -37,7 +39,7 @@ void AOTimer::update_time() if (manual_timer.get_time().operator<(old_manual_timer.get_time())) { set_time(QTime(0, 0)); - firing_timer.stop(); + firing_timer->stop(); redraw(); return; } @@ -50,7 +52,7 @@ void AOTimer::update_time() if (manual_timer.get_time().operator>(old_manual_timer.get_time())) { set_time(QTime(0, 0)); - firing_timer.stop(); + firing_timer->stop(); redraw(); return; } @@ -69,8 +71,8 @@ void AOTimer::update_time() // operation, while QT's built-in automatic restart is very efficient. // Therefore, we only restart firing_timer if its length was not // firing_timer_length already (see example above). - if (firing_timer.interval() != firing_timer_length) - firing_timer.start(firing_timer_length); + if (firing_timer->interval() != firing_timer_length) + firing_timer->start(firing_timer_length); } void AOTimer::set() @@ -81,15 +83,15 @@ void AOTimer::set() void AOTimer::resume() { paused = false; - firing_timer.start(); + firing_timer->start(); } void AOTimer::pause() { paused = true; - int remaining = firing_timer.remainingTime(); - firing_timer.stop(); - firing_timer.setInterval(remaining); + int remaining = firing_timer->remainingTime(); + firing_timer->stop(); + firing_timer->setInterval(remaining); } void AOTimer::redraw() @@ -123,7 +125,7 @@ void AOTimer::set_firing_interval(int new_firing_interval) */ // Update time spent so far and new future firing interval - time_spent_in_timestep += (firing_timer_length - firing_timer.remainingTime()); + time_spent_in_timestep += (firing_timer_length - firing_timer->remainingTime()); firing_timer_length = new_firing_interval; // For this timestep however, the firing interval will be shorter than // firing_timer_length to account for the fact the timer may have already been @@ -134,10 +136,10 @@ void AOTimer::set_firing_interval(int new_firing_interval) else this_step_firing_interval = new_firing_interval - time_spent_in_timestep; - firing_timer.setInterval(this_step_firing_interval); + firing_timer->setInterval(this_step_firing_interval); if (!paused) - firing_timer.start(); + firing_timer->start(); } void AOTimer::set_concentrate_mode() diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 761edb2bd..80452cdba 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index a100447ec..845c54c95 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include void Courtroom::create_widgets() From d25cfd0568e40784c1f470fd36cfa6d214732b00 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 15:04:34 +0200 Subject: [PATCH 390/842] #177 more #include removals --- include/aoapplication.h | 1 + include/aoblipplayer.h | 6 ------ include/aocharbutton.h | 3 +-- include/aocharmovie.h | 2 -- include/aoconfigpanel.h | 25 ++++++++++++------------- include/aoemotebutton.h | 6 ++++-- include/aoevidencebutton.h | 5 ++--- include/aoevidencedisplay.h | 8 +++----- include/aoguiloader.h | 1 - include/aoimagedisplay.h | 3 +-- include/aolabel.h | 2 +- include/aolineedit.h | 1 - include/aomovie.h | 5 ++--- include/aonotepad.h | 3 ++- include/aonotepicker.h | 9 ++++++--- include/draudiodevice.h | 1 + include/draudioengine_p.h | 3 ++- include/draudiostream.h | 1 - include/draudiostreamfamily.h | 1 - src/aoapplication.cpp | 2 ++ src/aocharbutton.cpp | 2 ++ src/aocharmovie.cpp | 1 + src/aoconfigpanel.cpp | 18 ++++++++++++++---- src/aoemotebutton.cpp | 4 +++- src/aoevidencebutton.cpp | 2 ++ src/aoevidencedisplay.cpp | 7 +++++-- src/aoguiloader.cpp | 2 +- src/aoimagedisplay.cpp | 6 +++--- src/aolabel.cpp | 2 +- src/aomovie.cpp | 2 ++ src/aonotearea.cpp | 2 ++ src/aonotepicker.cpp | 3 +++ src/charselect.cpp | 1 + src/courtroom_widgets.cpp | 2 ++ src/draudio.cpp | 2 ++ src/draudioengine.cpp | 4 +--- src/draudioengine_p.cpp | 1 + src/evidence.cpp | 1 + src/main.cpp | 2 +- 39 files changed, 88 insertions(+), 64 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 4efb97370..6ff462e1e 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -12,6 +12,7 @@ class Lobby; class NetworkManager; #include +#include class AOApplication : public QApplication { diff --git a/include/aoblipplayer.h b/include/aoblipplayer.h index af69bcdc0..973196563 100644 --- a/include/aoblipplayer.h +++ b/include/aoblipplayer.h @@ -3,15 +3,9 @@ #include "aoobject.h" #include "draudioengine.h" -class AOApplication; - // 3rd #include -// qt -#include -#include - // std #include diff --git a/include/aocharbutton.h b/include/aocharbutton.h index bff80c26d..b045f57a5 100644 --- a/include/aocharbutton.h +++ b/include/aocharbutton.h @@ -1,9 +1,8 @@ #ifndef AOCHARBUTTON_H #define AOCHARBUTTON_H -#include "aoimagedisplay.h" - class AOApplication; +class AOImageDisplay; #include diff --git a/include/aocharmovie.h b/include/aocharmovie.h index 1a600cff6..52f18b5e4 100644 --- a/include/aocharmovie.h +++ b/include/aocharmovie.h @@ -1,8 +1,6 @@ #ifndef AOCHARMOVIE_H #define AOCHARMOVIE_H -#include "aopixmap.h" - class AOApplication; #include diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 31afaa82f..d8c9bf21b 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -1,23 +1,22 @@ #ifndef AOCONFIGPANEL_H #define AOCONFIGPANEL_H -#include "aoconfig.h" -#include "aoguiloader.h" #include "draudioengine.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +class AOApplication; +class AOConfig; + #include -class AOApplication; +class QCheckBox; +class QComboBox; +class QGroupBox; +class QLabel; +class QLineEdit; +class QPushButton; +class QRadioButton; +class QSlider; +class QSpinBox; class AOConfigPanel : public QWidget { diff --git a/include/aoemotebutton.h b/include/aoemotebutton.h index 864ec2749..8d97dbaa5 100644 --- a/include/aoemotebutton.h +++ b/include/aoemotebutton.h @@ -2,13 +2,15 @@ #define AOEMOTEBUTTON_H // src -#include "aoapplication.h" #include "datatypes.h" +class AOApplication; + // qt -#include #include +class QLabel; + class AOEmoteButton : public QPushButton { Q_OBJECT diff --git a/include/aoevidencebutton.h b/include/aoevidencebutton.h index 6e9aa770c..c63b061bc 100644 --- a/include/aoevidencebutton.h +++ b/include/aoevidencebutton.h @@ -1,11 +1,10 @@ #ifndef AOEVIDENCEBUTTON_H #define AOEVIDENCEBUTTON_H -#include "aoapplication.h" -#include "aoimagedisplay.h" +class AOApplication; +class AOImageDisplay; #include -#include class AOEvidenceButton : public QPushButton { diff --git a/include/aoevidencedisplay.h b/include/aoevidencedisplay.h index 448bd3d3f..9a7a51f17 100644 --- a/include/aoevidencedisplay.h +++ b/include/aoevidencedisplay.h @@ -1,12 +1,10 @@ #ifndef AOEVIDENCEDISPLAY_H #define AOEVIDENCEDISPLAY_H -#include -#include +class AOApplication; +class AOSfxPlayer; -#include "aoapplication.h" -#include "aopixmap.h" -#include "aosfxplayer.h" +#include class AOEvidenceDisplay : public QLabel { diff --git a/include/aoguiloader.h b/include/aoguiloader.h index d0e35a985..af6b6b567 100644 --- a/include/aoguiloader.h +++ b/include/aoguiloader.h @@ -3,7 +3,6 @@ // qt #include -#include // we could make a smarter system but let's keep it simple ;) #define AO_GUI_WIDGET(p_type, p_name) findChild(p_name) diff --git a/include/aoimagedisplay.h b/include/aoimagedisplay.h index 0d2c13082..7ef522026 100644 --- a/include/aoimagedisplay.h +++ b/include/aoimagedisplay.h @@ -1,8 +1,7 @@ #ifndef AOIMAGEDISPLAY_H #define AOIMAGEDISPLAY_H -#include "aoapplication.h" -#include "aopixmap.h" +class AOApplication; #include diff --git a/include/aolabel.h b/include/aolabel.h index 02256977b..ea40f6ce6 100644 --- a/include/aolabel.h +++ b/include/aolabel.h @@ -1,7 +1,7 @@ #ifndef AOLABEL_HPP #define AOLABEL_HPP -#include "aoapplication.h" +class AOApplication; #include diff --git a/include/aolineedit.h b/include/aolineedit.h index 52e1d7966..4473577d9 100644 --- a/include/aolineedit.h +++ b/include/aolineedit.h @@ -2,7 +2,6 @@ #define AOLINEEDIT_H #include -#include class AOLineEdit : public QLineEdit { diff --git a/include/aomovie.h b/include/aomovie.h index 9a8ad13a2..e378da053 100644 --- a/include/aomovie.h +++ b/include/aomovie.h @@ -1,12 +1,11 @@ #ifndef AOMOVIE_H #define AOMOVIE_H +class AOApplication; + #include #include -class Courtroom; -class AOApplication; - class AOMovie : public QLabel { Q_OBJECT diff --git a/include/aonotepad.h b/include/aonotepad.h index 6684dcbe1..0ec099f81 100644 --- a/include/aonotepad.h +++ b/include/aonotepad.h @@ -1,9 +1,10 @@ #ifndef AONOTEPAD_H #define AONOTEPAD_H -#include "aoapplication.h" #include "drtextedit.h" +class AOApplication; + class AONotepad : public DRTextEdit { Q_OBJECT diff --git a/include/aonotepicker.h b/include/aonotepicker.h index 80dd8e56a..cb7ca120e 100644 --- a/include/aonotepicker.h +++ b/include/aonotepicker.h @@ -1,10 +1,13 @@ #ifndef AONOTEPICKER_HPP #define AONOTEPICKER_HPP -#include "aobutton.h" -#include +class AOApplication; +class AOButton; + #include -#include + +class QHBoxLayout; +class QLineEdit; class AONotePicker : public QLabel { diff --git a/include/draudiodevice.h b/include/draudiodevice.h index aa49ad75a..2cd78965b 100644 --- a/include/draudiodevice.h +++ b/include/draudiodevice.h @@ -3,6 +3,7 @@ #include #include +#include #include diff --git a/include/draudioengine_p.h b/include/draudioengine_p.h index d27c6c087..7eb8f394d 100644 --- a/include/draudioengine_p.h +++ b/include/draudioengine_p.h @@ -6,7 +6,8 @@ #include #include #include -#include + +class QTimer; #include diff --git a/include/draudiostream.h b/include/draudiostream.h index 43a872376..942236a42 100644 --- a/include/draudiostream.h +++ b/include/draudiostream.h @@ -9,7 +9,6 @@ #include #include -#include #include class DRAudioEngine; diff --git a/include/draudiostreamfamily.h b/include/draudiostreamfamily.h index e228d481e..bbf6bbab3 100644 --- a/include/draudiostreamfamily.h +++ b/include/draudiostreamfamily.h @@ -6,7 +6,6 @@ #include #include -#include #include class DRAudioEngine; diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index aff01b91e..131b149bc 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -9,6 +9,8 @@ #include "lobby.h" #include "networkmanager.h" +#include + #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) #include #else diff --git a/src/aocharbutton.cpp b/src/aocharbutton.cpp index 74dbdf231..15eba7a03 100644 --- a/src/aocharbutton.cpp +++ b/src/aocharbutton.cpp @@ -1,5 +1,7 @@ #include "aocharbutton.h" +#include "aoapplication.h" +#include "aoimagedisplay.h" #include "file_functions.h" #include diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index e33ebdb11..d1c7f31c9 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -1,6 +1,7 @@ #include "aocharmovie.h" #include "aoapplication.h" +#include "aopixmap.h" #include "file_functions.h" #include diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 30ae658e6..d286820e3 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -1,13 +1,23 @@ #include "aoconfigpanel.h" + +#include "aoapplication.h" +#include "aoconfig.h" +#include "aoguiloader.h" #include "datatypes.h" #include "drpather.h" -// qt +#include +#include #include #include - -// src -#include "aoapplication.h" // ruined +#include +#include +#include +#include +#include +#include +#include +#include AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) : QWidget(p_parent), m_config(new AOConfig(this)), m_engine(new DRAudioEngine(this)) diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index 9c6c4a19f..2acdabc88 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -1,7 +1,9 @@ #include "aoemotebutton.h" +#include "aoapplication.h" #include "file_functions.h" -#include + +#include AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) { diff --git a/src/aoevidencebutton.cpp b/src/aoevidencebutton.cpp index f2698ab7f..8b7fd8c09 100644 --- a/src/aoevidencebutton.cpp +++ b/src/aoevidencebutton.cpp @@ -1,5 +1,7 @@ #include "aoevidencebutton.h" +#include "aoapplication.h" +#include "aoimagedisplay.h" #include "file_functions.h" #include diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index 70dfc2ba6..f68617f5b 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -1,11 +1,14 @@ -#include - #include "aoevidencedisplay.h" +#include "aoapplication.h" +#include "aopixmap.h" +#include "aosfxplayer.h" #include "datatypes.h" #include "file_functions.h" #include "misc_functions.h" +#include + AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) { ao_app = p_ao_app; diff --git a/src/aoguiloader.cpp b/src/aoguiloader.cpp index 1db9c5b3d..dd5f8085d 100644 --- a/src/aoguiloader.cpp +++ b/src/aoguiloader.cpp @@ -1,8 +1,8 @@ #include "aoguiloader.h" -// qt #include #include +#include AOGuiLoader::AOGuiLoader(QObject *p_parent) : QUiLoader(p_parent) { diff --git a/src/aoimagedisplay.cpp b/src/aoimagedisplay.cpp index 6d6c2dd8c..56f0f14c6 100644 --- a/src/aoimagedisplay.cpp +++ b/src/aoimagedisplay.cpp @@ -1,8 +1,8 @@ -#include "file_functions.h" - #include "aoimagedisplay.h" -#include +#include "aoapplication.h" +#include "aopixmap.h" +#include "file_functions.h" /*! * @class AOImageDisplay diff --git a/src/aolabel.cpp b/src/aolabel.cpp index 06d098fc0..bf488d2da 100644 --- a/src/aolabel.cpp +++ b/src/aolabel.cpp @@ -1,6 +1,6 @@ #include "aolabel.h" -#include "file_functions.h" +#include "aoapplication.h" AOLabel::AOLabel(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) { diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 9d4346815..53f1ae485 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -4,6 +4,8 @@ #include "file_functions.h" #include "misc_functions.h" +#include + AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) { ao_app = p_ao_app; diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp index d901513c3..b86cc507a 100644 --- a/src/aonotearea.cpp +++ b/src/aonotearea.cpp @@ -1,5 +1,6 @@ #include "aonotearea.h" +#include "aoapplication.h" #include "aobutton.h" #include "aonotepicker.h" #include "courtroom.h" @@ -7,6 +8,7 @@ #include #include #include +#include #include #include diff --git a/src/aonotepicker.cpp b/src/aonotepicker.cpp index 7c10bed01..16da22e1f 100644 --- a/src/aonotepicker.cpp +++ b/src/aonotepicker.cpp @@ -1,11 +1,14 @@ #include "aonotepicker.h" +#include "aobutton.h" #include "aonotearea.h" #include "courtroom.h" #include "debug_functions.h" #include "drpather.h" #include +#include +#include AONotePicker::AONotePicker(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) { diff --git a/src/charselect.cpp b/src/charselect.cpp index e70154232..09c9b91a0 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -1,5 +1,6 @@ #include "courtroom.h" +#include "aoapplication.h" #include "aobutton.h" #include "aocharbutton.h" #include "aoimagedisplay.h" diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 845c54c95..cca899f76 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1,5 +1,6 @@ #include "courtroom.h" +#include "aoapplication.h" #include "aoblipplayer.h" #include "aobutton.h" #include "aocharmovie.h" @@ -23,6 +24,7 @@ #include #include +#include #include #include #include diff --git a/src/draudio.cpp b/src/draudio.cpp index a2f5cc677..bf968c599 100644 --- a/src/draudio.cpp +++ b/src/draudio.cpp @@ -2,6 +2,8 @@ #include +#include + QString DRAudio::get_bass_error(const int32_t p_error_code) { switch (p_error_code) diff --git a/src/draudioengine.cpp b/src/draudioengine.cpp index d721fd0f7..882f9b5fd 100644 --- a/src/draudioengine.cpp +++ b/src/draudioengine.cpp @@ -2,9 +2,7 @@ #include "draudioengine_p.h" -#include -#include -#include +#include static const int EVENT_TIMER_INTERVAL_DEFAULT = 100; diff --git a/src/draudioengine_p.cpp b/src/draudioengine_p.cpp index fccc068de..8a7742d09 100644 --- a/src/draudioengine_p.cpp +++ b/src/draudioengine_p.cpp @@ -11,6 +11,7 @@ #include #include +#include static const int EVENT_TIMER_INTERVAL_DEFAULT = 100; diff --git a/src/evidence.cpp b/src/evidence.cpp index 92a0b7c16..0f182fd56 100644 --- a/src/evidence.cpp +++ b/src/evidence.cpp @@ -1,5 +1,6 @@ #include "courtroom.h" +#include "aoapplication.h" #include "aobutton.h" #include "aoevidencebutton.h" #include "aoevidencedescription.h" diff --git a/src/main.cpp b/src/main.cpp index e2bb33d90..110014de9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,7 +22,7 @@ int main(int argc, char *argv[]) app.construct_lobby(); #ifdef QT_NO_DEBUG - app.net_manager->connect_to_master(); + app.get_network_manager()->connect_to_master(); #endif app.get_lobby()->show(); From d3b36e5f85f929d116bf5e31acfdf86a78db6bd2 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 15:26:34 +0200 Subject: [PATCH 391/842] Revert change from debugging session --- src/theme.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/theme.cpp b/src/theme.cpp index b5558bde7..f4e656d13 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -29,13 +29,13 @@ void set_text_alignment(QWidget *p_widget, QString p_identifier, QString p_ini_f { const QStringList l_values = ao_app->read_theme_ini(p_identifier + "_align", p_ini_file).split(",", DR::SkipEmptyParts); - p_widget->setProperty( - "alignment", QVariant(QHash{ - {"left", Qt::AlignHCenter}, {"center", Qt::AlignHCenter}, {"right", Qt::AlignRight}} - .value(l_values.value(0).toLower(), Qt::AlignLeft) | - QHash{ - {"top", Qt::AlignTop}, {"center", Qt::AlignVCenter}, {"bottom", Qt::AlignBottom}} - .value(l_values.value(1).toLower(), Qt::AlignVCenter))); + p_widget->setProperty("alignment", + QVariant(QHash{ + {"left", Qt::AlignLeft}, {"center", Qt::AlignHCenter}, {"right", Qt::AlignRight}} + .value(l_values.value(0).toLower(), Qt::AlignLeft) | + QHash{ + {"top", Qt::AlignTop}, {"center", Qt::AlignVCenter}, {"bottom", Qt::AlignBottom}} + .value(l_values.value(1).toLower(), Qt::AlignVCenter))); } void set_font(QWidget *p_widget, QString p_identifier, QString ini_file, AOApplication *ao_app) From 25b94262f4610faf413df4a393c9876c61da8c94 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 15:48:06 +0200 Subject: [PATCH 392/842] Trim space away from string value --- src/theme.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/theme.cpp b/src/theme.cpp index f4e656d13..037f2be6b 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -32,10 +32,10 @@ void set_text_alignment(QWidget *p_widget, QString p_identifier, QString p_ini_f p_widget->setProperty("alignment", QVariant(QHash{ {"left", Qt::AlignLeft}, {"center", Qt::AlignHCenter}, {"right", Qt::AlignRight}} - .value(l_values.value(0).toLower(), Qt::AlignLeft) | + .value(l_values.value(0).trimmed().toLower(), Qt::AlignLeft) | QHash{ {"top", Qt::AlignTop}, {"center", Qt::AlignVCenter}, {"bottom", Qt::AlignBottom}} - .value(l_values.value(1).toLower(), Qt::AlignVCenter))); + .value(l_values.value(1).trimmed().toLower(), Qt::AlignVCenter))); } void set_font(QWidget *p_widget, QString p_identifier, QString ini_file, AOApplication *ao_app) From a8d514df5aa74df792e4eabdf11fc9e0ce43a7b8 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 16:14:03 +0200 Subject: [PATCH 393/842] Solved extra merge conflicts --- include/lobby.h | 2 -- src/courtroom_widgets.cpp | 1 + src/evidence.cpp | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/lobby.h b/include/lobby.h index ea3269022..f4b53f391 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -72,8 +72,6 @@ class Lobby : public QMainWindow QProgressBar *ui_progress_bar = nullptr; AOButton *ui_cancel = nullptr; - server_type f_last_server; - private slots: void on_public_servers_clicked(); void on_favorites_clicked(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 14d30a386..c7b4ffbf8 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -21,6 +21,7 @@ #include "aotimer.h" #include "drtextedit.h" #include "file_functions.h" +#include "theme.h" #include #include diff --git a/src/evidence.cpp b/src/evidence.cpp index 35f5de25e..a99495eee 100644 --- a/src/evidence.cpp +++ b/src/evidence.cpp @@ -7,6 +7,7 @@ #include "aoimagedisplay.h" #include "aolineedit.h" #include "aopacket.h" +#include "theme.h" #include From ec7df95f5e2a93ba5e1a30619a77758824772692 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 18:09:15 +0200 Subject: [PATCH 394/842] Reorganized and refactored almost everything Does not include application, courtroom, and lobby --- include/aoapplication.h | 6 +- include/aobutton.h | 2 +- include/aocharbutton.h | 1 + include/aocharmovie.h | 8 +- include/aoconfigpanel.h | 90 ++++---- include/aoemotebutton.h | 6 +- include/aoevidencebutton.h | 4 +- include/aoevidencedisplay.h | 4 +- include/aoimagedisplay.h | 3 +- include/aomovie.h | 6 +- include/aonotepicker.h | 13 +- include/aoscene.h | 1 + include/aotextarea.h | 2 +- include/drdiscord.h | 10 +- include/drtextedit.h | 12 +- include/lobby.h | 4 +- include/networkmanager.h | 8 +- src/aoapplication.cpp | 4 +- src/aobutton.cpp | 22 +- src/aocharbutton.cpp | 23 +- src/aocharmovie.cpp | 12 +- src/aoconfigpanel.cpp | 416 +++++++++++++++++----------------- src/aoemotebutton.cpp | 42 ++-- src/aoevidencebutton.cpp | 10 +- src/aoevidencedescription.cpp | 2 - src/aoevidencedisplay.cpp | 33 ++- src/aoguiloader.cpp | 12 +- src/aoimagedisplay.cpp | 16 +- src/aolabel.cpp | 4 +- src/aolineedit.cpp | 5 +- src/aomovie.cpp | 5 +- src/aonotearea.cpp | 14 +- src/aonotepicker.cpp | 18 +- src/aopacket.cpp | 42 ++-- src/aopixmap.cpp | 11 +- src/aotextarea.cpp | 6 +- src/charselect.cpp | 2 +- src/courtroom.cpp | 14 +- src/courtroom_widgets.cpp | 16 +- src/drtextedit.cpp | 31 +-- src/emotes.cpp | 4 +- src/evidence.cpp | 4 +- src/lobby.cpp | 36 ++- src/networkmanager.cpp | 17 +- src/packet_distribution.cpp | 4 +- src/path_functions.cpp | 20 +- 46 files changed, 496 insertions(+), 529 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 6ff462e1e..2419caf9b 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -72,8 +72,10 @@ class AOApplication : public QApplication QString sanitize_path(QString p_file); - QString find_asset_path(QStringList possible_roots, QStringList possible_exts = {""}); - QString find_theme_asset_path(QString p_root, QStringList p_exts = {""}); + QString find_asset_path(QStringList file_list, QStringList extension_list); + QString find_asset_path(QStringList file_list); + QString find_theme_asset_path(QString file, QStringList extension_list); + QString find_theme_asset_path(QString file); QString get_case_sensitive_path(QString p_file); diff --git a/include/aobutton.h b/include/aobutton.h index 41fd22eb3..9c2daa093 100644 --- a/include/aobutton.h +++ b/include/aobutton.h @@ -17,7 +17,7 @@ class AOButton : public QPushButton private: AOApplication *ao_app = nullptr; - QString image_path; + QString m_image; }; #endif // AOBUTTON_H diff --git a/include/aocharbutton.h b/include/aocharbutton.h index b045f57a5..b3ac2e19c 100644 --- a/include/aocharbutton.h +++ b/include/aocharbutton.h @@ -27,6 +27,7 @@ class AOCharButton : public QPushButton private: AOApplication *ao_app = nullptr; + AOImageDisplay *ui_taken = nullptr; }; diff --git a/include/aocharmovie.h b/include/aocharmovie.h index 52f18b5e4..d5552bac8 100644 --- a/include/aocharmovie.h +++ b/include/aocharmovie.h @@ -20,7 +20,7 @@ class AOCharMovie : public QLabel bool play_pre(QString character, QString emote); bool play_talk(QString character, QString emote); bool play_idle(QString character, QString emote); - void set_mirror_enabled(bool enabled); + void set_mirrored(bool enabled); void combo_resize(QSize size); void stop(); @@ -29,11 +29,13 @@ class AOCharMovie : public QLabel private: AOApplication *ao_app = nullptr; + QMovie *m_movie = nullptr; QTimer *m_frame_timer = nullptr; QImage m_current_frame; - bool m_mirror = false; - bool m_play_once = false; + + bool is_mirrored = false; + bool is_play_once = false; void paint_frame(); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index d8c9bf21b..94c04d784 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -63,55 +63,55 @@ private slots: AOConfig *m_config = nullptr; DRAudioEngine *m_engine = nullptr; // behaviour - QPushButton *w_save = nullptr; - QPushButton *w_close = nullptr; - QCheckBox *w_autosave = nullptr; + QPushButton *ui_save = nullptr; + QPushButton *ui_close = nullptr; + QCheckBox *ui_autosave = nullptr; // general - QLineEdit *w_username = nullptr; - QLineEdit *w_callwords = nullptr; - QCheckBox *w_server_alerts = nullptr; - QGroupBox *w_discord_presence = nullptr; - QCheckBox *w_discord_hide_server = nullptr; - QCheckBox *w_discord_hide_character = nullptr; + QLineEdit *ui_username = nullptr; + QLineEdit *ui_callwords = nullptr; + QCheckBox *ui_server_alerts = nullptr; + QGroupBox *ui_discord_presence = nullptr; + QCheckBox *ui_discord_hide_server = nullptr; + QCheckBox *ui_discord_hide_character = nullptr; // game - QComboBox *w_theme = nullptr; - QPushButton *w_reload_theme = nullptr; - QComboBox *w_gamemode = nullptr; - QCheckBox *w_manual_gamemode = nullptr; - QComboBox *w_timeofday = nullptr; - QCheckBox *w_manual_timeofday = nullptr; - QLineEdit *w_showname = nullptr; - QCheckBox *w_always_pre = nullptr; - QSpinBox *w_chat_tick_interval = nullptr; + QComboBox *ui_theme = nullptr; + QPushButton *ui_reload_theme = nullptr; + QComboBox *ui_gamemode = nullptr; + QCheckBox *ui_manual_gamemode = nullptr; + QComboBox *ui_timeofday = nullptr; + QCheckBox *ui_manual_timeofday = nullptr; + QLineEdit *ui_showname = nullptr; + QCheckBox *ui_always_pre = nullptr; + QSpinBox *ui_chat_tick_interval = nullptr; // IC Chatlog - QSpinBox *w_log_max_lines = nullptr; - QCheckBox *w_log_display_timestamp = nullptr; - QCheckBox *w_log_display_self_highlight = nullptr; - QCheckBox *w_log_format_use_newline = nullptr; - QCheckBox *w_log_display_empty_messages = nullptr; - QCheckBox *w_log_display_music_switch = nullptr; - QRadioButton *w_log_orientation_top_down = nullptr; - QRadioButton *w_log_orientation_bottom_up = nullptr; - QCheckBox *w_log_is_recording = nullptr; + QSpinBox *ui_log_max_lines = nullptr; + QCheckBox *ui_log_display_timestamp = nullptr; + QCheckBox *ui_log_display_self_highlight = nullptr; + QCheckBox *ui_log_format_use_newline = nullptr; + QCheckBox *ui_log_display_empty_messages = nullptr; + QCheckBox *ui_log_display_music_switch = nullptr; + QRadioButton *ui_log_orientation_top_down = nullptr; + QRadioButton *ui_log_orientation_bottom_up = nullptr; + QCheckBox *ui_log_is_recording = nullptr; // audio - QComboBox *w_device = nullptr; - QCheckBox *w_favorite_device = nullptr; - QSlider *w_master = nullptr; - QLabel *w_master_value = nullptr; - QGroupBox *w_suppress_background_audio = nullptr; - QSlider *w_system = nullptr; - QLabel *w_system_value = nullptr; - QSlider *w_effect = nullptr; - QCheckBox *w_effect_ignore_suppression = nullptr; - QLabel *w_effect_value = nullptr; - QSlider *w_music = nullptr; - QCheckBox *w_music_ignore_suppression = nullptr; - QLabel *w_music_value = nullptr; - QSlider *w_blip = nullptr; - QCheckBox *w_blip_ignore_suppression = nullptr; - QLabel *w_blip_value = nullptr; - QSpinBox *w_blip_rate = nullptr; - QCheckBox *w_blank_blips = nullptr; + QComboBox *ui_device = nullptr; + QCheckBox *ui_favorite_device = nullptr; + QSlider *ui_master = nullptr; + QLabel *ui_master_value = nullptr; + QGroupBox *ui_suppress_background_audio = nullptr; + QSlider *ui_system = nullptr; + QLabel *ui_system_value = nullptr; + QSlider *ui_effect = nullptr; + QCheckBox *ui_effect_ignore_suppression = nullptr; + QLabel *ui_effect_value = nullptr; + QSlider *ui_music = nullptr; + QCheckBox *ui_music_ignore_suppression = nullptr; + QLabel *ui_music_value = nullptr; + QSlider *ui_blip = nullptr; + QCheckBox *ui_blip_ignore_suppression = nullptr; + QLabel *ui_blip_value = nullptr; + QSpinBox *ui_blip_rate = nullptr; + QCheckBox *ui_blank_blips = nullptr; private slots: void username_editing_finished(); diff --git a/include/aoemotebutton.h b/include/aoemotebutton.h index 8d97dbaa5..cfb576cf8 100644 --- a/include/aoemotebutton.h +++ b/include/aoemotebutton.h @@ -27,8 +27,10 @@ class AOEmoteButton : public QPushButton private: AOApplication *ao_app = nullptr; - QLabel *w_selected = nullptr; - int m_emote_number = 0; + + int m_index = 0; + + QLabel *ui_selected = nullptr; private slots: void on_clicked(); diff --git a/include/aoevidencebutton.h b/include/aoevidencebutton.h index c63b061bc..b50954097 100644 --- a/include/aoevidencebutton.h +++ b/include/aoevidencebutton.h @@ -33,11 +33,11 @@ class AOEvidenceButton : public QPushButton private: AOApplication *ao_app = nullptr; + int m_index = 0; + AOImageDisplay *ui_selected = nullptr; AOImageDisplay *ui_selector = nullptr; - int m_id = 0; - private slots: void on_clicked(); }; diff --git a/include/aoevidencedisplay.h b/include/aoevidencedisplay.h index 9a7a51f17..4813219c3 100644 --- a/include/aoevidencedisplay.h +++ b/include/aoevidencedisplay.h @@ -19,8 +19,10 @@ class AOEvidenceDisplay : public QLabel private: AOApplication *ao_app = nullptr; AOSfxPlayer *dr_sfx = nullptr; + QMovie *m_movie = nullptr; - QLabel *w_icon = nullptr; + + QLabel *ui_icon = nullptr; private slots: void frame_change(int p_frame); diff --git a/include/aoimagedisplay.h b/include/aoimagedisplay.h index 7ef522026..12c5e0fad 100644 --- a/include/aoimagedisplay.h +++ b/include/aoimagedisplay.h @@ -16,7 +16,8 @@ class AOImageDisplay : public QLabel private: AOApplication *ao_app = nullptr; - QString image_path; + + QString m_image; }; #endif // AOIMAGEDISPLAY_H diff --git a/include/aomovie.h b/include/aomovie.h index e378da053..8c49d86e4 100644 --- a/include/aomovie.h +++ b/include/aomovie.h @@ -25,9 +25,11 @@ class AOMovie : public QLabel void done(); private: - QMovie *m_movie = nullptr; AOApplication *ao_app = nullptr; - bool play_once = true; + + QMovie *m_movie = nullptr; + + bool is_play_once = true; private slots: void frame_change(int n_frame); diff --git a/include/aonotepicker.h b/include/aonotepicker.h index cb7ca120e..64f4ac169 100644 --- a/include/aonotepicker.h +++ b/include/aonotepicker.h @@ -16,12 +16,13 @@ class AONotePicker : public QLabel public: AONotePicker(QWidget *p_parent, AOApplication *p_ao_app); - QLineEdit *m_line = nullptr; - AOButton *m_button = nullptr; - AOButton *m_delete_button = nullptr; - AOButton *m_hover = nullptr; - QHBoxLayout *m_layout = nullptr; - QString real_file; + QString m_file; + + QLineEdit *ui_line = nullptr; + AOButton *ui_button = nullptr; + AOButton *ui_delete_button = nullptr; + AOButton *ui_hover = nullptr; + QHBoxLayout *ui_layout = nullptr; private: AOApplication *ao_app = nullptr; diff --git a/include/aoscene.h b/include/aoscene.h index 9e69f477a..25ef697cb 100644 --- a/include/aoscene.h +++ b/include/aoscene.h @@ -18,6 +18,7 @@ class AOScene : public QLabel private: AOApplication *ao_app = nullptr; + QMovie *m_reader = nullptr; }; diff --git a/include/aotextarea.h b/include/aotextarea.h index 033e8b03b..ed675c2c7 100644 --- a/include/aotextarea.h +++ b/include/aotextarea.h @@ -12,7 +12,7 @@ class AOTextArea : public QTextBrowser void append_error(QString p_message); private: - static const QRegExp omnis_dank_url_regex; + static const QRegExp URL_REGEXP; void auto_scroll(QTextCursor old_cursor, int scrollbar_value, bool is_scrolled_down); }; diff --git a/include/drdiscord.h b/include/drdiscord.h index c73caa620..29e591a1e 100644 --- a/include/drdiscord.h +++ b/include/drdiscord.h @@ -43,12 +43,12 @@ class DRDiscord : public QObject bool is_connected() const; public slots: - void set_options(const Options &f_options); - void set_option(const Option &f_option, const bool p_enabled); + void set_options(const DRDiscord::Options &f_options); + void set_option(const DRDiscord::Option &f_option, const bool p_enabled); void set_presence(const bool p_enabled); void set_hide_server(const bool p_enabled); void set_hide_character(const bool p_enabled); - void set_state(const State f_state); + void set_state(const DRDiscord::State f_state); void set_server_name(const QString &f_server_name); void clear_server_name(); void set_character_name(const QString &f_character_name); @@ -58,8 +58,8 @@ public slots: void stop(); signals: - void options_changed(Options); - void state_changed(State); + void options_changed(DRDiscord::Options); + void state_changed(DRDiscord::State); void server_name_changed(QString); void server_name_cleared(); void character_name_changed(QString); diff --git a/include/drtextedit.h b/include/drtextedit.h index e58e3184a..4566fae7a 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -10,29 +10,25 @@ class DRTextEdit : public QTextEdit public: DRTextEdit(QWidget *p_parent); - bool get_outline(); - bool get_auto_align(); Qt::Alignment get_vertical_alignment(); Qt::Alignment get_horizontal_alignment(); - void set_outline(bool p_outline); - void set_auto_align(bool new_auto_align); + void set_outline(bool enabled); + void set_auto_align(bool enabled); void set_vertical_alignment(Qt::Alignment p_align); void set_horizontal_alignment(Qt::Alignment p_align); private: - bool m_outline = false; Qt::Alignment m_valign = Qt::AlignTop; Qt::Alignment m_halign = Qt::AlignLeft; - + bool has_outline = false; + bool is_auto_align = true; enum class Status { Done, InProgress, }; Status m_status = Status::Done; - bool m_auto_align = true; - int m_current_document_blocks = 0; int m_current_document_height = 0; diff --git a/include/lobby.h b/include/lobby.h index f4b53f391..e0584d8fc 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -49,8 +49,8 @@ class Lobby : public QMainWindow AOApplication *ao_app = nullptr; AOConfig *ao_config = nullptr; - server_type f_last_server; - bool public_servers_selected = true; + server_type m_last_server; + bool is_public_server_selected = true; // ui AOImageDisplay *ui_background = nullptr; diff --git a/include/networkmanager.h b/include/networkmanager.h index 52171598b..ad1cfeb5c 100644 --- a/include/networkmanager.h +++ b/include/networkmanager.h @@ -16,11 +16,9 @@ class NetworkManager : public QObject Q_OBJECT public: - static const QString ms_srv_hostname; - static const QString ms_nosrv_hostname; - static const int ms_port; - static const int timeout_milliseconds; - static const int ms_reconnect_delay_ms; + static const QString MASTER_HOST; + static const int MASTER_PORT; + static const int MASTER_RECONNECT_DELAY; NetworkManager(AOApplication *parent); ~NetworkManager(); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 131b149bc..dde212fa1 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -20,7 +20,7 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) { net_manager = new NetworkManager(this); - connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), SLOT(ms_connect_finished(bool, bool))); + connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), this, SLOT(ms_connect_finished(bool, bool))); ao_config = new AOConfig(this); connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(on_config_theme_changed())); @@ -319,7 +319,7 @@ void AOApplication::ms_connect_finished(bool connected, bool will_retry) else if (will_retry) { m_lobby->append_error("Error connecting to master server. Will try again in " + - QString::number(net_manager->ms_reconnect_delay_ms / 1000.f) + " seconds."); + QString::number(net_manager->MASTER_RECONNECT_DELAY / 1000.f) + " seconds."); } else { diff --git a/src/aobutton.cpp b/src/aobutton.cpp index f92328c98..5f8e74b0f 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -13,29 +13,29 @@ AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) : QPushButton(paren QString AOButton::get_image() { - return image_path; + return m_image; } void AOButton::set_image(QString p_image) { - image_path = ao_app->find_theme_asset_path(p_image); + m_image = ao_app->find_theme_asset_path(p_image); // Get the path of the found image without the extension - QString image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); - QString hover_image_path = ao_app->find_theme_asset_path(image_name + "_hover.png"); + const QString l_image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); + const QString l_hover_image = ao_app->find_theme_asset_path(l_image_name + "_hover.png"); - if (file_exists(image_path)) + if (file_exists(m_image)) { - if (file_exists(hover_image_path)) - this->setStyleSheet("QPushButton {border-image:url(\"" + image_path + + if (file_exists(l_hover_image)) + this->setStyleSheet("QPushButton {border-image:url(\"" + m_image + "\");}" "QPushButton:hover {border-image:url(\"" + - hover_image_path + "\");}"); + l_hover_image + "\");}"); else - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); + this->setStyleSheet("border-image:url(\"" + m_image + "\")"); } else { - image_path = ""; - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); + m_image = ""; + this->setStyleSheet("border-image:url(\"" + m_image + "\")"); } } diff --git a/src/aocharbutton.cpp b/src/aocharbutton.cpp index 15eba7a03..56d86daf7 100644 --- a/src/aocharbutton.cpp +++ b/src/aocharbutton.cpp @@ -32,25 +32,10 @@ void AOCharButton::set_taken() void AOCharButton::set_image(QString p_character) { - QString image_path = ao_app->get_character_path(p_character, "char_icon.png"); - // QString legacy_path = ao_app->get_demothings_path() + p_character.toLower() - // + "_char_icon.png"; QString alt_path = ao_app->get_demothings_path() + - // p_character.toLower() + "_off.png"; - - this->setText(""); - - if (file_exists(image_path)) - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - // else if (file_exists(legacy_path)) { - // this->setStyleSheet("border-image:url(\"" + legacy_path + "\")"); - // // ninja optimization - // QFile::copy(legacy_path, image_path); - //} - else - { - this->setStyleSheet("border-image:url()"); - this->setText(p_character); - } + const QString l_image = ao_app->get_character_path(p_character, "char_icon.png"); + const bool l_file_exist = file_exists(l_image); + setStyleSheet(l_file_exist ? QString("border-image: url(\"%1);").arg(l_image) : nullptr); + setText(l_file_exist ? nullptr : p_character); } void AOCharButton::enterEvent(QEvent *e) diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index d1c7f31c9..1beda81aa 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -52,7 +52,7 @@ bool AOCharMovie::play(QString p_chr, QString p_emote, QString p_prefix, bool p_ stop(); m_movie->setFileName(l_file); - m_play_once = p_play_once; + is_play_once = p_play_once; m_movie->start(); return r_exist; @@ -78,9 +78,9 @@ bool AOCharMovie::play_idle(QString p_chr, QString p_emote) return play(p_chr, p_emote, "(a)", false); } -void AOCharMovie::set_mirror_enabled(bool p_enabled) +void AOCharMovie::set_mirrored(bool p_enabled) { - m_mirror = p_enabled; + is_mirrored = p_enabled; } void AOCharMovie::stop() @@ -101,8 +101,8 @@ void AOCharMovie::combo_resize(QSize p_size) void AOCharMovie::paint_frame() { - AOPixmap f_pixmap(QPixmap::fromImage(m_movie->currentImage().mirrored(m_mirror, false))); - this->setPixmap(f_pixmap.scale_to_height(this->size())); + AOPixmap l_pixmap(QPixmap::fromImage(m_movie->currentImage().mirrored(is_mirrored, false))); + this->setPixmap(l_pixmap.scale_to_height(this->size())); } void AOCharMovie::on_frame_changed(int p_frame_num) @@ -111,7 +111,7 @@ void AOCharMovie::on_frame_changed(int p_frame_num) paint_frame(); - if (m_play_once) + if (is_play_once) { const int f_frame_count = m_movie->frameCount(); if (f_frame_count == 0 || p_frame_num == (f_frame_count - 1)) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index d286820e3..4e6092ac5 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -36,59 +36,59 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) tab_widget->setCurrentIndex(0); // behaviour - w_save = AO_GUI_WIDGET(QPushButton, "save"); - w_close = AO_GUI_WIDGET(QPushButton, "close"); - w_autosave = AO_GUI_WIDGET(QCheckBox, "autosave"); + ui_save = AO_GUI_WIDGET(QPushButton, "save"); + ui_close = AO_GUI_WIDGET(QPushButton, "close"); + ui_autosave = AO_GUI_WIDGET(QCheckBox, "autosave"); // general - w_username = AO_GUI_WIDGET(QLineEdit, "username"); - w_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); - w_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); - w_discord_presence = AO_GUI_WIDGET(QGroupBox, "discord_presence"); - w_discord_hide_server = AO_GUI_WIDGET(QCheckBox, "discord_hide_server"); - w_discord_hide_character = AO_GUI_WIDGET(QCheckBox, "discord_hide_character"); + ui_username = AO_GUI_WIDGET(QLineEdit, "username"); + ui_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); + ui_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); + ui_discord_presence = AO_GUI_WIDGET(QGroupBox, "discord_presence"); + ui_discord_hide_server = AO_GUI_WIDGET(QCheckBox, "discord_hide_server"); + ui_discord_hide_character = AO_GUI_WIDGET(QCheckBox, "discord_hide_character"); // game - w_theme = AO_GUI_WIDGET(QComboBox, "theme"); - w_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); - w_gamemode = AO_GUI_WIDGET(QComboBox, "gamemode"); - w_manual_gamemode = AO_GUI_WIDGET(QCheckBox, "manual_gamemode"); - w_timeofday = AO_GUI_WIDGET(QComboBox, "timeofday"); - w_manual_timeofday = AO_GUI_WIDGET(QCheckBox, "manual_timeofday"); - w_showname = AO_GUI_WIDGET(QLineEdit, "showname"); - w_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); - w_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); + ui_theme = AO_GUI_WIDGET(QComboBox, "theme"); + ui_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); + ui_gamemode = AO_GUI_WIDGET(QComboBox, "gamemode"); + ui_manual_gamemode = AO_GUI_WIDGET(QCheckBox, "manual_gamemode"); + ui_timeofday = AO_GUI_WIDGET(QComboBox, "timeofday"); + ui_manual_timeofday = AO_GUI_WIDGET(QCheckBox, "manual_timeofday"); + ui_showname = AO_GUI_WIDGET(QLineEdit, "showname"); + ui_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); + ui_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); // IC Chatlog - w_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); - w_log_display_timestamp = AO_GUI_WIDGET(QCheckBox, "log_display_timestamp"); - w_log_display_self_highlight = AO_GUI_WIDGET(QCheckBox, "log_display_self_highlight"); - w_log_format_use_newline = AO_GUI_WIDGET(QCheckBox, "log_format_use_newline"); - w_log_display_empty_messages = AO_GUI_WIDGET(QCheckBox, "log_display_empty_messages"); - w_log_display_music_switch = AO_GUI_WIDGET(QCheckBox, "log_display_music_switch"); - w_log_orientation_top_down = AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); - w_log_orientation_bottom_up = AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); - w_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); + ui_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); + ui_log_display_timestamp = AO_GUI_WIDGET(QCheckBox, "log_display_timestamp"); + ui_log_display_self_highlight = AO_GUI_WIDGET(QCheckBox, "log_display_self_highlight"); + ui_log_format_use_newline = AO_GUI_WIDGET(QCheckBox, "log_format_use_newline"); + ui_log_display_empty_messages = AO_GUI_WIDGET(QCheckBox, "log_display_empty_messages"); + ui_log_display_music_switch = AO_GUI_WIDGET(QCheckBox, "log_display_music_switch"); + ui_log_orientation_top_down = AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); + ui_log_orientation_bottom_up = AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); + ui_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); // audio - w_device = AO_GUI_WIDGET(QComboBox, "device"); - w_favorite_device = AO_GUI_WIDGET(QCheckBox, "favorite_device"); - w_master = AO_GUI_WIDGET(QSlider, "master"); - w_master_value = AO_GUI_WIDGET(QLabel, "master_value"); - w_suppress_background_audio = AO_GUI_WIDGET(QGroupBox, "suppress_background_audio"); - w_system = AO_GUI_WIDGET(QSlider, "system"); - w_system_value = AO_GUI_WIDGET(QLabel, "system_value"); - w_effect = AO_GUI_WIDGET(QSlider, "effect"); - w_effect_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "effect_ignore_suppression"); - w_effect_value = AO_GUI_WIDGET(QLabel, "effect_value"); - w_music = AO_GUI_WIDGET(QSlider, "music"); - w_music_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "music_ignore_suppression"); - w_music_value = AO_GUI_WIDGET(QLabel, "music_value"); - w_blip = AO_GUI_WIDGET(QSlider, "blip"); - w_blip_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "blip_ignore_suppression"); - w_blip_value = AO_GUI_WIDGET(QLabel, "blip_value"); - w_blip_rate = AO_GUI_WIDGET(QSpinBox, "blip_rate"); - w_blank_blips = AO_GUI_WIDGET(QCheckBox, "blank_blips"); + ui_device = AO_GUI_WIDGET(QComboBox, "device"); + ui_favorite_device = AO_GUI_WIDGET(QCheckBox, "favorite_device"); + ui_master = AO_GUI_WIDGET(QSlider, "master"); + ui_master_value = AO_GUI_WIDGET(QLabel, "master_value"); + ui_suppress_background_audio = AO_GUI_WIDGET(QGroupBox, "suppress_background_audio"); + ui_system = AO_GUI_WIDGET(QSlider, "system"); + ui_system_value = AO_GUI_WIDGET(QLabel, "system_value"); + ui_effect = AO_GUI_WIDGET(QSlider, "effect"); + ui_effect_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "effect_ignore_suppression"); + ui_effect_value = AO_GUI_WIDGET(QLabel, "effect_value"); + ui_music = AO_GUI_WIDGET(QSlider, "music"); + ui_music_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "music_ignore_suppression"); + ui_music_value = AO_GUI_WIDGET(QLabel, "music_value"); + ui_blip = AO_GUI_WIDGET(QSlider, "blip"); + ui_blip_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "blip_ignore_suppression"); + ui_blip_value = AO_GUI_WIDGET(QLabel, "blip_value"); + ui_blip_rate = AO_GUI_WIDGET(QSpinBox, "blip_rate"); + ui_blank_blips = AO_GUI_WIDGET(QCheckBox, "blank_blips"); // themes refresh_theme_list(); @@ -97,54 +97,54 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // input // meta - connect(m_config, SIGNAL(autosave_changed(bool)), w_autosave, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(autosave_changed(bool)), ui_autosave, SLOT(setChecked(bool))); // general - connect(m_config, SIGNAL(username_changed(QString)), w_username, SLOT(setText(QString))); - connect(m_config, SIGNAL(callwords_changed(QString)), w_callwords, SLOT(setText(QString))); - connect(m_config, SIGNAL(server_alerts_changed(bool)), w_server_alerts, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(discord_presence_changed(bool)), w_discord_presence, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(discord_hide_server_changed(bool)), w_discord_hide_server, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(discord_hide_character_changed(bool)), w_discord_hide_character, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(username_changed(QString)), ui_username, SLOT(setText(QString))); + connect(m_config, SIGNAL(callwords_changed(QString)), ui_callwords, SLOT(setText(QString))); + connect(m_config, SIGNAL(server_alerts_changed(bool)), ui_server_alerts, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(discord_presence_changed(bool)), ui_discord_presence, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(discord_hide_server_changed(bool)), ui_discord_hide_server, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(discord_hide_character_changed(bool)), ui_discord_hide_character, SLOT(setChecked(bool))); // game - connect(m_config, SIGNAL(theme_changed(QString)), w_theme, SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(gamemode_changed(QString)), w_gamemode, SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_manual_gamemode, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(timeofday_changed(QString)), w_timeofday, SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_manual_timeofday, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(showname_changed(QString)), w_showname, SLOT(setText(QString))); + connect(m_config, SIGNAL(theme_changed(QString)), ui_theme, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(gamemode_changed(QString)), ui_gamemode, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(manual_gamemode_changed(bool)), ui_manual_gamemode, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(timeofday_changed(QString)), ui_timeofday, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(manual_timeofday_changed(bool)), ui_manual_timeofday, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(showname_changed(QString)), ui_showname, SLOT(setText(QString))); connect(m_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); - connect(m_config, SIGNAL(always_pre_changed(bool)), w_always_pre, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(chat_tick_interval_changed(int)), w_chat_tick_interval, SLOT(setValue(int))); + connect(m_config, SIGNAL(always_pre_changed(bool)), ui_always_pre, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(chat_tick_interval_changed(int)), ui_chat_tick_interval, SLOT(setValue(int))); // log - connect(m_config, SIGNAL(log_max_lines_changed(int)), w_log_max_lines, SLOT(setValue(int))); - connect(m_config, SIGNAL(log_display_timestamp_changed(bool)), w_log_display_timestamp, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_display_self_highlight_changed(bool)), w_log_display_self_highlight, + connect(m_config, SIGNAL(log_max_lines_changed(int)), ui_log_max_lines, SLOT(setValue(int))); + connect(m_config, SIGNAL(log_display_timestamp_changed(bool)), ui_log_display_timestamp, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_self_highlight_changed(bool)), ui_log_display_self_highlight, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_format_use_newline_changed(bool)), w_log_format_use_newline, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_display_empty_messages_changed(bool)), w_log_display_empty_messages, + connect(m_config, SIGNAL(log_format_use_newline_changed(bool)), ui_log_format_use_newline, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_empty_messages_changed(bool)), ui_log_display_empty_messages, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_display_music_switch_changed(bool)), w_log_display_music_switch, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_music_switch_changed(bool)), ui_log_display_music_switch, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); - connect(m_config, SIGNAL(log_is_recording_changed(bool)), w_log_is_recording, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_is_recording_changed(bool)), ui_log_is_recording, SLOT(setChecked(bool))); // audio - connect(m_config, SIGNAL(master_volume_changed(int)), w_master, SLOT(setValue(int))); - connect(m_config, SIGNAL(suppress_background_audio_changed(bool)), w_suppress_background_audio, + connect(m_config, SIGNAL(master_volume_changed(int)), ui_master, SLOT(setValue(int))); + connect(m_config, SIGNAL(suppress_background_audio_changed(bool)), ui_suppress_background_audio, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(system_volume_changed(int)), w_system, SLOT(setValue(int))); - connect(m_config, SIGNAL(effect_volume_changed(int)), w_effect, SLOT(setValue(int))); - connect(m_config, SIGNAL(effect_ignore_suppression_changed(bool)), w_effect_ignore_suppression, + connect(m_config, SIGNAL(system_volume_changed(int)), ui_system, SLOT(setValue(int))); + connect(m_config, SIGNAL(effect_volume_changed(int)), ui_effect, SLOT(setValue(int))); + connect(m_config, SIGNAL(effect_ignore_suppression_changed(bool)), ui_effect_ignore_suppression, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(music_volume_changed(int)), w_music, SLOT(setValue(int))); - connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), w_music_ignore_suppression, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(blip_volume_changed(int)), w_blip, SLOT(setValue(int))); - connect(m_config, SIGNAL(blip_ignore_suppression_changed(bool)), w_blip_ignore_suppression, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(blip_rate_changed(int)), w_blip_rate, SLOT(setValue(int))); - connect(m_config, SIGNAL(blank_blips_changed(bool)), w_blank_blips, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(music_volume_changed(int)), ui_music, SLOT(setValue(int))); + connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), ui_music_ignore_suppression, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(blip_volume_changed(int)), ui_blip, SLOT(setValue(int))); + connect(m_config, SIGNAL(blip_ignore_suppression_changed(bool)), ui_blip_ignore_suppression, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(blip_rate_changed(int)), ui_blip_rate, SLOT(setValue(int))); + connect(m_config, SIGNAL(blank_blips_changed(bool)), ui_blank_blips, SLOT(setChecked(bool))); connect(m_engine, SIGNAL(device_changed(DRAudioDevice)), this, SLOT(on_audio_device_changed(DRAudioDevice))); connect(m_engine, SIGNAL(device_list_changed(QList)), this, @@ -154,120 +154,120 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // output // meta - connect(w_close, SIGNAL(clicked()), this, SLOT(close())); - connect(w_save, SIGNAL(clicked()), m_config, SLOT(save_file())); - connect(w_autosave, SIGNAL(toggled(bool)), m_config, SLOT(set_autosave(bool))); + connect(ui_close, SIGNAL(clicked()), this, SLOT(close())); + connect(ui_save, SIGNAL(clicked()), m_config, SLOT(save_file())); + connect(ui_autosave, SIGNAL(toggled(bool)), m_config, SLOT(set_autosave(bool))); // general - connect(w_username, SIGNAL(editingFinished()), this, SLOT(username_editing_finished())); - connect(w_callwords, SIGNAL(editingFinished()), this, SLOT(callwords_editing_finished())); - connect(w_server_alerts, SIGNAL(toggled(bool)), m_config, SLOT(set_server_alerts(bool))); - connect(w_discord_presence, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_presence(bool))); - connect(w_discord_hide_server, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_server(const bool))); - connect(w_discord_hide_character, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_character(const bool))); + connect(ui_username, SIGNAL(editingFinished()), this, SLOT(username_editing_finished())); + connect(ui_callwords, SIGNAL(editingFinished()), this, SLOT(callwords_editing_finished())); + connect(ui_server_alerts, SIGNAL(toggled(bool)), m_config, SLOT(set_server_alerts(bool))); + connect(ui_discord_presence, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_presence(bool))); + connect(ui_discord_hide_server, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_server(const bool))); + connect(ui_discord_hide_character, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_character(const bool))); // game - connect(w_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); - connect(w_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); - connect(w_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); - connect(w_manual_gamemode, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_gamemode(bool))); - connect(w_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); - connect(w_manual_timeofday, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_timeofday(bool))); - connect(w_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); - connect(w_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); - connect(w_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); + connect(ui_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); + connect(ui_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); + connect(ui_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); + connect(ui_manual_gamemode, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_gamemode(bool))); + connect(ui_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); + connect(ui_manual_timeofday, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_timeofday(bool))); + connect(ui_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); + connect(ui_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); + connect(ui_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); // out, log - connect(w_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); - connect(w_log_display_timestamp, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_timestamp(bool))); - connect(w_log_display_self_highlight, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_self_highlight(bool))); - connect(w_log_format_use_newline, SIGNAL(toggled(bool)), m_config, SLOT(set_log_format_use_newline(bool))); - connect(w_log_display_empty_messages, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_empty_messages(bool))); - connect(w_log_display_music_switch, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_music_switch(bool))); - connect(w_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_topdown(bool))); - connect(w_log_is_recording, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_recording(bool))); - connect(w_suppress_background_audio, SIGNAL(toggled(bool)), m_config, SLOT(set_suppress_background_audio(bool))); - connect(w_device, SIGNAL(currentIndexChanged(int)), this, SLOT(on_device_current_index_changed(int))); - connect(w_master, SIGNAL(valueChanged(int)), m_config, SLOT(set_master_volume(int))); - connect(w_master, SIGNAL(valueChanged(int)), this, SLOT(on_master_value_changed(int))); - connect(w_system, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_volume(int))); - connect(w_system, SIGNAL(valueChanged(int)), this, SLOT(on_system_value_changed(int))); - connect(w_effect, SIGNAL(valueChanged(int)), m_config, SLOT(set_effect_volume(int))); - connect(w_effect, SIGNAL(valueChanged(int)), this, SLOT(on_effect_value_changed(int))); - connect(w_effect_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_effect_ignore_suppression(bool))); - connect(w_music, SIGNAL(valueChanged(int)), m_config, SLOT(set_music_volume(int))); - connect(w_music, SIGNAL(valueChanged(int)), this, SLOT(on_music_value_changed(int))); - connect(w_music_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_music_ignore_suppression(bool))); - connect(w_blip, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_volume(int))); - connect(w_blip, SIGNAL(valueChanged(int)), this, SLOT(on_blip_value_changed(int))); - connect(w_blip_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_blip_ignore_suppression(bool))); - connect(w_blip_rate, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_rate(int))); - connect(w_blank_blips, SIGNAL(toggled(bool)), m_config, SLOT(set_blank_blips(bool))); + connect(ui_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); + connect(ui_log_display_timestamp, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_timestamp(bool))); + connect(ui_log_display_self_highlight, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_self_highlight(bool))); + connect(ui_log_format_use_newline, SIGNAL(toggled(bool)), m_config, SLOT(set_log_format_use_newline(bool))); + connect(ui_log_display_empty_messages, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_empty_messages(bool))); + connect(ui_log_display_music_switch, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_music_switch(bool))); + connect(ui_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_topdown(bool))); + connect(ui_log_is_recording, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_recording(bool))); + connect(ui_suppress_background_audio, SIGNAL(toggled(bool)), m_config, SLOT(set_suppress_background_audio(bool))); + connect(ui_device, SIGNAL(currentIndexChanged(int)), this, SLOT(on_device_current_index_changed(int))); + connect(ui_master, SIGNAL(valueChanged(int)), m_config, SLOT(set_master_volume(int))); + connect(ui_master, SIGNAL(valueChanged(int)), this, SLOT(on_master_value_changed(int))); + connect(ui_system, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_volume(int))); + connect(ui_system, SIGNAL(valueChanged(int)), this, SLOT(on_system_value_changed(int))); + connect(ui_effect, SIGNAL(valueChanged(int)), m_config, SLOT(set_effect_volume(int))); + connect(ui_effect, SIGNAL(valueChanged(int)), this, SLOT(on_effect_value_changed(int))); + connect(ui_effect_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_effect_ignore_suppression(bool))); + connect(ui_music, SIGNAL(valueChanged(int)), m_config, SLOT(set_music_volume(int))); + connect(ui_music, SIGNAL(valueChanged(int)), this, SLOT(on_music_value_changed(int))); + connect(ui_music_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_music_ignore_suppression(bool))); + connect(ui_blip, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_volume(int))); + connect(ui_blip, SIGNAL(valueChanged(int)), this, SLOT(on_blip_value_changed(int))); + connect(ui_blip_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_blip_ignore_suppression(bool))); + connect(ui_blip_rate, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_rate(int))); + connect(ui_blank_blips, SIGNAL(toggled(bool)), m_config, SLOT(set_blank_blips(bool))); // set values // meta - w_autosave->setChecked(m_config->autosave()); + ui_autosave->setChecked(m_config->autosave()); // general - w_username->setText(m_config->username()); - w_callwords->setText(m_config->callwords()); - w_server_alerts->setChecked(m_config->server_alerts_enabled()); + ui_username->setText(m_config->username()); + ui_callwords->setText(m_config->callwords()); + ui_server_alerts->setChecked(m_config->server_alerts_enabled()); // game - w_theme->setCurrentText(m_config->theme()); - w_gamemode->setCurrentText(m_config->gamemode()); - w_manual_gamemode->setChecked(m_config->manual_gamemode_enabled()); - w_timeofday->setCurrentText(m_config->timeofday()); - w_manual_timeofday->setChecked(m_config->manual_timeofday_enabled()); - w_showname->setText(m_config->showname()); + ui_theme->setCurrentText(m_config->theme()); + ui_gamemode->setCurrentText(m_config->gamemode()); + ui_manual_gamemode->setChecked(m_config->manual_gamemode_enabled()); + ui_timeofday->setCurrentText(m_config->timeofday()); + ui_manual_timeofday->setChecked(m_config->manual_timeofday_enabled()); + ui_showname->setText(m_config->showname()); on_showname_placeholder_changed(m_config->showname_placeholder()); - w_always_pre->setChecked(m_config->always_pre_enabled()); - w_chat_tick_interval->setValue(m_config->chat_tick_interval()); + ui_always_pre->setChecked(m_config->always_pre_enabled()); + ui_chat_tick_interval->setValue(m_config->chat_tick_interval()); // log - w_log_max_lines->setValue(m_config->log_max_lines()); + ui_log_max_lines->setValue(m_config->log_max_lines()); if (m_config->log_is_topdown_enabled()) { - w_log_orientation_top_down->setChecked(true); + ui_log_orientation_top_down->setChecked(true); } else { - w_log_orientation_bottom_up->setChecked(true); + ui_log_orientation_bottom_up->setChecked(true); } - w_log_display_timestamp->setChecked(m_config->log_display_timestamp_enabled()); - w_log_display_self_highlight->setChecked(m_config->log_display_self_highlight_enabled()); - w_log_format_use_newline->setChecked(m_config->log_format_use_newline_enabled()); - w_log_display_empty_messages->setChecked(m_config->log_display_empty_messages_enabled()); - w_log_display_music_switch->setChecked(m_config->log_display_music_switch_enabled()); - w_log_is_recording->setChecked(m_config->log_is_recording_enabled()); + ui_log_display_timestamp->setChecked(m_config->log_display_timestamp_enabled()); + ui_log_display_self_highlight->setChecked(m_config->log_display_self_highlight_enabled()); + ui_log_format_use_newline->setChecked(m_config->log_format_use_newline_enabled()); + ui_log_display_empty_messages->setChecked(m_config->log_display_empty_messages_enabled()); + ui_log_display_music_switch->setChecked(m_config->log_display_music_switch_enabled()); + ui_log_is_recording->setChecked(m_config->log_is_recording_enabled()); - w_discord_presence->setChecked(m_config->discord_presence()); - w_discord_hide_server->setChecked(m_config->discord_hide_server()); - w_discord_hide_character->setChecked(m_config->discord_hide_character()); + ui_discord_presence->setChecked(m_config->discord_presence()); + ui_discord_hide_server->setChecked(m_config->discord_hide_server()); + ui_discord_hide_character->setChecked(m_config->discord_hide_character()); // audio update_audio_device_list(); - w_master->setValue(m_config->master_volume()); - w_suppress_background_audio->setChecked(m_config->suppress_background_audio()); - w_system->setValue(m_config->system_volume()); - w_effect->setValue(m_config->effect_volume()); - w_effect_ignore_suppression->setChecked(m_config->effect_ignore_suppression()); - w_music->setValue(m_config->music_volume()); - w_music_ignore_suppression->setChecked(m_config->music_ignore_suppression()); - w_blip->setValue(m_config->blip_volume()); - w_blip_ignore_suppression->setChecked(m_config->blip_ignore_suppression()); - w_blip_rate->setValue(m_config->blip_rate()); - w_blank_blips->setChecked(m_config->blank_blips_enabled()); + ui_master->setValue(m_config->master_volume()); + ui_suppress_background_audio->setChecked(m_config->suppress_background_audio()); + ui_system->setValue(m_config->system_volume()); + ui_effect->setValue(m_config->effect_volume()); + ui_effect_ignore_suppression->setChecked(m_config->effect_ignore_suppression()); + ui_music->setValue(m_config->music_volume()); + ui_music_ignore_suppression->setChecked(m_config->music_ignore_suppression()); + ui_blip->setValue(m_config->blip_volume()); + ui_blip_ignore_suppression->setChecked(m_config->blip_ignore_suppression()); + ui_blip_rate->setValue(m_config->blip_rate()); + ui_blank_blips->setChecked(m_config->blank_blips_enabled()); // Widget enabling connections - w_gamemode->setEnabled(m_config->manual_gamemode_enabled()); - w_timeofday->setEnabled(m_config->manual_timeofday_enabled()); + ui_gamemode->setEnabled(m_config->manual_gamemode_enabled()); + ui_timeofday->setEnabled(m_config->manual_timeofday_enabled()); // The manual gamemode checkbox enables browsing the gamemode combox // similarly with time of day - connect(m_config, SIGNAL(manual_gamemode_changed(bool)), w_gamemode, SLOT(setEnabled(bool))); - connect(m_config, SIGNAL(manual_timeofday_changed(bool)), w_timeofday, SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_gamemode_changed(bool)), ui_gamemode, SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_timeofday_changed(bool)), ui_timeofday, SLOT(setEnabled(bool))); } void AOConfigPanel::showEvent(QShowEvent *event) @@ -285,64 +285,64 @@ void AOConfigPanel::showEvent(QShowEvent *event) void AOConfigPanel::refresh_theme_list() { - const QString p_prev_text = w_theme->currentText(); + const QString p_prev_text = ui_theme->currentText(); // block signals - w_theme->blockSignals(true); - w_theme->clear(); + ui_theme->blockSignals(true); + ui_theme->clear(); // themes const QString path = DRPather::get_application_path() + "/base/themes"; - for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + for (const QString &i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; - w_theme->addItem(i_folder); + ui_theme->addItem(i_folder); } // restore previous selection - w_theme->setCurrentText(p_prev_text); + ui_theme->setCurrentText(p_prev_text); // unblock - w_theme->blockSignals(false); + ui_theme->blockSignals(false); } void AOConfigPanel::refresh_gamemode_list() { - const QString p_prev_text = w_gamemode->currentText(); + const QString p_prev_text = ui_gamemode->currentText(); // block signals - w_gamemode->blockSignals(true); - w_gamemode->clear(); + ui_gamemode->blockSignals(true); + ui_gamemode->clear(); // add empty entry indicating no gamemode chosen - w_gamemode->addItem(""); + ui_gamemode->addItem(""); // gamemodes QString path = DRPather::get_application_path() + "/base/themes/" + m_config->theme() + "/gamemodes/"; - for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + for (const QString &i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; - w_gamemode->addItem(i_folder, i_folder); + ui_gamemode->addItem(i_folder, i_folder); } // restore previous selection - w_gamemode->setCurrentText(p_prev_text); + ui_gamemode->setCurrentText(p_prev_text); // unblock - w_gamemode->blockSignals(false); + ui_gamemode->blockSignals(false); } void AOConfigPanel::refresh_timeofday_list() { - const QString p_prev_text = w_timeofday->currentText(); + const QString p_prev_text = ui_timeofday->currentText(); // block signals - w_timeofday->blockSignals(true); - w_timeofday->clear(); + ui_timeofday->blockSignals(true); + ui_timeofday->clear(); // add empty entry indicating no time of day chosen - w_timeofday->addItem(""); + ui_timeofday->addItem(""); // decide path to look for times of day. This differs whether there is a // gamemode chosen or not @@ -354,18 +354,18 @@ void AOConfigPanel::refresh_timeofday_list() m_config->gamemode() + "/times/"; // times of day - for (QString i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + for (const QString &i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; - w_timeofday->addItem(i_folder, i_folder); + ui_timeofday->addItem(i_folder, i_folder); } // restore previous selection - w_timeofday->setCurrentText(p_prev_text); + ui_timeofday->setCurrentText(p_prev_text); // unblock - w_timeofday->blockSignals(false); + ui_timeofday->blockSignals(false); } void AOConfigPanel::update_audio_device_list() @@ -373,8 +373,8 @@ void AOConfigPanel::update_audio_device_list() const auto current_device = m_engine->get_device(); const auto favorite_device = m_engine->get_favorite_device(); - QSignalBlocker device_blocker(w_device); - w_device->clear(); + QSignalBlocker device_blocker(ui_device); + ui_device->clear(); std::optional current_device_index; { @@ -382,24 +382,24 @@ void AOConfigPanel::update_audio_device_list() { if (!i_device.is_enabled()) continue; - w_device->addItem(i_device.get_name(), i_device.get_driver()); - int item_index = w_device->count() - 1; + ui_device->addItem(i_device.get_name(), i_device.get_driver()); + int item_index = ui_device->count() - 1; if (current_device.has_value() && current_device.value().get_driver() == i_device.get_driver()) current_device_index = item_index; if (favorite_device.has_value() && favorite_device.value().get_driver() == i_device.get_driver()) - w_device->setItemData(item_index, QColor(Qt::green), Qt::BackgroundRole); + ui_device->setItemData(item_index, QColor(Qt::green), Qt::BackgroundRole); } - const bool has_items(w_device->count()); + const bool has_items(ui_device->count()); if (!has_items) - w_device->addItem(tr("")); - w_device->setEnabled(has_items); + ui_device->addItem(tr("")); + ui_device->setEnabled(has_items); } if (current_device_index.has_value()) - w_device->setCurrentIndex(current_device_index.value()); + ui_device->setCurrentIndex(current_device_index.value()); } void AOConfigPanel::on_reload_theme_clicked() @@ -411,34 +411,34 @@ void AOConfigPanel::on_reload_theme_clicked() void AOConfigPanel::on_gamemode_index_changed(QString p_text) { Q_UNUSED(p_text); - m_config->set_gamemode(w_gamemode->currentData().toString()); + m_config->set_gamemode(ui_gamemode->currentData().toString()); } void AOConfigPanel::on_timeofday_index_changed(QString p_text) { Q_UNUSED(p_text); - m_config->set_timeofday(w_timeofday->currentData().toString()); + m_config->set_timeofday(ui_timeofday->currentData().toString()); } void AOConfigPanel::on_showname_placeholder_changed(QString p_text) { const QString l_showname(p_text.trimmed().isEmpty() ? "Showname" : p_text); - w_showname->setPlaceholderText(l_showname); - w_showname->setToolTip(l_showname); + ui_showname->setPlaceholderText(l_showname); + ui_showname->setToolTip(l_showname); } void AOConfigPanel::on_log_is_topdown_changed(bool p_enabled) { - w_log_orientation_top_down->setChecked(p_enabled); - w_log_orientation_bottom_up->setChecked(!p_enabled); + ui_log_orientation_top_down->setChecked(p_enabled); + ui_log_orientation_bottom_up->setChecked(!p_enabled); } void AOConfigPanel::on_device_current_index_changed(int p_index) { - if (p_index == -1 || p_index >= w_device->count()) + if (p_index == -1 || p_index >= ui_device->count()) return; - const QString target_device_driver = w_device->itemData(p_index).toString(); + const QString target_device_driver = ui_device->itemData(p_index).toString(); for (DRAudioDevice &i_device : m_engine->get_device_list()) if (target_device_driver == i_device.get_driver()) { @@ -469,42 +469,42 @@ void AOConfigPanel::on_audio_device_list_changed(QList p_device_l void AOConfigPanel::on_master_value_changed(int p_num) { - w_master_value->setText(QString::number(p_num) + "%"); + ui_master_value->setText(QString::number(p_num) + "%"); } void AOConfigPanel::on_system_value_changed(int p_num) { - w_system_value->setText(QString::number(p_num) + "%"); + ui_system_value->setText(QString::number(p_num) + "%"); } void AOConfigPanel::on_effect_value_changed(int p_num) { - w_effect_value->setText(QString::number(p_num) + "%"); + ui_effect_value->setText(QString::number(p_num) + "%"); } void AOConfigPanel::on_music_value_changed(int p_num) { - w_music_value->setText(QString::number(p_num) + "%"); + ui_music_value->setText(QString::number(p_num) + "%"); } void AOConfigPanel::on_blip_value_changed(int p_num) { - w_blip_value->setText(QString::number(p_num) + "%"); + ui_blip_value->setText(QString::number(p_num) + "%"); } void AOConfigPanel::username_editing_finished() { - m_config->set_username(w_username->text()); + m_config->set_username(ui_username->text()); } void AOConfigPanel::showname_editing_finished() { - m_config->set_showname(w_showname->text()); + m_config->set_showname(ui_showname->text()); } void AOConfigPanel::callwords_editing_finished() { - m_config->set_callwords(w_callwords->text()); + m_config->set_callwords(ui_callwords->text()); } void AOConfigPanel::on_config_reload_theme_requested() diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index 2acdabc88..cd892f786 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -12,68 +12,68 @@ AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x this->move(p_x, p_y); this->resize(40, 40); - w_selected = new QLabel(this); - w_selected->resize(size()); - w_selected->setAttribute(Qt::WA_TransparentForMouseEvents); - w_selected->hide(); + ui_selected = new QLabel(this); + ui_selected->resize(size()); + ui_selected->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_selected->hide(); connect(this, SIGNAL(clicked()), this, SLOT(on_clicked())); } void AOEmoteButton::set_emote_number(int p_emote_number) { - m_emote_number = p_emote_number; + m_index = p_emote_number; } int AOEmoteButton::get_emote_number() { - return m_emote_number; + return m_index; } void AOEmoteButton::set_image(DREmote p_emote, bool p_enabled) { - QString texture_path = + QString l_texture = ao_app->get_character_path(p_emote.character, QString("emotions/button%1_off.png").arg(p_emote.key)); // reset states - w_selected->hide(); + ui_selected->hide(); // nested ifs are okay if (p_enabled) { - const QString enabled_texture_path = + const QString l_enabled_texture = ao_app->get_character_path(p_emote.character, QString("emotions/button%1_on.png").arg(p_emote.key)); - if (file_exists(enabled_texture_path)) + if (file_exists(l_enabled_texture)) { - texture_path = enabled_texture_path; + l_texture = l_enabled_texture; } else { - const QString selected_texture_path = ao_app->get_character_path(p_emote.character, "emotions/selected.png"); + const QString l_selected_texture = ao_app->get_character_path(p_emote.character, "emotions/selected.png"); - if (file_exists(selected_texture_path)) + if (file_exists(l_selected_texture)) { - w_selected->setStyleSheet(QString("border-image: url(\"%1\")").arg(selected_texture_path)); + ui_selected->setStyleSheet(QString("border-image: url(\"%1\")").arg(l_selected_texture)); } else { - w_selected->setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, " + ui_selected->setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, " "y2:1, stop:0 rgba(0, 0, 0, 0), stop:1 rgba(0, 0, 0, 127)); }"); } - w_selected->show(); + ui_selected->show(); } } - const bool texture_exist = file_exists(texture_path); - setText(texture_exist ? nullptr : p_emote.comment); - setStyleSheet(texture_exist - ? QString("%1 { border-image: url(\"%2\"); }").arg(metaObject()->className()).arg(texture_path) + const bool l_texture_exist = file_exists(l_texture); + setText(l_texture_exist ? nullptr : p_emote.comment); + setStyleSheet(l_texture_exist + ? QString("%1 { border-image: url(\"%2\"); }").arg(metaObject()->className()).arg(l_texture) : QString()); } void AOEmoteButton::on_clicked() { - emote_clicked(m_emote_number); + Q_EMIT emote_clicked(m_index); } diff --git a/src/aoevidencebutton.cpp b/src/aoevidencebutton.cpp index 8b7fd8c09..cbc685adb 100644 --- a/src/aoevidencebutton.cpp +++ b/src/aoevidencebutton.cpp @@ -62,7 +62,7 @@ void AOEvidenceButton::set_theme_image(QString p_image) void AOEvidenceButton::set_id(int p_id) { - m_id = p_id; + m_index = p_id; } void AOEvidenceButton::set_selected(bool p_selected) @@ -75,20 +75,20 @@ void AOEvidenceButton::set_selected(bool p_selected) void AOEvidenceButton::on_clicked() { - evidence_clicked(m_id); + Q_EMIT evidence_clicked(m_index); } void AOEvidenceButton::mouseDoubleClickEvent(QMouseEvent *e) { QPushButton::mouseDoubleClickEvent(e); - evidence_double_clicked(m_id); + Q_EMIT evidence_double_clicked(m_index); } void AOEvidenceButton::enterEvent(QEvent *e) { ui_selector->show(); - on_hover(m_id, true); + Q_EMIT on_hover(m_index, true); setFlat(false); QPushButton::enterEvent(e); @@ -98,6 +98,6 @@ void AOEvidenceButton::leaveEvent(QEvent *e) { ui_selector->hide(); - on_hover(m_id, false); + Q_EMIT on_hover(m_index, false); QPushButton::leaveEvent(e); } diff --git a/src/aoevidencedescription.cpp b/src/aoevidencedescription.cpp index 0413e8a7e..0a3c4caec 100644 --- a/src/aoevidencedescription.cpp +++ b/src/aoevidencedescription.cpp @@ -3,8 +3,6 @@ AOEvidenceDescription::AOEvidenceDescription(QWidget *parent) : QPlainTextEdit(parent) { this->setReadOnly(true); - - // connect(this, SIGNAL(returnPressed()), this, SLOT(on_enter_pressed())); } void AOEvidenceDescription::mouseDoubleClickEvent(QMouseEvent *e) diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index f68617f5b..671cb6510 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -14,7 +14,7 @@ AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) ao_app = p_ao_app; m_movie = new QMovie(this); - w_icon = new QLabel(this); + ui_icon = new QLabel(this); dr_sfx = new AOSfxPlayer(ao_app, this); connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); @@ -24,32 +24,31 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid { this->reset(); - QString f_evidence_path = ao_app->get_evidence_path(p_evidence_image); + QString l_evidence = ao_app->get_evidence_path(p_evidence_image); - AOPixmap f_pixmap(f_evidence_path); + AOPixmap l_pixmap(l_evidence); - QString final_gif_path; - QString gif_name; - QString icon_identifier; + QString l_icon_animation; + QString l_icon_identifier; if (is_left_side) { - icon_identifier = "left_evidence_icon"; - gif_name = "evidence_appear_left.gif"; + l_icon_identifier = "left_evidence_icon"; + l_icon_animation = "evidence_appear_left.gif"; } else { - icon_identifier = "right_evidence_icon"; - gif_name = "evidence_appear_right.gif"; + l_icon_identifier = "right_evidence_icon"; + l_icon_animation = "evidence_appear_right.gif"; } - pos_size_type icon_dimensions = ao_app->get_element_dimensions(icon_identifier, "courtroom_design.ini"); + pos_size_type l_icon_dimensions = ao_app->get_element_dimensions(l_icon_identifier, "courtroom_design.ini"); - w_icon->move(icon_dimensions.x, icon_dimensions.y); - w_icon->resize(icon_dimensions.width, icon_dimensions.height); - w_icon->setPixmap(f_pixmap.scale(w_icon->size())); + ui_icon->move(l_icon_dimensions.x, l_icon_dimensions.y); + ui_icon->resize(l_icon_dimensions.width, l_icon_dimensions.height); + ui_icon->setPixmap(l_pixmap.scale(ui_icon->size())); - QString f_path = ao_app->find_theme_asset_path(gif_name); + QString f_path = ao_app->find_theme_asset_path(l_icon_animation); m_movie->setFileName(f_path); if (m_movie->frameCount() < 1) return; @@ -70,13 +69,13 @@ void AOEvidenceDisplay::frame_change(int p_frame) m_movie->stop(); this->clear(); - w_icon->show(); + ui_icon->show(); } } void AOEvidenceDisplay::reset() { m_movie->stop(); - w_icon->hide(); + ui_icon->hide(); this->clear(); } diff --git a/src/aoguiloader.cpp b/src/aoguiloader.cpp index dd5f8085d..14de2dac6 100644 --- a/src/aoguiloader.cpp +++ b/src/aoguiloader.cpp @@ -13,17 +13,17 @@ QWidget *AOGuiLoader::load_from_file(QString p_file_path, QWidget *p_parent) { QWidget *r_widget = nullptr; - QFile f_file(p_file_path); - if (f_file.open(QIODevice::ReadOnly)) + QFile l_file(p_file_path); + if (l_file.open(QIODevice::ReadOnly)) { - r_widget = load(&f_file, p_parent); + r_widget = load(&l_file, p_parent); // lazily replace the parent's layout with our own if (p_parent != nullptr) { - QVBoxLayout *f_parent_layout = new QVBoxLayout(p_parent); - f_parent_layout->addWidget(r_widget); - p_parent->setLayout(f_parent_layout); + QVBoxLayout *l_parent_layout = new QVBoxLayout(p_parent); + l_parent_layout->addWidget(r_widget); + p_parent->setLayout(l_parent_layout); } } diff --git a/src/aoimagedisplay.cpp b/src/aoimagedisplay.cpp index 56f0f14c6..61be43baf 100644 --- a/src/aoimagedisplay.cpp +++ b/src/aoimagedisplay.cpp @@ -15,20 +15,20 @@ AOImageDisplay::AOImageDisplay(QWidget *parent, AOApplication *p_ao_app) : QLabe QString AOImageDisplay::get_image() { - return image_path; + return m_image; } void AOImageDisplay::set_image(QString p_image) { - QString f_path = ao_app->find_theme_asset_path(p_image); - AOPixmap f_pixmap(f_path); + const QString l_path = ao_app->find_theme_asset_path(p_image); + AOPixmap f_pixmap(l_path); this->setPixmap(f_pixmap.scale(size())); // Store final path if the path exists - if (file_exists(f_path)) - image_path = f_path; + if (file_exists(l_path)) + m_image = l_path; else - image_path = ""; + m_image = ""; } void AOImageDisplay::set_image_from_path(QString p_path) @@ -47,7 +47,7 @@ void AOImageDisplay::set_image_from_path(QString p_path) // Store final path if the path exists if (file_exists(final_path)) - image_path = final_path; + m_image = final_path; else - image_path = ""; + m_image = ""; } diff --git a/src/aolabel.cpp b/src/aolabel.cpp index bf488d2da..c258dba32 100644 --- a/src/aolabel.cpp +++ b/src/aolabel.cpp @@ -9,6 +9,6 @@ AOLabel::AOLabel(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) void AOLabel::set_image(QString p_image) { - QString f_path = ao_app->find_theme_asset_path(p_image); - setStyleSheet("border-image:url(\"" + f_path + "\")"); + const QString l_image_path = ao_app->find_theme_asset_path(p_image); + setStyleSheet("border-image:url(\"" + l_image_path + "\")"); } diff --git a/src/aolineedit.cpp b/src/aolineedit.cpp index f6026e142..9f477d994 100644 --- a/src/aolineedit.cpp +++ b/src/aolineedit.cpp @@ -2,8 +2,8 @@ AOLineEdit::AOLineEdit(QWidget *parent) : QLineEdit(parent) { - this->setReadOnly(true); - this->setFrame(false); + setReadOnly(true); + setFrame(false); connect(this, SIGNAL(returnPressed()), this, SLOT(on_enter_pressed())); } @@ -11,7 +11,6 @@ AOLineEdit::AOLineEdit(QWidget *parent) : QLineEdit(parent) void AOLineEdit::mouseDoubleClickEvent(QMouseEvent *e) { QLineEdit::mouseDoubleClickEvent(e); - this->setReadOnly(false); } diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 53f1ae485..5584cd6f9 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -24,13 +24,12 @@ AOMovie::~AOMovie() void AOMovie::set_play_once(bool p_play_once) { - play_once = p_play_once; + is_play_once = p_play_once; } void AOMovie::play(QString p_file, QString p_char) { m_movie->stop(); - QVector f_vec; QString file_path = ""; // Remove ! at the beginning of p_file if needed @@ -132,7 +131,7 @@ void AOMovie::stop() void AOMovie::frame_change(int n_frame) { - if (n_frame == (m_movie->frameCount() - 1) && play_once) + if (n_frame == (m_movie->frameCount() - 1) && is_play_once) { // we need this or else the last frame wont show delay(m_movie->nextFrameDelay()); diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp index b86cc507a..100186401 100644 --- a/src/aonotearea.cpp +++ b/src/aonotearea.cpp @@ -29,11 +29,11 @@ void Courtroom::on_add_button_clicked() AOButton *f_hover = new AOButton(f_notepicker, ao_app); QHBoxLayout *f_layout = new QHBoxLayout(f_notepicker); - f_notepicker->m_line = f_line; - f_notepicker->m_button = f_button; - f_notepicker->m_layout = f_layout; - f_notepicker->m_delete_button = f_delete; - f_notepicker->m_hover = f_hover; + f_notepicker->ui_line = f_line; + f_notepicker->ui_button = f_button; + f_notepicker->ui_layout = f_layout; + f_notepicker->ui_delete_button = f_delete; + f_notepicker->ui_hover = f_hover; f_notepicker->setProperty("index", ui_note_area->m_layout->count() - 1); f_button->set_image("note_edit.png"); @@ -83,8 +83,8 @@ void Courtroom::set_note_files() for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) { AONotePicker *f_notepicker = static_cast(ui_note_area->m_layout->itemAt(i)->widget()); - QString f_filestring = f_notepicker->real_file; - QString f_filename = f_notepicker->m_line->text(); + QString f_filestring = f_notepicker->m_file; + QString f_filename = f_notepicker->ui_line->text(); t += QString("%1 = %2 = %3\n\n").arg(i).arg(f_filestring).arg(f_filename).toUtf8(); } diff --git a/src/aonotepicker.cpp b/src/aonotepicker.cpp index 16da22e1f..9a95ea71d 100644 --- a/src/aonotepicker.cpp +++ b/src/aonotepicker.cpp @@ -20,23 +20,23 @@ void Courtroom::on_file_selected() for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) { AONotePicker *f_notepicker = static_cast(ui_note_area->m_layout->itemAt(i)->widget()); - f_notepicker->m_hover->set_image("note_select.png"); + f_notepicker->ui_hover->set_image("note_select.png"); } AOButton *f_button = static_cast(sender()); AONotePicker *f_notepicker = static_cast(f_button->parent()); - if (f_notepicker->real_file.isEmpty()) + if (f_notepicker->m_file.isEmpty()) { call_notice("You must give a filepath to load a note from!"); return; } - if (current_file != f_notepicker->real_file) + if (current_file != f_notepicker->m_file) { - current_file = f_notepicker->real_file; + current_file = f_notepicker->m_file; load_note(); - f_notepicker->m_hover->set_image("note_select_selected.png"); + f_notepicker->ui_hover->set_image("note_select_selected.png"); } else { @@ -53,17 +53,17 @@ void Courtroom::on_set_file_button_clicked() if (f_filename != "") { - f_notepicker->m_line->setText(f_filename); + f_notepicker->ui_line->setText(f_filename); // If this notepicker is the currently selected slot, update the notepad as // the file given changes. - if (f_notepicker->real_file == current_file) + if (f_notepicker->m_file == current_file) { current_file = f_filename; load_note(); } - f_notepicker->real_file = f_filename; + f_notepicker->m_file = f_filename; set_note_files(); } @@ -74,7 +74,7 @@ void Courtroom::on_delete_button_clicked() AOButton *f_button = static_cast(sender()); AONotePicker *f_notepicker = static_cast(f_button->parent()); - if (current_file == f_notepicker->real_file) + if (current_file == f_notepicker->m_file) { current_file = ""; } diff --git a/src/aopacket.cpp b/src/aopacket.cpp index 86df4c36b..14ad566a0 100644 --- a/src/aopacket.cpp +++ b/src/aopacket.cpp @@ -4,13 +4,13 @@ AOPacket::AOPacket(QString p_packet_string) { - QStringList packet_contents = p_packet_string.split("#"); + QStringList l_contents = p_packet_string.split("#"); - m_header = packet_contents.at(0); + m_header = l_contents.at(0); - for (int n_string = 1; n_string < packet_contents.size() - 1; ++n_string) + for (int n_string = 1; n_string < l_contents.size() - 1; ++n_string) { - m_contents.append(packet_contents.at(n_string)); + m_contents.append(l_contents.at(n_string)); } } @@ -32,38 +32,24 @@ QStringList &AOPacket::get_contents() QString AOPacket::to_string() { - QString f_string = m_header; - - for (const QString &i_string : qAsConst(m_contents)) + QString r_data; + for (const QString &i_value : qAsConst(m_contents)) { - f_string += ("#" + i_string); + if (!r_data.isEmpty()) + r_data += "#"; + r_data += i_value; } - - f_string += "#%"; - - return f_string; + return m_header + "#" + r_data + (r_data.isEmpty() ? "%" : "#%"); } void AOPacket::net_encode() { - for (int n_element = 0; n_element < m_contents.size(); ++n_element) - { - QString f_element = m_contents.at(n_element); - f_element.replace("#", "").replace("%", "").replace("$", "").replace("&", ""); - - m_contents.removeAt(n_element); - m_contents.insert(n_element, f_element); - } + for (QString &i_value : m_contents) + i_value.replace("#", "").replace("%", "").replace("$", "").replace("&", ""); } void AOPacket::net_decode() { - for (int n_element = 0; n_element < m_contents.size(); ++n_element) - { - QString f_element = m_contents.at(n_element); - f_element.replace("", "#").replace("", "%").replace("", "$").replace("", "&"); - - m_contents.removeAt(n_element); - m_contents.insert(n_element, f_element); - } + for (QString &i_value : m_contents) + i_value.replace("", "#").replace("", "%").replace("", "$").replace("", "&"); } diff --git a/src/aopixmap.cpp b/src/aopixmap.cpp index cde27e318..9da66c643 100644 --- a/src/aopixmap.cpp +++ b/src/aopixmap.cpp @@ -9,8 +9,7 @@ AOPixmap::AOPixmap(QPixmap p_pixmap) : m_pixmap(p_pixmap) } AOPixmap::AOPixmap(QString p_file_path) : AOPixmap(QPixmap(p_file_path)) -{ -} +{} void AOPixmap::clear() { @@ -20,14 +19,14 @@ void AOPixmap::clear() QPixmap AOPixmap::scale(QSize p_size) { - const bool f_pixmap_is_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); - const Qt::TransformationMode f_mode = f_pixmap_is_larger ? Qt::SmoothTransformation : Qt::FastTransformation; + const bool l_pixmap_is_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); + const Qt::TransformationMode f_mode = l_pixmap_is_larger ? Qt::SmoothTransformation : Qt::FastTransformation; return m_pixmap.scaled(p_size, Qt::IgnoreAspectRatio, f_mode); } QPixmap AOPixmap::scale_to_height(QSize p_size) { - const bool f_is_pixmap_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); + const bool l_pixmap_is_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); return m_pixmap.scaledToHeight(p_size.height(), - f_is_pixmap_larger ? Qt::SmoothTransformation : Qt::FastTransformation); + l_pixmap_is_larger ? Qt::SmoothTransformation : Qt::FastTransformation); } diff --git a/src/aotextarea.cpp b/src/aotextarea.cpp index 640a07a97..00c6d4815 100644 --- a/src/aotextarea.cpp +++ b/src/aotextarea.cpp @@ -6,7 +6,7 @@ #include #include -const QRegExp AOTextArea::omnis_dank_url_regex = QRegExp("\\b(https?://\\S+\\.\\S+)\\b"); +const QRegExp AOTextArea::URL_REGEXP = QRegExp("\\b(https?://\\S+\\.\\S+)\\b"); AOTextArea::AOTextArea(QWidget *p_parent) : QTextBrowser(p_parent) {} @@ -26,7 +26,7 @@ void AOTextArea::append_chatmessage(QString p_name, QString p_message) // cheap workarounds ahoy p_message += " "; QString result = p_message.toHtmlEscaped().replace("\n", "
"); - result = result.replace(omnis_dank_url_regex, "
\\1"); + result = result.replace(URL_REGEXP, "\\1"); this->insertHtml(result); @@ -46,7 +46,7 @@ void AOTextArea::append_error(QString p_message) p_message += " "; QString result = p_message.replace("\n", "
"); - result = result.replace(omnis_dank_url_regex, "\\1"); + result = result.replace(URL_REGEXP, "\\1"); this->insertHtml("" + result + ""); diff --git a/src/charselect.cpp b/src/charselect.cpp index f65dd5c5f..48c699df6 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -124,7 +124,7 @@ void Courtroom::set_char_select_page() ui_char_select_left->hide(); ui_char_select_right->hide(); - for (AOCharButton *button : ui_char_button_list) + for (AOCharButton *button : qAsConst(ui_char_button_list)) { button->reset(); button->hide(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f4249de99..ef472da10 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -146,7 +146,7 @@ void Courtroom::enter_courtroom(int p_cid) set_widget_names(); set_widget_layers(); - for (AOTimer *i_timer : ui_timers) + for (AOTimer *i_timer : qAsConst(ui_timers)) i_timer->redraw(); ui_char_select_background->hide(); @@ -330,7 +330,7 @@ void Courtroom::set_scene() bool has_all_desks = true; QStringList alldesks{"defensedesk", "prosecutiondesk", "stand"}; - for (QString desk : alldesks) + for (const QString &desk : alldesks) { QString full_path = ao_app->find_asset_path({get_background_path(desk)}, animated_or_static_extensions()); if (full_path.isEmpty()) @@ -634,8 +634,8 @@ void Courtroom::list_note_files() on_add_button_clicked(); AONotePicker *f_notepicker = static_cast(f_layout->itemAt(f_index)->widget()); - f_notepicker->m_line->setText(f_filename); - f_notepicker->real_file = f_filestring; + f_notepicker->ui_line->setText(f_filename); + f_notepicker->m_file = f_filestring; } } @@ -1039,9 +1039,9 @@ void Courtroom::handle_chatmessage_2() // handles IC int emote_mod = m_chatmessage[CMEmoteModifier].toInt(); if (m_chatmessage[CMFlipState].toInt() == 1) - ui_vp_player_char->set_mirror_enabled(true); + ui_vp_player_char->set_mirrored(true); else - ui_vp_player_char->set_mirror_enabled(false); + ui_vp_player_char->set_mirrored(false); switch (emote_mod) { @@ -1189,7 +1189,7 @@ void Courtroom::handle_chatmessage_3() QString f_message = m_chatmessage[CMMessage]; QStringList callwords = ao_app->get_callwords(); - for (QString word : callwords) + for (const QString &word : callwords) { if (f_message.contains(word, Qt::CaseInsensitive)) { diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index c7b4ffbf8..7eb7460e4 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -453,7 +453,7 @@ void Courtroom::set_widget_names() reset_widget_names(); // set existing widget names - for (QString widget_name : widget_names.keys()) + for (const QString &widget_name : widget_names.keys()) widget_names[widget_name]->setObjectName(widget_name); // setup table of widgets and names @@ -1167,7 +1167,7 @@ void Courtroom::delete_widget(QWidget *p_widget) void Courtroom::load_effects() { // Close any existing effects to prevent memory leaks - for (QWidget *widget : ui_effects) + for (QWidget *widget : qAsConst(ui_effects)) delete_widget(widget); // And create new effects @@ -1206,7 +1206,7 @@ void Courtroom::load_effects() void Courtroom::load_free_blocks() { - for (QWidget *widget : ui_free_blocks) + for (QWidget *widget : qAsConst(ui_free_blocks)) delete_widget(widget); // And create new free block buttons @@ -1238,7 +1238,7 @@ void Courtroom::load_free_blocks() void Courtroom::load_shouts() { - for (QWidget *widget : ui_shouts) + for (QWidget *widget : qAsConst(ui_shouts)) delete_widget(widget); // And create new shouts @@ -1280,7 +1280,7 @@ void Courtroom::load_shouts() void Courtroom::load_wtce() { - for (QWidget *widget : ui_wtce) + for (QWidget *widget : qAsConst(ui_wtce)) delete_widget(widget); // And create new wtce buttons @@ -1379,7 +1379,7 @@ void Courtroom::set_judge_wtce() } else { - for (AOButton *i_wtce : ui_wtce) + for (AOButton *i_wtce : qAsConst(ui_wtce)) i_wtce->show(); } } @@ -1447,12 +1447,12 @@ void Courtroom::set_mute_list() QStringList sorted_mute_list; - for (char_type i_char : m_chr_list) + for (const char_type &i_char : qAsConst(m_chr_list)) sorted_mute_list.append(i_char.name); sorted_mute_list.sort(); - for (QString i_chr_name : sorted_mute_list) + for (const QString &i_chr_name : sorted_mute_list) { QListWidgetItem *i_item = new QListWidgetItem(i_chr_name, ui_mute_list); i_item->setFlags(i_item->flags() | Qt::ItemFlag::ItemIsUserCheckable); diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index b3db344d7..8c812038f 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -9,36 +9,25 @@ DRTextEdit::DRTextEdit(QWidget *parent) : QTextEdit(parent) connect(this, SIGNAL(textChanged()), this, SLOT(on_text_changed())); } -void DRTextEdit::set_outline(bool p_outline) +void DRTextEdit::set_outline(bool p_enabled) { + if (has_outline == p_enabled) + return; + has_outline = p_enabled; QTextCharFormat widget_format = currentCharFormat(); - if (p_outline) + if (p_enabled) widget_format.setTextOutline(QPen(Qt::black, 1)); else widget_format.setTextOutline(Qt::NoPen); setCurrentCharFormat(widget_format); - this->m_outline = p_outline; } -bool DRTextEdit::get_outline() +void DRTextEdit::set_auto_align(bool p_enabled) { - return this->m_outline; -} - -bool DRTextEdit::get_auto_align() -{ - return this->m_auto_align; -} - -void DRTextEdit::set_auto_align(bool new_auto_align) -{ - if (new_auto_align == m_auto_align) + if (is_auto_align == p_enabled) return; - m_auto_align = new_auto_align; - - if (m_auto_align) - on_text_changed(); - return; + is_auto_align = p_enabled; + on_text_changed(); } void DRTextEdit::set_vertical_alignment(Qt::Alignment p_align) @@ -85,7 +74,7 @@ Qt::Alignment DRTextEdit::get_horizontal_alignment() void DRTextEdit::on_text_changed() { - if (!m_auto_align) + if (!is_auto_align) return; // We need to "lock" access to on_text_changed. That is because the refresh methods trigger diff --git a/src/emotes.cpp b/src/emotes.cpp index 180cfff15..133633e1a 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -102,7 +102,7 @@ void Courtroom::set_emote_page() return; const int total_emotes = m_emote_list.length(); - for (AOEmoteButton *i_button : ui_emote_list) + for (AOEmoteButton *i_button : qAsConst(ui_emote_list)) i_button->hide(); int total_pages = total_emotes / max_emotes_on_page; @@ -141,7 +141,7 @@ void Courtroom::set_emote_dropdown() ui_emote_dropdown->clear(); QStringList l_emote_list; - for (const DREmote &i_emote : m_emote_list) + for (const DREmote &i_emote : qAsConst(m_emote_list)) l_emote_list.append(i_emote.comment); ui_emote_dropdown->addItems(l_emote_list); } diff --git a/src/evidence.cpp b/src/evidence.cpp index a99495eee..0a40d677b 100644 --- a/src/evidence.cpp +++ b/src/evidence.cpp @@ -109,7 +109,7 @@ void Courtroom::set_evidence_page() ui_evidence_left->hide(); ui_evidence_right->hide(); - for (AOEvidenceButton *i_button : ui_evidence_list) + for (AOEvidenceButton *i_button : qAsConst(ui_evidence_list)) { i_button->reset(); } @@ -239,7 +239,7 @@ void Courtroom::on_evidence_clicked(int p_id) ui_evidence_name->setText(local_evidence_list.at(f_real_id).name); - for (AOEvidenceButton *i_button : ui_evidence_list) + for (AOEvidenceButton *i_button : qAsConst(ui_evidence_list)) i_button->set_selected(false); ui_evidence_list.at(p_id)->set_selected(true); diff --git a/src/lobby.cpp b/src/lobby.cpp index 9c0b49b44..a63f7b61e 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -81,19 +81,17 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() bool Lobby::is_public_server() const { - return public_servers_selected; + return is_public_server_selected; } // sets images, position and size void Lobby::set_widgets() { - QString filename = "lobby_design.ini"; - - pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", filename); + pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", INI_DESIGN); if (f_lobby.width < 0 || f_lobby.height < 0) { - qDebug() << "W: did not find lobby width or height in " << filename; + qDebug() << "W: did not find lobby width or height in " << INI_DESIGN; // Most common symptom of bad config files, missing assets, or misnamed // theme folder @@ -255,7 +253,7 @@ void Lobby::on_public_servers_clicked() list_servers(); - public_servers_selected = true; + is_public_server_selected = true; } void Lobby::on_favorites_clicked() @@ -268,7 +266,7 @@ void Lobby::on_favorites_clicked() list_favorites(); - public_servers_selected = false; + is_public_server_selected = false; } void Lobby::on_refresh_pressed() @@ -295,7 +293,7 @@ void Lobby::on_add_to_fav_released() ui_add_to_fav->set_image("addtofav.png"); // you cant add favorites from favorites m8 - if (!public_servers_selected) + if (!is_public_server_selected) return; ao_app->add_favorite_server(ui_server_list->currentRow()); @@ -349,30 +347,30 @@ void Lobby::on_server_list_clicked(QModelIndex p_model) if (n_server < 0) return; - if (public_servers_selected) + if (is_public_server_selected) { QVector f_server_list = ao_app->get_server_list(); if (n_server >= f_server_list.size()) return; - f_last_server = f_server_list.at(p_model.row()); + m_last_server = f_server_list.at(p_model.row()); } else { if (n_server >= ao_app->get_favorite_list().size()) return; - f_last_server = ao_app->get_favorite_list().at(p_model.row()); + m_last_server = ao_app->get_favorite_list().at(p_model.row()); } ui_player_count->setText(nullptr); ui_description->moveCursor(QTextCursor::Start); - ui_description->setText("Connecting to " + f_last_server.name + "...\n\n"); - ui_description->append(f_last_server.desc); + ui_description->setText("Connecting to " + m_last_server.name + "...\n\n"); + ui_description->append(m_last_server.desc); ui_description->ensureCursorVisible(); - ao_app->get_network_manager()->connect_to_server(f_last_server); + ao_app->get_network_manager()->connect_to_server(m_last_server); } void Lobby::on_chatfield_return_pressed() @@ -393,13 +391,13 @@ void Lobby::on_chatfield_return_pressed() void Lobby::list_servers() { - public_servers_selected = true; + is_public_server_selected = true; ui_favorites->set_image("favorites.png"); ui_public_servers->set_image("publicservers_selected.png"); ui_server_list->clear(); - for (server_type i_server : ao_app->get_server_list()) + for (const server_type &i_server : ao_app->get_server_list()) { ui_server_list->addItem(i_server.name); } @@ -409,7 +407,7 @@ void Lobby::list_favorites() { ui_server_list->clear(); - for (server_type i_server : ao_app->get_favorite_list()) + for (const server_type &i_server : ao_app->get_favorite_list()) { ui_server_list->addItem(i_server.name); } @@ -437,7 +435,7 @@ void Lobby::set_player_count(int players_online, int max_players) ui_player_count->setText(f_string); ui_player_count->setAlignment(Qt::AlignHCenter); - ui_description->setText("Connected to " + f_last_server.name + "\n\n"); - ui_description->append(f_last_server.desc); + ui_description->setText("Connected to " + m_last_server.name + "\n\n"); + ui_description->append(m_last_server.desc); ui_description->ensureCursorVisible(); } diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp index a6e6e4962..dcb3ec35e 100644 --- a/src/networkmanager.cpp +++ b/src/networkmanager.cpp @@ -6,12 +6,9 @@ #include #include -const QString NetworkManager::ms_srv_hostname = "_aoms._tcp.aceattorneyonline.com"; -const QString NetworkManager::ms_nosrv_hostname = "master.aceattorneyonline.com"; - -const int NetworkManager::ms_port = 27016; -const int NetworkManager::timeout_milliseconds = 2000; -const int NetworkManager::ms_reconnect_delay_ms = 7000; +const QString NetworkManager::MASTER_HOST = "master.aceattorneyonline.com"; +const int NetworkManager::MASTER_PORT = 27016; +const int NetworkManager::MASTER_RECONNECT_DELAY = 7000; NetworkManager::NetworkManager(AOApplication *parent) : QObject(parent) { @@ -45,7 +42,7 @@ void NetworkManager::connect_to_master_nosrv() SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); QObject::connect(ms_socket, SIGNAL(connected()), this, SLOT(on_ms_nosrv_connect_success())); - ms_socket->connectToHost(ms_nosrv_hostname, ms_port); + ms_socket->connectToHost(MASTER_HOST, MASTER_PORT); } void NetworkManager::connect_to_server(server_type p_server) @@ -101,7 +98,7 @@ void NetworkManager::handle_ms_packet() QStringList packet_list = in_data.split("%", DR::SkipEmptyParts); - for (QString packet : packet_list) + for (const QString &packet : packet_list) { AOPacket *f_packet = new AOPacket(packet); @@ -130,7 +127,7 @@ void NetworkManager::on_ms_socket_error(QAbstractSocket::SocketError error) Q_EMIT ms_connect_finished(false, true); - ms_reconnect_timer->start(ms_reconnect_delay_ms); + ms_reconnect_timer->start(MASTER_RECONNECT_DELAY); } void NetworkManager::retry_ms_connect() @@ -163,7 +160,7 @@ void NetworkManager::handle_server_packet() QStringList packet_list = in_data.split("%", DR::SkipEmptyParts); - for (QString packet : packet_list) + for (const QString &packet : packet_list) { AOPacket *f_packet = new AOPacket(packet); diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 5942cf3f7..30d39ff05 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -27,7 +27,7 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) { server_list.clear(); - for (QString i_string : p_packet->get_contents()) + for (const QString &i_string : p_packet->get_contents()) { server_type f_server; QStringList sub_contents = i_string.split("&"); @@ -488,7 +488,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) { QVector f_evi_list; - for (QString f_string : f_contents) + for (const QString &f_string : f_contents) { QStringList sub_contents = f_string.split("&"); diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 92f1e9632..744ff30aa 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -151,9 +151,9 @@ QString AOApplication::get_case_sensitive_path(QString p_file) * @return The first case-sensitive root+extension path for which a file * exists, or an empty string, if not one does. */ -QString AOApplication::find_asset_path(QStringList p_root_list, QStringList p_ext_list) +QString AOApplication::find_asset_path(QStringList p_file_list, QStringList p_extension_list) { - for (QString &i_root : p_root_list) + for (QString &i_root : p_file_list) { // We can assume that possible_exts will only be populated with hardcoded strings. // Therefore, the only place where sanitize_path could catch something bad is in the root. @@ -165,7 +165,7 @@ QString AOApplication::find_asset_path(QStringList p_root_list, QStringList p_ex if (!dir_exists(QFileInfo(i_root).absolutePath())) continue; - for (QString &i_ext : p_ext_list) + for (QString &i_ext : p_extension_list) { QString full_path = get_case_sensitive_path(i_root + i_ext); if (file_exists(full_path)) @@ -176,6 +176,11 @@ QString AOApplication::find_asset_path(QStringList p_root_list, QStringList p_ex return nullptr; } +QString AOApplication::find_asset_path(QStringList p_file_list) +{ + return find_asset_path(p_file_list, QStringList{""}); +} + /** * @brief Returns the first case-sensitive file in the theme folder that is * of the form name+extension, or empty string if it fails. @@ -197,7 +202,7 @@ QString AOApplication::find_asset_path(QStringList p_root_list, QStringList p_ex * @return The first case-sensitive root+extension path that corresponds to an * actual file, or an empty string, if not one does. */ -QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_ext_list) +QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_extension_list) { QStringList l_path_list; @@ -224,5 +229,10 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_ext_l if (dir_exists(l_default_theme_path)) l_path_list.append(l_default_theme_path + p_file); - return find_asset_path(l_path_list, p_ext_list); + return find_asset_path(l_path_list, p_extension_list); +} + +QString AOApplication::find_theme_asset_path(QString p_file) +{ + return find_theme_asset_path(p_file, QStringList{""}); } From 786c5cc643fec072bc6b0f07668b103ea1127ee0 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 18:48:42 +0200 Subject: [PATCH 395/842] More renaming and reorganization --- include/aoapplication.h | 29 ++++--- include/courtroom.h | 74 +++++++---------- src/aoapplication.cpp | 46 +++++------ src/aocharbutton.cpp | 2 +- src/audio_functions.cpp | 8 +- src/courtroom.cpp | 156 ++++++++++++++++++------------------ src/courtroom_widgets.cpp | 63 +++++++-------- src/packet_distribution.cpp | 150 +++++++++++++++++----------------- src/path_functions.cpp | 2 +- 9 files changed, 252 insertions(+), 278 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 2419caf9b..493605f38 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -200,13 +200,14 @@ public slots: AOConfigPanel *ao_config_panel = nullptr; DRDiscord *dr_discord = nullptr; - NetworkManager *net_manager = nullptr; + NetworkManager *m_network_manager = nullptr; Lobby *m_lobby = nullptr; - bool lobby_constructed = false; + bool is_lobby_constructed = false; Courtroom *m_courtroom = nullptr; - bool courtroom_constructed = false; + bool is_courtroom_constructed = false; + bool is_courtroom_loaded = false; ///////////////server metadata//////////////// #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release @@ -218,21 +219,19 @@ public slots: ///////////////loading info/////////////////// // player number, it's hardly used but might be needed for some old servers - int s_pv = 0; + int m_client_id = 0; - QString server_software; + QString m_server_software; - int char_list_size = 0; - int loaded_chars = 0; - int evidence_list_size = 0; - int loaded_evidence = 0; - int music_list_size = 0; - int loaded_music = 0; + int m_character_count = 0; + int m_loaded_characters = 0; + int m_evidence_count = 0; + int m_loaded_evidence = 0; + int m_music_count = 0; + int m_loaded_music = 0; - bool courtroom_loaded = false; - - QVector server_list; - QVector favorite_list; + QVector m_server_list; + QVector m_favorite_server_list; private slots: void ms_connect_finished(bool connected, bool will_retry); diff --git a/include/courtroom.h b/include/courtroom.h index e4ad0a50d..8b6fb85bc 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -54,7 +54,6 @@ class Courtroom : public QMainWindow ~Courtroom(); void append_char(char_type p_char); - void append_evidence(evi_type p_evi); void set_area_list(QStringList area_list); void set_music_list(QStringList music_list); @@ -219,60 +218,57 @@ class Courtroom : public QMainWindow void closing(); private: + static const int DEFAULT_WIDTH; + static const int DEFAULT_HEIGHT; + AOApplication *ao_app = nullptr; AOConfig *ao_config = nullptr; - QTimer *m_reload_delay = nullptr; - - int m_courtroom_width = 714; - int m_courtroom_height = 668; - int m_viewport_x = 0; - int m_viewport_y = 0; - - int m_viewport_width = 256; - int m_viewport_height = 192; + QTimer *m_reload_timer = nullptr; QVector m_chr_list; QVector m_evidence_list; QStringList m_area_list; QStringList m_music_list; - QVector note_list; QSignalMapper *char_button_mapper = nullptr; // triggers ping_server() every 60 seconds - QTimer *keepalive_timer = nullptr; + QTimer *m_keepalive_timer = nullptr; // maintains a timer for how fast messages tick onto screen - QTimer *chat_tick_timer = nullptr; - std::optional m_server_chat_tick_rate; - int m_chat_tick_speed = 0; + QTimer *m_tick_timer = nullptr; + std::optional m_server_tick_rate; + int m_tick_speed = 0; // which tick position(character in chat message) we are at - int tick_pos = 0; - bool ignore_next_character = false; + int m_tick_step = 0; + bool is_ignore_next_letter = false; // used to determine how often blips sound - int blip_pos = 0; - int rainbow_counter = 0; - bool m_showname_sent = false; - bool rainbow_appended = false; - bool note_shown = false; + int m_blip_step = 0; + int m_rainbow_step = 0; + bool is_showname_sent = false; + bool is_rainbow_enabled = false; + bool is_note_shown = false; bool contains_add_button = false; ////////////// - QScrollArea *note_scroll_area = nullptr; + QScrollArea *ui_note_scroll_area = nullptr; // delay before sfx plays - QTimer *sfx_delay_timer = nullptr; + QTimer *m_sound_timer = nullptr; // keeps track of how long realization is visible(it's just a white square and // should be visible less than a second) - QTimer *realization_timer = nullptr; + QTimer *m_flash_timer = nullptr; // times how long the blinking testimony should be shown(green one in the // corner) - QTimer *testimony_show_timer = nullptr; + static const int TESTIMONY_SHOW_INTERVAL = 1500; + QTimer *m_testimony_show_timer = nullptr; // times how long the blinking testimony should be hidden - QTimer *testimony_hide_timer = nullptr; + static const int TESTIMONY_HIDE_INTERVAL = 500; + QTimer *m_testimony_hide_timer = nullptr; + bool is_testimony_in_progress = false; // Generate a File Name based on the time you launched the client QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); @@ -288,27 +284,15 @@ class Courtroom : public QMainWindow static const QString INI_CONFIG; static const QString INI_SOUNDS; - // every time point in char.inis times this equals the final time - const int time_mod = 40; - - static const int chatmessage_size = 16; - QString m_chatmessage[chatmessage_size]; + static const int MESSAGE_SIZE = 16; + QString m_chatmessage[MESSAGE_SIZE]; bool chatmessage_is_empty = false; QString previous_ic_message; - QColor m_base_string_color; - QString m_string_color; - - QStack m_color_stack; - - bool testimony_in_progress = false; - - // in milliseconds - const int testimony_show_time = 1500; - - // in milliseconds - const int testimony_hide_time = 500; + QColor m_message_color; + QString m_message_color_name; + QStack m_message_color_stack; // char id, muted or not QMap mute_map; @@ -742,12 +726,12 @@ public slots: void stop_all_audio(); private: - bool m_audio_mute = false; AOSfxPlayer *m_effects_player = nullptr; AOShoutPlayer *m_shouts_player = nullptr; AOSystemPlayer *m_system_player = nullptr; AOMusicPlayer *m_music_player = nullptr; AOBlipPlayer *m_blips_player = nullptr; + bool is_audio_muted = false; // QWidget interface protected: diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index dde212fa1..418446cea 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -19,8 +19,8 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) { - net_manager = new NetworkManager(this); - connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), this, SLOT(ms_connect_finished(bool, bool))); + m_network_manager = new NetworkManager(this); + connect(m_network_manager, SIGNAL(ms_connect_finished(bool, bool)), this, SLOT(ms_connect_finished(bool, bool))); ao_config = new AOConfig(this); connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(on_config_theme_changed())); @@ -49,12 +49,12 @@ AOApplication::~AOApplication() int AOApplication::get_client_id() const { - return s_pv; + return m_client_id; } void AOApplication::set_client_id(int id) { - s_pv = id; + m_client_id = id; } Lobby *AOApplication::get_lobby() const @@ -64,14 +64,14 @@ Lobby *AOApplication::get_lobby() const void AOApplication::construct_lobby() { - if (lobby_constructed) + if (is_lobby_constructed) { qDebug() << "W: lobby was attempted constructed when it already exists"; return; } m_lobby = new Lobby(this); - lobby_constructed = true; + is_lobby_constructed = true; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QRect screen_geometry = QApplication::desktop()->screenGeometry(); @@ -93,14 +93,14 @@ void AOApplication::construct_lobby() void AOApplication::destruct_lobby() { - if (!lobby_constructed) + if (!is_lobby_constructed) { qDebug() << "W: lobby was attempted destructed when it did not exist"; return; } delete m_lobby; - lobby_constructed = false; + is_lobby_constructed = false; } Courtroom *AOApplication::get_courtroom() const @@ -110,7 +110,7 @@ Courtroom *AOApplication::get_courtroom() const void AOApplication::construct_courtroom() { - if (courtroom_constructed) + if (is_courtroom_constructed) { qDebug() << "W: courtroom was attempted constructed when it already exists"; return; @@ -119,7 +119,7 @@ void AOApplication::construct_courtroom() m_courtroom = new Courtroom(this); connect(m_courtroom, SIGNAL(closing()), this, SLOT(on_courtroom_closing())); connect(m_courtroom, SIGNAL(destroyed()), this, SLOT(on_courtroom_destroyed())); - courtroom_constructed = true; + is_courtroom_constructed = true; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QRect screen_geometry = QApplication::desktop()->screenGeometry(); @@ -137,10 +137,10 @@ void AOApplication::construct_courtroom() void AOApplication::destruct_courtroom() { // destruct courtroom - if (courtroom_constructed) + if (is_courtroom_constructed) { delete m_courtroom; - courtroom_constructed = false; + is_courtroom_constructed = false; } else { @@ -148,7 +148,7 @@ void AOApplication::destruct_courtroom() } // gracefully close our connection to the current server - net_manager->disconnect_from_server(); + m_network_manager->disconnect_from_server(); } DRDiscord *AOApplication::get_discord() const @@ -158,7 +158,7 @@ DRDiscord *AOApplication::get_discord() const NetworkManager *AOApplication::get_network_manager() { - return net_manager; + return m_network_manager; } bool AOApplication::has_message_acknowledgement_feature() const @@ -203,17 +203,17 @@ void AOApplication::on_config_timeofday_changed() void AOApplication::set_favorite_list() { - favorite_list = read_serverlist_txt(); + m_favorite_server_list = read_serverlist_txt(); } QVector &AOApplication::get_favorite_list() { - return favorite_list; + return m_favorite_server_list; } QString AOApplication::get_current_char() { - if (courtroom_constructed) + if (is_courtroom_constructed) return m_courtroom->get_current_character(); else return ""; @@ -269,10 +269,10 @@ bool AOApplication::get_first_person_enabled() void AOApplication::add_favorite_server(int p_server) { - if (p_server < 0 || p_server >= server_list.size()) + if (p_server < 0 || p_server >= m_server_list.size()) return; - server_type fav_server = server_list.at(p_server); + server_type fav_server = m_server_list.at(p_server); QString str_port = QString::number(fav_server.port); @@ -283,12 +283,12 @@ void AOApplication::add_favorite_server(int p_server) QVector &AOApplication::get_server_list() { - return server_list; + return m_server_list; } void AOApplication::server_disconnected() { - if (!courtroom_constructed) + if (!is_courtroom_constructed) return; m_courtroom->stop_all_audio(); call_notice("Disconnected from server."); @@ -312,14 +312,14 @@ void AOApplication::ms_connect_finished(bool connected, bool will_retry) } else { - if (!lobby_constructed) + if (!is_lobby_constructed) { return; } else if (will_retry) { m_lobby->append_error("Error connecting to master server. Will try again in " + - QString::number(net_manager->MASTER_RECONNECT_DELAY / 1000.f) + " seconds."); + QString::number(m_network_manager->MASTER_RECONNECT_DELAY / 1000.f) + " seconds."); } else { diff --git a/src/aocharbutton.cpp b/src/aocharbutton.cpp index 56d86daf7..ae6ce6520 100644 --- a/src/aocharbutton.cpp +++ b/src/aocharbutton.cpp @@ -34,7 +34,7 @@ void AOCharButton::set_image(QString p_character) { const QString l_image = ao_app->get_character_path(p_character, "char_icon.png"); const bool l_file_exist = file_exists(l_image); - setStyleSheet(l_file_exist ? QString("border-image: url(\"%1);").arg(l_image) : nullptr); + setStyleSheet(l_file_exist ? QString("border-image: url(\"%1\");").arg(l_image) : nullptr); setText(l_file_exist ? nullptr : p_character); } diff --git a/src/audio_functions.cpp b/src/audio_functions.cpp index d852e6bea..e0395508d 100644 --- a/src/audio_functions.cpp +++ b/src/audio_functions.cpp @@ -4,18 +4,18 @@ bool Courtroom::is_audio_suppressed() const { - return m_audio_mute; + return is_audio_muted; } void Courtroom::suppress_audio(bool p_enabled) { - if (m_audio_mute == p_enabled) + if (is_audio_muted == p_enabled) return; - m_audio_mute = p_enabled; + is_audio_muted = p_enabled; // suppress audio for (auto &family : DRAudioEngine::get_family_list()) - family->set_suppressed(m_audio_mute); + family->set_suppressed(is_audio_muted); } void Courtroom::stop_all_audio() diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ef472da10..18f061dbb 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -43,18 +43,21 @@ const QString Courtroom::INI_FONTS = "courtroom_fonts.ini"; const QString Courtroom::INI_CONFIG = "courtroom_config.ini"; const QString Courtroom::INI_SOUNDS = "courtroom_sounds.ini"; +const int Courtroom::DEFAULT_WIDTH = 714; +const int Courtroom::DEFAULT_HEIGHT = 668; + Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; ao_config = new AOConfig(this); - m_reload_delay = new QTimer(this); - m_reload_delay->setInterval(200); - m_reload_delay->setSingleShot(true); - connect(m_reload_delay, SIGNAL(timeout()), this, SLOT(on_app_reload_theme_requested())); - connect(ao_config, SIGNAL(theme_changed(QString)), m_reload_delay, SLOT(start())); - connect(ao_config, SIGNAL(gamemode_changed(QString)), m_reload_delay, SLOT(start())); - connect(ao_config, SIGNAL(timeofday_changed(QString)), m_reload_delay, SLOT(start())); + m_reload_timer = new QTimer(this); + m_reload_timer->setInterval(200); + m_reload_timer->setSingleShot(true); + connect(m_reload_timer, SIGNAL(timeout()), this, SLOT(on_app_reload_theme_requested())); + connect(ao_config, SIGNAL(theme_changed(QString)), m_reload_timer, SLOT(start())); + connect(ao_config, SIGNAL(gamemode_changed(QString)), m_reload_timer, SLOT(start())); + connect(ao_config, SIGNAL(timeofday_changed(QString)), m_reload_timer, SLOT(start())); create_widgets(); connect_widgets(); @@ -74,11 +77,6 @@ void Courtroom::append_char(char_type p_char) m_chr_list.append(p_char); } -void Courtroom::append_evidence(evi_type p_evi) -{ - m_evidence_list.append(p_evi); -} - void Courtroom::set_area_list(QStringList area_list) { m_area_list = area_list; @@ -141,7 +139,7 @@ void Courtroom::enter_courtroom(int p_cid) list_music(); list_areas(); - testimony_in_progress = false; + is_testimony_in_progress = false; set_widget_names(); set_widget_layers(); @@ -276,7 +274,7 @@ void Courtroom::set_window_title(QString p_title) void Courtroom::set_scene() { - if (testimony_in_progress) + if (is_testimony_in_progress) show_testimony(); // witness is default if pos is invalid @@ -371,13 +369,13 @@ void Courtroom::set_taken(int n_char, bool p_taken) void Courtroom::set_background(QString p_background) { - testimony_in_progress = false; + is_testimony_in_progress = false; current_background = p_background; } void Courtroom::set_tick_rate(const std::optional &tick_rate) { - m_server_chat_tick_rate = tick_rate; + m_server_tick_rate = tick_rate; } void Courtroom::handle_music_anim() @@ -607,8 +605,6 @@ void Courtroom::list_note_files() return; } - note_list.clear(); - QString f_filestring = ""; QString f_filename = ""; @@ -723,9 +719,9 @@ void Courtroom::on_ic_message_return_pressed() if ((anim_state < 3 || text_state < 2) && m_shout_state == 0) return; - if (!m_showname_sent) + if (!is_showname_sent) { - m_showname_sent = true; + is_showname_sent = true; send_showname_packet(ao_config->showname()); } @@ -864,7 +860,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) else if (p_contents.size() == 15) p_contents.append(QString()); - for (int i = 0; i < chatmessage_size; ++i) + for (int i = 0; i < MESSAGE_SIZE; ++i) m_chatmessage[i] = p_contents[i]; int f_char_id = m_chatmessage[CMChrId].toInt(); @@ -934,7 +930,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) text_state = 0; anim_state = 0; ui_vp_objection->stop(); - chat_tick_timer->stop(); + m_tick_timer->stop(); ui_vp_evidence_display->reset(); // reset effect @@ -1444,7 +1440,7 @@ void Courtroom::play_preanim() // all time values in char.inis are multiplied by a constant(time_mod) to get // the actual time int sfx_delay = m_chatmessage[CMSoundDelay].toInt() * 60; - sfx_delay_timer->start(sfx_delay); + m_sound_timer->start(sfx_delay); QString f_preanim = m_chatmessage[CMPreAnim]; @@ -1494,7 +1490,7 @@ void Courtroom::setup_chat() ui_vp_message->clear(); set_text_color(); - rainbow_counter = 0; + m_rainbow_step = 0; // we need to ensure that the text isn't already ticking because this function // can be called by two logic paths if (text_state != 0) @@ -1509,10 +1505,10 @@ void Courtroom::setup_chat() ui_vp_chatbox->show(); - m_chat_tick_speed = 0; - tick_pos = 0; - ignore_next_character = false; - blip_pos = 0; + m_tick_speed = 0; + m_tick_step = 0; + is_ignore_next_letter = false; + m_blip_step = 0; // Cache these so chat_tick performs better m_chatbox_message_outline = (ao_app->get_font_property("message_outline", INI_FONTS) == 1); @@ -1531,16 +1527,16 @@ void Courtroom::setup_chat() void Courtroom::start_chat_timer() { double l_tick_rate = ao_config->chat_tick_interval(); - if (m_server_chat_tick_rate.has_value()) - l_tick_rate = qMax(m_server_chat_tick_rate.value(), 0); - l_tick_rate = qBound(l_tick_rate * (1.0 - qBound(0.4 * m_chat_tick_speed, -1.0, 1.0)), 0.0, l_tick_rate * 2.0); - chat_tick_timer->start(l_tick_rate); + if (m_server_tick_rate.has_value()) + l_tick_rate = qMax(m_server_tick_rate.value(), 0); + l_tick_rate = qBound(l_tick_rate * (1.0 - qBound(0.4 * m_tick_speed, -1.0, 1.0)), 0.0, l_tick_rate * 2.0); + m_tick_timer->start(l_tick_rate); } void Courtroom::next_chat_letter() { const QString &f_message = m_chatmessage[CMMessage]; - if (tick_pos >= f_message.length()) + if (m_tick_step >= f_message.length()) { post_chat(); return; @@ -1554,19 +1550,19 @@ void Courtroom::next_chat_letter() else vp_message_format.setTextOutline(Qt::NoPen); - const QChar f_character = f_message.at(tick_pos); - if (!ignore_next_character && f_character == Qt::Key_Backslash) + const QChar f_character = f_message.at(m_tick_step); + if (!is_ignore_next_letter && f_character == Qt::Key_Backslash) { - ++tick_pos; - ignore_next_character = true; + ++m_tick_step; + is_ignore_next_letter = true; next_chat_letter(); return; } - else if (!ignore_next_character && (f_character == Qt::Key_BraceLeft || f_character == Qt::Key_BraceRight)) // { or } + else if (!is_ignore_next_letter && (f_character == Qt::Key_BraceLeft || f_character == Qt::Key_BraceRight)) // { or } { - ++tick_pos; + ++m_tick_step; const bool is_positive = f_character == Qt::Key_BraceRight; - m_chat_tick_speed = qBound(m_chat_tick_speed + (is_positive ? 1 : -1), -3, 3); + m_tick_speed = qBound(m_tick_speed + (is_positive ? 1 : -1), -3, 3); next_chat_letter(); return; } @@ -1578,7 +1574,7 @@ void Courtroom::next_chat_letter() { QString html_color; - switch (rainbow_counter) + switch (m_rainbow_step) { case 0: html_color = "#BA1518"; @@ -1594,10 +1590,10 @@ void Courtroom::next_chat_letter() break; default: html_color = "#1596C8"; - rainbow_counter = -1; + m_rainbow_step = -1; } - ++rainbow_counter; + ++m_rainbow_step; // Apply color to the next character QColor text_color; text_color.setNamedColor(html_color); @@ -1612,15 +1608,15 @@ void Courtroom::next_chat_letter() // render_character should only be false if the character is a highlight // character specifically marked as a character that should not be // rendered. - if (m_color_stack.isEmpty()) - m_color_stack.push(""); + if (m_message_color_stack.isEmpty()) + m_message_color_stack.push(""); for (const auto &col : qAsConst(m_chatbox_message_highlight_colors)) { - if (f_character == col[0][0] && m_string_color != col[1]) + if (f_character == col[0][0] && m_message_color_name != col[1]) { - m_color_stack.push(col[1]); - m_string_color = m_color_stack.top(); + m_message_color_stack.push(col[1]); + m_message_color_name = m_message_color_stack.top(); highlight_found = true; render_character = (col[2] != "0"); break; @@ -1628,24 +1624,24 @@ void Courtroom::next_chat_letter() } // Apply color to the next character - if (m_string_color.isEmpty()) - vp_message_format.setForeground(m_base_string_color); + if (m_message_color_name.isEmpty()) + vp_message_format.setForeground(m_message_color); else { QColor textColor; - textColor.setNamedColor(m_string_color); + textColor.setNamedColor(m_message_color_name); vp_message_format.setForeground(textColor); } - QString m_future_string_color = m_string_color; + QString m_future_string_color = m_message_color_name; for (const auto &col : qAsConst(m_chatbox_message_highlight_colors)) { if (f_character == col[0][1] && !highlight_found) { - if (m_color_stack.size() > 1) - m_color_stack.pop(); - m_future_string_color = m_color_stack.top(); + if (m_message_color_stack.size() > 1) + m_message_color_stack.pop(); + m_future_string_color = m_message_color_stack.top(); render_character = (col[2] != "0"); break; } @@ -1654,7 +1650,7 @@ void Courtroom::next_chat_letter() if (render_character) ui_vp_message->textCursor().insertText(f_character, vp_message_format); - m_string_color = m_future_string_color; + m_message_color_name = m_future_string_color; } else { @@ -1664,31 +1660,31 @@ void Courtroom::next_chat_letter() QScrollBar *scroll = ui_vp_message->verticalScrollBar(); scroll->setValue(scroll->maximum()); - if ((f_message.at(tick_pos) != ' ' || ao_config->blank_blips_enabled())) + if ((f_message.at(m_tick_step) != ' ' || ao_config->blank_blips_enabled())) { - if (blip_pos % ao_config->blip_rate() == 0) + if (m_blip_step % ao_config->blip_rate() == 0) { - blip_pos = 0; + m_blip_step = 0; // play blip m_blips_player->blip_tick(); } - ++blip_pos; + ++m_blip_step; } ui_vp_message->repaint(); - ++tick_pos; - ignore_next_character = false; + ++m_tick_step; + is_ignore_next_letter = false; start_chat_timer(); } void Courtroom::post_chat() { text_state = 2; - chat_tick_timer->stop(); + m_tick_timer->stop(); anim_state = 3; if (m_msg_is_first_person == false) @@ -1696,28 +1692,28 @@ void Courtroom::post_chat() ui_vp_player_char->play_idle(m_chatmessage[CMChrName], m_chatmessage[CMEmote]); } - m_string_color = ""; - m_color_stack.clear(); + m_message_color_name = ""; + m_message_color_stack.clear(); } void Courtroom::show_testimony() { - if (!testimony_in_progress || m_chatmessage[CMPosition] != "wit") + if (!is_testimony_in_progress || m_chatmessage[CMPosition] != "wit") return; ui_vp_testimony->show(); - testimony_show_timer->start(testimony_show_time); + m_testimony_show_timer->start(TESTIMONY_SHOW_INTERVAL); } void Courtroom::hide_testimony() { ui_vp_testimony->hide(); - if (!testimony_in_progress) + if (!is_testimony_in_progress) return; - testimony_hide_timer->start(testimony_hide_time); + m_testimony_hide_timer->start(TESTIMONY_HIDE_INTERVAL); } void Courtroom::play_sfx() @@ -1735,7 +1731,7 @@ void Courtroom::set_text_color() const DR::Color color = DR::Color(m_chatmessage[CMTextColor].toInt()); const QString color_code = color_map[color_map.contains(color) ? color : DR::CDefault].code; ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); - m_base_string_color.setNamedColor(color_code); + m_message_color.setNamedColor(color_code); } void Courtroom::set_ip_list(QString p_list) @@ -1873,10 +1869,10 @@ void Courtroom::handle_wtce(QString p_wtce) ui_vp_wtce->play(wtce_names[index - 1]); if (index == 1) { - testimony_in_progress = true; + is_testimony_in_progress = true; } else if (index == 2) - testimony_in_progress = false; + is_testimony_in_progress = false; } } } @@ -1972,11 +1968,11 @@ void Courtroom::on_ooc_return_pressed() const QString ooc_name = ui_ooc_chat_name->text(); const QString ooc_message = ui_ooc_chat_message->text(); - if (ooc_message.startsWith("/rainbow") && !rainbow_appended) + if (ooc_message.startsWith("/rainbow") && !is_rainbow_enabled) { ui_text_color->addItem("Rainbow"); ui_ooc_chat_message->clear(); - rainbow_appended = true; + is_rainbow_enabled = true; return; } else if (ooc_message.startsWith("/switch_am")) @@ -2579,13 +2575,13 @@ void Courtroom::on_config_panel_clicked() void Courtroom::on_note_button_clicked() { - if (!note_shown) + if (!is_note_shown) { load_note(); ui_vp_notepad_image->show(); ui_vp_notepad->show(); ui_vp_notepad->setFocus(); - note_shown = true; + is_note_shown = true; } else { @@ -2593,7 +2589,7 @@ void Courtroom::on_note_button_clicked() ui_vp_notepad_image->hide(); ui_vp_notepad->hide(); ui_ic_chat_message->setFocus(); - note_shown = false; + is_note_shown = false; } } @@ -2615,10 +2611,10 @@ void Courtroom::closeEvent(QCloseEvent *event) void Courtroom::on_set_notes_clicked() { - if (note_scroll_area->isHidden()) - note_scroll_area->show(); + if (ui_note_scroll_area->isHidden()) + ui_note_scroll_area->show(); else - note_scroll_area->hide(); + ui_note_scroll_area->hide(); } void Courtroom::resume_timer(int p_id) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 7eb7460e4..63160defe 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -36,24 +36,24 @@ void Courtroom::create_widgets() { - keepalive_timer = new QTimer(this); - keepalive_timer->start(60000); + m_keepalive_timer = new QTimer(this); + m_keepalive_timer->start(60000); - chat_tick_timer = new QTimer(this); - chat_tick_timer->setSingleShot(true); - chat_tick_timer->setTimerType(Qt::PreciseTimer); // Prevents drift + m_tick_timer = new QTimer(this); + m_tick_timer->setSingleShot(true); + m_tick_timer->setTimerType(Qt::PreciseTimer); // Prevents drift - sfx_delay_timer = new QTimer(this); - sfx_delay_timer->setSingleShot(true); + m_sound_timer = new QTimer(this); + m_sound_timer->setSingleShot(true); - realization_timer = new QTimer(this); - realization_timer->setSingleShot(true); + m_flash_timer = new QTimer(this); + m_flash_timer->setSingleShot(true); - testimony_show_timer = new QTimer(this); - testimony_show_timer->setSingleShot(true); + m_testimony_show_timer = new QTimer(this); + m_testimony_show_timer->setSingleShot(true); - testimony_hide_timer = new QTimer(this); - testimony_hide_timer->setSingleShot(true); + m_testimony_hide_timer = new QTimer(this); + m_testimony_hide_timer->setSingleShot(true); char_button_mapper = new QSignalMapper(this); @@ -149,11 +149,11 @@ void Courtroom::create_widgets() ui_note_area = new AONoteArea(this, ao_app); ui_note_area->add_button = new AOButton(ui_note_area, ao_app); ui_note_area->m_layout = new QVBoxLayout(ui_note_area); - note_scroll_area = new QScrollArea(this); + ui_note_scroll_area = new QScrollArea(this); - note_scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - note_scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - note_scroll_area->setWidgetResizable(false); + ui_note_scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + ui_note_scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_note_scroll_area->setWidgetResizable(false); ui_set_notes = new AOButton(this, ao_app); @@ -246,21 +246,21 @@ void Courtroom::create_widgets() void Courtroom::connect_widgets() { - connect(keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); + connect(m_keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); connect(ao_app, SIGNAL(reload_theme()), this, SLOT(on_app_reload_theme_requested())); connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); - connect(sfx_delay_timer, SIGNAL(timeout()), this, SLOT(play_sfx())); + connect(m_sound_timer, SIGNAL(timeout()), this, SLOT(play_sfx())); - connect(chat_tick_timer, SIGNAL(timeout()), this, SLOT(next_chat_letter())); + connect(m_tick_timer, SIGNAL(timeout()), this, SLOT(next_chat_letter())); - connect(realization_timer, SIGNAL(timeout()), this, SLOT(realization_done())); + connect(m_flash_timer, SIGNAL(timeout()), this, SLOT(realization_done())); - connect(testimony_show_timer, SIGNAL(timeout()), this, SLOT(hide_testimony())); - connect(testimony_hide_timer, SIGNAL(timeout()), this, SLOT(show_testimony())); + connect(m_testimony_show_timer, SIGNAL(timeout()), this, SLOT(hide_testimony())); + connect(m_testimony_hide_timer, SIGNAL(timeout()), this, SLOT(show_testimony())); connect(ui_emote_left, SIGNAL(clicked()), this, SLOT(on_emote_left_clicked())); connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); @@ -379,7 +379,7 @@ void Courtroom::reset_widget_names() {"ooc_chat_name", ui_ooc_chat_name}, {"music_search", ui_music_search}, {"sfx_search", ui_sfx_search}, - {"note_scroll_area", note_scroll_area}, + {"note_scroll_area", ui_note_scroll_area}, {"note_area", ui_note_area}, // add_button // m_layout @@ -566,18 +566,15 @@ void Courtroom::set_widgets() { qDebug() << "W: did not find courtroom width or height in " << filename; - this->resize(714, 668); + resize(DEFAULT_WIDTH, DEFAULT_HEIGHT); } else { - m_courtroom_width = f_courtroom.width; - m_courtroom_height = f_courtroom.height; - - this->resize(f_courtroom.width, f_courtroom.height); + resize(f_courtroom.width, f_courtroom.height); } ui_background->move(0, 0); - ui_background->resize(m_courtroom_width, m_courtroom_height); + ui_background->resize(size()); ui_background->set_image("courtroombackground.png"); set_size_and_pos(ui_viewport, "viewport", INI_DESIGN, ao_app); @@ -978,14 +975,14 @@ void Courtroom::set_widgets() ui_set_notes->set_image("set_notes.png"); ui_note_area->m_layout->setSpacing(10); set_size_and_pos(ui_note_area, "note_area", INI_DESIGN, ao_app); - set_size_and_pos(note_scroll_area, "note_area", INI_DESIGN, ao_app); - note_scroll_area->setWidget(ui_note_area); + set_size_and_pos(ui_note_scroll_area, "note_area", INI_DESIGN, ao_app); + ui_note_scroll_area->setWidget(ui_note_area); ui_note_area->set_image("note_area.png"); ui_note_area->add_button->set_image("add_button.png"); ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); ui_note_area->setLayout(ui_note_area->m_layout); ui_note_area->show(); - note_scroll_area->hide(); + ui_note_scroll_area->hide(); list_note_files(); diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index 30d39ff05..f960d63a4 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -25,7 +25,7 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) if (header == "ALL") { - server_list.clear(); + m_server_list.clear(); for (const QString &i_string : p_packet->get_contents()) { @@ -43,10 +43,10 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) f_server.ip = sub_contents.at(2); f_server.port = sub_contents.at(3).toInt(); - server_list.append(f_server); + m_server_list.append(f_server); } - if (lobby_constructed) + if (is_lobby_constructed) { m_lobby->list_servers(); } @@ -68,7 +68,7 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) else goto end; - if (lobby_constructed) + if (is_lobby_constructed) { m_lobby->append_chatmessage(f_name, f_message); } @@ -150,8 +150,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (f_contents.size() < 2) goto end; - s_pv = f_contents.at(0).toInt(); - server_software = f_contents.at(1); + m_client_id = f_contents.at(0).toInt(); + m_server_software = f_contents.at(1); send_server_packet(new AOPacket("ID#DRO#" + get_version_string() + "#%")); } @@ -160,7 +160,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (f_contents.size() < 2) goto end; - if (courtroom_constructed) + if (is_courtroom_constructed) m_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1)); } else if (header == "FL") @@ -184,20 +184,20 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (f_contents.size() != 3) goto end; - char_list_size = f_contents.at(0).toInt(); - evidence_list_size = f_contents.at(1).toInt(); - music_list_size = f_contents.at(2).toInt(); + m_character_count = f_contents.at(0).toInt(); + m_evidence_count = f_contents.at(1).toInt(); + m_music_count = f_contents.at(2).toInt(); - if (char_list_size < 1 || evidence_list_size < 0 || music_list_size < 0) + if (m_character_count < 1 || m_evidence_count < 0 || m_music_count < 0) goto end; - loaded_chars = 0; - loaded_evidence = 0; - loaded_music = 0; + m_loaded_characters = 0; + m_loaded_evidence = 0; + m_loaded_music = 0; construct_courtroom(); - courtroom_loaded = false; + is_courtroom_loaded = false; QString window_title = "Danganronpa Online"; int selected_server = m_lobby->get_selected_server(); @@ -206,9 +206,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) bool is_favorite = false; if (m_lobby->is_public_server()) { - if (selected_server >= 0 && selected_server < server_list.size()) + if (selected_server >= 0 && selected_server < m_server_list.size()) { - auto info = server_list.at(selected_server); + auto info = m_server_list.at(selected_server); server_name = info.name; server_address = info.ip + info.port; window_title += ": " + server_name; @@ -216,9 +216,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else { - if (selected_server >= 0 && selected_server < favorite_list.size()) + if (selected_server >= 0 && selected_server < m_favorite_server_list.size()) { - auto info = favorite_list.at(selected_server); + auto info = m_favorite_server_list.at(selected_server); server_name = info.name; server_address = info.ip + info.port; is_favorite = true; @@ -240,7 +240,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // look for the server inside the known public list and report it if (is_favorite) { - for (server_type &server : server_list) + for (server_type &server : m_server_list) { const QString l_address = server.ip + server.port; if (server_address == l_address) @@ -255,12 +255,12 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "CI") { - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; for (int n_element = 0; n_element < f_contents.size(); n_element += 2) { - if (f_contents.at(n_element).toInt() != loaded_chars) + if (f_contents.at(n_element).toInt() != m_loaded_characters) break; // this means we are on the last element and checking n + 1 element will @@ -279,28 +279,28 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // temporary. the CharsCheck packet sets this properly f_char.taken = false; - ++loaded_chars; + ++m_loaded_characters; - m_lobby->set_loading_text("Loading chars:\n" + QString::number(loaded_chars) + "/" + - QString::number(char_list_size)); + m_lobby->set_loading_text("Loading chars:\n" + QString::number(m_loaded_characters) + "/" + + QString::number(m_character_count)); m_courtroom->append_char(f_char); } - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = (loaded_chars / static_cast(total_loading_size)) * 100; + int total_loading_size = m_character_count + m_evidence_count + m_music_count; + int loading_value = (m_loaded_characters / static_cast(total_loading_size)) * 100; m_lobby->set_loading_value(loading_value); send_server_packet(new AOPacket("RE#%")); } else if (header == "EI") { - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; // +1 because evidence starts at 1 rather than 0 for whatever reason // enjoy fanta - if (f_contents.at(0).toInt() != loaded_evidence + 1) + if (f_contents.at(0).toInt() != m_loaded_evidence + 1) goto end; if (f_contents.size() < 2) @@ -316,23 +316,21 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // no idea what the number at position 2 is. probably an identifier? f_evi.image = sub_elements.at(3); - ++loaded_evidence; + ++m_loaded_evidence; - m_lobby->set_loading_text("Loading evidence:\n" + QString::number(loaded_evidence) + "/" + - QString::number(evidence_list_size)); + m_lobby->set_loading_text("Loading evidence:\n" + QString::number(m_loaded_evidence) + "/" + + QString::number(m_evidence_count)); - m_courtroom->append_evidence(f_evi); - - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = ((loaded_chars + loaded_evidence) / static_cast(total_loading_size)) * 100; + int total_loading_size = m_character_count + m_evidence_count + m_music_count; + int loading_value = ((m_loaded_characters + m_loaded_evidence) / static_cast(total_loading_size)) * 100; m_lobby->set_loading_value(loading_value); - QString next_packet_number = QString::number(loaded_evidence); + QString next_packet_number = QString::number(m_loaded_evidence); send_server_packet(new AOPacket("AE#" + next_packet_number + "#%")); } else if (header == "CharsCheck") { - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; for (int n_char = 0; n_char < f_contents.size(); ++n_char) @@ -346,7 +344,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) else if (header == "SC") { - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; for (int n_element = 0; n_element < f_contents.size(); ++n_element) @@ -361,23 +359,23 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // temporary. the CharsCheck packet sets this properly f_char.taken = false; - ++loaded_chars; + ++m_loaded_characters; - m_lobby->set_loading_text("Loading chars:\n" + QString::number(loaded_chars) + "/" + - QString::number(char_list_size)); + m_lobby->set_loading_text("Loading chars:\n" + QString::number(m_loaded_characters) + "/" + + QString::number(m_character_count)); m_courtroom->append_char(f_char); } - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = (loaded_chars / static_cast(total_loading_size)) * 100; + int total_loading_size = m_character_count + m_evidence_count + m_music_count; + int loading_value = (m_loaded_characters / static_cast(total_loading_size)) * 100; m_lobby->set_loading_value(loading_value); send_server_packet(new AOPacket("RM#%")); } else if (header == "SM" || header == "FM") { - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; QStringList l_area_list; @@ -409,24 +407,24 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (header == "SM") { - loaded_music = music_list_size; - m_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + - QString::number(music_list_size)); - int total_loading_size = char_list_size + evidence_list_size + music_list_size; + m_loaded_music = m_music_count; + m_lobby->set_loading_text("Loading music:\n" + QString::number(m_loaded_music) + "/" + + QString::number(m_music_count)); + int total_loading_size = m_character_count + m_evidence_count + m_music_count; int loading_value = - ((loaded_chars + loaded_evidence + loaded_music) / static_cast(total_loading_size)) * 100; + ((m_loaded_characters + m_loaded_evidence + m_loaded_music) / static_cast(total_loading_size)) * 100; m_lobby->set_loading_value(loading_value); send_server_packet(new AOPacket("RD#%")); } } else if (header == "DONE") { - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; m_courtroom->done_received(); - courtroom_loaded = true; + is_courtroom_loaded = true; destruct_lobby(); } @@ -435,7 +433,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (f_contents.size() < 1) goto end; - if (courtroom_constructed) + if (is_courtroom_constructed) { m_courtroom->set_background(f_contents.at(0)); m_courtroom->set_scene(); @@ -443,7 +441,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "chat_tick_rate") { - if (courtroom_constructed) + if (is_courtroom_constructed) m_courtroom->set_tick_rate(f_contents.isEmpty() ? std::nullopt : std::optional(f_contents.at(0).toInt())); } // server accepting char request(CC) packet @@ -452,39 +450,39 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (f_contents.size() < 3) goto end; - if (courtroom_constructed) + if (is_courtroom_constructed) m_courtroom->enter_courtroom(f_contents.at(2).toInt()); } else if (header == "MS") { - if (courtroom_constructed && courtroom_loaded) + if (is_courtroom_constructed && is_courtroom_loaded) m_courtroom->handle_chatmessage(p_packet->get_contents()); } else if (header == "ackMS") { - if (courtroom_constructed && courtroom_loaded) + if (is_courtroom_constructed && is_courtroom_loaded) m_courtroom->handle_acknowledged_ms(); } else if (header == "MC") { - if (courtroom_constructed && courtroom_loaded) + if (is_courtroom_constructed && is_courtroom_loaded) m_courtroom->handle_song(p_packet->get_contents()); } else if (header == "RT") { if (f_contents.size() < 1) goto end; - if (courtroom_constructed) + if (is_courtroom_constructed) m_courtroom->handle_wtce(f_contents.at(0)); } else if (header == "HP") { - if (courtroom_constructed && f_contents.size() > 1) + if (is_courtroom_constructed && f_contents.size() > 1) m_courtroom->set_hp_bar(f_contents.at(0).toInt(), f_contents.at(1).toInt()); } else if (header == "LE") { - if (courtroom_constructed) + if (is_courtroom_constructed) { QVector f_evi_list; @@ -508,22 +506,22 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "IL") { - if (courtroom_constructed && f_contents.size() > 0) + if (is_courtroom_constructed && f_contents.size() > 0) m_courtroom->set_ip_list(f_contents.at(0)); } else if (header == "MU") { - if (courtroom_constructed && f_contents.size() > 0) + if (is_courtroom_constructed && f_contents.size() > 0) m_courtroom->set_mute(true, f_contents.at(0).toInt()); } else if (header == "UM") { - if (courtroom_constructed && f_contents.size() > 0) + if (is_courtroom_constructed && f_contents.size() > 0) m_courtroom->set_mute(false, f_contents.at(0).toInt()); } else if (header == "KK") { - if (courtroom_constructed && f_contents.size() > 0) + if (is_courtroom_constructed && f_contents.size() > 0) { int f_cid = m_courtroom->get_character_id(); int remote_cid = f_contents.at(0).toInt(); @@ -538,7 +536,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "KB") { - if (courtroom_constructed && f_contents.size() > 0) + if (is_courtroom_constructed && f_contents.size() > 0) m_courtroom->set_ban(f_contents.at(0).toInt()); } else if (header == "BD") @@ -547,7 +545,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "ZZ") { - if (courtroom_constructed && f_contents.size() > 0) + if (is_courtroom_constructed && f_contents.size() > 0) m_courtroom->mod_called(f_contents.at(0)); } else if (header == "CL") @@ -576,7 +574,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Timer resume if (f_contents.size() != 1) goto end; - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; int timer_id = f_contents.at(0).toInt(); m_courtroom->resume_timer(timer_id); @@ -586,7 +584,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Timer set time if (f_contents.size() != 2) goto end; - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; int timer_id = f_contents.at(0).toInt(); int new_time = f_contents.at(1).toInt(); @@ -597,7 +595,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Timer set timeStep length if (f_contents.size() != 2) goto end; - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; int timer_id = f_contents.at(0).toInt(); int timestep_length = f_contents.at(1).toInt(); @@ -608,7 +606,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Timer set Firing interval if (f_contents.size() != 2) goto end; - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; int timer_id = f_contents.at(0).toInt(); int firing_interval = f_contents.at(1).toInt(); @@ -619,7 +617,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Timer pause if (f_contents.size() != 1) goto end; - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; int timer_id = f_contents.at(0).toInt(); m_courtroom->pause_timer(timer_id); @@ -629,7 +627,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // Set position if (f_contents.size() != 1) goto end; - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; m_courtroom->set_character_position(f_contents.at(0)); } @@ -639,7 +637,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) // This has priority over user-set showname. if (f_contents.size() != 1) goto end; - if (!courtroom_constructed) + if (!is_courtroom_constructed) goto end; const QString l_showname = f_contents.at(0); // By updating now, we prevent the client from sending an SN back when we later on go ahead and modify the @@ -658,7 +656,7 @@ void AOApplication::send_ms_packet(AOPacket *p_packet) QString f_packet = p_packet->to_string(); - net_manager->ship_ms_packet(f_packet); + m_network_manager->ship_ms_packet(f_packet); qDebug() << "S(ms):" << f_packet; @@ -673,7 +671,7 @@ void AOApplication::send_server_packet(AOPacket *p_packet, bool encoded) QString f_packet = p_packet->to_string(); qDebug() << "S:" << f_packet; - net_manager->ship_server_packet(f_packet); + m_network_manager->ship_server_packet(f_packet); delete p_packet; } diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 744ff30aa..207fa8697 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -50,7 +50,7 @@ QString AOApplication::get_music_path(QString p_song) QString AOApplication::get_background_path(QString p_file) { - if (courtroom_constructed) + if (is_courtroom_constructed) { return get_case_sensitive_path(m_courtroom->get_background_path(p_file)); } From 73f1429ee0ad49aa9fba7f0abbdc74f49a640762 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 22:40:49 +0200 Subject: [PATCH 396/842] Replaced NetworkManager with individual sockets Next: remove packet pointers --- dronline-client.pro | 7 +- include/aoapplication.h | 29 +- include/aopacket.h | 2 +- include/datatypes.h | 16 + include/drserversocket.h | 52 ++ include/networkmanager.h | 58 --- src/aoapplication.cpp | 68 +-- src/aopacket.cpp | 2 +- src/courtroom.cpp | 4 +- src/drdiscord.cpp | 4 +- src/drserversocket.cpp | 116 +++++ src/lobby.cpp | 10 +- src/main.cpp | 4 - src/master_socket.cpp | 132 +++++ src/networkmanager.cpp | 169 ------- ...ket_distribution.cpp => server_socket.cpp} | 456 +++++++----------- 16 files changed, 538 insertions(+), 591 deletions(-) create mode 100644 include/drserversocket.h delete mode 100644 include/networkmanager.h create mode 100644 src/drserversocket.cpp create mode 100644 src/master_socket.cpp delete mode 100644 src/networkmanager.cpp rename src/{packet_distribution.cpp => server_socket.cpp} (51%) diff --git a/dronline-client.pro b/dronline-client.pro index 06d90ce5c..383efb06c 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -55,13 +55,13 @@ HEADERS += \ include/draudiostream.h \ include/draudiostreamfamily.h \ include/drpather.h \ + include/drserversocket.h \ include/drtextedit.h \ include/drdiscord.h \ include/file_functions.h \ include/hardware_functions.h \ include/lobby.h \ include/misc_functions.h \ - include/networkmanager.h \ include/theme.h \ include/version.h @@ -109,6 +109,7 @@ SOURCES += \ src/draudiostream.cpp \ src/draudiostreamfamily.cpp \ src/drpather.cpp \ + src/drserversocket.cpp \ src/drtextedit.cpp \ src/drdiscord.cpp \ src/emotes.cpp \ @@ -117,10 +118,10 @@ SOURCES += \ src/hardware_functions.cpp \ src/lobby.cpp \ src/main.cpp \ + src/master_socket.cpp \ src/misc_functions.cpp \ - src/networkmanager.cpp \ - src/packet_distribution.cpp \ src/path_functions.cpp \ + src/server_socket.cpp \ src/text_file_functions.cpp \ src/theme.cpp \ src/version.cpp diff --git a/include/aoapplication.h b/include/aoapplication.h index 493605f38..b9d879bc9 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -1,15 +1,15 @@ #ifndef AOAPPLICATION_H #define AOAPPLICATION_H +#include "aopacket.h" #include "datatypes.h" class AOConfig; class AOConfigPanel; -class AOPacket; class Courtroom; class DRDiscord; +class DRServerSocket; class Lobby; -class NetworkManager; #include #include @@ -19,12 +19,21 @@ class AOApplication : public QApplication Q_OBJECT public: + static const QString MASTER_HOST; + static const int MASTER_PORT; + static const int MASTER_RECONNECT_DELAY; + AOApplication(int &argc, char **argv); ~AOApplication(); int get_client_id() const; void set_client_id(int id); + void send_master_packet(AOPacket *packet); + void request_server_list(); + void connect_to_server(server_type server); + void send_server_packet(AOPacket *packet); + Lobby *get_lobby() const; void construct_lobby(); void destruct_lobby(); @@ -35,19 +44,11 @@ class AOApplication : public QApplication DRDiscord *get_discord() const; - NetworkManager *get_network_manager(); - bool has_message_acknowledgement_feature() const; bool has_character_declaration_feature() const; bool has_showname_declaration_feature() const; bool has_chat_speed_feature() const; - void ms_packet_received(AOPacket *p_packet); - void server_packet_received(AOPacket *p_packet); - - void send_ms_packet(AOPacket *p_packet); - void send_server_packet(AOPacket *p_packet, bool encoded = true); - /////////////////////////////////////////// void set_favorite_list(); @@ -200,7 +201,8 @@ public slots: AOConfigPanel *ao_config_panel = nullptr; DRDiscord *dr_discord = nullptr; - NetworkManager *m_network_manager = nullptr; + DRServerSocket *m_master_socket = nullptr; + DRServerSocket *m_server_socket = nullptr; Lobby *m_lobby = nullptr; bool is_lobby_constructed = false; @@ -234,7 +236,10 @@ public slots: QVector m_favorite_server_list; private slots: - void ms_connect_finished(bool connected, bool will_retry); + void _p_send_master_handshake(); + void _p_handle_master_error(QString); + void _p_handle_master_packet(AOPacket); + void _p_handle_server_packet(AOPacket); void on_courtroom_closing(); void on_courtroom_destroyed(); void on_config_theme_changed(); diff --git a/include/aopacket.h b/include/aopacket.h index 6889fb6d5..481af8708 100644 --- a/include/aopacket.h +++ b/include/aopacket.h @@ -8,7 +8,7 @@ class AOPacket { public: AOPacket(QString p_packet_string); - AOPacket(QString header, QStringList &p_contents); + AOPacket(QString header, const QStringList &p_contents); QString get_header(); QStringList &get_contents(); diff --git a/include/datatypes.h b/include/datatypes.h index 0a8550fb6..4717c48cb 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -98,6 +98,22 @@ struct server_type QString desc; QString ip; int port; + + QString to_info() const + { + QString r_info; + + if (!name.isEmpty()) + { + r_info = name; + } + else if (!ip.isEmpty()) + { + r_info = ip + ":" + QString::number(port); + } + + return r_info; + } }; struct char_type diff --git a/include/drserversocket.h b/include/drserversocket.h new file mode 100644 index 000000000..a05e0b633 --- /dev/null +++ b/include/drserversocket.h @@ -0,0 +1,52 @@ +#pragma once + +#include "aopacket.h" +#include "datatypes.h" + +#include +#include + +class QTcpSocket; +class QTimer; + +class DRServerSocket : public QObject +{ + Q_OBJECT + +public: + DRServerSocket(QObject *parent = nullptr); + + bool is_connected() const; + void connect_to_server(server_type server, bool is_reconnectable); + void disconnect_from_server(); + +public slots: + void send_packet(AOPacket packet); + inline void send_packet(AOPacket *packet) + { + send_packet(*packet); + } + +signals: + void connected_to_server(); + void disconnected_from_server(); + void reconnecting_to_server(); + void packet_received(AOPacket); + void socket_error(QString); + +private: + static const int RECONNECT_DELAY; + + server_type m_server; + QTcpSocket *m_socket = nullptr; + bool m_is_connected = false; + QTimer *m_reconnect_timer = nullptr; + bool is_reconnectable = false; + QString m_data; + +private slots: + void _p_update_state(QAbstractSocket::SocketState); + void _p_reconnect_to_server(); + void _p_check_socket_error(); + void _p_read_socket(); +}; diff --git a/include/networkmanager.h b/include/networkmanager.h deleted file mode 100644 index ad1cfeb5c..000000000 --- a/include/networkmanager.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef NETWORKMANAGER_H -#define NETWORKMANAGER_H - -#include "datatypes.h" - -class AOApplication; - -#include -#include - -class QTcpSocket; -class QTimer; - -class NetworkManager : public QObject -{ - Q_OBJECT - -public: - static const QString MASTER_HOST; - static const int MASTER_PORT; - static const int MASTER_RECONNECT_DELAY; - - NetworkManager(AOApplication *parent); - ~NetworkManager(); - - void connect_to_master(); - void connect_to_master_nosrv(); - void connect_to_server(server_type p_server); - void disconnect_from_server(); - -public slots: - void ship_ms_packet(QString p_packet); - void ship_server_packet(QString p_packet); - -signals: - void ms_connect_finished(bool success, bool will_retry); - -private: - AOApplication *ao_app = nullptr; - QTcpSocket *ms_socket = nullptr; - QTcpSocket *server_socket = nullptr; - QTimer *ms_reconnect_timer = nullptr; - - bool ms_partial_packet = false; - QString ms_temp_packet; - - bool partial_packet = false; - QString temp_packet; - -private slots: - void handle_ms_packet(); - void handle_server_packet(); - void on_ms_nosrv_connect_success(); - void on_ms_socket_error(QAbstractSocket::SocketError error); - void retry_ms_connect(); -}; - -#endif // NETWORKMANAGER_H diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 418446cea..fd6827cec 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -6,8 +6,8 @@ #include "courtroom.h" #include "debug_functions.h" #include "drdiscord.h" +#include "drserversocket.h" #include "lobby.h" -#include "networkmanager.h" #include @@ -17,28 +17,45 @@ #include #endif +const QString AOApplication::MASTER_HOST = "master.aceattorneyonline.com"; +const int AOApplication::MASTER_PORT = 27016; +const int AOApplication::MASTER_RECONNECT_DELAY = 5000; + AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) { - m_network_manager = new NetworkManager(this); - connect(m_network_manager, SIGNAL(ms_connect_finished(bool, bool)), this, SLOT(ms_connect_finished(bool, bool))); - ao_config = new AOConfig(this); + ao_config_panel = new AOConfigPanel(this); + dr_discord = new DRDiscord(this); + m_master_socket = new DRServerSocket(this); + m_server_socket = new DRServerSocket(this); + connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(on_config_theme_changed())); connect(ao_config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_config_gamemode_changed())); connect(ao_config, SIGNAL(timeofday_changed(QString)), this, SLOT(on_config_timeofday_changed())); - ao_config_panel = new AOConfigPanel(this); connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(on_config_reload_theme_requested())); connect(this, SIGNAL(reload_theme()), ao_config_panel, SLOT(on_config_reload_theme_requested())); ao_config_panel->hide(); - dr_discord = new DRDiscord(this); dr_discord->set_presence(ao_config->discord_presence()); dr_discord->set_hide_server(ao_config->discord_hide_server()); dr_discord->set_hide_character(ao_config->discord_hide_character()); connect(ao_config, SIGNAL(discord_presence_changed(bool)), dr_discord, SLOT(set_presence(bool))); connect(ao_config, SIGNAL(discord_hide_server_changed(bool)), dr_discord, SLOT(set_hide_server(bool))); connect(ao_config, SIGNAL(discord_hide_character_changed(bool)), dr_discord, SLOT(set_hide_character(bool))); + + connect(m_master_socket, SIGNAL(connected_to_server()), this, SLOT(_p_send_master_handshake())); + connect(m_master_socket, SIGNAL(socket_error(QString)), this, SLOT(_p_handle_master_error(QString))); + connect(m_master_socket, SIGNAL(packet_received(AOPacket)), this, SLOT(_p_handle_master_packet(AOPacket))); + + connect(m_server_socket, SIGNAL(connected_to_server()), this, SLOT(_p_send_master_handshake())); + connect(m_server_socket, SIGNAL(packet_received(AOPacket)), this, SLOT(_p_handle_server_packet(AOPacket))); + + server_type l_server; + l_server.name = "Master Server"; + l_server.ip = MASTER_HOST; + l_server.port = MASTER_PORT; + m_master_socket->connect_to_server(l_server, true); } AOApplication::~AOApplication() @@ -148,7 +165,7 @@ void AOApplication::destruct_courtroom() } // gracefully close our connection to the current server - m_network_manager->disconnect_from_server(); + m_server_socket->disconnect_from_server(); } DRDiscord *AOApplication::get_discord() const @@ -156,11 +173,6 @@ DRDiscord *AOApplication::get_discord() const return dr_discord; } -NetworkManager *AOApplication::get_network_manager() -{ - return m_network_manager; -} - bool AOApplication::has_message_acknowledgement_feature() const { return feature_ackMS; @@ -303,38 +315,6 @@ void AOApplication::loading_cancelled() m_lobby->hide_loading_overlay(); } -void AOApplication::ms_connect_finished(bool connected, bool will_retry) -{ - if (connected) - { - AOPacket *f_packet = new AOPacket("ALL#%"); - send_ms_packet(f_packet); - } - else - { - if (!is_lobby_constructed) - { - return; - } - else if (will_retry) - { - m_lobby->append_error("Error connecting to master server. Will try again in " + - QString::number(m_network_manager->MASTER_RECONNECT_DELAY / 1000.f) + " seconds."); - } - else - { - call_error("There was an error connecting to the master server.\n" - "We deploy multiple master servers to mitigate any possible " - "downtime, " - "but the client appears to have exhausted all possible " - "methods of finding " - "and connecting to one.\n" - "Please check your Internet connection and firewall, and " - "please try again."); - } - } -} - void AOApplication::on_courtroom_closing() { ao_config_panel->hide(); diff --git a/src/aopacket.cpp b/src/aopacket.cpp index 14ad566a0..dd73d2b7e 100644 --- a/src/aopacket.cpp +++ b/src/aopacket.cpp @@ -14,7 +14,7 @@ AOPacket::AOPacket(QString p_packet_string) } } -AOPacket::AOPacket(QString p_header, QStringList &p_contents) +AOPacket::AOPacket(QString p_header, const QStringList &p_contents) { m_header = p_header; m_contents = p_contents; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 18f061dbb..e66c21d7d 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2136,7 +2136,7 @@ void Courtroom::on_music_list_double_clicked(QModelIndex p_model) QString p_song = ui_music_list->item(p_model.row())->text(); - ao_app->send_server_packet(new AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%"), false); + ao_app->send_server_packet(new AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%")); ui_ic_chat_message->setFocus(); } @@ -2145,7 +2145,7 @@ void Courtroom::on_area_list_double_clicked(QModelIndex p_model) { QString p_area = ui_area_list->item(p_model.row())->text(); - ao_app->send_server_packet(new AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%"), false); + ao_app->send_server_packet(new AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%")); ui_ic_chat_message->setFocus(); } diff --git a/src/drdiscord.cpp b/src/drdiscord.cpp index 19b13f6eb..b3b726881 100644 --- a/src/drdiscord.cpp +++ b/src/drdiscord.cpp @@ -33,8 +33,8 @@ DRDiscord::DRDiscord(QObject *f_parent) : QObject(f_parent) m_waiter->setInterval(std::chrono::seconds(1)); m_waiter->setSingleShot(true); connect(m_waiter, SIGNAL(timeout()), this, SLOT(on_update_queued())); - connect(this, SIGNAL(options_changed(Options)), m_waiter, SLOT(start())); - connect(this, SIGNAL(state_changed(State)), m_waiter, SLOT(start())); + connect(this, SIGNAL(options_changed(DRDiscord::Options)), m_waiter, SLOT(start())); + connect(this, SIGNAL(state_changed(DRDiscord::State)), m_waiter, SLOT(start())); connect(this, SIGNAL(server_name_changed(QString)), m_waiter, SLOT(start())); connect(this, SIGNAL(server_name_cleared()), m_waiter, SLOT(start())); connect(this, SIGNAL(character_name_changed(QString)), m_waiter, SLOT(start())); diff --git a/src/drserversocket.cpp b/src/drserversocket.cpp new file mode 100644 index 000000000..700cb8ca2 --- /dev/null +++ b/src/drserversocket.cpp @@ -0,0 +1,116 @@ +#include "drserversocket.h" + +#include +#include + +const int DRServerSocket::RECONNECT_DELAY = 5000; + +namespace +{ +QString drFormatServerInfo(const server_type &server) +{ + const QString l_server_info = server.to_info(); + return !l_server_info.isEmpty() ? QString("<%1>").arg(l_server_info) : nullptr; +} +} // namespace + +DRServerSocket::DRServerSocket(QObject *p_parent) : QObject(p_parent) +{ + m_socket = new QTcpSocket(this); + m_reconnect_timer = new QTimer(this); + + m_reconnect_timer->setSingleShot(false); + m_reconnect_timer->setInterval(RECONNECT_DELAY); + + connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(_p_check_socket_error())); + connect(m_socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, + SLOT(_p_update_state(QAbstractSocket::SocketState))); + connect(m_socket, SIGNAL(readyRead()), this, SLOT(_p_read_socket())); + + connect(m_reconnect_timer, SIGNAL(timeout()), this, SLOT(_p_reconnect_to_server())); +} + +bool DRServerSocket::is_connected() const +{ + return m_socket->state() == QTcpSocket::ConnectedState; +} + +void DRServerSocket::connect_to_server(server_type p_server, bool p_is_reconnectable) +{ + disconnect_from_server(); + m_server = p_server; + is_reconnectable = p_is_reconnectable; + if (is_reconnectable) + m_reconnect_timer->start(); + m_socket->connectToHost(p_server.ip, p_server.port); +} + +void DRServerSocket::disconnect_from_server() +{ + m_socket->close(); + m_socket->abort(); +} + +void DRServerSocket::send_packet(AOPacket p_packet) +{ + if (!is_connected()) + { + const QString l_server_info = m_server.to_info(); + qWarning() << QString("Failed to send packet; not connected to server%1").arg(drFormatServerInfo(m_server)); + return; + } + m_socket->write(p_packet.to_string().toUtf8()); +} + +void DRServerSocket::_p_update_state(QAbstractSocket::SocketState p_state) +{ + switch (p_state) + { + case QAbstractSocket::ConnectedState: + m_is_connected = true; + m_reconnect_timer->stop(); + Q_EMIT connected_to_server(); + break; + + case QAbstractSocket::UnconnectedState: + if (m_is_connected) + { + m_is_connected = false; + if (is_reconnectable) + m_reconnect_timer->start(); + Q_EMIT disconnected_from_server(); + } + break; + + default: + break; + } +} + +void DRServerSocket::_p_reconnect_to_server() +{ + Q_EMIT reconnecting_to_server(); + connect_to_server(m_server, is_reconnectable); +} + +void DRServerSocket::_p_check_socket_error() +{ + const QString l_error = + QString("Connection error for server%1: %2").arg(drFormatServerInfo(m_server), m_socket->errorString()); + qWarning() << l_error; + Q_EMIT socket_error(l_error); +} + +void DRServerSocket::_p_read_socket() +{ + m_data += QString::fromUtf8(m_socket->readAll()); + + QStringList l_raw_packet_list = m_data.split("#%", DR::KeepEmptyParts); + m_data = l_raw_packet_list.takeLast(); + + for (const QString &i_raw_packet : l_raw_packet_list) + { + QStringList l_raw_data = i_raw_packet.split("#"); + packet_received(AOPacket(l_raw_data.takeFirst(), l_raw_data)); + } +} diff --git a/src/lobby.cpp b/src/lobby.cpp index a63f7b61e..dddede300 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -9,7 +9,6 @@ #include "debug_functions.h" #include "drpather.h" #include "drtextedit.h" -#include "networkmanager.h" #include "theme.h" #include "version.h" @@ -277,10 +276,7 @@ void Lobby::on_refresh_pressed() void Lobby::on_refresh_released() { ui_refresh->set_image("refresh.png"); - - AOPacket *f_packet = new AOPacket("ALL#%"); - - ao_app->send_ms_packet(f_packet); + ao_app->request_server_list(); } void Lobby::on_add_to_fav_pressed() @@ -370,7 +366,7 @@ void Lobby::on_server_list_clicked(QModelIndex p_model) ui_description->append(m_last_server.desc); ui_description->ensureCursorVisible(); - ao_app->get_network_manager()->connect_to_server(m_last_server); + ao_app->connect_to_server(m_last_server); } void Lobby::on_chatfield_return_pressed() @@ -384,7 +380,7 @@ void Lobby::on_chatfield_return_pressed() AOPacket *f_packet = new AOPacket(f_header, f_contents); - ao_app->send_ms_packet(f_packet); + ao_app->send_master_packet(f_packet); ui_chatmessage->clear(); } diff --git a/src/main.cpp b/src/main.cpp index 110014de9..ef7d3dd07 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,6 @@ #include "aoapplication.h" #include "lobby.h" -#include "networkmanager.h" int main(int argc, char *argv[]) { @@ -21,9 +20,6 @@ int main(int argc, char *argv[]) AOApplication app(argc, argv); app.construct_lobby(); -#ifdef QT_NO_DEBUG - app.get_network_manager()->connect_to_master(); -#endif app.get_lobby()->show(); return app.exec(); diff --git a/src/master_socket.cpp b/src/master_socket.cpp new file mode 100644 index 000000000..f17978e85 --- /dev/null +++ b/src/master_socket.cpp @@ -0,0 +1,132 @@ +#include "aoapplication.h" + +#include "aopacket.h" +#include "debug_functions.h" +#include "drserversocket.h" +#include "hardware_functions.h" +#include "lobby.h" +#include "version.h" + +#include + +void AOApplication::send_master_packet(AOPacket *p_packet) +{ + qDebug().noquote() << "M/S:" << p_packet->to_string(); + m_master_socket->send_packet(*p_packet); +} + +void AOApplication::request_server_list() +{ + m_master_socket->send_packet(AOPacket("ALL", {})); +} + +void AOApplication::_p_send_master_handshake() +{ +#ifndef QT_DEBUG + send_master_packet(new AOPacket("ALL#%")); +#endif +} + +void AOApplication::_p_handle_master_error(QString p_error) +{ + if (!is_lobby_constructed) + return; + m_lobby->append_error(p_error); +} + +void AOApplication::_p_handle_master_packet(AOPacket p_packet) +{ + p_packet.net_decode(); + + QString l_header = p_packet.get_header(); + QStringList l_content = p_packet.get_contents(); + + if (l_header != "CHECK") + qDebug().noquote() << "M/R:" << p_packet.to_string(); + + if (l_header == "ALL") + { + m_server_list.clear(); + + for (const QString &i_string : p_packet.get_contents()) + { + server_type f_server; + QStringList sub_contents = i_string.split("&"); + + if (sub_contents.size() < 4) + { + qDebug() << "W: malformed packet"; + continue; + } + + f_server.name = sub_contents.at(0); + f_server.desc = sub_contents.at(1); + f_server.ip = sub_contents.at(2); + f_server.port = sub_contents.at(3).toInt(); + + m_server_list.append(f_server); + } + + if (is_lobby_constructed) + { + m_lobby->list_servers(); + } + } + else if (l_header == "CT") + { + QString f_name, f_message; + + if (l_content.size() == 1) + { + f_name = ""; + f_message = l_content.at(0); + } + else if (l_content.size() >= 2) + { + f_name = l_content.at(0); + f_message = l_content.at(1); + } + else + return; + + if (is_lobby_constructed) + { + m_lobby->append_chatmessage(f_name, f_message); + } + } + else if (l_header == "AO2CHECK") + { + send_master_packet(new AOPacket("ID#DRO#" + get_version_string() + "#%")); + send_master_packet(new AOPacket("HI#" + get_hdid() + "#%")); + + if (l_content.size() < 1) + return; + + QStringList version_contents = l_content.at(0).split("."); + + if (version_contents.size() < 3) + return; + + int f_release = version_contents.at(0).toInt(); + int f_major = version_contents.at(1).toInt(); + int f_minor = version_contents.at(2).toInt(); + + if (get_release_version() > f_release) + return; + else if (get_release_version() == f_release) + { + if (get_major_version() > f_major) + return; + else if (get_major_version() == f_major) + { + if (get_minor_version() >= f_minor) + return; + } + } + + call_notice("Outdated version! Your version: " + get_version_string() + + "\nPlease go to aceattorneyonline.com to update."); + destruct_courtroom(); + destruct_lobby(); + } +} diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp deleted file mode 100644 index dcb3ec35e..000000000 --- a/src/networkmanager.cpp +++ /dev/null @@ -1,169 +0,0 @@ -#include "networkmanager.h" - -#include "aoapplication.h" -#include "aopacket.h" - -#include -#include - -const QString NetworkManager::MASTER_HOST = "master.aceattorneyonline.com"; -const int NetworkManager::MASTER_PORT = 27016; -const int NetworkManager::MASTER_RECONNECT_DELAY = 7000; - -NetworkManager::NetworkManager(AOApplication *parent) : QObject(parent) -{ - ao_app = parent; - - ms_socket = new QTcpSocket(this); - server_socket = new QTcpSocket(this); - - ms_reconnect_timer = new QTimer(this); - ms_reconnect_timer->setSingleShot(true); - QObject::connect(ms_reconnect_timer, SIGNAL(timeout()), this, SLOT(retry_ms_connect())); - - QObject::connect(ms_socket, SIGNAL(readyRead()), this, SLOT(handle_ms_packet())); - QObject::connect(server_socket, SIGNAL(readyRead()), this, SLOT(handle_server_packet())); - QObject::connect(server_socket, SIGNAL(disconnected()), ao_app, SLOT(server_disconnected())); -} - -NetworkManager::~NetworkManager() -{} - -void NetworkManager::connect_to_master() -{ - ms_socket->close(); - ms_socket->abort(); - connect_to_master_nosrv(); -} - -void NetworkManager::connect_to_master_nosrv() -{ - QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, - SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); - - QObject::connect(ms_socket, SIGNAL(connected()), this, SLOT(on_ms_nosrv_connect_success())); - ms_socket->connectToHost(MASTER_HOST, MASTER_PORT); -} - -void NetworkManager::connect_to_server(server_type p_server) -{ - disconnect_from_server(); - server_socket->connectToHost(p_server.ip, p_server.port); -} - -void NetworkManager::disconnect_from_server() -{ - server_socket->close(); - server_socket->abort(); -} - -void NetworkManager::ship_ms_packet(QString p_packet) -{ - if (!ms_socket->isOpen()) - { - retry_ms_connect(); - } - else - { - ms_socket->write(p_packet.toUtf8()); - } -} - -void NetworkManager::ship_server_packet(QString p_packet) -{ - server_socket->write(p_packet.toUtf8()); -} - -void NetworkManager::handle_ms_packet() -{ - QByteArray buffer = ms_socket->readAll(); - QString in_data = QString::fromUtf8(buffer, buffer.size()); - - if (!in_data.endsWith("%")) - { - ms_partial_packet = true; - ms_temp_packet += in_data; - return; - } - - else - { - if (ms_partial_packet) - { - in_data = ms_temp_packet + in_data; - ms_temp_packet = ""; - ms_partial_packet = false; - } - } - - QStringList packet_list = in_data.split("%", DR::SkipEmptyParts); - - for (const QString &packet : packet_list) - { - AOPacket *f_packet = new AOPacket(packet); - - ao_app->ms_packet_received(f_packet); - } -} - -void NetworkManager::on_ms_nosrv_connect_success() -{ - Q_EMIT ms_connect_finished(true, false); - - QObject::disconnect(ms_socket, SIGNAL(connected()), this, SLOT(on_ms_nosrv_connect_success())); - - QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, - SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); -} - -void NetworkManager::on_ms_socket_error(QAbstractSocket::SocketError error) -{ - qWarning() << "Master server socket error:" << ms_socket->errorString() << "(" << error << ")"; - - // Disconnect the one-shot signal - this way, failover connect attempts - // don't trigger a full retry - QObject::disconnect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, - SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); - - Q_EMIT ms_connect_finished(false, true); - - ms_reconnect_timer->start(MASTER_RECONNECT_DELAY); -} - -void NetworkManager::retry_ms_connect() -{ - if (!ms_reconnect_timer->isActive() && ms_socket->state() != QAbstractSocket::ConnectingState) - connect_to_master(); -} - -void NetworkManager::handle_server_packet() -{ - QByteArray buffer = server_socket->readAll(); - QString in_data = QString::fromUtf8(buffer, buffer.size()); - - 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("%", DR::SkipEmptyParts); - - for (const QString &packet : packet_list) - { - AOPacket *f_packet = new AOPacket(packet); - - ao_app->server_packet_received(f_packet); - } -} diff --git a/src/packet_distribution.cpp b/src/server_socket.cpp similarity index 51% rename from src/packet_distribution.cpp rename to src/server_socket.cpp index f960d63a4..f5dbd507d 100644 --- a/src/packet_distribution.cpp +++ b/src/server_socket.cpp @@ -5,132 +5,42 @@ #include "courtroom.h" #include "debug_functions.h" #include "drdiscord.h" +#include "drserversocket.h" #include "file_functions.h" #include "hardware_functions.h" #include "lobby.h" -#include "networkmanager.h" #include "version.h" #include -void AOApplication::ms_packet_received(AOPacket *p_packet) +void AOApplication::connect_to_server(server_type p_server) { - p_packet->net_decode(); - - QString header = p_packet->get_header(); - QStringList f_contents = p_packet->get_contents(); - - if (header != "CHECK") - qDebug() << "R(ms):" << p_packet->to_string(); - - if (header == "ALL") - { - m_server_list.clear(); - - for (const QString &i_string : p_packet->get_contents()) - { - server_type f_server; - QStringList sub_contents = i_string.split("&"); - - if (sub_contents.size() < 4) - { - qDebug() << "W: malformed packet"; - continue; - } - - f_server.name = sub_contents.at(0); - f_server.desc = sub_contents.at(1); - f_server.ip = sub_contents.at(2); - f_server.port = sub_contents.at(3).toInt(); - - m_server_list.append(f_server); - } - - if (is_lobby_constructed) - { - m_lobby->list_servers(); - } - } - else if (header == "CT") - { - QString f_name, f_message; - - if (f_contents.size() == 1) - { - f_name = ""; - f_message = f_contents.at(0); - } - else if (f_contents.size() >= 2) - { - f_name = f_contents.at(0); - f_message = f_contents.at(1); - } - else - goto end; - - if (is_lobby_constructed) - { - m_lobby->append_chatmessage(f_name, f_message); - } - } - else if (header == "AO2CHECK") - { - send_ms_packet(new AOPacket("ID#DRO#" + get_version_string() + "#%")); - send_ms_packet(new AOPacket("HI#" + get_hdid() + "#%")); - - if (f_contents.size() < 1) - goto end; - - QStringList version_contents = f_contents.at(0).split("."); - - if (version_contents.size() < 3) - goto end; - - int f_release = version_contents.at(0).toInt(); - int f_major = version_contents.at(1).toInt(); - int f_minor = version_contents.at(2).toInt(); - - if (get_release_version() > f_release) - goto end; - else if (get_release_version() == f_release) - { - if (get_major_version() > f_major) - goto end; - else if (get_major_version() == f_major) - { - if (get_minor_version() >= f_minor) - goto end; - } - } - - call_notice("Outdated version! Your version: " + get_version_string() + - "\nPlease go to aceattorneyonline.com to update."); - destruct_courtroom(); - destruct_lobby(); - } + m_server_socket->connect_to_server(p_server, false); +} -end: - delete p_packet; +void AOApplication::send_server_packet(AOPacket *p_packet) +{ + qDebug().noquote() << "S/S:" << p_packet->to_string(); + m_server_socket->send_packet(*p_packet); } -void AOApplication::server_packet_received(AOPacket *p_packet) +void AOApplication::_p_handle_server_packet(AOPacket p_packet) { - p_packet->net_decode(); + p_packet.net_decode(); - QString header = p_packet->get_header(); - QStringList f_contents = p_packet->get_contents(); - QString f_packet = p_packet->to_string(); + QString l_header = p_packet.get_header(); + QStringList l_content = p_packet.get_contents(); - if (header != "checkconnection") - qDebug() << "R:" << f_packet; + if (l_header != "checkconnection") + qDebug().noquote() << "S/R:" << p_packet.to_string(); - if (header == "decryptor") + if (l_header == "decryptor") { // This packet is maintained as is for legacy purposes, // even though its sole argument is no longer used for anything // productive - if (f_contents.size() == 0) - goto end; + if (l_content.size() == 0) + return; QString f_hdid; f_hdid = get_hdid(); @@ -145,51 +55,51 @@ void AOApplication::server_packet_received(AOPacket *p_packet) AOPacket *hi_packet = new AOPacket("HI#" + f_hdid + "#%"); send_server_packet(hi_packet); } - else if (header == "ID") + else if (l_header == "ID") { - if (f_contents.size() < 2) - goto end; + if (l_content.size() < 2) + return; - m_client_id = f_contents.at(0).toInt(); - m_server_software = f_contents.at(1); + m_client_id = l_content.at(0).toInt(); + m_server_software = l_content.at(1); send_server_packet(new AOPacket("ID#DRO#" + get_version_string() + "#%")); } - else if (header == "CT") + else if (l_header == "CT") { - if (f_contents.size() < 2) - goto end; + if (l_content.size() < 2) + return; if (is_courtroom_constructed) - m_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1)); + m_courtroom->append_server_chatmessage(l_content.at(0), l_content.at(1)); } - else if (header == "FL") + else if (l_header == "FL") { #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - feature_ackMS = f_contents.contains("ackMS", Qt::CaseInsensitive); + feature_ackMS = l_content.contains("ackMS", Qt::CaseInsensitive); #endif - feature_showname = f_contents.contains("showname", Qt::CaseInsensitive); - feature_chrini = f_contents.contains("chrini", Qt::CaseInsensitive); - feature_chat_speed = f_contents.contains("chat_speed", Qt::CaseInsensitive); + feature_showname = l_content.contains("showname", Qt::CaseInsensitive); + feature_chrini = l_content.contains("chrini", Qt::CaseInsensitive); + feature_chat_speed = l_content.contains("chat_speed", Qt::CaseInsensitive); } - else if (header == "PN") + else if (l_header == "PN") { - if (f_contents.size() < 2) - goto end; + if (l_content.size() < 2) + return; - m_lobby->set_player_count(f_contents.at(0).toInt(), f_contents.at(1).toInt()); + m_lobby->set_player_count(l_content.at(0).toInt(), l_content.at(1).toInt()); } - else if (header == "SI") + else if (l_header == "SI") { - if (f_contents.size() != 3) - goto end; + if (l_content.size() != 3) + return; - m_character_count = f_contents.at(0).toInt(); - m_evidence_count = f_contents.at(1).toInt(); - m_music_count = f_contents.at(2).toInt(); + m_character_count = l_content.at(0).toInt(); + m_evidence_count = l_content.at(1).toInt(); + m_music_count = l_content.at(2).toInt(); if (m_character_count < 1 || m_evidence_count < 0 || m_music_count < 0) - goto end; + return; m_loaded_characters = 0; m_loaded_evidence = 0; @@ -253,22 +163,22 @@ void AOApplication::server_packet_received(AOPacket *p_packet) dr_discord->set_state(DRDiscord::State::Connected); dr_discord->set_server_name(server_name); } - else if (header == "CI") + else if (l_header == "CI") { if (!is_courtroom_constructed) - goto end; + return; - for (int n_element = 0; n_element < f_contents.size(); n_element += 2) + for (int n_element = 0; n_element < l_content.size(); n_element += 2) { - if (f_contents.at(n_element).toInt() != m_loaded_characters) + if (l_content.at(n_element).toInt() != m_loaded_characters) break; // this means we are on the last element and checking n + 1 element will // be game over so - if (n_element == f_contents.size() - 1) + if (n_element == l_content.size() - 1) break; - QStringList sub_elements = f_contents.at(n_element + 1).split("&"); + QStringList sub_elements = l_content.at(n_element + 1).split("&"); if (sub_elements.size() < 2) break; @@ -293,22 +203,22 @@ void AOApplication::server_packet_received(AOPacket *p_packet) send_server_packet(new AOPacket("RE#%")); } - else if (header == "EI") + else if (l_header == "EI") { if (!is_courtroom_constructed) - goto end; + return; // +1 because evidence starts at 1 rather than 0 for whatever reason // enjoy fanta - if (f_contents.at(0).toInt() != m_loaded_evidence + 1) - goto end; + if (l_content.at(0).toInt() != m_loaded_evidence + 1) + return; - if (f_contents.size() < 2) - goto end; + if (l_content.size() < 2) + return; - QStringList sub_elements = f_contents.at(1).split("&"); + QStringList sub_elements = l_content.at(1).split("&"); if (sub_elements.size() < 4) - goto end; + return; evi_type f_evi; f_evi.name = sub_elements.at(0); @@ -328,28 +238,28 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QString next_packet_number = QString::number(m_loaded_evidence); send_server_packet(new AOPacket("AE#" + next_packet_number + "#%")); } - else if (header == "CharsCheck") + else if (l_header == "CharsCheck") { if (!is_courtroom_constructed) - goto end; + return; - for (int n_char = 0; n_char < f_contents.size(); ++n_char) + for (int n_char = 0; n_char < l_content.size(); ++n_char) { - if (f_contents.at(n_char) == "-1") + if (l_content.at(n_char) == "-1") m_courtroom->set_taken(n_char, true); else m_courtroom->set_taken(n_char, false); } } - else if (header == "SC") + else if (l_header == "SC") { if (!is_courtroom_constructed) - goto end; + return; - for (int n_element = 0; n_element < f_contents.size(); ++n_element) + for (int n_element = 0; n_element < l_content.size(); ++n_element) { - QStringList sub_elements = f_contents.at(n_element).split("&"); + QStringList sub_elements = l_content.at(n_element).split("&"); char_type f_char; f_char.name = sub_elements.at(0); @@ -373,20 +283,20 @@ void AOApplication::server_packet_received(AOPacket *p_packet) send_server_packet(new AOPacket("RM#%")); } - else if (header == "SM" || header == "FM") + else if (l_header == "SM" || l_header == "FM") { if (!is_courtroom_constructed) - goto end; + return; QStringList l_area_list; QStringList l_music_list; - for (int i = 0; i < f_contents.length(); ++i) + for (int i = 0; i < l_content.length(); ++i) { bool l_found_music = false; { // look for first song - const QString &i_value = f_contents.at(i); + const QString &i_value = l_content.at(i); for (const QString &i_ext : audio_extensions(true)) { if (!i_value.endsWith(i_ext, Qt::CaseInsensitive)) @@ -398,14 +308,14 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (!l_found_music) continue; - l_area_list = f_contents.mid(0, i - 1); - l_music_list = f_contents.mid(i - 1); + l_area_list = l_content.mid(0, i - 1); + l_music_list = l_content.mid(i - 1); break; } m_courtroom->set_area_list(l_area_list); m_courtroom->set_music_list(l_music_list); - if (header == "SM") + if (l_header == "SM") { m_loaded_music = m_music_count; m_lobby->set_loading_text("Loading music:\n" + QString::number(m_loaded_music) + "/" + @@ -417,10 +327,10 @@ void AOApplication::server_packet_received(AOPacket *p_packet) send_server_packet(new AOPacket("RD#%")); } } - else if (header == "DONE") + else if (l_header == "DONE") { if (!is_courtroom_constructed) - goto end; + return; m_courtroom->done_received(); @@ -428,65 +338,65 @@ void AOApplication::server_packet_received(AOPacket *p_packet) destruct_lobby(); } - else if (header == "BN") + else if (l_header == "BN") { - if (f_contents.size() < 1) - goto end; + if (l_content.size() < 1) + return; if (is_courtroom_constructed) { - m_courtroom->set_background(f_contents.at(0)); + m_courtroom->set_background(l_content.at(0)); m_courtroom->set_scene(); } } - else if (header == "chat_tick_rate") + else if (l_header == "chat_tick_rate") { if (is_courtroom_constructed) - m_courtroom->set_tick_rate(f_contents.isEmpty() ? std::nullopt : std::optional(f_contents.at(0).toInt())); + m_courtroom->set_tick_rate(l_content.isEmpty() ? std::nullopt : std::optional(l_content.at(0).toInt())); } // server accepting char request(CC) packet - else if (header == "PV") + else if (l_header == "PV") { - if (f_contents.size() < 3) - goto end; + if (l_content.size() < 3) + return; if (is_courtroom_constructed) - m_courtroom->enter_courtroom(f_contents.at(2).toInt()); + m_courtroom->enter_courtroom(l_content.at(2).toInt()); } - else if (header == "MS") + else if (l_header == "MS") { if (is_courtroom_constructed && is_courtroom_loaded) - m_courtroom->handle_chatmessage(p_packet->get_contents()); + m_courtroom->handle_chatmessage(p_packet.get_contents()); } - else if (header == "ackMS") + else if (l_header == "ackMS") { if (is_courtroom_constructed && is_courtroom_loaded) m_courtroom->handle_acknowledged_ms(); } - else if (header == "MC") + else if (l_header == "MC") { if (is_courtroom_constructed && is_courtroom_loaded) - m_courtroom->handle_song(p_packet->get_contents()); + m_courtroom->handle_song(p_packet.get_contents()); } - else if (header == "RT") + else if (l_header == "RT") { - if (f_contents.size() < 1) - goto end; + if (l_content.size() < 1) + return; if (is_courtroom_constructed) - m_courtroom->handle_wtce(f_contents.at(0)); + m_courtroom->handle_wtce(l_content.at(0)); } - else if (header == "HP") + else if (l_header == "HP") { - if (is_courtroom_constructed && f_contents.size() > 1) - m_courtroom->set_hp_bar(f_contents.at(0).toInt(), f_contents.at(1).toInt()); + if (is_courtroom_constructed && l_content.size() > 1) + m_courtroom->set_hp_bar(l_content.at(0).toInt(), l_content.at(1).toInt()); } - else if (header == "LE") + else if (l_header == "LE") { if (is_courtroom_constructed) { QVector f_evi_list; - for (const QString &f_string : f_contents) + for (const QString &f_string : l_content) { QStringList sub_contents = f_string.split("&"); @@ -504,174 +414,144 @@ void AOApplication::server_packet_received(AOPacket *p_packet) m_courtroom->set_evidence_list(f_evi_list); } } - else if (header == "IL") + else if (l_header == "IL") { - if (is_courtroom_constructed && f_contents.size() > 0) - m_courtroom->set_ip_list(f_contents.at(0)); + if (is_courtroom_constructed && l_content.size() > 0) + m_courtroom->set_ip_list(l_content.at(0)); } - else if (header == "MU") + else if (l_header == "MU") { - if (is_courtroom_constructed && f_contents.size() > 0) - m_courtroom->set_mute(true, f_contents.at(0).toInt()); + if (is_courtroom_constructed && l_content.size() > 0) + m_courtroom->set_mute(true, l_content.at(0).toInt()); } - else if (header == "UM") + else if (l_header == "UM") { - if (is_courtroom_constructed && f_contents.size() > 0) - m_courtroom->set_mute(false, f_contents.at(0).toInt()); + if (is_courtroom_constructed && l_content.size() > 0) + m_courtroom->set_mute(false, l_content.at(0).toInt()); } - else if (header == "KK") + else if (l_header == "KK") { - if (is_courtroom_constructed && f_contents.size() > 0) + if (is_courtroom_constructed && l_content.size() > 0) { int f_cid = m_courtroom->get_character_id(); - int remote_cid = f_contents.at(0).toInt(); + int remote_cid = l_content.at(0).toInt(); if (f_cid != remote_cid && remote_cid != -1) - goto end; + return; call_notice("You have been kicked."); construct_lobby(); destruct_courtroom(); } } - else if (header == "KB") + else if (l_header == "KB") { - if (is_courtroom_constructed && f_contents.size() > 0) - m_courtroom->set_ban(f_contents.at(0).toInt()); + if (is_courtroom_constructed && l_content.size() > 0) + m_courtroom->set_ban(l_content.at(0).toInt()); } - else if (header == "BD") + else if (l_header == "BD") { call_notice("You are banned on this server."); } - else if (header == "ZZ") + else if (l_header == "ZZ") { - if (is_courtroom_constructed && f_contents.size() > 0) - m_courtroom->mod_called(f_contents.at(0)); + if (is_courtroom_constructed && l_content.size() > 0) + m_courtroom->mod_called(l_content.at(0)); } - else if (header == "CL") + else if (l_header == "CL") { - qDebug() << f_contents; - m_courtroom->handle_clock(f_contents.at(1)); + qDebug() << l_content; + m_courtroom->handle_clock(l_content.at(1)); } - else if (header == "GM") + else if (l_header == "GM") { - if (f_contents.length() < 1) - goto end; + if (l_content.length() < 1) + return; if (ao_config->manual_gamemode_enabled()) - goto end; - ao_config->set_gamemode(f_contents.at(0)); + return; + ao_config->set_gamemode(l_content.at(0)); } - else if (header == "TOD") + else if (l_header == "TOD") { - if (f_contents.length() < 1) - goto end; + if (l_content.length() < 1) + return; if (ao_config->manual_timeofday_enabled()) - goto end; - ao_config->set_timeofday(f_contents.at(0)); + return; + ao_config->set_timeofday(l_content.at(0)); } - else if (header == "TR") + else if (l_header == "TR") { // Timer resume - if (f_contents.size() != 1) - goto end; + if (l_content.size() != 1) + return; if (!is_courtroom_constructed) - goto end; - int timer_id = f_contents.at(0).toInt(); + return; + int timer_id = l_content.at(0).toInt(); m_courtroom->resume_timer(timer_id); } - else if (header == "TST") + else if (l_header == "TST") { // Timer set time - if (f_contents.size() != 2) - goto end; + if (l_content.size() != 2) + return; if (!is_courtroom_constructed) - goto end; - int timer_id = f_contents.at(0).toInt(); - int new_time = f_contents.at(1).toInt(); + return; + int timer_id = l_content.at(0).toInt(); + int new_time = l_content.at(1).toInt(); m_courtroom->set_timer_time(timer_id, new_time); } - else if (header == "TSS") + else if (l_header == "TSS") { // Timer set timeStep length - if (f_contents.size() != 2) - goto end; + if (l_content.size() != 2) + return; if (!is_courtroom_constructed) - goto end; - int timer_id = f_contents.at(0).toInt(); - int timestep_length = f_contents.at(1).toInt(); + return; + int timer_id = l_content.at(0).toInt(); + int timestep_length = l_content.at(1).toInt(); m_courtroom->set_timer_timestep(timer_id, timestep_length); } - else if (header == "TSF") + else if (l_header == "TSF") { // Timer set Firing interval - if (f_contents.size() != 2) - goto end; + if (l_content.size() != 2) + return; if (!is_courtroom_constructed) - goto end; - int timer_id = f_contents.at(0).toInt(); - int firing_interval = f_contents.at(1).toInt(); + return; + int timer_id = l_content.at(0).toInt(); + int firing_interval = l_content.at(1).toInt(); m_courtroom->set_timer_firing(timer_id, firing_interval); } - else if (header == "TP") + else if (l_header == "TP") { // Timer pause - if (f_contents.size() != 1) - goto end; + if (l_content.size() != 1) + return; if (!is_courtroom_constructed) - goto end; - int timer_id = f_contents.at(0).toInt(); + return; + int timer_id = l_content.at(0).toInt(); m_courtroom->pause_timer(timer_id); } - else if (header == "SP") + else if (l_header == "SP") { // Set position - if (f_contents.size() != 1) - goto end; + if (l_content.size() != 1) + return; if (!is_courtroom_constructed) - goto end; - m_courtroom->set_character_position(f_contents.at(0)); + return; + m_courtroom->set_character_position(l_content.at(0)); } - else if (header == "SN") + else if (l_header == "SN") { // Server-set showname. // This has priority over user-set showname. - if (f_contents.size() != 1) - goto end; + if (l_content.size() != 1) + return; if (!is_courtroom_constructed) - goto end; - const QString l_showname = f_contents.at(0); + return; + const QString l_showname = l_content.at(0); // By updating now, we prevent the client from sending an SN back when we later on go ahead and modify the // showname box. ao_config->set_showname(l_showname); } - -end: - - delete p_packet; -} - -void AOApplication::send_ms_packet(AOPacket *p_packet) -{ - p_packet->net_encode(); - - QString f_packet = p_packet->to_string(); - - m_network_manager->ship_ms_packet(f_packet); - - qDebug() << "S(ms):" << f_packet; - - delete p_packet; -} - -void AOApplication::send_server_packet(AOPacket *p_packet, bool encoded) -{ - if (encoded) - p_packet->net_encode(); - - QString f_packet = p_packet->to_string(); - - qDebug() << "S:" << f_packet; - m_network_manager->ship_server_packet(f_packet); - - delete p_packet; } From 19f7a5662f175b6238d049b62f094a74ebd3410c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 22:53:17 +0200 Subject: [PATCH 397/842] Removed packet pointers --- include/aoapplication.h | 6 ++++-- include/drserversocket.h | 4 ---- src/aoapplication.cpp | 9 ++++----- src/charselect.cpp | 2 +- src/courtroom.cpp | 37 ++++++++++++++++++------------------- src/evidence.cpp | 10 +++++----- src/lobby.cpp | 11 ++--------- src/master_socket.cpp | 28 ++++++++++++++++++++-------- src/server_socket.cpp | 24 ++++++++++-------------- 9 files changed, 64 insertions(+), 67 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index b9d879bc9..1086fc6fe 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -19,6 +19,7 @@ class AOApplication : public QApplication Q_OBJECT public: + static const QString MASTER_NAME; static const QString MASTER_HOST; static const int MASTER_PORT; static const int MASTER_RECONNECT_DELAY; @@ -29,10 +30,11 @@ class AOApplication : public QApplication int get_client_id() const; void set_client_id(int id); - void send_master_packet(AOPacket *packet); + void connect_to_master(); + void send_master_packet(AOPacket packet); void request_server_list(); void connect_to_server(server_type server); - void send_server_packet(AOPacket *packet); + void send_server_packet(AOPacket packet); Lobby *get_lobby() const; void construct_lobby(); diff --git a/include/drserversocket.h b/include/drserversocket.h index a05e0b633..fd8a90739 100644 --- a/include/drserversocket.h +++ b/include/drserversocket.h @@ -22,10 +22,6 @@ class DRServerSocket : public QObject public slots: void send_packet(AOPacket packet); - inline void send_packet(AOPacket *packet) - { - send_packet(*packet); - } signals: void connected_to_server(); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index fd6827cec..7c6b76696 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -17,6 +17,7 @@ #include #endif +const QString AOApplication::MASTER_NAME = "Master"; const QString AOApplication::MASTER_HOST = "master.aceattorneyonline.com"; const int AOApplication::MASTER_PORT = 27016; const int AOApplication::MASTER_RECONNECT_DELAY = 5000; @@ -51,11 +52,9 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(m_server_socket, SIGNAL(connected_to_server()), this, SLOT(_p_send_master_handshake())); connect(m_server_socket, SIGNAL(packet_received(AOPacket)), this, SLOT(_p_handle_server_packet(AOPacket))); - server_type l_server; - l_server.name = "Master Server"; - l_server.ip = MASTER_HOST; - l_server.port = MASTER_PORT; - m_master_socket->connect_to_server(l_server, true); +#ifndef QT_DEBUG + connect_to_master(); +#endif } AOApplication::~AOApplication() diff --git a/src/charselect.cpp b/src/charselect.cpp index 48c699df6..cf96963e1 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -190,7 +190,7 @@ void Courtroom::char_clicked(int n_char) { QString content = "CC#" + QString::number(ao_app->get_client_id()) + "#" + QString::number(n_real_char) + "#" + get_hdid() + "#%"; - ao_app->send_server_packet(new AOPacket(content)); + ao_app->send_server_packet(AOPacket(content)); } } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index e66c21d7d..081d6ba64 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -203,8 +203,7 @@ void Courtroom::enter_courtroom(int p_cid) if (ao_app->has_character_declaration_feature()) { QStringList l_content{l_chr_name, l_final_showname}; - AOPacket *l_packet = new AOPacket("chrini", l_content); - ao_app->send_server_packet(l_packet); + ao_app->send_server_packet(AOPacket("chrini", l_content)); } } const bool l_changed_chr = l_chr_name != get_current_character(); @@ -685,7 +684,7 @@ void Courtroom::send_showname_packet(QString p_showname) if (ao_app->has_showname_declaration_feature()) { QStringList l_content = {p_showname}; - ao_app->send_server_packet(new AOPacket("SN", l_content)); + ao_app->send_server_packet(AOPacket("SN", l_content)); } else { @@ -834,7 +833,7 @@ void Courtroom::on_ic_message_return_pressed() f_text_color = QString::number(m_text_color); packet_contents.append(f_text_color); - ao_app->send_server_packet(new AOPacket("MS", packet_contents)); + ao_app->send_server_packet(AOPacket("MS", packet_contents)); } void Courtroom::handle_acknowledged_ms() @@ -1933,7 +1932,7 @@ void Courtroom::send_ooc_packet(QString ooc_name, QString ooc_message) } QStringList l_content{ooc_name, ooc_message}; - ao_app->send_server_packet(new AOPacket("CT", l_content)); + ao_app->send_server_packet(AOPacket("CT", l_content)); } void Courtroom::mod_called(QString p_ip) @@ -2087,10 +2086,10 @@ void Courtroom::on_pos_dropdown_changed(int p_index) set_judge_enabled(f_pos == "jud"); - ao_app->send_server_packet(new AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); + ao_app->send_server_packet(AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); // Uncomment later and remove above // Will only work in TSDR 4.3+ servers - // ao_app->send_server_packet(new AOPacket("SP#" + f_pos + "#%")); + // ao_app->send_server_packet(AOPacket("SP#" + f_pos + "#%")); } void Courtroom::on_mute_list_item_changed(QListWidgetItem *p_item) @@ -2136,7 +2135,7 @@ void Courtroom::on_music_list_double_clicked(QModelIndex p_model) QString p_song = ui_music_list->item(p_model.row())->text(); - ao_app->send_server_packet(new AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%")); + ao_app->send_server_packet(AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%")); ui_ic_chat_message->setFocus(); } @@ -2145,7 +2144,7 @@ void Courtroom::on_area_list_double_clicked(QModelIndex p_model) { QString p_area = ui_area_list->item(p_model.row())->text(); - ao_app->send_server_packet(new AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%")); + ao_app->send_server_packet(AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%")); ui_ic_chat_message->setFocus(); } @@ -2347,7 +2346,7 @@ void Courtroom::on_defense_minus_clicked() int f_state = defense_bar_state - 1; if (f_state >= 0) - ao_app->send_server_packet(new AOPacket("HP#1#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(AOPacket("HP#1#" + QString::number(f_state) + "#%")); } void Courtroom::on_defense_plus_clicked() @@ -2355,7 +2354,7 @@ void Courtroom::on_defense_plus_clicked() int f_state = defense_bar_state + 1; if (f_state <= 10) - ao_app->send_server_packet(new AOPacket("HP#1#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(AOPacket("HP#1#" + QString::number(f_state) + "#%")); } void Courtroom::on_prosecution_minus_clicked() @@ -2363,7 +2362,7 @@ void Courtroom::on_prosecution_minus_clicked() int f_state = prosecution_bar_state - 1; if (f_state >= 0) - ao_app->send_server_packet(new AOPacket("HP#2#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(AOPacket("HP#2#" + QString::number(f_state) + "#%")); } void Courtroom::on_prosecution_plus_clicked() @@ -2371,7 +2370,7 @@ void Courtroom::on_prosecution_plus_clicked() int f_state = prosecution_bar_state + 1; if (f_state <= 10) - ao_app->send_server_packet(new AOPacket("HP#2#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(AOPacket("HP#2#" + QString::number(f_state) + "#%")); } void Courtroom::on_text_color_changed(int p_color) @@ -2385,7 +2384,7 @@ void Courtroom::on_witness_testimony_clicked() if (is_client_muted) return; - ao_app->send_server_packet(new AOPacket("RT#testimony1#%")); + ao_app->send_server_packet(AOPacket("RT#testimony1#%")); ui_ic_chat_message->setFocus(); } @@ -2395,7 +2394,7 @@ void Courtroom::on_cross_examination_clicked() if (is_client_muted) return; - ao_app->send_server_packet(new AOPacket("RT#testimony2#%")); + ao_app->send_server_packet(AOPacket("RT#testimony2#%")); ui_ic_chat_message->setFocus(); } @@ -2436,7 +2435,7 @@ void Courtroom::on_wtce_clicked() QString packet = QString("RT#testimony%1#%").arg(id); - ao_app->send_server_packet(new AOPacket(packet)); + ao_app->send_server_packet(AOPacket(packet)); ui_ic_chat_message->setFocus(); } @@ -2498,7 +2497,7 @@ void Courtroom::on_char_select_right_clicked() void Courtroom::on_spectator_clicked() { QString content = "CC#" + QString::number(ao_app->get_client_id()) + "#-1#" + get_hdid() + "#%"; - ao_app->send_server_packet(new AOPacket(content)); + ao_app->send_server_packet(AOPacket(content)); enter_courtroom(-1); ui_emotes->hide(); @@ -2516,7 +2515,7 @@ void Courtroom::on_call_mod_clicked() if (reply == QMessageBox::Yes) { - ao_app->send_server_packet(new AOPacket("ZZ#%")); + ao_app->send_server_packet(AOPacket("ZZ#%")); qDebug() << "Called mod"; } else @@ -2600,7 +2599,7 @@ void Courtroom::on_note_text_changed() void Courtroom::ping_server() { - ao_app->send_server_packet(new AOPacket("CH#" + QString::number(m_cid) + "#%")); + ao_app->send_server_packet(AOPacket("CH#" + QString::number(m_cid) + "#%")); } void Courtroom::closeEvent(QCloseEvent *event) diff --git a/src/evidence.cpp b/src/evidence.cpp index 0a40d677b..ad62a4748 100644 --- a/src/evidence.cpp +++ b/src/evidence.cpp @@ -176,7 +176,7 @@ void Courtroom::on_evidence_name_edited() f_contents.append(f_evi.description); f_contents.append(f_evi.image); - ao_app->send_server_packet(new AOPacket("EE", f_contents)); + ao_app->send_server_packet(AOPacket("EE", f_contents)); } void Courtroom::on_evidence_image_name_edited() @@ -193,7 +193,7 @@ void Courtroom::on_evidence_image_name_edited() f_contents.append(f_evi.description); f_contents.append(ui_evidence_image_name->text()); - ao_app->send_server_packet(new AOPacket("EE", f_contents)); + ao_app->send_server_packet(AOPacket("EE", f_contents)); } void Courtroom::on_evidence_image_button_clicked() @@ -231,7 +231,7 @@ void Courtroom::on_evidence_clicked(int p_id) if (f_real_id == local_evidence_list.size()) { - ao_app->send_server_packet(new AOPacket("PE###empty.png#%")); + ao_app->send_server_packet(AOPacket("PE###empty.png#%")); return; } else if (f_real_id > local_evidence_list.size()) @@ -323,7 +323,7 @@ void Courtroom::on_evidence_delete_clicked() ui_evidence_description->setReadOnly(true); ui_evidence_overlay->hide(); - ao_app->send_server_packet(new AOPacket("DE#" + QString::number(current_evidence) + "#%")); + ao_app->send_server_packet(AOPacket("DE#" + QString::number(current_evidence) + "#%")); current_evidence = 0; @@ -347,7 +347,7 @@ void Courtroom::on_evidence_x_clicked() f_contents.append(ui_evidence_description->toPlainText()); f_contents.append(f_evi.image); - ao_app->send_server_packet(new AOPacket("EE", f_contents)); + ao_app->send_server_packet(AOPacket("EE", f_contents)); ui_ic_chat_message->setFocus(); } diff --git a/src/lobby.cpp b/src/lobby.cpp index dddede300..775a83a07 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -303,12 +303,7 @@ void Lobby::on_connect_pressed() void Lobby::on_connect_released() { ui_connect->set_image("connect.png"); - - AOPacket *f_packet = nullptr; - - f_packet = new AOPacket("askchaa#%"); - - ao_app->send_server_packet(f_packet); + ao_app->send_server_packet(AOPacket("askchaa#%")); } void Lobby::on_about_clicked() @@ -378,9 +373,7 @@ void Lobby::on_chatfield_return_pressed() QString f_header = "CT"; QStringList f_contents{ui_chatname->text(), ui_chatmessage->text()}; - AOPacket *f_packet = new AOPacket(f_header, f_contents); - - ao_app->send_master_packet(f_packet); + ao_app->send_master_packet(AOPacket(f_header, f_contents)); ui_chatmessage->clear(); } diff --git a/src/master_socket.cpp b/src/master_socket.cpp index f17978e85..75dd43368 100644 --- a/src/master_socket.cpp +++ b/src/master_socket.cpp @@ -9,22 +9,34 @@ #include -void AOApplication::send_master_packet(AOPacket *p_packet) +void AOApplication::connect_to_master() { - qDebug().noquote() << "M/S:" << p_packet->to_string(); - m_master_socket->send_packet(*p_packet); + server_type l_server; + l_server.name = MASTER_NAME; + l_server.ip = MASTER_HOST; + l_server.port = MASTER_PORT; + m_master_socket->connect_to_server(l_server, true); +} + +void AOApplication::send_master_packet(AOPacket p_packet) +{ + qDebug().noquote() << "M/S:" << p_packet.to_string(); + m_master_socket->send_packet(p_packet); } void AOApplication::request_server_list() { + if (!m_master_socket->is_connected()) + { + connect_to_master(); + return; + } m_master_socket->send_packet(AOPacket("ALL", {})); } void AOApplication::_p_send_master_handshake() { -#ifndef QT_DEBUG - send_master_packet(new AOPacket("ALL#%")); -#endif + send_master_packet(AOPacket("ALL#%")); } void AOApplication::_p_handle_master_error(QString p_error) @@ -96,8 +108,8 @@ void AOApplication::_p_handle_master_packet(AOPacket p_packet) } else if (l_header == "AO2CHECK") { - send_master_packet(new AOPacket("ID#DRO#" + get_version_string() + "#%")); - send_master_packet(new AOPacket("HI#" + get_hdid() + "#%")); + send_master_packet(AOPacket("ID#DRO#" + get_version_string() + "#%")); + send_master_packet(AOPacket("HI#" + get_hdid() + "#%")); if (l_content.size() < 1) return; diff --git a/src/server_socket.cpp b/src/server_socket.cpp index f5dbd507d..07669e013 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -18,10 +18,10 @@ void AOApplication::connect_to_server(server_type p_server) m_server_socket->connect_to_server(p_server, false); } -void AOApplication::send_server_packet(AOPacket *p_packet) +void AOApplication::send_server_packet(AOPacket p_packet) { - qDebug().noquote() << "S/S:" << p_packet->to_string(); - m_server_socket->send_packet(*p_packet); + qDebug().noquote() << "S/S:" << p_packet.to_string(); + m_server_socket->send_packet(p_packet); } void AOApplication::_p_handle_server_packet(AOPacket p_packet) @@ -52,8 +52,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) feature_chrini = false; feature_chat_speed = false; - AOPacket *hi_packet = new AOPacket("HI#" + f_hdid + "#%"); - send_server_packet(hi_packet); + send_server_packet(AOPacket("HI#" + f_hdid + "#%")); } else if (l_header == "ID") { @@ -63,7 +62,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) m_client_id = l_content.at(0).toInt(); m_server_software = l_content.at(1); - send_server_packet(new AOPacket("ID#DRO#" + get_version_string() + "#%")); + send_server_packet(AOPacket("ID#DRO#" + get_version_string() + "#%")); } else if (l_header == "CT") { @@ -142,10 +141,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) m_lobby->set_loading_text("Loading"); m_lobby->set_loading_value(0); - AOPacket *f_packet = nullptr; - - f_packet = new AOPacket("RC#%"); - send_server_packet(f_packet); + send_server_packet(AOPacket("RC#%")); // look for the server inside the known public list and report it if (is_favorite) @@ -201,7 +197,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) int loading_value = (m_loaded_characters / static_cast(total_loading_size)) * 100; m_lobby->set_loading_value(loading_value); - send_server_packet(new AOPacket("RE#%")); + send_server_packet(AOPacket("RE#%")); } else if (l_header == "EI") { @@ -236,7 +232,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) m_lobby->set_loading_value(loading_value); QString next_packet_number = QString::number(m_loaded_evidence); - send_server_packet(new AOPacket("AE#" + next_packet_number + "#%")); + send_server_packet(AOPacket("AE#" + next_packet_number + "#%")); } else if (l_header == "CharsCheck") { @@ -281,7 +277,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) int loading_value = (m_loaded_characters / static_cast(total_loading_size)) * 100; m_lobby->set_loading_value(loading_value); - send_server_packet(new AOPacket("RM#%")); + send_server_packet(AOPacket("RM#%")); } else if (l_header == "SM" || l_header == "FM") { @@ -324,7 +320,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) int loading_value = ((m_loaded_characters + m_loaded_evidence + m_loaded_music) / static_cast(total_loading_size)) * 100; m_lobby->set_loading_value(loading_value); - send_server_packet(new AOPacket("RD#%")); + send_server_packet(AOPacket("RD#%")); } } else if (l_header == "DONE") From 45cf1d5851d7b81ac52f6c52acce50960ce8d1db Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 29 May 2021 22:58:45 +0200 Subject: [PATCH 398/842] Small optimizations --- src/aoapplication.cpp | 1 - src/master_socket.cpp | 4 ++-- src/server_socket.cpp | 9 +++------ 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 7c6b76696..4c9d8b7d1 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -49,7 +49,6 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(m_master_socket, SIGNAL(socket_error(QString)), this, SLOT(_p_handle_master_error(QString))); connect(m_master_socket, SIGNAL(packet_received(AOPacket)), this, SLOT(_p_handle_master_packet(AOPacket))); - connect(m_server_socket, SIGNAL(connected_to_server()), this, SLOT(_p_send_master_handshake())); connect(m_server_socket, SIGNAL(packet_received(AOPacket)), this, SLOT(_p_handle_server_packet(AOPacket))); #ifndef QT_DEBUG diff --git a/src/master_socket.cpp b/src/master_socket.cpp index 75dd43368..8bcb27fde 100644 --- a/src/master_socket.cpp +++ b/src/master_socket.cpp @@ -50,8 +50,8 @@ void AOApplication::_p_handle_master_packet(AOPacket p_packet) { p_packet.net_decode(); - QString l_header = p_packet.get_header(); - QStringList l_content = p_packet.get_contents(); + const QString l_header = p_packet.get_header(); + const QStringList l_content = p_packet.get_contents(); if (l_header != "CHECK") qDebug().noquote() << "M/R:" << p_packet.to_string(); diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 07669e013..e2fb1dd17 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -28,8 +28,8 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) { p_packet.net_decode(); - QString l_header = p_packet.get_header(); - QStringList l_content = p_packet.get_contents(); + const QString l_header = p_packet.get_header(); + const QStringList l_content = p_packet.get_contents(); if (l_header != "checkconnection") qDebug().noquote() << "S/R:" << p_packet.to_string(); @@ -42,9 +42,6 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) if (l_content.size() == 0) return; - QString f_hdid; - f_hdid = get_hdid(); - #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release feature_ackMS = false; #endif @@ -52,7 +49,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) feature_chrini = false; feature_chat_speed = false; - send_server_packet(AOPacket("HI#" + f_hdid + "#%")); + send_server_packet(AOPacket("HI#" + get_hdid() + "#%")); } else if (l_header == "ID") { From f262747b26513331b82dd5f77adcccad057d93e7 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 00:13:37 +0200 Subject: [PATCH 399/842] Added text_alignment for DRTextArea, as replacement for halign and valign --- include/drtextedit.h | 15 +++++++----- src/aoapplication.cpp | 7 +++--- src/aoconfig.cpp | 2 +- src/aoemotebutton.cpp | 4 +-- src/aonotearea.cpp | 2 +- src/courtroom.cpp | 2 +- src/courtroom_widgets.cpp | 2 +- src/drtextedit.cpp | 46 +++++++---------------------------- src/theme.cpp | 51 +++++++++------------------------------ 9 files changed, 39 insertions(+), 92 deletions(-) diff --git a/include/drtextedit.h b/include/drtextedit.h index 4566fae7a..b4b71292a 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -6,21 +6,24 @@ class DRTextEdit : public QTextEdit { Q_OBJECT + Q_PROPERTY( + Qt::Alignment text_alignment READ get_text_alignment WRITE set_text_alignment NOTIFY text_alignment_changed) public: DRTextEdit(QWidget *p_parent); - Qt::Alignment get_vertical_alignment(); - Qt::Alignment get_horizontal_alignment(); + Qt::Alignment get_text_alignment() const; +public slots: void set_outline(bool enabled); void set_auto_align(bool enabled); - void set_vertical_alignment(Qt::Alignment p_align); - void set_horizontal_alignment(Qt::Alignment p_align); + void set_text_alignment(Qt::Alignment alignment); + +signals: + void text_alignment_changed(Qt::Alignment); private: - Qt::Alignment m_valign = Qt::AlignTop; - Qt::Alignment m_halign = Qt::AlignLeft; + Qt::Alignment m_text_align = Qt::AlignTop | Qt::AlignLeft; bool has_outline = false; bool is_auto_align = true; enum class Status diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 4c9d8b7d1..4a546af15 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -223,10 +223,9 @@ QVector &AOApplication::get_favorite_list() QString AOApplication::get_current_char() { - if (is_courtroom_constructed) - return m_courtroom->get_current_character(); - else - return ""; + if (!is_courtroom_constructed) + return nullptr; + return m_courtroom->get_current_character(); } /** diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 9f622637c..6689948b9 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -125,7 +125,7 @@ void AOConfigPrivate::read_file() gamemode = cfg.value("gamemode").toString(); manual_gamemode = cfg.value("manual_gamemode", false).toBool(); - timeofday = cfg.value("timeofday", "").toString(); + timeofday = cfg.value("timeofday").toString(); manual_timeofday = cfg.value("manual_timeofday", false).toBool(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index cd892f786..1d535b4d5 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -59,7 +59,7 @@ void AOEmoteButton::set_image(DREmote p_emote, bool p_enabled) else { ui_selected->setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, " - "y2:1, stop:0 rgba(0, 0, 0, 0), stop:1 rgba(0, 0, 0, 127)); }"); + "y2:1, stop:0 rgba(0, 0, 0, 0), stop:1 rgba(0, 0, 0, 127)); }"); } ui_selected->show(); @@ -70,7 +70,7 @@ void AOEmoteButton::set_image(DREmote p_emote, bool p_enabled) setText(l_texture_exist ? nullptr : p_emote.comment); setStyleSheet(l_texture_exist ? QString("%1 { border-image: url(\"%2\"); }").arg(metaObject()->className()).arg(l_texture) - : QString()); + : nullptr); } void AOEmoteButton::on_clicked() diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp index 100186401..4d5bf8b66 100644 --- a/src/aonotearea.cpp +++ b/src/aonotearea.cpp @@ -78,7 +78,7 @@ void Courtroom::set_note_files() QTextStream in(&config_file); - QByteArray t = ""; + QByteArray t; for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) { diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 081d6ba64..21f0e5a2b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -857,7 +857,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) if (p_contents.size() < 15) return; else if (p_contents.size() == 15) - p_contents.append(QString()); + p_contents.append(nullptr); for (int i = 0; i < MESSAGE_SIZE; ++i) m_chatmessage[i] = p_contents[i]; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 63160defe..b7e4c9d10 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1154,7 +1154,7 @@ void Courtroom::delete_widget(QWidget *p_widget) grand_parent = this; // set new parent - for (QWidget *child : p_widget->findChildren(QString(), Qt::FindDirectChildrenOnly)) + for (QWidget *child : p_widget->findChildren(nullptr, Qt::FindDirectChildrenOnly)) child->setParent(grand_parent); // delete widget diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index 8c812038f..0dde3ba0f 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -7,6 +7,7 @@ DRTextEdit::DRTextEdit(QWidget *parent) : QTextEdit(parent) { connect(this, SIGNAL(textChanged()), this, SLOT(on_text_changed())); + connect(this, SIGNAL(text_alignment_changed(Qt::Alignment)), this, SLOT(on_text_changed())); } void DRTextEdit::set_outline(bool p_enabled) @@ -30,46 +31,17 @@ void DRTextEdit::set_auto_align(bool p_enabled) on_text_changed(); } -void DRTextEdit::set_vertical_alignment(Qt::Alignment p_align) +void DRTextEdit::set_text_alignment(Qt::Alignment p_align) { - switch (p_align) - { - case Qt::AlignTop: - case Qt::AlignVCenter: - case Qt::AlignBottom: - break; - default: - set_vertical_alignment(Qt::AlignTop); + if (m_text_align == p_align) return; - } - m_valign = p_align; - on_text_changed(); -} - -Qt::Alignment DRTextEdit::get_vertical_alignment() -{ - return this->m_valign; -} - -void DRTextEdit::set_horizontal_alignment(Qt::Alignment p_align) -{ - switch (p_align) - { - case Qt::AlignLeft: - case Qt::AlignHCenter: - case Qt::AlignRight: - break; - default: - set_horizontal_alignment(Qt::AlignLeft); - return; - } - m_halign = p_align; - on_text_changed(); + m_text_align = p_align; + Q_EMIT text_alignment_changed(m_text_align); } -Qt::Alignment DRTextEdit::get_horizontal_alignment() +Qt::Alignment DRTextEdit::get_text_alignment() const { - return this->m_halign; + return m_text_align; } void DRTextEdit::on_text_changed() @@ -111,7 +83,7 @@ void DRTextEdit::refresh_horizontal_alignment() // Otherwise, we have changed the number of blocks. By induction only the current block needs to be // updated, which is why we can get away with doing this setAlignment once, and right here. // qDebug() << this << document()->toPlainText() << new_document_blocks; - setAlignment(m_halign); + setAlignment(m_text_align); } void DRTextEdit::refresh_vertical_alignment() @@ -135,7 +107,7 @@ void DRTextEdit::refresh_vertical_alignment() // The way we will simulate vertical alignment is by adjusting the top margin to simulate // center alignment, or bottom alignment. int top_margin = 0; - switch (m_valign) + switch (m_text_align & (Qt::AlignVCenter | Qt::AlignBottom)) { case Qt::AlignVCenter: top_margin = (height() - new_document_height) / 2; diff --git a/src/theme.cpp b/src/theme.cpp index 037f2be6b..eed0ce7ce 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -9,6 +9,8 @@ #include #include +#include + void set_size_and_pos(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) { pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, p_ini_file); @@ -25,11 +27,12 @@ void set_size_and_pos(QWidget *p_widget, QString p_identifier, QString p_ini_fil } } -void set_text_alignment(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) +void set_text_alignment_property(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app, + std::string p_property) { const QStringList l_values = ao_app->read_theme_ini(p_identifier + "_align", p_ini_file).split(",", DR::SkipEmptyParts); - p_widget->setProperty("alignment", + p_widget->setProperty(p_property.c_str(), QVariant(QHash{ {"left", Qt::AlignLeft}, {"center", Qt::AlignHCenter}, {"right", Qt::AlignRight}} .value(l_values.value(0).trimmed().toLower(), Qt::AlignLeft) | @@ -38,6 +41,11 @@ void set_text_alignment(QWidget *p_widget, QString p_identifier, QString p_ini_f .value(l_values.value(1).trimmed().toLower(), Qt::AlignVCenter))); } +void set_text_alignment(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) +{ + set_text_alignment_property(p_widget, p_identifier, p_ini_file, ao_app, "alignment"); +} + void set_font(QWidget *p_widget, QString p_identifier, QString ini_file, AOApplication *ao_app) { QString class_name = p_widget->metaObject()->className(); @@ -77,41 +85,6 @@ void set_drtextedit_font(DRTextEdit *p_widget, QString p_identifier, QString ini bool outline = (ao_app->get_font_property(p_identifier + "_outline", ini_file) == 1); p_widget->set_outline(outline); - // Do horizontal alignments - int raw_halign = ao_app->get_font_property(p_identifier + "_halign", ini_file); - switch (raw_halign) - { - default: - qWarning() << "Unknown horizontal alignment for " + p_identifier + ". Assuming Left."; - [[fallthrough]]; - case DR::Left: - p_widget->set_horizontal_alignment(Qt::AlignLeft); - break; - case DR::Middle: - p_widget->set_horizontal_alignment(Qt::AlignHCenter); - break; - case DR::Right: - p_widget->set_horizontal_alignment(Qt::AlignRight); - break; - } - - // Do vertical alignments - int raw_valign = ao_app->get_font_property(p_identifier + "_valign", ini_file); - Qt::Alignment valignment; - switch (raw_valign) - { - default: - qWarning() << "Unknown vertical alignment for" << p_identifier << ":" << raw_valign << "Assuming Top."; - [[fallthrough]]; - case DR::Top: - valignment = Qt::AlignTop; - break; - case DR::Middle: - valignment = Qt::AlignVCenter; - break; - case DR::Bottom: - valignment = Qt::AlignBottom; - break; - } - p_widget->set_vertical_alignment(valignment); + // alignment + set_text_alignment_property(p_widget, p_identifier, ini_file, ao_app, "text_alignment"); } From dce0fe2eab55c113fb9501d10a0d0c13f86560c7 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 00:56:12 +0200 Subject: [PATCH 400/842] Fixed Courtroom not being removed --- include/aoapplication.h | 2 +- src/aoapplication.cpp | 11 +---------- src/courtroom.cpp | 10 ++++------ src/drserversocket.cpp | 8 ++++---- src/server_socket.cpp | 10 ++++++++++ src/theme.cpp | 21 +++++++++++++-------- 6 files changed, 33 insertions(+), 29 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 1086fc6fe..cdc1caf24 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -192,7 +192,6 @@ class AOApplication : public QApplication QStringList get_overlay(QString p_char, int p_effect); public slots: - void server_disconnected(); void loading_cancelled(); signals: @@ -241,6 +240,7 @@ private slots: void _p_send_master_handshake(); void _p_handle_master_error(QString); void _p_handle_master_packet(AOPacket); + void _p_handle_server_disconnection(); void _p_handle_server_packet(AOPacket); void on_courtroom_closing(); void on_courtroom_destroyed(); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 4a546af15..f3c670aa3 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -49,6 +49,7 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(m_master_socket, SIGNAL(socket_error(QString)), this, SLOT(_p_handle_master_error(QString))); connect(m_master_socket, SIGNAL(packet_received(AOPacket)), this, SLOT(_p_handle_master_packet(AOPacket))); + connect(m_server_socket, SIGNAL(disconnected_from_server()), this, SLOT(_p_handle_server_disconnection())); connect(m_server_socket, SIGNAL(packet_received(AOPacket)), this, SLOT(_p_handle_server_packet(AOPacket))); #ifndef QT_DEBUG @@ -295,16 +296,6 @@ QVector &AOApplication::get_server_list() return m_server_list; } -void AOApplication::server_disconnected() -{ - if (!is_courtroom_constructed) - return; - m_courtroom->stop_all_audio(); - call_notice("Disconnected from server."); - construct_lobby(); - destruct_courtroom(); -} - void AOApplication::loading_cancelled() { destruct_courtroom(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 21f0e5a2b..beb8b2b58 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -247,6 +247,9 @@ void Courtroom::enter_courtroom(int p_cid) // restore line field focus l_current_field->setFocus(); l_current_field->setCursorPosition(l_current_cursor_pos); + + if (!is_showname_sent) + send_showname_packet(ao_config->showname()); } void Courtroom::done_received() @@ -681,6 +684,7 @@ void Courtroom::on_showname_changed(QString p_showname) */ void Courtroom::send_showname_packet(QString p_showname) { + is_showname_sent = true; if (ao_app->has_showname_declaration_feature()) { QStringList l_content = {p_showname}; @@ -718,12 +722,6 @@ void Courtroom::on_ic_message_return_pressed() if ((anim_state < 3 || text_state < 2) && m_shout_state == 0) return; - if (!is_showname_sent) - { - is_showname_sent = true; - send_showname_packet(ao_config->showname()); - } - // qDebug() << "prev_emote = " << prev_emote << "current_emote = " << // current_emote; diff --git a/src/drserversocket.cpp b/src/drserversocket.cpp index 700cb8ca2..542401dc9 100644 --- a/src/drserversocket.cpp +++ b/src/drserversocket.cpp @@ -56,7 +56,8 @@ void DRServerSocket::send_packet(AOPacket p_packet) if (!is_connected()) { const QString l_server_info = m_server.to_info(); - qWarning() << QString("Failed to send packet; not connected to server%1").arg(drFormatServerInfo(m_server)); + qWarning().noquote() + << QString("Failed to send packet; not connected to server%1").arg(drFormatServerInfo(m_server)); return; } m_socket->write(p_packet.to_string().toUtf8()); @@ -95,9 +96,8 @@ void DRServerSocket::_p_reconnect_to_server() void DRServerSocket::_p_check_socket_error() { - const QString l_error = - QString("Connection error for server%1: %2").arg(drFormatServerInfo(m_server), m_socket->errorString()); - qWarning() << l_error; + const QString l_error = QString("Server%1 error: %2").arg(drFormatServerInfo(m_server), m_socket->errorString()); + qWarning().noquote() << l_error; Q_EMIT socket_error(l_error); } diff --git a/src/server_socket.cpp b/src/server_socket.cpp index e2fb1dd17..26bc0a891 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -24,6 +24,16 @@ void AOApplication::send_server_packet(AOPacket p_packet) m_server_socket->send_packet(p_packet); } +void AOApplication::_p_handle_server_disconnection() +{ + if (!is_courtroom_constructed) + return; + m_courtroom->stop_all_audio(); + call_notice("Disconnected from server."); + construct_lobby(); + destruct_courtroom(); +} + void AOApplication::_p_handle_server_packet(AOPacket p_packet) { p_packet.net_decode(); diff --git a/src/theme.cpp b/src/theme.cpp index eed0ce7ce..f7545a6df 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -27,23 +27,27 @@ void set_size_and_pos(QWidget *p_widget, QString p_identifier, QString p_ini_fil } } -void set_text_alignment_property(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app, - std::string p_property) +void set_text_alignment_or_default(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app, + std::string p_property, Qt::Alignment p_default_horizontal, + Qt::Alignment p_default_vertical) { const QStringList l_values = ao_app->read_theme_ini(p_identifier + "_align", p_ini_file).split(",", DR::SkipEmptyParts); + if (!p_widget->property(p_property.c_str()).isValid()) + return; p_widget->setProperty(p_property.c_str(), - QVariant(QHash{ + QVariant(QHash{ {"left", Qt::AlignLeft}, {"center", Qt::AlignHCenter}, {"right", Qt::AlignRight}} - .value(l_values.value(0).trimmed().toLower(), Qt::AlignLeft) | - QHash{ + .value(l_values.value(0).trimmed().toLower(), p_default_horizontal) | + QHash{ {"top", Qt::AlignTop}, {"center", Qt::AlignVCenter}, {"bottom", Qt::AlignBottom}} - .value(l_values.value(1).trimmed().toLower(), Qt::AlignVCenter))); + .value(l_values.value(1).trimmed().toLower(), p_default_vertical))); } void set_text_alignment(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) { - set_text_alignment_property(p_widget, p_identifier, p_ini_file, ao_app, "alignment"); + set_text_alignment_or_default(p_widget, p_identifier, p_ini_file, ao_app, "alignment", Qt::AlignLeft, + Qt::AlignVCenter); } void set_font(QWidget *p_widget, QString p_identifier, QString ini_file, AOApplication *ao_app) @@ -86,5 +90,6 @@ void set_drtextedit_font(DRTextEdit *p_widget, QString p_identifier, QString ini p_widget->set_outline(outline); // alignment - set_text_alignment_property(p_widget, p_identifier, ini_file, ao_app, "text_alignment"); + set_text_alignment_or_default(p_widget, p_identifier, ini_file, ao_app, "text_alignment", Qt::AlignLeft, + Qt::AlignTop); } From e54e2534190c6e097d09d5cc25e4f80a63445e02 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 01:34:27 +0200 Subject: [PATCH 401/842] When receiving a showname from the server, do not send back the received showname as confirmation --- include/courtroom.h | 4 +++- src/courtroom.cpp | 22 +++++++++++++++++----- src/server_socket.cpp | 8 ++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 8b6fb85bc..e73b1035a 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -88,6 +88,7 @@ class Courtroom : public QMainWindow void send_ooc_packet(QString ooc_name, QString ooc_message); + void ignore_next_showname(); void send_showname_packet(QString p_showname); // called when a DONE#% from the server was received @@ -246,7 +247,8 @@ class Courtroom : public QMainWindow // used to determine how often blips sound int m_blip_step = 0; int m_rainbow_step = 0; - bool is_showname_sent = false; + bool is_first_showname_sent = false; + bool is_next_showname_ignored = false; bool is_rainbow_enabled = false; bool is_note_shown = false; bool contains_add_button = false; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index beb8b2b58..b65b11b6a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -248,7 +248,7 @@ void Courtroom::enter_courtroom(int p_cid) l_current_field->setFocus(); l_current_field->setCursorPosition(l_current_cursor_pos); - if (!is_showname_sent) + if (!is_first_showname_sent) send_showname_packet(ao_config->showname()); } @@ -671,10 +671,9 @@ void Courtroom::append_server_chatmessage(QString p_name, QString p_message) save_textlog("(OOC)" + p_name + ": " + p_message); } -void Courtroom::on_showname_changed(QString p_showname) +void Courtroom::ignore_next_showname() { - ui_ic_chat_showname->setText(p_showname); - send_showname_packet(p_showname); + is_next_showname_ignored = true; } /** @@ -684,7 +683,14 @@ void Courtroom::on_showname_changed(QString p_showname) */ void Courtroom::send_showname_packet(QString p_showname) { - is_showname_sent = true; + is_first_showname_sent = true; + + if (is_next_showname_ignored) + { + is_next_showname_ignored = false; + return; + } + if (ao_app->has_showname_declaration_feature()) { QStringList l_content = {p_showname}; @@ -696,6 +702,12 @@ void Courtroom::send_showname_packet(QString p_showname) } } +void Courtroom::on_showname_changed(QString p_showname) +{ + ui_ic_chat_showname->setText(p_showname); + send_showname_packet(p_showname); +} + bool Courtroom::is_spectating() { return m_cid == -1; diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 26bc0a891..0dd631fce 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -546,15 +546,11 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) } else if (l_header == "SN") { - // Server-set showname. - // This has priority over user-set showname. if (l_content.size() != 1) return; if (!is_courtroom_constructed) return; - const QString l_showname = l_content.at(0); - // By updating now, we prevent the client from sending an SN back when we later on go ahead and modify the - // showname box. - ao_config->set_showname(l_showname); + m_courtroom->ignore_next_showname(); + ao_config->set_showname(l_content.at(0)); } } From cab5ad6b165b8f440adbcf6630d755f6f27e78c5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 01:38:58 +0200 Subject: [PATCH 402/842] Changed order of arguments for sending showname packet --- src/courtroom.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index b65b11b6a..23a444153 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -683,18 +683,17 @@ void Courtroom::ignore_next_showname() */ void Courtroom::send_showname_packet(QString p_showname) { - is_first_showname_sent = true; - if (is_next_showname_ignored) { is_next_showname_ignored = false; return; } + is_first_showname_sent = true; + if (ao_app->has_showname_declaration_feature()) { - QStringList l_content = {p_showname}; - ao_app->send_server_packet(AOPacket("SN", l_content)); + ao_app->send_server_packet(AOPacket("SN", {p_showname})); } else { From 1b76870b8c96511ceddb73440ceb09ea4492bc9b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 01:43:19 +0200 Subject: [PATCH 403/842] BUGFIX: do not ignore a different showname sent from the server --- src/server_socket.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 0dd631fce..52b1ef97a 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -550,7 +550,9 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) return; if (!is_courtroom_constructed) return; - m_courtroom->ignore_next_showname(); - ao_config->set_showname(l_content.at(0)); + const QString &l_showname = l_content.at(0); + if (ao_config->showname() != l_showname) + m_courtroom->ignore_next_showname(); + ao_config->set_showname(l_showname); } } From 9367b67c0cf3b401a41c9db03515b2d3bcfe6c32 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 03:10:47 +0200 Subject: [PATCH 404/842] Simplified math for character and emote page --- include/courtroom.h | 12 +++---- src/charselect.cpp | 67 ++++++++++++++++----------------------- src/courtroom.cpp | 12 +++---- src/courtroom_widgets.cpp | 12 +++---- src/emotes.cpp | 55 ++++++++++++++------------------ 5 files changed, 68 insertions(+), 90 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index e73b1035a..54ac71d8f 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -340,17 +340,17 @@ class Courtroom : public QMainWindow int defense_bar_state = 0; int prosecution_bar_state = 0; - int current_char_page = 0; + int m_current_chr_page = 0; int char_columns = 10; int char_rows = 9; - int max_chars_on_page = 90; + int m_page_max_chr_count = 90; QVector m_emote_list; int m_emote_id = 0; - int m_emote_page = 0; + int m_current_emote_page = 0; int emote_columns = 5; int emote_rows = 2; - int max_emotes_on_page = 10; + int m_page_max_emote_count = 10; // inmchatlog_changed; @@ -539,8 +539,8 @@ class Courtroom : public QMainWindow AOButton *ui_back_to_lobby = nullptr; - AOButton *ui_char_select_left = nullptr; - AOButton *ui_char_select_right = nullptr; + AOButton *ui_chr_select_left = nullptr; + AOButton *ui_chr_select_right = nullptr; AOButton *ui_spectator = nullptr; diff --git a/src/charselect.cpp b/src/charselect.cpp index cf96963e1..9e720f2ef 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -12,6 +12,7 @@ #include #include +#include void Courtroom::construct_char_select() { @@ -25,8 +26,8 @@ void Courtroom::construct_char_select() ui_back_to_lobby = new AOButton(ui_char_select_background, ao_app); - ui_char_select_left = new AOButton(ui_char_select_background, ao_app); - ui_char_select_right = new AOButton(ui_char_select_background, ao_app); + ui_chr_select_left = new AOButton(ui_char_select_background, ao_app); + ui_chr_select_right = new AOButton(ui_char_select_background, ao_app); ui_spectator = new AOButton(ui_char_select_background, ao_app); ui_spectator->setText("Spectator"); @@ -34,8 +35,8 @@ void Courtroom::construct_char_select() connect(char_button_mapper, SIGNAL(mapped(int)), this, SLOT(char_clicked(int))); connect(ui_back_to_lobby, SIGNAL(clicked()), this, SLOT(on_back_to_lobby_clicked())); - connect(ui_char_select_left, SIGNAL(clicked()), this, SLOT(on_char_select_left_clicked())); - connect(ui_char_select_right, SIGNAL(clicked()), this, SLOT(on_char_select_right_clicked())); + connect(ui_chr_select_left, SIGNAL(clicked()), this, SLOT(on_char_select_left_clicked())); + connect(ui_chr_select_right, SIGNAL(clicked()), this, SLOT(on_char_select_right_clicked())); connect(ui_spectator, SIGNAL(clicked()), this, SLOT(on_spectator_clicked())); @@ -62,9 +63,9 @@ void Courtroom::reconstruct_char_select() char_columns = ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + 1; char_rows = ((ui_char_buttons->height() - button_height) / (y_spacing + button_height)) + 1; - max_chars_on_page = char_columns * char_rows; + m_page_max_chr_count = char_columns * char_rows; - for (int n = 0; n < max_chars_on_page; ++n) + for (int n = 0; n < m_page_max_chr_count; ++n) { int x_pos = (button_width + x_spacing) * x_mod_count; int y_pos = (button_height + y_spacing) * y_mod_count; @@ -93,7 +94,7 @@ void Courtroom::reconstruct_char_select() void Courtroom::reset_char_select() { - current_char_page = 0; + m_current_chr_page = 0; set_char_select(); set_char_select_page(); @@ -121,8 +122,8 @@ void Courtroom::set_char_select_page() { ui_char_select_background->show(); - ui_char_select_left->hide(); - ui_char_select_right->hide(); + ui_chr_select_left->hide(); + ui_chr_select_right->hide(); for (AOCharButton *button : qAsConst(ui_char_button_list)) { @@ -130,47 +131,33 @@ void Courtroom::set_char_select_page() button->hide(); } - int total_pages = m_chr_list.size() / max_chars_on_page; - int chars_on_page = 0; + const int l_item_count = m_chr_list.length(); + const int l_page_count = qFloor(l_item_count / m_page_max_chr_count) + bool(l_item_count % m_page_max_chr_count); + m_current_chr_page = qBound(0, m_current_chr_page, l_page_count - 1); + const int l_current_page_emote_count = + qBound(0, l_item_count - m_current_chr_page * m_page_max_chr_count, m_page_max_chr_count); - if (m_chr_list.size() % max_chars_on_page != 0) - { - ++total_pages; - // i. e. not on the last page - if (total_pages > current_char_page + 1) - chars_on_page = max_chars_on_page; - else - chars_on_page = m_chr_list.size() % max_chars_on_page; - } - else - chars_on_page = max_chars_on_page; - - if (total_pages > current_char_page + 1) - ui_char_select_right->show(); + if (m_current_chr_page + 1 < l_page_count) + ui_chr_select_right->show(); - if (current_char_page > 0) - ui_char_select_left->show(); + if (m_current_chr_page > 0) + ui_chr_select_left->show(); // show all buttons for this page - for (int n_button = 0; n_button < chars_on_page; ++n_button) + for (int i = 0; i < l_current_page_emote_count; ++i) { - if (m_chr_list.length() <= n_button) - continue; - - int n_real_char = n_button + current_char_page * max_chars_on_page; - AOCharButton *f_button = ui_char_button_list.at(n_button); - - f_button->set_image(m_chr_list.at(n_real_char).name); - f_button->show(); - - if (m_chr_list.at(n_real_char).taken) - f_button->set_taken(); + int l_real_i = i + m_current_chr_page * m_page_max_chr_count; + AOCharButton *l_button = ui_char_button_list.at(i); + l_button->set_image(m_chr_list.at(l_real_i).name); + l_button->show(); + if (m_chr_list.at(l_real_i).taken) + l_button->set_taken(); } } void Courtroom::char_clicked(int n_char) { - int n_real_char = n_char + current_char_page * max_chars_on_page; + int n_real_char = n_char + m_current_chr_page * m_page_max_chr_count; QString char_ini_path = ao_app->get_character_path(m_chr_list.at(n_real_char).name, "char.ini"); qDebug() << "char_ini_path" << char_ini_path; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 23a444153..f10c69d8f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -95,7 +95,7 @@ void Courtroom::enter_courtroom(int p_cid) suppress_audio(false); const int l_prev_emote_id = m_emote_id; - const int l_prev_emote_page = m_emote_page; + const int l_prev_emote_page = m_current_emote_page; // widgets =================================================================== current_evidence_page = 0; @@ -222,7 +222,7 @@ void Courtroom::enter_courtroom(int p_cid) else { m_emote_id = l_prev_emote_id; - m_emote_page = l_prev_emote_page; + m_current_emote_page = l_prev_emote_page; ui_emote_dropdown->setCurrentText(l_prev_emote); } set_emote_page(); @@ -1537,7 +1537,7 @@ void Courtroom::start_chat_timer() double l_tick_rate = ao_config->chat_tick_interval(); if (m_server_tick_rate.has_value()) l_tick_rate = qMax(m_server_tick_rate.value(), 0); - l_tick_rate = qBound(l_tick_rate * (1.0 - qBound(0.4 * m_tick_speed, -1.0, 1.0)), 0.0, l_tick_rate * 2.0); + l_tick_rate = qBound(0.0, l_tick_rate * (1.0 - qBound(-1.0, 0.4 * m_tick_speed, 1.0)), l_tick_rate * 2.0); m_tick_timer->start(l_tick_rate); } @@ -1570,7 +1570,7 @@ void Courtroom::next_chat_letter() { ++m_tick_step; const bool is_positive = f_character == Qt::Key_BraceRight; - m_tick_speed = qBound(m_tick_speed + (is_positive ? 1 : -1), -3, 3); + m_tick_speed = qBound(-3, m_tick_speed + (is_positive ? 1 : -1), 3); next_chat_letter(); return; } @@ -2493,13 +2493,13 @@ void Courtroom::on_back_to_lobby_clicked() void Courtroom::on_char_select_left_clicked() { - --current_char_page; + --m_current_chr_page; set_char_select_page(); } void Courtroom::on_char_select_right_clicked() { - ++current_char_page; + ++m_current_chr_page; set_char_select_page(); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index b7e4c9d10..081c3d0f9 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -425,8 +425,8 @@ void Courtroom::reset_widget_names() {"char_select", ui_char_select_background}, {"back_to_lobby", ui_back_to_lobby}, {"char_buttons", ui_char_buttons}, - {"char_select_left", ui_char_select_left}, - {"char_select_right", ui_char_select_right}, + {"char_select_left", ui_chr_select_left}, + {"char_select_right", ui_chr_select_right}, {"spectator", ui_spectator}, }; } @@ -961,11 +961,11 @@ void Courtroom::set_widgets() set_size_and_pos(ui_char_buttons, "char_buttons", INI_DESIGN, ao_app); - set_size_and_pos(ui_char_select_left, "char_select_left", INI_DESIGN, ao_app); - ui_char_select_left->set_image("arrow_left.png"); + set_size_and_pos(ui_chr_select_left, "char_select_left", INI_DESIGN, ao_app); + ui_chr_select_left->set_image("arrow_left.png"); - set_size_and_pos(ui_char_select_right, "char_select_right", INI_DESIGN, ao_app); - ui_char_select_right->set_image("arrow_right.png"); + set_size_and_pos(ui_chr_select_right, "char_select_right", INI_DESIGN, ao_app); + ui_chr_select_right->set_image("arrow_right.png"); set_size_and_pos(ui_spectator, "spectator", INI_DESIGN, ao_app); diff --git a/src/emotes.cpp b/src/emotes.cpp index 133633e1a..e7b7bf918 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -10,6 +10,7 @@ #include #include #include +#include void Courtroom::construct_emotes() { @@ -52,9 +53,9 @@ void Courtroom::reconstruct_emotes() emote_columns = ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; emote_rows = ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; - max_emotes_on_page = emote_columns * emote_rows; + m_page_max_emote_count = emote_columns * emote_rows; - for (int n = 0; n < max_emotes_on_page; ++n) + for (int n = 0; n < m_page_max_emote_count; ++n) { int x_pos = (button_width + x_spacing) * x_mod_count; int y_pos = (button_height + y_spacing) * y_mod_count; @@ -81,7 +82,7 @@ void Courtroom::reconstruct_emotes() void Courtroom::reset_emote_page() { - m_emote_page = 0; + m_current_emote_page = 0; m_emote_id = 0; if (is_spectating()) @@ -101,36 +102,27 @@ void Courtroom::set_emote_page() if (is_spectating()) return; - const int total_emotes = m_emote_list.length(); + const int l_emote_count = m_emote_list.length(); for (AOEmoteButton *i_button : qAsConst(ui_emote_list)) i_button->hide(); - int total_pages = total_emotes / max_emotes_on_page; - int emotes_on_page = 0; + const int l_page_count = + qFloor(l_emote_count / m_page_max_emote_count) + bool(l_emote_count % m_page_max_emote_count); + m_current_emote_page = qBound(0, m_current_emote_page, l_page_count - 1); + const int l_current_page_emote_count = + qBound(0, l_emote_count - m_current_emote_page * m_page_max_emote_count, m_page_max_emote_count); - if (total_emotes % max_emotes_on_page != 0) - { - ++total_pages; - // i. e. not on the last page - if (total_pages > m_emote_page + 1) - emotes_on_page = max_emotes_on_page; - else - emotes_on_page = total_emotes % max_emotes_on_page; - } - else - emotes_on_page = max_emotes_on_page; - - if (total_pages > m_emote_page + 1) + if (m_current_emote_page + 1 < l_page_count) ui_emote_right->show(); - if (m_emote_page > 0) + if (m_current_emote_page > 0) ui_emote_left->show(); - for (int n_emote = 0; n_emote < emotes_on_page; ++n_emote) + for (int i = 0; i < l_current_page_emote_count; ++i) { - const int l_real_id = n_emote + m_emote_page * max_emotes_on_page; - AOEmoteButton *l_button = ui_emote_list.at(n_emote); - l_button->set_image(get_emote(l_real_id), l_real_id == m_emote_id); + const int l_real_i = i + m_current_emote_page * m_page_max_emote_count; + AOEmoteButton *l_button = ui_emote_list.at(i); + l_button->set_image(get_emote(l_real_i), l_real_i == m_emote_id); l_button->show(); } } @@ -160,19 +152,19 @@ DREmote Courtroom::get_current_emote() void Courtroom::select_emote(int p_id) { - const int l_min = m_emote_page * max_emotes_on_page; - const int l_max = (max_emotes_on_page - 1) + m_emote_page * max_emotes_on_page; + const int l_min = m_current_emote_page * m_page_max_emote_count; + const int l_max = (m_page_max_emote_count - 1) + m_current_emote_page * m_page_max_emote_count; const DREmote &l_prev_emote = get_emote(m_emote_id); if (m_emote_id >= l_min && m_emote_id <= l_max) - ui_emote_list.at(m_emote_id % max_emotes_on_page)->set_image(l_prev_emote, false); + ui_emote_list.at(m_emote_id % m_page_max_emote_count)->set_image(l_prev_emote, false); const int l_prev_emote_id = m_emote_id; m_emote_id = p_id; const DREmote &l_emote = get_emote(m_emote_id); if (m_emote_id >= l_min && m_emote_id <= l_max) - ui_emote_list.at(m_emote_id % max_emotes_on_page)->set_image(l_emote, true); + ui_emote_list.at(m_emote_id % m_page_max_emote_count)->set_image(l_emote, true); const int emote_mod = l_emote.modifier; if (l_prev_emote_id == m_emote_id) // toggle @@ -189,12 +181,12 @@ void Courtroom::select_emote(int p_id) void Courtroom::on_emote_clicked(int p_id) { - select_emote(p_id + max_emotes_on_page * m_emote_page); + select_emote(p_id + m_page_max_emote_count * m_current_emote_page); } void Courtroom::on_emote_left_clicked() { - --m_emote_page; + --m_current_emote_page; set_emote_page(); @@ -203,8 +195,7 @@ void Courtroom::on_emote_left_clicked() void Courtroom::on_emote_right_clicked() { - qDebug() << "emote right clicked"; - ++m_emote_page; + ++m_current_emote_page; set_emote_page(); From ca5dd3fba30770485e888b631c020a435ab14eef Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 03:21:35 +0200 Subject: [PATCH 405/842] Only send showname if not empty upon entering the server --- src/aoconfig.cpp | 18 ++++++++++-------- src/courtroom.cpp | 5 +++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 6689948b9..021cf74a9 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -537,20 +537,22 @@ void AOConfig::set_autosave(bool p_enabled) d->invoke_signal("autosave_changed", Q_ARG(bool, p_enabled)); } -void AOConfig::set_username(QString p_string) +void AOConfig::set_username(QString p_value) { - if (d->username == p_string) + const QString l_trimmed_value = p_value.trimmed(); + if (d->username == l_trimmed_value) return; - d->username = p_string; - d->invoke_signal("username_changed", Q_ARG(QString, p_string)); + d->username = l_trimmed_value; + d->invoke_signal("username_changed", Q_ARG(QString, p_value)); } -void AOConfig::set_showname(QString p_string) +void AOConfig::set_showname(QString p_value) { - if (d->showname == p_string) + const QString l_trimmed_value = p_value.trimmed(); + if (d->showname == l_trimmed_value) return; - d->showname = p_string; - d->invoke_signal("showname_changed", Q_ARG(QString, p_string)); + d->showname = l_trimmed_value; + d->invoke_signal("showname_changed", Q_ARG(QString, d->showname)); } void AOConfig::set_showname_placeholder(QString p_string) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f10c69d8f..ca36bcb64 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -248,8 +248,9 @@ void Courtroom::enter_courtroom(int p_cid) l_current_field->setFocus(); l_current_field->setCursorPosition(l_current_cursor_pos); - if (!is_first_showname_sent) - send_showname_packet(ao_config->showname()); + const QString l_showname = ao_config->showname(); + if (!l_showname.isEmpty() && !is_first_showname_sent) + send_showname_packet(l_showname); } void Courtroom::done_received() From 6c3c766ca55adf03c324fd676588391bd628f369 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 03:24:09 +0200 Subject: [PATCH 406/842] Provide final value, not parameter --- src/aoconfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 021cf74a9..384c9ca21 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -543,7 +543,7 @@ void AOConfig::set_username(QString p_value) if (d->username == l_trimmed_value) return; d->username = l_trimmed_value; - d->invoke_signal("username_changed", Q_ARG(QString, p_value)); + d->invoke_signal("username_changed", Q_ARG(QString, d->username)); } void AOConfig::set_showname(QString p_value) From 5c16cb6c9613f4fd277fcf358e782b21cfd34725 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 04:03:08 +0200 Subject: [PATCH 407/842] Fixed OOC not using trimmed username --- src/courtroom.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ca36bcb64..caeed327c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1974,7 +1974,7 @@ void Courtroom::on_ooc_name_editing_finished() void Courtroom::on_ooc_return_pressed() { - const QString ooc_name = ui_ooc_chat_name->text(); + const QString ooc_name = ao_config->username(); const QString ooc_message = ui_ooc_chat_message->text(); if (ooc_message.startsWith("/rainbow") && !is_rainbow_enabled) @@ -2091,12 +2091,12 @@ void Courtroom::on_pos_dropdown_changed(int p_index) f_pos = ""; } - if (f_pos == "" || ui_ooc_chat_name->text() == "") + if (f_pos == "" || ao_config->username() == "") return; set_judge_enabled(f_pos == "jud"); - ao_app->send_server_packet(AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); + ao_app->send_server_packet(AOPacket("CT#" + ao_config->username() + "#/pos " + f_pos + "#%")); // Uncomment later and remove above // Will only work in TSDR 4.3+ servers // ao_app->send_server_packet(AOPacket("SP#" + f_pos + "#%")); From 02f72969cbd453141e2590f695503416fc4318b1 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 04:06:23 +0200 Subject: [PATCH 408/842] Change username and showname to be simplified (remove extra spaces) --- src/aoconfig.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 384c9ca21..bd0929b67 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -539,19 +539,19 @@ void AOConfig::set_autosave(bool p_enabled) void AOConfig::set_username(QString p_value) { - const QString l_trimmed_value = p_value.trimmed(); - if (d->username == l_trimmed_value) + const QString l_simplified_value = p_value.simplified(); + if (d->username == l_simplified_value) return; - d->username = l_trimmed_value; + d->username = l_simplified_value; d->invoke_signal("username_changed", Q_ARG(QString, d->username)); } void AOConfig::set_showname(QString p_value) { - const QString l_trimmed_value = p_value.trimmed(); - if (d->showname == l_trimmed_value) + const QString l_simplified_value = p_value.simplified(); + if (d->showname == l_simplified_value) return; - d->showname = l_trimmed_value; + d->showname = l_simplified_value; d->invoke_signal("showname_changed", Q_ARG(QString, d->showname)); } From b437c1c5480154a387a121ea34024ce1c18b9e0e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 04:15:17 +0200 Subject: [PATCH 409/842] Reflect username and showname to be simplified like the config panel --- src/courtroom.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index caeed327c..a0ea7277b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1959,7 +1959,9 @@ void Courtroom::mod_called(QString p_ip) void Courtroom::on_ic_showname_editing_finished() { - set_showname(ui_ic_chat_showname->text()); + const QString l_text = ui_ic_chat_showname->text().simplified(); + ui_ic_chat_showname->setText(l_text); + set_showname(l_text); } void Courtroom::set_showname(QString p_showname) @@ -1969,7 +1971,9 @@ void Courtroom::set_showname(QString p_showname) void Courtroom::on_ooc_name_editing_finished() { - ao_config->set_username(ui_ooc_chat_name->text()); + const QString l_text = ui_ooc_chat_name->text().simplified(); + ui_ooc_chat_name->setText(l_text); + ao_config->set_username(l_text); } void Courtroom::on_ooc_return_pressed() From c8601e6de3a6d7d84a8172c0c6d7f8103f7b57aa Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 04:31:47 +0200 Subject: [PATCH 410/842] Fix ooc message being cleared even if not sent --- include/courtroom.h | 1 + src/courtroom.cpp | 45 ++++++++++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 54ac71d8f..4e56868df 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -86,6 +86,7 @@ class Courtroom : public QMainWindow // sets the character position void set_character_position(QString p_pos); + bool request_username(); void send_ooc_packet(QString ooc_name, QString ooc_message); void ignore_next_showname(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a0ea7277b..e68989f55 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1913,6 +1913,25 @@ void Courtroom::set_character_position(QString p_pos) set_judge_enabled(p_pos == "jud"); } +bool Courtroom::request_username() +{ + QString l_username = ao_config->username(); + if (l_username.trimmed().isEmpty()) + { + bool ok; + do + { + l_username = QInputDialog::getText(this, "Enter a name", + "You must have a name to talk in OOC chat. Enter a name: ", QLineEdit::Normal, + "user", &ok); + } while (ok && l_username.isEmpty()); + if (!ok) + return false; + ao_config->set_username(l_username); + } + return true; +} + /** * @brief Send a OOC packet (CT) out to the server. * @param ooc_name The username. @@ -1920,27 +1939,16 @@ void Courtroom::set_character_position(QString p_pos) */ void Courtroom::send_ooc_packet(QString ooc_name, QString ooc_message) { - if (ooc_name.trimmed().isEmpty()) + if (!request_username()) { - bool ok; - do - { - ooc_name = QInputDialog::getText(this, "Enter a name", - "You must have a name to talk in OOC chat. Enter a name: ", QLineEdit::Normal, - "user", &ok); - } while (ok && ooc_name.isEmpty()); - if (!ok) - return; - - ao_config->set_username(ooc_name); + append_server_chatmessage("CLIENT", "You cannot send a message without a username."); + return; } - if (ooc_message.trimmed().isEmpty()) { - append_server_chatmessage("CLIENT", "You cannot send an empty message."); + append_server_chatmessage("CLIENT", "You cannot send empty messages."); return; } - QStringList l_content{ooc_name, ooc_message}; ao_app->send_server_packet(AOPacket("CT", l_content)); } @@ -2050,9 +2058,12 @@ void Courtroom::on_ooc_return_pressed() pause_timer(timer_id); } - send_ooc_packet(ooc_name, ooc_message); + if (request_username()) + { + send_ooc_packet(ooc_name, ooc_message); + ui_ooc_chat_message->clear(); + } - ui_ooc_chat_message->clear(); ui_ooc_chat_message->setFocus(); } From c7ea51e70aba03feb0cf955fdf3c9b5cb139f222 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 04:33:30 +0200 Subject: [PATCH 411/842] Remove username placeholder --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index e68989f55..39142f9a5 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1923,7 +1923,7 @@ bool Courtroom::request_username() { l_username = QInputDialog::getText(this, "Enter a name", "You must have a name to talk in OOC chat. Enter a name: ", QLineEdit::Normal, - "user", &ok); + nullptr, &ok); } while (ok && l_username.isEmpty()); if (!ok) return false; From 351e683c74a3cb751a6015dab15dd88dae442054 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 05:10:03 +0200 Subject: [PATCH 412/842] Force the user to input a username --- include/courtroom.h | 3 +- src/courtroom.cpp | 67 +++++++++++++++------------------------------ 2 files changed, 23 insertions(+), 47 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 4e56868df..87992487f 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -86,8 +86,7 @@ class Courtroom : public QMainWindow // sets the character position void set_character_position(QString p_pos); - bool request_username(); - void send_ooc_packet(QString ooc_name, QString ooc_message); + void send_ooc_packet(QString ooc_message); void ignore_next_showname(); void send_showname_packet(QString p_showname); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 39142f9a5..79751ac73 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -698,7 +698,7 @@ void Courtroom::send_showname_packet(QString p_showname) } else { - send_ooc_packet(ao_config->username(), QString("/showname %1").arg(p_showname)); + send_ooc_packet(QString("/showname %1").arg(p_showname)); } } @@ -1913,43 +1913,24 @@ void Courtroom::set_character_position(QString p_pos) set_judge_enabled(p_pos == "jud"); } -bool Courtroom::request_username() -{ - QString l_username = ao_config->username(); - if (l_username.trimmed().isEmpty()) - { - bool ok; - do - { - l_username = QInputDialog::getText(this, "Enter a name", - "You must have a name to talk in OOC chat. Enter a name: ", QLineEdit::Normal, - nullptr, &ok); - } while (ok && l_username.isEmpty()); - if (!ok) - return false; - ao_config->set_username(l_username); - } - return true; -} - /** * @brief Send a OOC packet (CT) out to the server. * @param ooc_name The username. * @param ooc_message The message. */ -void Courtroom::send_ooc_packet(QString ooc_name, QString ooc_message) +void Courtroom::send_ooc_packet(QString ooc_message) { - if (!request_username()) + while (ao_config->username().isEmpty()) { - append_server_chatmessage("CLIENT", "You cannot send a message without a username."); - return; + ao_config->set_username(QInputDialog::getText(this, "Enter a name", "You must have a username to talk in OOC chat.", + QLineEdit::Normal, nullptr)); } if (ooc_message.trimmed().isEmpty()) { append_server_chatmessage("CLIENT", "You cannot send empty messages."); return; } - QStringList l_content{ooc_name, ooc_message}; + QStringList l_content{ao_config->username(), ooc_message}; ao_app->send_server_packet(AOPacket("CT", l_content)); } @@ -1986,50 +1967,49 @@ void Courtroom::on_ooc_name_editing_finished() void Courtroom::on_ooc_return_pressed() { - const QString ooc_name = ao_config->username(); - const QString ooc_message = ui_ooc_chat_message->text(); + const QString l_message = ui_ooc_chat_message->text(); - if (ooc_message.startsWith("/rainbow") && !is_rainbow_enabled) + if (l_message.startsWith("/rainbow") && !is_rainbow_enabled) { ui_text_color->addItem("Rainbow"); ui_ooc_chat_message->clear(); is_rainbow_enabled = true; return; } - else if (ooc_message.startsWith("/switch_am")) + else if (l_message.startsWith("/switch_am")) { on_switch_area_music_clicked(); ui_ooc_chat_message->clear(); return; } - else if (ooc_message.startsWith("/rollp")) + else if (l_message.startsWith("/rollp")) { m_effects_player->play_effect(ao_app->get_sfx("dice")); } - else if (ooc_message.startsWith("/roll")) + else if (l_message.startsWith("/roll")) { m_effects_player->play_effect(ao_app->get_sfx("dice")); } - else if (ooc_message.startsWith("/coinflip")) + else if (l_message.startsWith("/coinflip")) { m_effects_player->play_effect(ao_app->get_sfx("coinflip")); } - else if (ooc_message.startsWith("/tr ")) + else if (l_message.startsWith("/tr ")) { // Timer resume - int space_location = ooc_message.indexOf(" "); + int space_location = l_message.indexOf(" "); int timer_id; if (space_location == -1) timer_id = 0; else - timer_id = ooc_message.mid(space_location + 1).toInt(); + timer_id = l_message.mid(space_location + 1).toInt(); resume_timer(timer_id); } - else if (ooc_message.startsWith("/ts ")) + else if (l_message.startsWith("/ts ")) { // Timer set - QStringList arguments = ooc_message.split(" "); + QStringList arguments = l_message.split(" "); int size = arguments.size(); // Note arguments[0] == "/ts", so every index (and thus length) is off by @@ -2045,24 +2025,21 @@ void Courtroom::on_ooc_return_pressed() set_timer_timestep(timer_id, timestep_length); set_timer_firing(timer_id, firing_interval); } - else if (ooc_message.startsWith("/tp ")) + else if (l_message.startsWith("/tp ")) { // Timer pause - int space_location = ooc_message.indexOf(" "); + int space_location = l_message.indexOf(" "); int timer_id; if (space_location == -1) timer_id = 0; else - timer_id = ooc_message.mid(space_location + 1).toInt(); + timer_id = l_message.mid(space_location + 1).toInt(); pause_timer(timer_id); } - if (request_username()) - { - send_ooc_packet(ooc_name, ooc_message); - ui_ooc_chat_message->clear(); - } + send_ooc_packet(l_message); + ui_ooc_chat_message->clear(); ui_ooc_chat_message->setFocus(); } From 80023f6466be1c78a652d92e2c553d2b3c67fafb Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 18:41:47 +0200 Subject: [PATCH 413/842] Replaced AOPacket with DRPacket, improved packet crafting --- dronline-client.pro | 4 +-- include/aoapplication.h | 10 +++--- include/aopacket.h | 25 ------------- include/courtroom.h | 2 +- include/drpacket.h | 22 ++++++++++++ include/drserversocket.h | 6 ++-- src/aoapplication.cpp | 6 ++-- src/aopacket.cpp | 55 ----------------------------- src/charselect.cpp | 11 +++--- src/courtroom.cpp | 76 ++++++++++++++++++---------------------- src/drpacket.cpp | 40 +++++++++++++++++++++ src/drserversocket.cpp | 13 ++++--- src/evidence.cpp | 12 +++---- src/lobby.cpp | 6 ++-- src/master_socket.cpp | 25 +++++++------ src/server_socket.cpp | 33 +++++++++-------- 16 files changed, 165 insertions(+), 181 deletions(-) delete mode 100644 include/aopacket.h create mode 100644 include/drpacket.h delete mode 100644 src/aopacket.cpp create mode 100644 src/drpacket.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 383efb06c..b742249c6 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -36,7 +36,6 @@ HEADERS += \ include/aonotepad.h \ include/aonotepicker.h \ include/aoobject.h \ - include/aopacket.h \ include/aopixmap.h \ include/aoscene.h \ include/aosfxplayer.h \ @@ -54,6 +53,7 @@ HEADERS += \ include/draudioerror.h \ include/draudiostream.h \ include/draudiostreamfamily.h \ + include/drpacket.h \ include/drpather.h \ include/drserversocket.h \ include/drtextedit.h \ @@ -87,7 +87,6 @@ SOURCES += \ src/aonotepad.cpp \ src/aonotepicker.cpp \ src/aoobject.cpp \ - src/aopacket.cpp \ src/aopixmap.cpp \ src/aoscene.cpp \ src/aosfxplayer.cpp \ @@ -108,6 +107,7 @@ SOURCES += \ src/draudioerror.cpp \ src/draudiostream.cpp \ src/draudiostreamfamily.cpp \ + src/drpacket.cpp \ src/drpather.cpp \ src/drserversocket.cpp \ src/drtextedit.cpp \ diff --git a/include/aoapplication.h b/include/aoapplication.h index cdc1caf24..6505aa07f 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -1,7 +1,7 @@ #ifndef AOAPPLICATION_H #define AOAPPLICATION_H -#include "aopacket.h" +#include "drpacket.h" #include "datatypes.h" class AOConfig; @@ -31,10 +31,10 @@ class AOApplication : public QApplication void set_client_id(int id); void connect_to_master(); - void send_master_packet(AOPacket packet); + void send_master_packet(DRPacket packet); void request_server_list(); void connect_to_server(server_type server); - void send_server_packet(AOPacket packet); + void send_server_packet(DRPacket packet); Lobby *get_lobby() const; void construct_lobby(); @@ -239,9 +239,9 @@ public slots: private slots: void _p_send_master_handshake(); void _p_handle_master_error(QString); - void _p_handle_master_packet(AOPacket); + void _p_handle_master_packet(DRPacket); void _p_handle_server_disconnection(); - void _p_handle_server_packet(AOPacket); + void _p_handle_server_packet(DRPacket); void on_courtroom_closing(); void on_courtroom_destroyed(); void on_config_theme_changed(); diff --git a/include/aopacket.h b/include/aopacket.h deleted file mode 100644 index 481af8708..000000000 --- a/include/aopacket.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef AOPACKET_H -#define AOPACKET_H - -#include -#include - -class AOPacket -{ -public: - AOPacket(QString p_packet_string); - AOPacket(QString header, const QStringList &p_contents); - - QString get_header(); - QStringList &get_contents(); - QString to_string(); - - void net_encode(); - void net_decode(); - -private: - QString m_header; - QStringList m_contents; -}; - -#endif // AOPACKET_H diff --git a/include/courtroom.h b/include/courtroom.h index 87992487f..cea6cdb27 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -311,7 +311,7 @@ class Courtroom : public QMainWindow int text_state = 2; // character id, which index of the char_list the player is - int m_cid = -1; + int m_chr_id = -1; // if enabled, disable showing our own sprites when we talk in ic bool m_msg_is_first_person = false; diff --git a/include/drpacket.h b/include/drpacket.h new file mode 100644 index 000000000..81552a89c --- /dev/null +++ b/include/drpacket.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +class DRPacket +{ +public: + static QString encode(QString data); + static QString decode(QString data); + + DRPacket(QString header); + DRPacket(QString header, QStringList content); + + const QString &get_header() const; + const QStringList &get_content() const; + QString to_string(const bool encode = false) const; + +private: + QString m_header; + QStringList m_content; +}; diff --git a/include/drserversocket.h b/include/drserversocket.h index fd8a90739..912d7de7f 100644 --- a/include/drserversocket.h +++ b/include/drserversocket.h @@ -1,6 +1,6 @@ #pragma once -#include "aopacket.h" +#include "drpacket.h" #include "datatypes.h" #include @@ -21,13 +21,13 @@ class DRServerSocket : public QObject void disconnect_from_server(); public slots: - void send_packet(AOPacket packet); + void send_packet(DRPacket packet); signals: void connected_to_server(); void disconnected_from_server(); void reconnecting_to_server(); - void packet_received(AOPacket); + void packet_received(DRPacket); void socket_error(QString); private: diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index f3c670aa3..6ac1f9d16 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -2,10 +2,10 @@ #include "aoconfig.h" #include "aoconfigpanel.h" -#include "aopacket.h" #include "courtroom.h" #include "debug_functions.h" #include "drdiscord.h" +#include "drpacket.h" #include "drserversocket.h" #include "lobby.h" @@ -47,10 +47,10 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(m_master_socket, SIGNAL(connected_to_server()), this, SLOT(_p_send_master_handshake())); connect(m_master_socket, SIGNAL(socket_error(QString)), this, SLOT(_p_handle_master_error(QString))); - connect(m_master_socket, SIGNAL(packet_received(AOPacket)), this, SLOT(_p_handle_master_packet(AOPacket))); + connect(m_master_socket, SIGNAL(packet_received(DRPacket)), this, SLOT(_p_handle_master_packet(DRPacket))); connect(m_server_socket, SIGNAL(disconnected_from_server()), this, SLOT(_p_handle_server_disconnection())); - connect(m_server_socket, SIGNAL(packet_received(AOPacket)), this, SLOT(_p_handle_server_packet(AOPacket))); + connect(m_server_socket, SIGNAL(packet_received(DRPacket)), this, SLOT(_p_handle_server_packet(DRPacket))); #ifndef QT_DEBUG connect_to_master(); diff --git a/src/aopacket.cpp b/src/aopacket.cpp deleted file mode 100644 index dd73d2b7e..000000000 --- a/src/aopacket.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "aopacket.h" - -#include - -AOPacket::AOPacket(QString p_packet_string) -{ - QStringList l_contents = p_packet_string.split("#"); - - m_header = l_contents.at(0); - - for (int n_string = 1; n_string < l_contents.size() - 1; ++n_string) - { - m_contents.append(l_contents.at(n_string)); - } -} - -AOPacket::AOPacket(QString p_header, const QStringList &p_contents) -{ - m_header = p_header; - m_contents = p_contents; -} - -QString AOPacket::get_header() -{ - return m_header; -} - -QStringList &AOPacket::get_contents() -{ - return m_contents; -} - -QString AOPacket::to_string() -{ - QString r_data; - for (const QString &i_value : qAsConst(m_contents)) - { - if (!r_data.isEmpty()) - r_data += "#"; - r_data += i_value; - } - return m_header + "#" + r_data + (r_data.isEmpty() ? "%" : "#%"); -} - -void AOPacket::net_encode() -{ - for (QString &i_value : m_contents) - i_value.replace("#", "").replace("%", "").replace("$", "").replace("&", ""); -} - -void AOPacket::net_decode() -{ - for (QString &i_value : m_contents) - i_value.replace("", "#").replace("", "%").replace("", "$").replace("", "&"); -} diff --git a/src/charselect.cpp b/src/charselect.cpp index 9e720f2ef..a4ed5bf90 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -4,8 +4,8 @@ #include "aobutton.h" #include "aocharbutton.h" #include "aoimagedisplay.h" -#include "aopacket.h" #include "debug_functions.h" +#include "drpacket.h" #include "file_functions.h" #include "hardware_functions.h" #include "theme.h" @@ -169,15 +169,14 @@ void Courtroom::char_clicked(int n_char) return; } - if (n_real_char == m_cid) + if (n_real_char == m_chr_id) { - enter_courtroom(m_cid); + enter_courtroom(m_chr_id); } else { - QString content = - "CC#" + QString::number(ao_app->get_client_id()) + "#" + QString::number(n_real_char) + "#" + get_hdid() + "#%"; - ao_app->send_server_packet(AOPacket(content)); + ao_app->send_server_packet( + DRPacket("CC", {QString::number(ao_app->get_client_id()), QString::number(n_real_char), get_hdid()})); } } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 79751ac73..e058c9b48 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -11,7 +11,6 @@ #include "aomusicplayer.h" #include "aonotearea.h" #include "aonotepicker.h" -#include "aopacket.h" #include "aoscene.h" #include "aosfxplayer.h" #include "aoshoutplayer.h" @@ -20,6 +19,7 @@ #include "aotimer.h" #include "debug_functions.h" #include "drdiscord.h" +#include "drpacket.h" #include "file_functions.h" #include "hardware_functions.h" #include "lobby.h" @@ -150,8 +150,8 @@ void Courtroom::enter_courtroom(int p_cid) ui_char_select_background->hide(); // character ================================================================= - const bool l_changed_chr_id = (m_cid != p_cid); - m_cid = p_cid; + const bool l_changed_chr_id = (m_chr_id != p_cid); + m_chr_id = p_cid; QLineEdit *l_current_field = ui_ic_chat_message; if (ui_ooc_chat_message->hasFocus()) @@ -203,7 +203,7 @@ void Courtroom::enter_courtroom(int p_cid) if (ao_app->has_character_declaration_feature()) { QStringList l_content{l_chr_name, l_final_showname}; - ao_app->send_server_packet(AOPacket("chrini", l_content)); + ao_app->send_server_packet(DRPacket("chrini", l_content)); } } const bool l_changed_chr = l_chr_name != get_current_character(); @@ -255,7 +255,7 @@ void Courtroom::enter_courtroom(int p_cid) void Courtroom::done_received() { - m_cid = -1; + m_chr_id = -1; suppress_audio(true); @@ -694,7 +694,7 @@ void Courtroom::send_showname_packet(QString p_showname) if (ao_app->has_showname_declaration_feature()) { - ao_app->send_server_packet(AOPacket("SN", {p_showname})); + ao_app->send_server_packet(DRPacket("SN", {p_showname})); } else { @@ -710,7 +710,7 @@ void Courtroom::on_showname_changed(QString p_showname) bool Courtroom::is_spectating() { - return m_cid == -1; + return m_chr_id == -1; } void Courtroom::on_showname_placeholder_changed(QString p_showname_placeholder) @@ -723,7 +723,7 @@ void Courtroom::on_showname_placeholder_changed(QString p_showname_placeholder) void Courtroom::on_character_ini_changed(QString p_base_chr) { Q_UNUSED(p_base_chr) - enter_courtroom(m_cid); + enter_courtroom(m_chr_id); } void Courtroom::on_ic_message_return_pressed() @@ -805,7 +805,7 @@ void Courtroom::on_ic_message_return_pressed() } packet_contents.append(QString::number(l_emote_modifier)); - packet_contents.append(QString::number(m_cid)); + packet_contents.append(QString::number(m_chr_id)); if (l_emote.sound_file == current_sfx_file()) packet_contents.append(QString::number(l_emote.sound_delay)); @@ -843,7 +843,7 @@ void Courtroom::on_ic_message_return_pressed() f_text_color = QString::number(m_text_color); packet_contents.append(f_text_color); - ao_app->send_server_packet(AOPacket("MS", packet_contents)); + ao_app->send_server_packet(DRPacket("MS", packet_contents)); } void Courtroom::handle_acknowledged_ms() @@ -896,7 +896,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) m_msg_is_first_person = false; // reset our ui state if client just spoke - if (m_cid == f_char_id && is_system_speaking == false) + if (m_chr_id == f_char_id && is_system_speaking == false) { #ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release // If the server does not have the feature of acknowledging our MS @@ -948,7 +948,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) if (is_system_speaking) append_system_text(f_showname, l_message); else - append_ic_text(f_showname, l_message, false, false, f_char_id == m_cid); + append_ic_text(f_showname, l_message, false, false, f_char_id == m_chr_id); if (ao_config->log_is_recording_enabled() && (!chatmessage_is_empty || !is_system_speaking)) { @@ -1021,7 +1021,7 @@ void Courtroom::handle_chatmessage_2() // handles IC { l_chatbox_name = "chatmed.png"; - if (ao_config->log_display_self_highlight_enabled() && m_chatmessage[CMChrId].toInt() == m_cid) + if (ao_config->log_display_self_highlight_enabled() && m_chatmessage[CMChrId].toInt() == m_chr_id) { const QString l_chatbox_self_name = "chatbox_self.png"; if (file_exists(ao_app->find_theme_asset_path(l_chatbox_self_name))) @@ -1752,7 +1752,7 @@ void Courtroom::set_ip_list(QString p_list) void Courtroom::set_mute(bool p_muted, int p_cid) { - if (p_cid != m_cid && p_cid != -1) + if (p_cid != m_chr_id && p_cid != -1) return; if (p_muted) @@ -1772,7 +1772,7 @@ void Courtroom::set_mute(bool p_muted, int p_cid) void Courtroom::set_ban(int p_cid) { - if (p_cid != m_cid && p_cid != -1) + if (p_cid != m_chr_id && p_cid != -1) return; call_notice("You have been banned."); @@ -1783,12 +1783,12 @@ void Courtroom::set_ban(int p_cid) int Courtroom::get_character_id() { - return m_cid; + return m_chr_id; } QString Courtroom::get_base_character() { - return is_spectating() ? nullptr : m_chr_list.at(m_cid).name; + return is_spectating() ? nullptr : m_chr_list.at(m_chr_id).name; } QString Courtroom::get_current_character() @@ -1849,7 +1849,7 @@ void Courtroom::handle_song(QStringList p_contents) if (!mute_map.value(l_chr_id)) { - append_ic_text(str_char, "has played a song: " + f_song, false, true, l_chr_id == m_cid); + append_ic_text(str_char, "has played a song: " + f_song, false, true, l_chr_id == m_chr_id); if (ao_config->log_is_recording_enabled()) save_textlog(str_char + " has played a song: " + f_song); m_music_player->play(f_song); @@ -1931,7 +1931,7 @@ void Courtroom::send_ooc_packet(QString ooc_message) return; } QStringList l_content{ao_config->username(), ooc_message}; - ao_app->send_server_packet(AOPacket("CT", l_content)); + ao_app->send_server_packet(DRPacket("CT", l_content)); } void Courtroom::mod_called(QString p_ip) @@ -2088,10 +2088,10 @@ void Courtroom::on_pos_dropdown_changed(int p_index) set_judge_enabled(f_pos == "jud"); - ao_app->send_server_packet(AOPacket("CT#" + ao_config->username() + "#/pos " + f_pos + "#%")); + send_ooc_packet("/pos " + f_pos); // Uncomment later and remove above // Will only work in TSDR 4.3+ servers - // ao_app->send_server_packet(AOPacket("SP#" + f_pos + "#%")); + // ao_app->send_server_packet(DRPacket("SP", {f_pos})); } void Courtroom::on_mute_list_item_changed(QListWidgetItem *p_item) @@ -2134,20 +2134,15 @@ void Courtroom::on_music_list_double_clicked(QModelIndex p_model) { if (is_client_muted) return; - QString p_song = ui_music_list->item(p_model.row())->text(); - - ao_app->send_server_packet(AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%")); - + ao_app->send_server_packet(DRPacket("MC", {p_song, QString::number(m_chr_id)})); ui_ic_chat_message->setFocus(); } void Courtroom::on_area_list_double_clicked(QModelIndex p_model) { QString p_area = ui_area_list->item(p_model.row())->text(); - - ao_app->send_server_packet(AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%")); - + ao_app->send_server_packet(DRPacket("MC", {p_area, QString::number(m_chr_id)})); ui_ic_chat_message->setFocus(); } @@ -2348,7 +2343,7 @@ void Courtroom::on_defense_minus_clicked() int f_state = defense_bar_state - 1; if (f_state >= 0) - ao_app->send_server_packet(AOPacket("HP#1#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(DRPacket("HP", {QString::number(1), QString::number(f_state)})); } void Courtroom::on_defense_plus_clicked() @@ -2356,7 +2351,7 @@ void Courtroom::on_defense_plus_clicked() int f_state = defense_bar_state + 1; if (f_state <= 10) - ao_app->send_server_packet(AOPacket("HP#1#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(DRPacket("HP", {QString::number(1), QString::number(f_state)})); } void Courtroom::on_prosecution_minus_clicked() @@ -2364,7 +2359,7 @@ void Courtroom::on_prosecution_minus_clicked() int f_state = prosecution_bar_state - 1; if (f_state >= 0) - ao_app->send_server_packet(AOPacket("HP#2#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(DRPacket("HP", {QString::number(2), QString::number(f_state)})); } void Courtroom::on_prosecution_plus_clicked() @@ -2372,7 +2367,7 @@ void Courtroom::on_prosecution_plus_clicked() int f_state = prosecution_bar_state + 1; if (f_state <= 10) - ao_app->send_server_packet(AOPacket("HP#2#" + QString::number(f_state) + "#%")); + ao_app->send_server_packet(DRPacket("HP", {QString::number(2), QString::number(f_state)})); } void Courtroom::on_text_color_changed(int p_color) @@ -2386,7 +2381,7 @@ void Courtroom::on_witness_testimony_clicked() if (is_client_muted) return; - ao_app->send_server_packet(AOPacket("RT#testimony1#%")); + ao_app->send_server_packet(DRPacket("RT", {"testimony1"})); ui_ic_chat_message->setFocus(); } @@ -2396,7 +2391,7 @@ void Courtroom::on_cross_examination_clicked() if (is_client_muted) return; - ao_app->send_server_packet(AOPacket("RT#testimony2#%")); + ao_app->send_server_packet(DRPacket("RT", {"testimony2"})); ui_ic_chat_message->setFocus(); } @@ -2435,9 +2430,7 @@ void Courtroom::on_wtce_clicked() AOButton *f_sig = static_cast(sender()); QString id = f_sig->property("wtce_id").toString(); - QString packet = QString("RT#testimony%1#%").arg(id); - - ao_app->send_server_packet(AOPacket(packet)); + ao_app->send_server_packet(DRPacket("RT", {QString("testimony%1").arg(id)})); ui_ic_chat_message->setFocus(); } @@ -2470,7 +2463,7 @@ void Courtroom::on_app_reload_theme_requested() // to update status on the background set_background(current_background); - enter_courtroom(m_cid); + enter_courtroom(m_chr_id); } void Courtroom::on_back_to_lobby_clicked() @@ -2498,8 +2491,7 @@ void Courtroom::on_char_select_right_clicked() void Courtroom::on_spectator_clicked() { - QString content = "CC#" + QString::number(ao_app->get_client_id()) + "#-1#" + get_hdid() + "#%"; - ao_app->send_server_packet(AOPacket(content)); + ao_app->send_server_packet(DRPacket("CC", {QString::number(ao_app->get_client_id()), "-1", get_hdid()})); enter_courtroom(-1); ui_emotes->hide(); @@ -2517,7 +2509,7 @@ void Courtroom::on_call_mod_clicked() if (reply == QMessageBox::Yes) { - ao_app->send_server_packet(AOPacket("ZZ#%")); + ao_app->send_server_packet(DRPacket("ZZ")); qDebug() << "Called mod"; } else @@ -2601,7 +2593,7 @@ void Courtroom::on_note_text_changed() void Courtroom::ping_server() { - ao_app->send_server_packet(AOPacket("CH#" + QString::number(m_cid) + "#%")); + ao_app->send_server_packet(DRPacket("CH", {QString::number(m_chr_id)})); } void Courtroom::closeEvent(QCloseEvent *event) diff --git a/src/drpacket.cpp b/src/drpacket.cpp new file mode 100644 index 000000000..898d47db0 --- /dev/null +++ b/src/drpacket.cpp @@ -0,0 +1,40 @@ +#include "drpacket.h" + +#include + +QString DRPacket::encode(QString p_data) +{ + return p_data.replace("#", "").replace("%", "").replace("$", "").replace("&", ""); +} + +QString DRPacket::decode(QString p_data) +{ + return p_data.replace("", "#").replace("", "%").replace("", "$").replace("", "&"); +} + +DRPacket::DRPacket(QString p_header) : DRPacket(p_header, {}) +{} + +DRPacket::DRPacket(QString p_header, QStringList p_content) +{ + m_header = p_header; + m_content = p_content; +} + +const QString &DRPacket::get_header() const +{ + return m_header; +} + +const QStringList &DRPacket::get_content() const +{ + return m_content; +} + +QString DRPacket::to_string(const bool p_encode) const +{ + QString r_data; + for (const QString &i_value : qAsConst(m_content)) + r_data += p_encode ? encode(i_value) : i_value + "#"; + return m_header + "#" + r_data + "%"; +} diff --git a/src/drserversocket.cpp b/src/drserversocket.cpp index 542401dc9..3d41a41d8 100644 --- a/src/drserversocket.cpp +++ b/src/drserversocket.cpp @@ -51,7 +51,7 @@ void DRServerSocket::disconnect_from_server() m_socket->abort(); } -void DRServerSocket::send_packet(AOPacket p_packet) +void DRServerSocket::send_packet(DRPacket p_packet) { if (!is_connected()) { @@ -60,7 +60,7 @@ void DRServerSocket::send_packet(AOPacket p_packet) << QString("Failed to send packet; not connected to server%1").arg(drFormatServerInfo(m_server)); return; } - m_socket->write(p_packet.to_string().toUtf8()); + m_socket->write(p_packet.to_string(true).toUtf8()); } void DRServerSocket::_p_update_state(QAbstractSocket::SocketState p_state) @@ -104,13 +104,18 @@ void DRServerSocket::_p_check_socket_error() void DRServerSocket::_p_read_socket() { m_data += QString::fromUtf8(m_socket->readAll()); + qDebug() << "_p_read_socket" << m_data; QStringList l_raw_packet_list = m_data.split("#%", DR::KeepEmptyParts); m_data = l_raw_packet_list.takeLast(); for (const QString &i_raw_packet : l_raw_packet_list) { - QStringList l_raw_data = i_raw_packet.split("#"); - packet_received(AOPacket(l_raw_data.takeFirst(), l_raw_data)); + qDebug() << l_raw_packet_list; + QStringList l_raw_data_list = i_raw_packet.split("#"); + const QString l_header = l_raw_data_list.takeFirst(); + for (QString &i_raw_data : l_raw_data_list) + i_raw_data = DRPacket::decode(i_raw_data); + Q_EMIT packet_received(DRPacket(l_header, l_raw_data_list)); } } diff --git a/src/evidence.cpp b/src/evidence.cpp index ad62a4748..940834bbf 100644 --- a/src/evidence.cpp +++ b/src/evidence.cpp @@ -6,7 +6,7 @@ #include "aoevidencedescription.h" #include "aoimagedisplay.h" #include "aolineedit.h" -#include "aopacket.h" +#include "drpacket.h" #include "theme.h" #include @@ -176,7 +176,7 @@ void Courtroom::on_evidence_name_edited() f_contents.append(f_evi.description); f_contents.append(f_evi.image); - ao_app->send_server_packet(AOPacket("EE", f_contents)); + ao_app->send_server_packet(DRPacket("EE", f_contents)); } void Courtroom::on_evidence_image_name_edited() @@ -193,7 +193,7 @@ void Courtroom::on_evidence_image_name_edited() f_contents.append(f_evi.description); f_contents.append(ui_evidence_image_name->text()); - ao_app->send_server_packet(AOPacket("EE", f_contents)); + ao_app->send_server_packet(DRPacket("EE", f_contents)); } void Courtroom::on_evidence_image_button_clicked() @@ -231,7 +231,7 @@ void Courtroom::on_evidence_clicked(int p_id) if (f_real_id == local_evidence_list.size()) { - ao_app->send_server_packet(AOPacket("PE###empty.png#%")); + ao_app->send_server_packet(DRPacket("PE", {"", "", "empty.png"})); return; } else if (f_real_id > local_evidence_list.size()) @@ -323,7 +323,7 @@ void Courtroom::on_evidence_delete_clicked() ui_evidence_description->setReadOnly(true); ui_evidence_overlay->hide(); - ao_app->send_server_packet(AOPacket("DE#" + QString::number(current_evidence) + "#%")); + ao_app->send_server_packet(DRPacket("DE", {QString::number(current_evidence)})); current_evidence = 0; @@ -347,7 +347,7 @@ void Courtroom::on_evidence_x_clicked() f_contents.append(ui_evidence_description->toPlainText()); f_contents.append(f_evi.image); - ao_app->send_server_packet(AOPacket("EE", f_contents)); + ao_app->send_server_packet(DRPacket("EE", f_contents)); ui_ic_chat_message->setFocus(); } diff --git a/src/lobby.cpp b/src/lobby.cpp index 775a83a07..ca7080bf1 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -4,9 +4,9 @@ #include "aobutton.h" #include "aoconfig.h" #include "aoimagedisplay.h" -#include "aopacket.h" #include "aotextarea.h" #include "debug_functions.h" +#include "drpacket.h" #include "drpather.h" #include "drtextedit.h" #include "theme.h" @@ -303,7 +303,7 @@ void Lobby::on_connect_pressed() void Lobby::on_connect_released() { ui_connect->set_image("connect.png"); - ao_app->send_server_packet(AOPacket("askchaa#%")); + ao_app->send_server_packet(DRPacket("askchaa")); } void Lobby::on_about_clicked() @@ -373,7 +373,7 @@ void Lobby::on_chatfield_return_pressed() QString f_header = "CT"; QStringList f_contents{ui_chatname->text(), ui_chatmessage->text()}; - ao_app->send_master_packet(AOPacket(f_header, f_contents)); + ao_app->send_master_packet(DRPacket(f_header, f_contents)); ui_chatmessage->clear(); } diff --git a/src/master_socket.cpp b/src/master_socket.cpp index 8bcb27fde..29f8abbc1 100644 --- a/src/master_socket.cpp +++ b/src/master_socket.cpp @@ -1,7 +1,7 @@ #include "aoapplication.h" -#include "aopacket.h" #include "debug_functions.h" +#include "drpacket.h" #include "drserversocket.h" #include "hardware_functions.h" #include "lobby.h" @@ -18,8 +18,13 @@ void AOApplication::connect_to_master() m_master_socket->connect_to_server(l_server, true); } -void AOApplication::send_master_packet(AOPacket p_packet) +void AOApplication::send_master_packet(DRPacket p_packet) { + if (!m_master_socket->is_connected()) + { + qDebug() << "Failed to send packet: not connected to master"; + return; + } qDebug().noquote() << "M/S:" << p_packet.to_string(); m_master_socket->send_packet(p_packet); } @@ -31,12 +36,12 @@ void AOApplication::request_server_list() connect_to_master(); return; } - m_master_socket->send_packet(AOPacket("ALL", {})); + m_master_socket->send_packet(DRPacket("ALL")); } void AOApplication::_p_send_master_handshake() { - send_master_packet(AOPacket("ALL#%")); + send_master_packet(DRPacket("ALL")); } void AOApplication::_p_handle_master_error(QString p_error) @@ -46,12 +51,10 @@ void AOApplication::_p_handle_master_error(QString p_error) m_lobby->append_error(p_error); } -void AOApplication::_p_handle_master_packet(AOPacket p_packet) +void AOApplication::_p_handle_master_packet(DRPacket p_packet) { - p_packet.net_decode(); - const QString l_header = p_packet.get_header(); - const QStringList l_content = p_packet.get_contents(); + const QStringList l_content = p_packet.get_content(); if (l_header != "CHECK") qDebug().noquote() << "M/R:" << p_packet.to_string(); @@ -60,7 +63,7 @@ void AOApplication::_p_handle_master_packet(AOPacket p_packet) { m_server_list.clear(); - for (const QString &i_string : p_packet.get_contents()) + for (const QString &i_string : qAsConst(l_content)) { server_type f_server; QStringList sub_contents = i_string.split("&"); @@ -108,8 +111,8 @@ void AOApplication::_p_handle_master_packet(AOPacket p_packet) } else if (l_header == "AO2CHECK") { - send_master_packet(AOPacket("ID#DRO#" + get_version_string() + "#%")); - send_master_packet(AOPacket("HI#" + get_hdid() + "#%")); + send_master_packet(DRPacket("ID", {"DRO", get_version_string()})); + send_master_packet(DRPacket("HI", {get_hdid()})); if (l_content.size() < 1) return; diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 52b1ef97a..4fdb60e65 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -1,10 +1,10 @@ #include "aoapplication.h" #include "aoconfig.h" -#include "aopacket.h" #include "courtroom.h" #include "debug_functions.h" #include "drdiscord.h" +#include "drpacket.h" #include "drserversocket.h" #include "file_functions.h" #include "hardware_functions.h" @@ -18,8 +18,13 @@ void AOApplication::connect_to_server(server_type p_server) m_server_socket->connect_to_server(p_server, false); } -void AOApplication::send_server_packet(AOPacket p_packet) +void AOApplication::send_server_packet(DRPacket p_packet) { + if (!m_server_socket->is_connected()) + { + qDebug() << "Failed to send packet: not connected to server"; + return; + } qDebug().noquote() << "S/S:" << p_packet.to_string(); m_server_socket->send_packet(p_packet); } @@ -34,12 +39,10 @@ void AOApplication::_p_handle_server_disconnection() destruct_courtroom(); } -void AOApplication::_p_handle_server_packet(AOPacket p_packet) +void AOApplication::_p_handle_server_packet(DRPacket p_packet) { - p_packet.net_decode(); - const QString l_header = p_packet.get_header(); - const QStringList l_content = p_packet.get_contents(); + const QStringList l_content = p_packet.get_content(); if (l_header != "checkconnection") qDebug().noquote() << "S/R:" << p_packet.to_string(); @@ -59,7 +62,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) feature_chrini = false; feature_chat_speed = false; - send_server_packet(AOPacket("HI#" + get_hdid() + "#%")); + send_server_packet(DRPacket("HI", {get_hdid()})); } else if (l_header == "ID") { @@ -69,7 +72,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) m_client_id = l_content.at(0).toInt(); m_server_software = l_content.at(1); - send_server_packet(AOPacket("ID#DRO#" + get_version_string() + "#%")); + send_server_packet(DRPacket("ID", {"DRO", get_version_string()})); } else if (l_header == "CT") { @@ -148,7 +151,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) m_lobby->set_loading_text("Loading"); m_lobby->set_loading_value(0); - send_server_packet(AOPacket("RC#%")); + send_server_packet(DRPacket("RC")); // look for the server inside the known public list and report it if (is_favorite) @@ -204,7 +207,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) int loading_value = (m_loaded_characters / static_cast(total_loading_size)) * 100; m_lobby->set_loading_value(loading_value); - send_server_packet(AOPacket("RE#%")); + send_server_packet(DRPacket("RE")); } else if (l_header == "EI") { @@ -239,7 +242,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) m_lobby->set_loading_value(loading_value); QString next_packet_number = QString::number(m_loaded_evidence); - send_server_packet(AOPacket("AE#" + next_packet_number + "#%")); + send_server_packet(DRPacket("AE", {next_packet_number})); } else if (l_header == "CharsCheck") { @@ -284,7 +287,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) int loading_value = (m_loaded_characters / static_cast(total_loading_size)) * 100; m_lobby->set_loading_value(loading_value); - send_server_packet(AOPacket("RM#%")); + send_server_packet(DRPacket("RM")); } else if (l_header == "SM" || l_header == "FM") { @@ -327,7 +330,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) int loading_value = ((m_loaded_characters + m_loaded_evidence + m_loaded_music) / static_cast(total_loading_size)) * 100; m_lobby->set_loading_value(loading_value); - send_server_packet(AOPacket("RD#%")); + send_server_packet(DRPacket("RD")); } } else if (l_header == "DONE") @@ -369,7 +372,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) else if (l_header == "MS") { if (is_courtroom_constructed && is_courtroom_loaded) - m_courtroom->handle_chatmessage(p_packet.get_contents()); + m_courtroom->handle_chatmessage(l_content); } else if (l_header == "ackMS") { @@ -379,7 +382,7 @@ void AOApplication::_p_handle_server_packet(AOPacket p_packet) else if (l_header == "MC") { if (is_courtroom_constructed && is_courtroom_loaded) - m_courtroom->handle_song(p_packet.get_contents()); + m_courtroom->handle_song(l_content); } else if (l_header == "RT") { From 0d3a8d7113f150d660282f0d92ddf4838cffd257 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 18:47:17 +0200 Subject: [PATCH 414/842] Removed debug comments and fixed encode not working as intended --- src/drpacket.cpp | 2 +- src/drserversocket.cpp | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/drpacket.cpp b/src/drpacket.cpp index 898d47db0..3aeddcf17 100644 --- a/src/drpacket.cpp +++ b/src/drpacket.cpp @@ -35,6 +35,6 @@ QString DRPacket::to_string(const bool p_encode) const { QString r_data; for (const QString &i_value : qAsConst(m_content)) - r_data += p_encode ? encode(i_value) : i_value + "#"; + r_data += (p_encode ? encode(i_value) : i_value) + "#"; return m_header + "#" + r_data + "%"; } diff --git a/src/drserversocket.cpp b/src/drserversocket.cpp index 3d41a41d8..ffae72b6e 100644 --- a/src/drserversocket.cpp +++ b/src/drserversocket.cpp @@ -104,14 +104,10 @@ void DRServerSocket::_p_check_socket_error() void DRServerSocket::_p_read_socket() { m_data += QString::fromUtf8(m_socket->readAll()); - qDebug() << "_p_read_socket" << m_data; - QStringList l_raw_packet_list = m_data.split("#%", DR::KeepEmptyParts); m_data = l_raw_packet_list.takeLast(); - for (const QString &i_raw_packet : l_raw_packet_list) { - qDebug() << l_raw_packet_list; QStringList l_raw_data_list = i_raw_packet.split("#"); const QString l_header = l_raw_data_list.takeFirst(); for (QString &i_raw_data : l_raw_data_list) From 2284b22cb77aaff59c4f81498824eb9d36caf808 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 30 May 2021 23:47:06 +0200 Subject: [PATCH 415/842] Renamed ini_dropdown to iniswap_dropdown --- include/courtroom.h | 4 ++-- src/courtroom.cpp | 10 +++++----- src/courtroom_widgets.cpp | 10 +++++----- src/emotes.cpp | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index cea6cdb27..5d4676c78 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -433,7 +433,7 @@ class Courtroom : public QMainWindow AOButton *ui_emote_right = nullptr; QComboBox *ui_emote_dropdown = nullptr; - QComboBox *ui_ini_dropdown = nullptr; + QComboBox *ui_iniswap_dropdown = nullptr; QComboBox *ui_pos_dropdown = nullptr; AOImageDisplay *ui_defense_bar = nullptr; @@ -626,7 +626,7 @@ private slots: void on_emote_right_clicked(); void on_emote_dropdown_changed(int p_index); - void on_ini_dropdown_changed(int p_index); + void on_iniswap_dropdown_changed(int p_index); void on_pos_dropdown_changed(int p_index); void on_evidence_name_edited(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index e058c9b48..2f4e3b5f1 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -160,8 +160,8 @@ void Courtroom::enter_courtroom(int p_cid) const QString l_chr_name = get_current_character(); { // repopulate ini-swapper - QSignalBlocker b_ini_list(ui_ini_dropdown); - ui_ini_dropdown->clear(); + QSignalBlocker b_ini_list(ui_iniswap_dropdown); + ui_iniswap_dropdown->clear(); QStringList l_name_list{"Default"}; const QString l_path = ao_app->get_base_path() + "/characters"; @@ -183,9 +183,9 @@ void Courtroom::enter_courtroom(int p_cid) const QString &i_name = l_name_list.at(i); const QString l_real_name = i == 0 ? get_base_character() : i_name; const QString l_icon_file = ao_app->get_character_path(l_real_name, "char_icon.png"); - ui_ini_dropdown->addItem(file_exists(l_icon_file) ? QIcon(l_icon_file) : QIcon(l_blank_image), i_name); + ui_iniswap_dropdown->addItem(file_exists(l_icon_file) ? QIcon(l_icon_file) : QIcon(l_blank_image), i_name); } - ui_ini_dropdown->setCurrentText(get_current_character()); + ui_iniswap_dropdown->setCurrentText(get_current_character()); } if (is_spectating()) @@ -241,7 +241,7 @@ void Courtroom::enter_courtroom(int p_cid) ui_emotes->setHidden(is_spectating()); ui_emote_dropdown->setHidden(is_spectating()); - ui_ini_dropdown->setHidden(is_spectating()); + ui_iniswap_dropdown->setHidden(is_spectating()); ui_ic_chat_message->setEnabled(!is_spectating()); // restore line field focus diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 081c3d0f9..4420b9ddd 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -107,7 +107,7 @@ void Courtroom::create_widgets() ui_vp_wtce = new AOMovie(this, ao_app); ui_vp_objection = new AOMovie(this, ao_app); - ui_ini_dropdown = new QComboBox(this); + ui_iniswap_dropdown = new QComboBox(this); ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); @@ -266,7 +266,7 @@ void Courtroom::connect_widgets() connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); connect(ui_emote_dropdown, SIGNAL(activated(int)), this, SLOT(on_emote_dropdown_changed(int))); - connect(ui_ini_dropdown, SIGNAL(activated(int)), this, SLOT(on_ini_dropdown_changed(int))); + connect(ui_iniswap_dropdown, SIGNAL(activated(int)), this, SLOT(on_iniswap_dropdown_changed(int))); connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); connect(ui_mute_list, SIGNAL(itemChanged(QListWidgetItem *)), this, @@ -388,7 +388,7 @@ void Courtroom::reset_widget_names() {"emote_left", ui_emote_left}, {"emote_right", ui_emote_right}, {"emote_dropdown", ui_emote_dropdown}, - {"ini_dropdown", ui_ini_dropdown}, + {"iniswap_dropdown", ui_iniswap_dropdown}, {"pos_dropdown", ui_pos_dropdown}, {"defense_bar", ui_defense_bar}, {"prosecution_bar", ui_prosecution_bar}, @@ -701,7 +701,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_emote_dropdown, "emote_dropdown", INI_DESIGN, ao_app); - set_size_and_pos(ui_ini_dropdown, "ini_dropdown", INI_DESIGN, ao_app); + set_size_and_pos(ui_iniswap_dropdown, "iniswap_dropdown", INI_DESIGN, ao_app); set_size_and_pos(ui_pos_dropdown, "pos_dropdown", INI_DESIGN, ao_app); @@ -1405,7 +1405,7 @@ void Courtroom::set_dropdowns() { set_dropdown(ui_text_color, "[TEXT COLOR]"); set_dropdown(ui_emote_dropdown, "[EMOTE DROPDOWN]"); - set_dropdown(ui_ini_dropdown, "[INI DROPDOWN]"); + set_dropdown(ui_iniswap_dropdown, "[INISWAP DROPDOWN]"); set_dropdown(ui_pos_dropdown, "[POS DROPDOWN]"); set_dropdown(ui_mute_list, "[MUTE LIST]"); set_dropdown(ui_ic_chat_message, "[IC LINE]"); diff --git a/src/emotes.cpp b/src/emotes.cpp index e7b7bf918..77324c12f 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -207,8 +207,8 @@ void Courtroom::on_emote_dropdown_changed(int p_index) select_emote(p_index); } -void Courtroom::on_ini_dropdown_changed(int p_index) +void Courtroom::on_iniswap_dropdown_changed(int p_index) { ao_config->set_character_ini(get_base_character(), - p_index == 0 ? get_base_character() : ui_ini_dropdown->itemText(p_index)); + p_index == 0 ? get_base_character() : ui_iniswap_dropdown->itemText(p_index)); } From bd4191f955ef2ab0124c6a55b4ea570acd0b6d8d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 31 May 2021 05:09:47 +0200 Subject: [PATCH 416/842] Removed unused description and evidence field from char_type * Also removed deprecated packet section --- include/courtroom.h | 2 +- include/datatypes.h | 4 +- src/courtroom.cpp | 12 +++--- src/server_socket.cpp | 99 +++---------------------------------------- 4 files changed, 15 insertions(+), 102 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 5d4676c78..ffb1a03c2 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -53,7 +53,7 @@ class Courtroom : public QMainWindow explicit Courtroom(AOApplication *p_ao_app); ~Courtroom(); - void append_char(char_type p_char); + void set_character_list(QVector character_list); void set_area_list(QStringList area_list); void set_music_list(QStringList music_list); diff --git a/include/datatypes.h b/include/datatypes.h index 4717c48cb..ef2089f62 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -119,9 +119,7 @@ struct server_type struct char_type { QString name; - QString description; - QString evidence_string; - bool taken; + bool taken = false; }; struct evi_type diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2f4e3b5f1..d057ac2f3 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -72,19 +72,19 @@ Courtroom::~Courtroom() stop_all_audio(); } -void Courtroom::append_char(char_type p_char) +void Courtroom::set_character_list(QVector p_chr_list) { - m_chr_list.append(p_char); + m_chr_list = p_chr_list; } -void Courtroom::set_area_list(QStringList area_list) +void Courtroom::set_area_list(QStringList p_area_list) { - m_area_list = area_list; + m_area_list = p_area_list; } -void Courtroom::set_music_list(QStringList music_list) +void Courtroom::set_music_list(QStringList p_music_list) { - m_music_list = music_list; + m_music_list = p_music_list; } void Courtroom::enter_courtroom(int p_cid) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 4fdb60e65..763ac89f7 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -169,81 +169,6 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) dr_discord->set_state(DRDiscord::State::Connected); dr_discord->set_server_name(server_name); } - else if (l_header == "CI") - { - if (!is_courtroom_constructed) - return; - - for (int n_element = 0; n_element < l_content.size(); n_element += 2) - { - if (l_content.at(n_element).toInt() != m_loaded_characters) - break; - - // this means we are on the last element and checking n + 1 element will - // be game over so - if (n_element == l_content.size() - 1) - break; - - QStringList sub_elements = l_content.at(n_element + 1).split("&"); - if (sub_elements.size() < 2) - break; - - char_type f_char; - f_char.name = sub_elements.at(0); - f_char.description = sub_elements.at(1); - f_char.evidence_string = sub_elements.at(3); - // temporary. the CharsCheck packet sets this properly - f_char.taken = false; - - ++m_loaded_characters; - - m_lobby->set_loading_text("Loading chars:\n" + QString::number(m_loaded_characters) + "/" + - QString::number(m_character_count)); - - m_courtroom->append_char(f_char); - } - - int total_loading_size = m_character_count + m_evidence_count + m_music_count; - int loading_value = (m_loaded_characters / static_cast(total_loading_size)) * 100; - m_lobby->set_loading_value(loading_value); - - send_server_packet(DRPacket("RE")); - } - else if (l_header == "EI") - { - if (!is_courtroom_constructed) - return; - - // +1 because evidence starts at 1 rather than 0 for whatever reason - // enjoy fanta - if (l_content.at(0).toInt() != m_loaded_evidence + 1) - return; - - if (l_content.size() < 2) - return; - - QStringList sub_elements = l_content.at(1).split("&"); - if (sub_elements.size() < 4) - return; - - evi_type f_evi; - f_evi.name = sub_elements.at(0); - f_evi.description = sub_elements.at(1); - // no idea what the number at position 2 is. probably an identifier? - f_evi.image = sub_elements.at(3); - - ++m_loaded_evidence; - - m_lobby->set_loading_text("Loading evidence:\n" + QString::number(m_loaded_evidence) + "/" + - QString::number(m_evidence_count)); - - int total_loading_size = m_character_count + m_evidence_count + m_music_count; - int loading_value = ((m_loaded_characters + m_loaded_evidence) / static_cast(total_loading_size)) * 100; - m_lobby->set_loading_value(loading_value); - - QString next_packet_number = QString::number(m_loaded_evidence); - send_server_packet(DRPacket("AE", {next_packet_number})); - } else if (l_header == "CharsCheck") { if (!is_courtroom_constructed) @@ -257,31 +182,21 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) m_courtroom->set_taken(n_char, false); } } - else if (l_header == "SC") { if (!is_courtroom_constructed) return; - for (int n_element = 0; n_element < l_content.size(); ++n_element) + QVector l_chr_list; + for (const QString &i_chr_name : qAsConst(l_content)) { - QStringList sub_elements = l_content.at(n_element).split("&"); - - char_type f_char; - f_char.name = sub_elements.at(0); - if (sub_elements.size() >= 2) - f_char.description = sub_elements.at(1); - - // temporary. the CharsCheck packet sets this properly - f_char.taken = false; - - ++m_loaded_characters; - - m_lobby->set_loading_text("Loading chars:\n" + QString::number(m_loaded_characters) + "/" + + char_type l_chr; + l_chr.name = i_chr_name; + l_chr_list.append(std::move(l_chr)); + m_lobby->set_loading_text("Loading chars:\n" + QString::number(++m_loaded_characters) + "/" + QString::number(m_character_count)); - - m_courtroom->append_char(f_char); } + m_courtroom->set_character_list(l_chr_list); int total_loading_size = m_character_count + m_evidence_count + m_music_count; int loading_value = (m_loaded_characters / static_cast(total_loading_size)) * 100; From 0f0e57ea29d19ccba8e54eacb1e676c93b1ebf50 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 31 May 2021 05:10:43 +0200 Subject: [PATCH 417/842] Missed some items to be removed --- src/courtroom.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d057ac2f3..a1d4eb585 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -363,9 +363,7 @@ void Courtroom::set_taken(int n_char, bool p_taken) char_type f_char; f_char.name = m_chr_list.at(n_char).name; - f_char.description = m_chr_list.at(n_char).description; f_char.taken = p_taken; - f_char.evidence_string = m_chr_list.at(n_char).evidence_string; m_chr_list.replace(n_char, f_char); } From 394c040af6503bb6b12e0662254142eba140c86f Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 31 May 2021 05:33:19 +0200 Subject: [PATCH 418/842] Allow & to be visible within character buttons --- src/aocharbutton.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aocharbutton.cpp b/src/aocharbutton.cpp index ae6ce6520..92912cb0d 100644 --- a/src/aocharbutton.cpp +++ b/src/aocharbutton.cpp @@ -35,7 +35,7 @@ void AOCharButton::set_image(QString p_character) const QString l_image = ao_app->get_character_path(p_character, "char_icon.png"); const bool l_file_exist = file_exists(l_image); setStyleSheet(l_file_exist ? QString("border-image: url(\"%1\");").arg(l_image) : nullptr); - setText(l_file_exist ? nullptr : p_character); + setText(l_file_exist ? nullptr : p_character.replace("&", "&&")); } void AOCharButton::enterEvent(QEvent *e) From ccb4a3ec17607b96b51b9ea537863ebad9962dae Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 31 May 2021 07:28:31 +0200 Subject: [PATCH 419/842] Fixed text-alignment not adjusting itself --- include/courtroom.h | 2 +- include/drtextedit.h | 3 ++- src/courtroom.cpp | 9 +++++---- src/drtextedit.cpp | 26 ++++++++++++-------------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index ffb1a03c2..0bc983201 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -405,7 +405,7 @@ class Courtroom : public QMainWindow QList m_ic_record_list; QQueue m_ic_record_queue; - AOTextArea *ui_server_chatlog = nullptr; + AOTextArea *ui_ooc_chatlog = nullptr; QListWidget *ui_mute_list = nullptr; QListWidget *ui_area_list = nullptr; diff --git a/include/drtextedit.h b/include/drtextedit.h index b4b71292a..b27579632 100644 --- a/include/drtextedit.h +++ b/include/drtextedit.h @@ -10,7 +10,7 @@ class DRTextEdit : public QTextEdit Qt::Alignment text_alignment READ get_text_alignment WRITE set_text_alignment NOTIFY text_alignment_changed) public: - DRTextEdit(QWidget *p_parent); + DRTextEdit(QWidget *p_parent = nullptr); Qt::Alignment get_text_alignment() const; @@ -18,6 +18,7 @@ public slots: void set_outline(bool enabled); void set_auto_align(bool enabled); void set_text_alignment(Qt::Alignment alignment); + void realign_text(); signals: void text_alignment_changed(Qt::Alignment); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a1d4eb585..f4ceee088 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -665,7 +665,7 @@ void Courtroom::save_textlog(QString p_text) void Courtroom::append_server_chatmessage(QString p_name, QString p_message) { - ui_server_chatlog->append_chatmessage(p_name, p_message); + ui_ooc_chatlog->append_chatmessage(p_name, p_message); if (ao_config->log_is_recording_enabled()) save_textlog("(OOC)" + p_name + ": " + p_message); } @@ -1201,7 +1201,7 @@ void Courtroom::handle_chatmessage_3() const QString name = "CLIENT"; const QString message = ui_vp_showname->toPlainText() + " has called you via your callword \"" + word + "\": \"" + f_message + "\""; - ui_server_chatlog->append_chatmessage(name, message); + ui_ooc_chatlog->append_chatmessage(name, message); if (ao_config->log_is_recording_enabled()) save_textlog("(OOC)" + name + ": " + message); break; @@ -1237,6 +1237,7 @@ void Courtroom::update_ic_log(bool p_reset_log) // clear log ui_ic_chatlog->clear(); + ui_ic_chatlog->realign_text(); } // prepare the formats we need @@ -1745,7 +1746,7 @@ void Courtroom::set_ip_list(QString p_list) { QString f_list = p_list.replace("|", ":").replace("*", "\n"); - ui_server_chatlog->append(f_list); + ui_ooc_chatlog->append(f_list); } void Courtroom::set_mute(bool p_muted, int p_cid) @@ -1934,7 +1935,7 @@ void Courtroom::send_ooc_packet(QString ooc_message) void Courtroom::mod_called(QString p_ip) { - ui_server_chatlog->append(p_ip); + ui_ooc_chatlog->append(p_ip); if (ao_config->server_alerts_enabled()) { m_system_player->play(ao_app->get_sfx("mod_call")); diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index 0dde3ba0f..cbc1b7eb8 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -28,7 +28,6 @@ void DRTextEdit::set_auto_align(bool p_enabled) if (is_auto_align == p_enabled) return; is_auto_align = p_enabled; - on_text_changed(); } void DRTextEdit::set_text_alignment(Qt::Alignment p_align) @@ -39,6 +38,17 @@ void DRTextEdit::set_text_alignment(Qt::Alignment p_align) Q_EMIT text_alignment_changed(m_text_align); } +void DRTextEdit::realign_text() +{ + if (m_status == Status::InProgress) + return; + m_status = Status::InProgress; + setAlignment(m_text_align); + // refresh_horizontal_alignment(); + refresh_vertical_alignment(); + m_status = Status::Done; +} + Qt::Alignment DRTextEdit::get_text_alignment() const { return m_text_align; @@ -48,19 +58,7 @@ void DRTextEdit::on_text_changed() { if (!is_auto_align) return; - - // We need to "lock" access to on_text_changed. That is because the refresh methods trigger - // QT's textChanged signal as well. - if (m_status == Status::InProgress) - return; - - m_status = Status::InProgress; - - refresh_horizontal_alignment(); - refresh_vertical_alignment(); - - // we're done - m_status = Status::Done; + realign_text(); } void DRTextEdit::refresh_horizontal_alignment() From 316eeb3577df886628511c38daaad7e98fb36158 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 31 May 2021 07:29:09 +0200 Subject: [PATCH 420/842] File got omitted by git for some reason. --- src/courtroom_widgets.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 4420b9ddd..d774053c3 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -112,9 +112,9 @@ void Courtroom::create_widgets() ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); - ui_server_chatlog = new AOTextArea(this); - ui_server_chatlog->setReadOnly(true); - ui_server_chatlog->setOpenExternalLinks(true); + ui_ooc_chatlog = new AOTextArea(this); + ui_ooc_chatlog->setReadOnly(true); + ui_ooc_chatlog->setOpenExternalLinks(true); ui_mute_list = new QListWidget(this); ui_area_list = new QListWidget(this); @@ -367,7 +367,7 @@ void Courtroom::reset_widget_names() {"vp_wtce", ui_vp_wtce}, {"vp_objection", ui_vp_objection}, {"ic_chatlog", ui_ic_chatlog}, - {"server_chatlog", ui_server_chatlog}, + {"server_chatlog", ui_ooc_chatlog}, {"mute_list", ui_mute_list}, {"area_list", ui_area_list}, {"music_list", ui_music_list}, @@ -629,7 +629,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_ic_chatlog, "ic_chatlog", INI_DESIGN, ao_app); - set_size_and_pos(ui_server_chatlog, "server_chatlog", INI_DESIGN, ao_app); + set_size_and_pos(ui_ooc_chatlog, "server_chatlog", INI_DESIGN, ao_app); set_size_and_pos(ui_mute_list, "mute_list", INI_DESIGN, ao_app); ui_mute_list->hide(); @@ -1418,7 +1418,7 @@ void Courtroom::set_fonts() set_drtextedit_font(ui_vp_message, "message", INI_FONTS, ao_app); set_drtextedit_font(ui_ic_chatlog, "ic_chatlog", INI_FONTS, ao_app); // Chatlog does not support drtextedit because html - set_font(ui_server_chatlog, "server_chatlog", INI_FONTS, ao_app); + set_font(ui_ooc_chatlog, "server_chatlog", INI_FONTS, ao_app); set_font(ui_music_list, "music_list", INI_FONTS, ao_app); set_font(ui_area_list, "area_list", INI_FONTS, ao_app); set_font(ui_sfx_list, "sfx_list", INI_FONTS, ao_app); From 6baa9b9b25c191932da5bdfa354d6b0fe6595a90 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 31 May 2021 17:54:33 +0200 Subject: [PATCH 421/842] Fixed area and music list not updating themselves * Added common definitions, replacing a lot of needless random strings --- dronline-client.pro | 2 + include/commondefs.h | 23 ++++ include/courtroom.h | 17 +-- include/lobby.h | 3 - src/aoconfig.cpp | 3 +- src/aoevidencedisplay.cpp | 4 +- src/aonotearea.cpp | 3 +- src/charselect.cpp | 11 +- src/commondefs.cpp | 23 ++++ src/courtroom.cpp | 117 ++++++------------- src/courtroom_widgets.cpp | 223 ++++++++++++++++++------------------ src/emotes.cpp | 5 +- src/evidence.cpp | 7 +- src/lobby.cpp | 60 +++++----- src/text_file_functions.cpp | 31 +++-- 15 files changed, 263 insertions(+), 269 deletions(-) create mode 100644 include/commondefs.h create mode 100644 src/commondefs.cpp diff --git a/dronline-client.pro b/dronline-client.pro index b742249c6..4d40c8035 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -43,6 +43,7 @@ HEADERS += \ include/aosystemplayer.h \ include/aotextarea.h \ include/aotimer.h \ + include/commondefs.h \ include/courtroom.h \ include/datatypes.h \ include/debug_functions.h \ @@ -96,6 +97,7 @@ SOURCES += \ src/aotimer.cpp \ src/audio_functions.cpp \ src/charselect.cpp \ + src/commondefs.cpp \ src/courtroom.cpp \ src/courtroom_widgets.cpp \ src/datatypes.cpp \ diff --git a/include/commondefs.h b/include/commondefs.h new file mode 100644 index 000000000..0d5a07267 --- /dev/null +++ b/include/commondefs.h @@ -0,0 +1,23 @@ +#pragma once + +class QString; + +extern const QString BACKGROUND_BACKGROUNDS_INI; + +extern const QString BASE_CONFIG_INI; + +extern const QString CHARACTER_CHAR_INI; +extern const QString CHARACTER_SOUNDS_INI; + +extern const QString CONFIG_SOUNDS_INI; +extern const QString CONFIG_FILESABSTRACT_INI; + +extern const QString COURTROOM_CONFIG_INI; +extern const QString COURTROOM_DESIGN_INI; +extern const QString COURTROOM_FONTS_INI; +extern const QString COURTROOM_LAYERS_INI; +extern const QString COURTROOM_SOUNDS_INI; +extern const QString COURTROOM_TEXT_COLOR_INI; + +extern const QString LOBBY_DESIGN_INI; +extern const QString LOBBY_FONTS_INI; diff --git a/include/courtroom.h b/include/courtroom.h index 0bc983201..f4a41c072 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -50,6 +50,9 @@ class Courtroom : public QMainWindow Q_OBJECT public: + static const int DEFAULT_WIDTH; + static const int DEFAULT_HEIGHT; + explicit Courtroom(AOApplication *p_ao_app); ~Courtroom(); @@ -219,9 +222,6 @@ class Courtroom : public QMainWindow void closing(); private: - static const int DEFAULT_WIDTH; - static const int DEFAULT_HEIGHT; - AOApplication *ao_app = nullptr; AOConfig *ao_config = nullptr; @@ -275,17 +275,6 @@ class Courtroom : public QMainWindow // Generate a File Name based on the time you launched the client QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); - // configuration files locations - QString rpc_ini = "configs/rpccharlist.ini"; - QString file_select_ini = "configs/filesabstract.ini"; - QString shownames_ini = "configs/shownames.ini"; - - // theme files locations - static const QString INI_DESIGN; - static const QString INI_FONTS; - static const QString INI_CONFIG; - static const QString INI_SOUNDS; - static const int MESSAGE_SIZE = 16; QString m_chatmessage[MESSAGE_SIZE]; bool chatmessage_is_empty = false; diff --git a/include/lobby.h b/include/lobby.h index e0584d8fc..c2baab912 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -43,9 +43,6 @@ class Lobby : public QMainWindow void set_loading_value(int p_value); private: - static const QString INI_DESIGN; - static const QString INI_FONTS; - AOApplication *ao_app = nullptr; AOConfig *ao_config = nullptr; diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index bd0929b67..b6cdf177b 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -1,4 +1,5 @@ #include "aoconfig.h" +#include "commondefs.h" #include "datatypes.h" #include "draudioengine.h" #include "drpather.h" @@ -89,7 +90,7 @@ private slots: }; AOConfigPrivate::AOConfigPrivate() - : QObject(nullptr), cfg(DRPather::get_application_path() + "/base/config.ini", QSettings::IniFormat), + : QObject(nullptr), cfg(DRPather::get_application_path() + BASE_CONFIG_INI, QSettings::IniFormat), audio_engine(new DRAudioEngine(this)) { Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index 671cb6510..fbae69643 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -3,6 +3,8 @@ #include "aoapplication.h" #include "aopixmap.h" #include "aosfxplayer.h" +#include "commondefs.h" +#include "courtroom.h" #include "datatypes.h" #include "file_functions.h" #include "misc_functions.h" @@ -42,7 +44,7 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_sid l_icon_animation = "evidence_appear_right.gif"; } - pos_size_type l_icon_dimensions = ao_app->get_element_dimensions(l_icon_identifier, "courtroom_design.ini"); + pos_size_type l_icon_dimensions = ao_app->get_element_dimensions(l_icon_identifier, COURTROOM_DESIGN_INI); ui_icon->move(l_icon_dimensions.x, l_icon_dimensions.y); ui_icon->resize(l_icon_dimensions.width, l_icon_dimensions.height); diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp index 4d5bf8b66..164825e8f 100644 --- a/src/aonotearea.cpp +++ b/src/aonotearea.cpp @@ -3,6 +3,7 @@ #include "aoapplication.h" #include "aobutton.h" #include "aonotepicker.h" +#include "commondefs.h" #include "courtroom.h" #include @@ -67,7 +68,7 @@ void Courtroom::on_add_button_clicked() void Courtroom::set_note_files() { - QString filename = ao_app->get_base_path() + "configs/filesabstract.ini"; + QString filename = ao_app->get_base_path() + CONFIG_FILESABSTRACT_INI; QFile config_file(filename); if (!config_file.open(QIODevice::ReadOnly | QIODevice::Text)) diff --git a/src/charselect.cpp b/src/charselect.cpp index a4ed5bf90..6b8d4905c 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -4,6 +4,7 @@ #include "aobutton.h" #include "aocharbutton.h" #include "aoimagedisplay.h" +#include "commondefs.h" #include "debug_functions.h" #include "drpacket.h" #include "file_functions.h" @@ -48,7 +49,7 @@ void Courtroom::reconstruct_char_select() while (!ui_char_button_list.isEmpty()) delete ui_char_button_list.takeLast(); - QPoint f_spacing = ao_app->get_button_spacing("char_button_spacing", "courtroom_design.ini"); + QPoint f_spacing = ao_app->get_button_spacing("char_button_spacing", COURTROOM_DESIGN_INI); const int button_width = 60; int x_spacing = f_spacing.x(); @@ -58,7 +59,7 @@ void Courtroom::reconstruct_char_select() int y_spacing = f_spacing.y(); int y_mod_count = 0; - set_size_and_pos(ui_char_buttons, "char_buttons", INI_DESIGN, ao_app); + set_size_and_pos(ui_char_buttons, "char_buttons", COURTROOM_DESIGN_INI, ao_app); char_columns = ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + 1; char_rows = ((ui_char_buttons->height() - button_height) / (y_spacing + button_height)) + 1; @@ -102,9 +103,7 @@ void Courtroom::reset_char_select() void Courtroom::set_char_select() { - QString filename = "courtroom_design.ini"; - - pos_size_type f_charselect = ao_app->get_element_dimensions("char_select", filename); + pos_size_type f_charselect = ao_app->get_element_dimensions("char_select", COURTROOM_DESIGN_INI); if (f_charselect.width < 0 || f_charselect.height < 0) { @@ -159,7 +158,7 @@ void Courtroom::char_clicked(int n_char) { int n_real_char = n_char + m_current_chr_page * m_page_max_chr_count; - QString char_ini_path = ao_app->get_character_path(m_chr_list.at(n_real_char).name, "char.ini"); + QString char_ini_path = ao_app->get_character_path(m_chr_list.at(n_real_char).name, CHARACTER_CHAR_INI); qDebug() << "char_ini_path" << char_ini_path; if (!file_exists(char_ini_path)) diff --git a/src/commondefs.cpp b/src/commondefs.cpp new file mode 100644 index 000000000..7baa41134 --- /dev/null +++ b/src/commondefs.cpp @@ -0,0 +1,23 @@ +#include "commondefs.h" + +#include + +const QString BACKGROUND_BACKGROUNDS_INI = "backgrounds.ini"; + +const QString BASE_CONFIG_INI = "/base/config.ini"; + +const QString CHARACTER_CHAR_INI = "char.ini"; +const QString CHARACTER_SOUNDS_INI = "sounds.ini"; + +const QString CONFIG_SOUNDS_INI = "configs/sounds.ini"; +const QString CONFIG_FILESABSTRACT_INI = "configs/filesabstract.ini"; + +const QString COURTROOM_CONFIG_INI = "courtroom_config.ini"; +const QString COURTROOM_DESIGN_INI = "courtroom_design.ini"; +const QString COURTROOM_FONTS_INI = "courtroom_fonts.ini"; +const QString COURTROOM_LAYERS_INI = "courtroom_layers.ini"; +const QString COURTROOM_SOUNDS_INI = "courtroom_sounds.ini"; +const QString COURTROOM_TEXT_COLOR_INI = "courtroom_text_colors.ini"; + +const QString LOBBY_DESIGN_INI = "lobby_design.ini"; +const QString LOBBY_FONTS_INI = "lobby_fonts.ini"; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f4ceee088..c8ffc516d 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -17,6 +17,7 @@ #include "aosystemplayer.h" #include "aotextarea.h" #include "aotimer.h" +#include "commondefs.h" #include "debug_functions.h" #include "drdiscord.h" #include "drpacket.h" @@ -38,11 +39,6 @@ #include #include -const QString Courtroom::INI_DESIGN = "courtroom_design.ini"; -const QString Courtroom::INI_FONTS = "courtroom_fonts.ini"; -const QString Courtroom::INI_CONFIG = "courtroom_config.ini"; -const QString Courtroom::INI_SOUNDS = "courtroom_sounds.ini"; - const int Courtroom::DEFAULT_WIDTH = 714; const int Courtroom::DEFAULT_HEIGHT = 668; @@ -80,11 +76,13 @@ void Courtroom::set_character_list(QVector p_chr_list) void Courtroom::set_area_list(QStringList p_area_list) { m_area_list = p_area_list; + list_music(); } void Courtroom::set_music_list(QStringList p_music_list) { m_music_list = p_music_list; + list_areas(); } void Courtroom::enter_courtroom(int p_cid) @@ -171,7 +169,7 @@ void Courtroom::enter_courtroom(int p_cid) const QString l_name = i_info.fileName(); if (get_base_character() == l_name) continue; - if (!file_exists(ao_app->get_character_path(l_name, "char.ini"))) + if (!file_exists(ao_app->get_character_path(l_name, CHARACTER_CHAR_INI))) continue; l_name_list.append(l_name); } @@ -285,7 +283,7 @@ void Courtroom::set_scene() QString f_desk_image = "stand"; QString f_desk_mod = m_chatmessage[CMDeskModifier]; QString f_side = m_chatmessage[CMPosition]; - QString ini_path = ao_app->get_background_path("backgrounds.ini"); + QString ini_path = ao_app->get_background_path(BACKGROUND_BACKGROUNDS_INI); if (file_exists(ini_path)) { @@ -381,16 +379,14 @@ void Courtroom::set_tick_rate(const std::optional &tick_rate) void Courtroom::handle_music_anim() { - QString file_a = INI_DESIGN; - QString file_b = INI_FONTS; - pos_size_type res_a = ao_app->get_element_dimensions("music_name", file_a); - pos_size_type res_b = ao_app->get_element_dimensions("music_area", file_a); - float speed = static_cast(ao_app->get_font_property("music_name_speed", file_b)); + pos_size_type res_a = ao_app->get_element_dimensions("music_name", COURTROOM_DESIGN_INI); + pos_size_type res_b = ao_app->get_element_dimensions("music_area", COURTROOM_DESIGN_INI); + float speed = static_cast(ao_app->get_font_property("music_name_speed", COURTROOM_FONTS_INI)); QFont f_font = ui_vp_music_name->font(); QFontMetrics fm(f_font); int dist; - if (ao_app->read_theme_ini_bool("enable_const_music_speed", INI_CONFIG)) + if (ao_app->read_theme_ini_bool("enable_const_music_speed", COURTROOM_CONFIG_INI)) dist = res_b.width; else dist = fm.horizontalAdvance(ui_vp_music_name->toPlainText()); @@ -433,66 +429,31 @@ void Courtroom::list_music() { ui_music_list->clear(); - QString f_file = INI_DESIGN; - - QBrush found_brush(ao_app->get_color("found_song_color", f_file)); - QBrush missing_brush(ao_app->get_color("missing_song_color", f_file)); - - int n_listed_songs = 0; - - for (int n_song = 0; n_song < m_music_list.size(); ++n_song) + const QString l_item_filter = ui_music_search->text(); + const QBrush l_song_brush(ao_app->get_color("found_song_color", COURTROOM_DESIGN_INI)); + const QBrush l_missing_song_brush(ao_app->get_color("missing_song_color", COURTROOM_DESIGN_INI)); + for (const QString &i_item_name : qAsConst(m_music_list)) { - QString i_song = m_music_list.at(n_song); - - if (i_song.toLower().contains(ui_music_search->text().toLower())) - { - ui_music_list->addItem(i_song); - - QString song_root = ao_app->get_music_path(i_song); - QString song_path = ao_app->find_asset_path({song_root}, audio_extensions()); - - if (!song_path.isEmpty()) - ui_music_list->item(n_listed_songs)->setBackground(found_brush); - else - ui_music_list->item(n_listed_songs)->setBackground(missing_brush); - - ++n_listed_songs; - } + if (!i_item_name.contains(l_item_filter, Qt::CaseInsensitive)) + continue; + QListWidgetItem *l_item = new QListWidgetItem(i_item_name, ui_music_list); + const QString l_song_path = ao_app->find_asset_path({ao_app->get_music_path(i_item_name)}, audio_extensions()); + l_item->setBackground(l_song_path.isEmpty() ? l_missing_song_brush : l_song_brush); } } void Courtroom::list_areas() { ui_area_list->clear(); - // area_names.clear(); - - QString f_file = "courtroom_design.ini"; - - QBrush free_brush(ao_app->get_color("area_free_color", f_file)); - QBrush lfp_brush(ao_app->get_color("area_lfp_color", f_file)); - QBrush casing_brush(ao_app->get_color("area_casing_color", f_file)); - QBrush recess_brush(ao_app->get_color("area_recess_color", f_file)); - QBrush rp_brush(ao_app->get_color("area_rp_color", f_file)); - QBrush gaming_brush(ao_app->get_color("area_gaming_color", f_file)); - QBrush locked_brush(ao_app->get_color("area_locked_color", f_file)); - int n_listed_areas = 0; - - for (int n_area = 0; n_area < m_area_list.size(); ++n_area) + const QString l_item_filter = ui_music_search->text(); + const QBrush l_area_brush(ao_app->get_color("area_free_color", COURTROOM_DESIGN_INI)); + for (const QString &i_item_name : qAsConst(m_area_list)) { - QString i_area = ""; - - i_area.append(m_area_list.at(n_area)); - - if (i_area.toLower().contains(ui_music_search->text().toLower())) - { - ui_area_list->addItem(i_area); - // area_names.append(i_); - - ui_area_list->item(n_listed_areas)->setBackground(free_brush); - - ++n_listed_areas; - } + if (!i_item_name.contains(l_item_filter, Qt::CaseInsensitive)) + continue; + QListWidgetItem *l_item = new QListWidgetItem(i_item_name, ui_area_list); + l_item->setBackground(l_area_brush); } } @@ -508,8 +469,8 @@ QString Courtroom::current_sfx_file() void Courtroom::update_sfx_list() { // colors - m_sfx_color_found = ao_app->get_color("found_song_color", INI_DESIGN); - m_sfx_color_missing = ao_app->get_color("missing_song_color", INI_DESIGN); + m_sfx_color_found = ao_app->get_color("found_song_color", COURTROOM_DESIGN_INI); + m_sfx_color_missing = ao_app->get_color("missing_song_color", COURTROOM_DESIGN_INI); // items m_sfx_list.clear(); @@ -598,7 +559,7 @@ void Courtroom::on_sfx_widget_list_row_changed() void Courtroom::list_note_files() { - QString f_config = ao_app->get_base_path() + file_select_ini; + QString f_config = ao_app->get_base_path() + CONFIG_FILESABSTRACT_INI; QFile f_file(f_config); if (!f_file.open(QIODevice::ReadOnly)) { @@ -1112,7 +1073,7 @@ void Courtroom::handle_chatmessage_3() const bool l_hide_emote = (f_emote == "../../misc/blank"); QString path; - if (!chatmessage_is_empty && ao_app->read_theme_ini_bool("enable_showname_image", INI_CONFIG)) + if (!chatmessage_is_empty && ao_app->read_theme_ini_bool("enable_showname_image", COURTROOM_CONFIG_INI)) { // Asset lookup order // 1. In the theme folder (gamemode-timeofday/main/default), in the character @@ -1242,16 +1203,16 @@ void Courtroom::update_ic_log(bool p_reset_log) // prepare the formats we need // default color - QColor default_color = ao_app->get_color("ic_chatlog_color", INI_FONTS); + QColor default_color = ao_app->get_color("ic_chatlog_color", COURTROOM_FONTS_INI); QColor not_found_color = QColor(255, 255, 255); QTextCharFormat name_format = ui_ic_chatlog->currentCharFormat(); - if (ao_app->get_font_property("ic_chatlog_bold", INI_FONTS)) + if (ao_app->get_font_property("ic_chatlog_bold", COURTROOM_FONTS_INI)) name_format.setFontWeight(QFont::Bold); else name_format.setFontWeight(QFont::Normal); - QColor showname_color = ao_app->get_color("ic_chatlog_showname_color", INI_FONTS); + QColor showname_color = ao_app->get_color("ic_chatlog_showname_color", COURTROOM_FONTS_INI); if (showname_color == not_found_color) showname_color = default_color; name_format.setForeground(showname_color); @@ -1260,7 +1221,7 @@ void Courtroom::update_ic_log(bool p_reset_log) if (ao_config->log_display_self_highlight_enabled()) { - QColor selfname_color = ao_app->get_color("ic_chatlog_selfname_color", INI_FONTS); + QColor selfname_color = ao_app->get_color("ic_chatlog_selfname_color", COURTROOM_FONTS_INI); if (selfname_color == not_found_color) selfname_color = showname_color; selfname_format.setForeground(selfname_color); @@ -1268,14 +1229,14 @@ void Courtroom::update_ic_log(bool p_reset_log) QTextCharFormat line_format = ui_ic_chatlog->currentCharFormat(); line_format.setFontWeight(QFont::Normal); - QColor message_color = ao_app->get_color("ic_chatlog_message_color", INI_FONTS); + QColor message_color = ao_app->get_color("ic_chatlog_message_color", COURTROOM_FONTS_INI); if (message_color == not_found_color) message_color = default_color; line_format.setForeground(message_color); QTextCharFormat system_format = ui_ic_chatlog->currentCharFormat(); system_format.setFontWeight(QFont::Normal); - QColor system_color = ao_app->get_color("ic_chatlog_system_color", INI_FONTS); + QColor system_color = ao_app->get_color("ic_chatlog_system_color", COURTROOM_FONTS_INI); if (system_color == not_found_color) system_color = not_found_color; system_format.setForeground(system_color); @@ -1519,8 +1480,8 @@ void Courtroom::setup_chat() m_blip_step = 0; // Cache these so chat_tick performs better - m_chatbox_message_outline = (ao_app->get_font_property("message_outline", INI_FONTS) == 1); - m_chatbox_message_enable_highlighting = (ao_app->read_theme_ini_bool("enable_highlighting", INI_CONFIG)); + m_chatbox_message_outline = (ao_app->get_font_property("message_outline", COURTROOM_FONTS_INI) == 1); + m_chatbox_message_enable_highlighting = (ao_app->read_theme_ini_bool("enable_highlighting", COURTROOM_CONFIG_INI)); m_chatbox_message_highlight_colors = ao_app->get_highlight_colors(); QString f_gender = ao_app->get_gender(m_chatmessage[CMChrName]); @@ -1865,8 +1826,6 @@ void Courtroom::handle_song(QStringList p_contents) void Courtroom::handle_wtce(QString p_wtce) { - QString sfx_file = INI_SOUNDS; - int index = p_wtce.at(p_wtce.size() - 1).digitValue(); if (index > 0 && index < wtce_names.size() + 1 && wtce_names.size() > 0) // check to prevent crash { @@ -2227,7 +2186,7 @@ void Courtroom::on_cycle_clicked() break; } - if (ao_app->read_theme_ini_bool("enable_cycle_ding", INI_CONFIG)) + if (ao_app->read_theme_ini_bool("enable_cycle_ding", COURTROOM_CONFIG_INI)) m_system_player->play(ao_app->get_sfx("cycle")); set_shouts(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index d774053c3..237514f68 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -19,6 +19,7 @@ #include "aosystemplayer.h" #include "aotextarea.h" #include "aotimer.h" +#include "commondefs.h" #include "drtextedit.h" #include "file_functions.h" #include "theme.h" @@ -473,9 +474,8 @@ void Courtroom::set_widget_layers() { // File lookup order // 1. In the theme folder (gamemode-timeofday/main/default), look for - // "courtroom_layers.ini". - QString path = ao_app->find_theme_asset_path("courtroom_layers.ini"); + QString path = ao_app->find_theme_asset_path(COURTROOM_LAYERS_INI); QFile layer_ini(path); // needed to avoid cyclic parenting QStringList recorded_widgets; @@ -559,12 +559,11 @@ void Courtroom::set_widget_layers() void Courtroom::set_widgets() { - QString filename = INI_DESIGN; - pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", filename); + pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", COURTROOM_DESIGN_INI); if (f_courtroom.width < 0 || f_courtroom.height < 0) { - qDebug() << "W: did not find courtroom width or height in " << filename; + qDebug() << "W: did not find courtroom width or height in " << COURTROOM_DESIGN_INI; resize(DEFAULT_WIDTH, DEFAULT_HEIGHT); } @@ -577,7 +576,7 @@ void Courtroom::set_widgets() ui_background->resize(size()); ui_background->set_image("courtroombackground.png"); - set_size_and_pos(ui_viewport, "viewport", INI_DESIGN, ao_app); + set_size_and_pos(ui_viewport, "viewport", COURTROOM_DESIGN_INI, ao_app); ui_vp_background->move(0, 0); ui_vp_background->combo_resize(ui_viewport->size()); @@ -595,19 +594,19 @@ void Courtroom::set_widgets() ui_vp_evidence_display->move(0, 0); ui_vp_evidence_display->resize(ui_viewport->width(), ui_viewport->height()); - set_size_and_pos(ui_vp_notepad_image, "notepad_image", INI_DESIGN, ao_app); + set_size_and_pos(ui_vp_notepad_image, "notepad_image", COURTROOM_DESIGN_INI, ao_app); ui_vp_notepad_image->set_image("notepad_image.png"); ui_vp_notepad_image->hide(); - set_size_and_pos(ui_vp_notepad, "notepad", INI_DESIGN, ao_app); + set_size_and_pos(ui_vp_notepad, "notepad", COURTROOM_DESIGN_INI, ao_app); ui_vp_notepad->hide(); - set_size_and_pos(ui_vp_showname, "showname", INI_DESIGN, ao_app); + set_size_and_pos(ui_vp_showname, "showname", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_vp_showname_image, "showname_image", INI_DESIGN, ao_app); + set_size_and_pos(ui_vp_showname_image, "showname_image", COURTROOM_DESIGN_INI, ao_app); ui_vp_showname_image->hide(); - set_size_and_pos(ui_vp_message, "message", INI_DESIGN, ao_app); + set_size_and_pos(ui_vp_message, "message", COURTROOM_DESIGN_INI, ao_app); ui_vp_message->setTextInteractionFlags(Qt::NoTextInteraction); ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "color: white"); @@ -627,43 +626,43 @@ void Courtroom::set_widgets() ui_vp_objection->move(ui_viewport->x(), ui_viewport->y()); ui_vp_objection->combo_resize(ui_viewport->width(), ui_viewport->height()); - set_size_and_pos(ui_ic_chatlog, "ic_chatlog", INI_DESIGN, ao_app); + set_size_and_pos(ui_ic_chatlog, "ic_chatlog", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_ooc_chatlog, "server_chatlog", INI_DESIGN, ao_app); + set_size_and_pos(ui_ooc_chatlog, "server_chatlog", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_mute_list, "mute_list", INI_DESIGN, ao_app); + set_size_and_pos(ui_mute_list, "mute_list", COURTROOM_DESIGN_INI, ao_app); ui_mute_list->hide(); - set_size_and_pos(ui_music_list, "music_list", INI_DESIGN, ao_app); - set_size_and_pos(ui_area_list, "area_list", INI_DESIGN, ao_app); + set_size_and_pos(ui_music_list, "music_list", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_area_list, "area_list", COURTROOM_DESIGN_INI, ao_app); if (ui_music_list->isVisible()) ui_area_list->hide(); // ui_area_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - set_size_and_pos(ui_sfx_list, "sfx_list", INI_DESIGN, ao_app); + set_size_and_pos(ui_sfx_list, "sfx_list", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_ic_chat_showname, "ic_chat_name", INI_DESIGN, ao_app); - set_text_alignment(ui_ic_chat_showname, "ic_chat_name", INI_FONTS, ao_app); + set_size_and_pos(ui_ic_chat_showname, "ic_chat_name", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_ic_chat_showname, "ic_chat_name", COURTROOM_FONTS_INI, ao_app); ui_ic_chat_showname->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); - set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message", INI_DESIGN, ao_app); - set_text_alignment(ui_ic_chat_message, "ao2_ic_chat_message", INI_FONTS, ao_app); + set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_ic_chat_message, "ao2_ic_chat_message", COURTROOM_FONTS_INI, ao_app); ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); - set_size_and_pos(ui_vp_chatbox, "ao2_chatbox", INI_DESIGN, ao_app); + set_size_and_pos(ui_vp_chatbox, "ao2_chatbox", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_vp_music_area, "music_area", INI_DESIGN, ao_app); + set_size_and_pos(ui_vp_music_area, "music_area", COURTROOM_DESIGN_INI, ao_app); ui_vp_music_area->show(); - set_size_and_pos(ui_vp_music_name, "music_name", INI_DESIGN, ao_app); + set_size_and_pos(ui_vp_music_name, "music_name", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_vp_music_display_a, "music_display_a", INI_DESIGN, ao_app); + set_size_and_pos(ui_vp_music_display_a, "music_display_a", COURTROOM_DESIGN_INI, ao_app); ui_vp_music_display_a->set_image("music_display_a.png"); ui_vp_music_display_a->show(); - set_size_and_pos(ui_vp_music_display_b, "music_display_b", INI_DESIGN, ao_app); + set_size_and_pos(ui_vp_music_display_b, "music_display_b", COURTROOM_DESIGN_INI, ao_app); ui_vp_music_display_b->set_image("music_display_b.png"); ui_vp_music_display_b->show(); - set_size_and_pos(ui_vp_clock, "clock", INI_DESIGN, ao_app); + set_size_and_pos(ui_vp_clock, "clock", COURTROOM_DESIGN_INI, ao_app); ui_vp_clock->hide(); ui_vp_chatbox->set_image("chatmed.png"); @@ -672,60 +671,60 @@ void Courtroom::set_widgets() ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); ui_muted->set_image("muted.png"); - set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message", INI_DESIGN, ao_app); - set_text_alignment(ui_ooc_chat_message, "ooc_chat_message", INI_FONTS, ao_app); + set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_FONTS_INI, ao_app); ui_ooc_chat_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name", INI_DESIGN, ao_app); - set_text_alignment(ui_ooc_chat_name, "ooc_chat_name", INI_FONTS, ao_app); + set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_ooc_chat_name, "ooc_chat_name", COURTROOM_FONTS_INI, ao_app); ui_ooc_chat_name->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - set_size_and_pos(ui_music_search, "music_search", INI_DESIGN, ao_app); - set_text_alignment(ui_music_search, "music_search", INI_FONTS, ao_app); + set_size_and_pos(ui_music_search, "music_search", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_music_search, "music_search", COURTROOM_FONTS_INI, ao_app); - set_size_and_pos(ui_sfx_search, "sfx_search", INI_DESIGN, ao_app); - set_text_alignment(ui_sfx_search, "sfx_search", INI_FONTS, ao_app); + set_size_and_pos(ui_sfx_search, "sfx_search", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_sfx_search, "sfx_search", COURTROOM_FONTS_INI, ao_app); // char select reconstruct_char_select(); // emotes - set_size_and_pos(ui_emotes, "emotes", INI_DESIGN, ao_app); + set_size_and_pos(ui_emotes, "emotes", COURTROOM_DESIGN_INI, ao_app); reconstruct_emotes(); - set_size_and_pos(ui_emote_left, "emote_left", INI_DESIGN, ao_app); + set_size_and_pos(ui_emote_left, "emote_left", COURTROOM_DESIGN_INI, ao_app); ui_emote_left->set_image("arrow_left.png"); - set_size_and_pos(ui_emote_right, "emote_right", INI_DESIGN, ao_app); + set_size_and_pos(ui_emote_right, "emote_right", COURTROOM_DESIGN_INI, ao_app); ui_emote_right->set_image("arrow_right.png"); - set_size_and_pos(ui_emote_dropdown, "emote_dropdown", INI_DESIGN, ao_app); + set_size_and_pos(ui_emote_dropdown, "emote_dropdown", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_iniswap_dropdown, "iniswap_dropdown", INI_DESIGN, ao_app); + set_size_and_pos(ui_iniswap_dropdown, "iniswap_dropdown", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_pos_dropdown, "pos_dropdown", INI_DESIGN, ao_app); + set_size_and_pos(ui_pos_dropdown, "pos_dropdown", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_defense_bar, "defense_bar", INI_DESIGN, ao_app); + set_size_and_pos(ui_defense_bar, "defense_bar", COURTROOM_DESIGN_INI, ao_app); ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + ".png"); - set_size_and_pos(ui_prosecution_bar, "prosecution_bar", INI_DESIGN, ao_app); + set_size_and_pos(ui_prosecution_bar, "prosecution_bar", COURTROOM_DESIGN_INI, ao_app); ui_prosecution_bar->set_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); for (int i = 0; i < shout_names.size(); ++i) { - set_size_and_pos(ui_shouts[i], shout_names[i], INI_DESIGN, ao_app); + set_size_and_pos(ui_shouts[i], shout_names[i], COURTROOM_DESIGN_INI, ao_app); } reset_shout_buttons(); - set_size_and_pos(ui_shout_up, "shout_up", INI_DESIGN, ao_app); + set_size_and_pos(ui_shout_up, "shout_up", COURTROOM_DESIGN_INI, ao_app); ui_shout_up->set_image("shoutup.png"); ui_shout_up->hide(); - set_size_and_pos(ui_shout_down, "shout_down", INI_DESIGN, ao_app); + set_size_and_pos(ui_shout_down, "shout_down", COURTROOM_DESIGN_INI, ao_app); ui_shout_down->set_image("shoutdown.png"); ui_shout_down->hide(); // courtroom_config.ini necessary + check for crash - if (ao_app->read_theme_ini_bool("enable_single_shout", INI_CONFIG) && ui_shouts.size() > 0) + if (ao_app->read_theme_ini_bool("enable_single_shout", COURTROOM_CONFIG_INI) && ui_shouts.size() > 0) { for (auto &shout : ui_shouts) move_widget(shout, "bullet"); @@ -738,18 +737,18 @@ void Courtroom::set_widgets() for (int i = 0; i < effect_names.size(); ++i) { - set_size_and_pos(ui_effects[i], effect_names[i], INI_DESIGN, ao_app); + set_size_and_pos(ui_effects[i], effect_names[i], COURTROOM_DESIGN_INI, ao_app); } reset_effect_buttons(); - set_size_and_pos(ui_effect_up, "effect_up", INI_DESIGN, ao_app); + set_size_and_pos(ui_effect_up, "effect_up", COURTROOM_DESIGN_INI, ao_app); ui_effect_up->set_image("effectup.png"); ui_effect_up->hide(); - set_size_and_pos(ui_effect_down, "effect_down", INI_DESIGN, ao_app); + set_size_and_pos(ui_effect_down, "effect_down", COURTROOM_DESIGN_INI, ao_app); ui_effect_down->set_image("effectdown.png"); ui_effect_down->hide(); - if (ao_app->read_theme_ini_bool("enable_single_effect", INI_CONFIG) && + if (ao_app->read_theme_ini_bool("enable_single_effect", COURTROOM_CONFIG_INI) && ui_effects.size() > 0) // check to prevent crashing { for (auto &effect : ui_effects) @@ -761,19 +760,19 @@ void Courtroom::set_widgets() ui_effect_down->show(); } - set_size_and_pos(ui_wtce_up, "wtce_up", INI_DESIGN, ao_app); + set_size_and_pos(ui_wtce_up, "wtce_up", COURTROOM_DESIGN_INI, ao_app); ui_wtce_up->set_image("wtceup.png"); ui_wtce_up->hide(); - set_size_and_pos(ui_wtce_down, "wtce_down", INI_DESIGN, ao_app); + set_size_and_pos(ui_wtce_down, "wtce_down", COURTROOM_DESIGN_INI, ao_app); ui_wtce_down->set_image("wtcedown.png"); ui_wtce_down->hide(); for (int i = 0; i < wtce_names.size(); ++i) { - set_size_and_pos(ui_wtce[i], wtce_names[i], INI_DESIGN, ao_app); + set_size_and_pos(ui_wtce[i], wtce_names[i], COURTROOM_DESIGN_INI, ao_app); } - if (ao_app->read_theme_ini_bool("enable_single_wtce", INI_CONFIG)) // courtroom_config.ini necessary + if (ao_app->read_theme_ini_bool("enable_single_wtce", COURTROOM_CONFIG_INI)) // courtroom_config.ini necessary { for (auto &wtce : ui_wtce) move_widget(wtce, "wtce"); @@ -784,17 +783,17 @@ void Courtroom::set_widgets() for (int i = 0; i < free_block_names.size(); ++i) { - set_size_and_pos(ui_free_blocks[i], free_block_names[i], INI_DESIGN, ao_app); + set_size_and_pos(ui_free_blocks[i], free_block_names[i], COURTROOM_DESIGN_INI, ao_app); } set_free_blocks(); // Set the default values for the buttons, then determine if they should be // replaced by images - set_size_and_pos(ui_change_character, "change_character", INI_DESIGN, ao_app); - set_size_and_pos(ui_call_mod, "call_mod", INI_DESIGN, ao_app); - set_size_and_pos(ui_note_button, "note_button", INI_DESIGN, ao_app); - set_size_and_pos(ui_switch_area_music, "switch_area_music", INI_DESIGN, ao_app); - set_size_and_pos(ui_config_panel, "config_panel", INI_DESIGN, ao_app); + set_size_and_pos(ui_change_character, "change_character", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_call_mod, "call_mod", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_note_button, "note_button", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_switch_area_music, "switch_area_music", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_config_panel, "config_panel", COURTROOM_DESIGN_INI, ao_app); ui_change_character->setText(""); ui_call_mod->setText(""); @@ -808,7 +807,7 @@ void Courtroom::set_widgets() ui_config_panel->setStyleSheet(""); ui_note_button->setStyleSheet(""); - if (ao_app->read_theme_ini_bool("enable_button_images", INI_CONFIG)) + if (ao_app->read_theme_ini_bool("enable_button_images", COURTROOM_CONFIG_INI)) { // Set files, ask questions later // set_image first tries the gamemode-timeofday folder, then the theme @@ -849,19 +848,19 @@ void Courtroom::set_widgets() ui_config_panel->resize(64, 64); } - set_size_and_pos(ui_pre, "pre", INI_DESIGN, ao_app); + set_size_and_pos(ui_pre, "pre", COURTROOM_DESIGN_INI, ao_app); ui_pre->setText("Pre"); - set_size_and_pos(ui_flip, "flip", INI_DESIGN, ao_app); + set_size_and_pos(ui_flip, "flip", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_hidden, "hidden", INI_DESIGN, ao_app); + set_size_and_pos(ui_hidden, "hidden", COURTROOM_DESIGN_INI, ao_app); for (int i = 0; i < ui_label_images.size(); ++i) { - set_size_and_pos(ui_label_images[i], label_images[i].toLower() + "_image", INI_DESIGN, ao_app); + set_size_and_pos(ui_label_images[i], label_images[i].toLower() + "_image", COURTROOM_DESIGN_INI, ao_app); } - if (ao_app->read_theme_ini_bool("enable_label_images", INI_CONFIG)) + if (ao_app->read_theme_ini_bool("enable_label_images", COURTROOM_CONFIG_INI)) { for (int i = 0; i < ui_checks.size(); ++i) // loop through checks { @@ -902,80 +901,80 @@ void Courtroom::set_widgets() } } - set_size_and_pos(ui_mute, "mute_button", INI_DESIGN, ao_app); + set_size_and_pos(ui_mute, "mute_button", COURTROOM_DESIGN_INI, ao_app); ui_mute->set_image("mute.png"); - set_size_and_pos(ui_defense_plus, "defense_plus", INI_DESIGN, ao_app); + set_size_and_pos(ui_defense_plus, "defense_plus", COURTROOM_DESIGN_INI, ao_app); ui_defense_plus->set_image("defplus.png"); - set_size_and_pos(ui_defense_minus, "defense_minus", INI_DESIGN, ao_app); + set_size_and_pos(ui_defense_minus, "defense_minus", COURTROOM_DESIGN_INI, ao_app); ui_defense_minus->set_image("defminus.png"); - set_size_and_pos(ui_prosecution_plus, "prosecution_plus", INI_DESIGN, ao_app); + set_size_and_pos(ui_prosecution_plus, "prosecution_plus", COURTROOM_DESIGN_INI, ao_app); ui_prosecution_plus->set_image("proplus.png"); - set_size_and_pos(ui_prosecution_minus, "prosecution_minus", INI_DESIGN, ao_app); + set_size_and_pos(ui_prosecution_minus, "prosecution_minus", COURTROOM_DESIGN_INI, ao_app); ui_prosecution_minus->set_image("prominus.png"); - set_size_and_pos(ui_text_color, "text_color", INI_DESIGN, ao_app); + set_size_and_pos(ui_text_color, "text_color", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_evidence_button, "evidence_button", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_button, "evidence_button", COURTROOM_DESIGN_INI, ao_app); ui_evidence_button->set_image("evidencebutton.png"); - set_size_and_pos(ui_evidence, "evidence_background", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence, "evidence_background", COURTROOM_DESIGN_INI, ao_app); ui_evidence->set_image("evidencebackground.png"); - set_size_and_pos(ui_evidence_name, "evidence_name", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_name, "evidence_name", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_evidence_buttons, "evidence_buttons", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_buttons, "evidence_buttons", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_evidence_left, "evidence_left", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_left, "evidence_left", COURTROOM_DESIGN_INI, ao_app); ui_evidence_left->set_image("arrow_left.png"); - set_size_and_pos(ui_evidence_right, "evidence_right", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_right, "evidence_right", COURTROOM_DESIGN_INI, ao_app); ui_evidence_right->set_image("arrow_right.png"); - set_size_and_pos(ui_evidence_present, "evidence_present", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_present, "evidence_present", COURTROOM_DESIGN_INI, ao_app); ui_evidence_present->set_image("present_disabled.png"); - set_size_and_pos(ui_evidence_overlay, "evidence_overlay", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_overlay, "evidence_overlay", COURTROOM_DESIGN_INI, ao_app); ui_evidence_overlay->set_image("evidenceoverlay.png"); - set_size_and_pos(ui_evidence_delete, "evidence_delete", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_delete, "evidence_delete", COURTROOM_DESIGN_INI, ao_app); ui_evidence_delete->set_image("deleteevidence.png"); - set_size_and_pos(ui_evidence_image_name, "evidence_image_name", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_image_name, "evidence_image_name", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_evidence_image_button, "evidence_image_button", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_image_button, "evidence_image_button", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_evidence_x, "evidence_x", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_x, "evidence_x", COURTROOM_DESIGN_INI, ao_app); ui_evidence_x->set_image("evidencex.png"); - set_size_and_pos(ui_evidence_description, "evidence_description", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence_description, "evidence_description", COURTROOM_DESIGN_INI, ao_app); ui_char_button_selector->set_image("char_selector.png"); ui_char_button_selector->hide(); - set_size_and_pos(ui_back_to_lobby, "back_to_lobby", INI_DESIGN, ao_app); + set_size_and_pos(ui_back_to_lobby, "back_to_lobby", COURTROOM_DESIGN_INI, ao_app); ui_back_to_lobby->setText("Back to Lobby"); - set_size_and_pos(ui_char_buttons, "char_buttons", INI_DESIGN, ao_app); + set_size_and_pos(ui_char_buttons, "char_buttons", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_chr_select_left, "char_select_left", INI_DESIGN, ao_app); + set_size_and_pos(ui_chr_select_left, "char_select_left", COURTROOM_DESIGN_INI, ao_app); ui_chr_select_left->set_image("arrow_left.png"); - set_size_and_pos(ui_chr_select_right, "char_select_right", INI_DESIGN, ao_app); + set_size_and_pos(ui_chr_select_right, "char_select_right", COURTROOM_DESIGN_INI, ao_app); ui_chr_select_right->set_image("arrow_right.png"); - set_size_and_pos(ui_spectator, "spectator", INI_DESIGN, ao_app); + set_size_and_pos(ui_spectator, "spectator", COURTROOM_DESIGN_INI, ao_app); handle_music_anim(); - set_size_and_pos(ui_set_notes, "set_notes_button", INI_DESIGN, ao_app); + set_size_and_pos(ui_set_notes, "set_notes_button", COURTROOM_DESIGN_INI, ao_app); ui_set_notes->set_image("set_notes.png"); ui_note_area->m_layout->setSpacing(10); - set_size_and_pos(ui_note_area, "note_area", INI_DESIGN, ao_app); - set_size_and_pos(ui_note_scroll_area, "note_area", INI_DESIGN, ao_app); + set_size_and_pos(ui_note_area, "note_area", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_note_scroll_area, "note_area", COURTROOM_DESIGN_INI, ao_app); ui_note_scroll_area->setWidget(ui_note_area); ui_note_area->set_image("note_area.png"); ui_note_area->add_button->set_image("add_button.png"); @@ -999,7 +998,7 @@ void Courtroom::set_widgets() void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) { - QString filename = INI_DESIGN; + QString filename = COURTROOM_DESIGN_INI; pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); @@ -1020,7 +1019,7 @@ int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_it // &item_vector must be a vector of size at least 1! // Redraw the new correct number of items. - int new_item_number = ao_app->read_theme_ini_int(config_item_number, INI_CONFIG); + int new_item_number = ao_app->read_theme_ini_int(config_item_number, COURTROOM_CONFIG_INI); int current_item_number = item_vector.size(); // Note we use the fact that, if config_item_number is not there, // read_theme_ini returns an empty string, which .toInt() would fail to @@ -1054,7 +1053,7 @@ int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_it for (int i = 0; i < new_item_number; i++) { item_vector[i]->show(); - set_size_and_pos(item_vector[i], item_name + "_" + QString::number(i), INI_DESIGN, ao_app); + set_size_and_pos(item_vector[i], item_name + "_" + QString::number(i), COURTROOM_DESIGN_INI, ao_app); // Note that show is deliberately placed before set_size_and_pos // That is because we want to retain the functionality set_size_and_pos // includes where hides a widget if it is unable to find a position for it, @@ -1168,7 +1167,7 @@ void Courtroom::load_effects() delete_widget(widget); // And create new effects - int effect_number = ao_app->read_theme_ini_int("effect_number", INI_CONFIG); + int effect_number = ao_app->read_theme_ini_int("effect_number", COURTROOM_CONFIG_INI); effects_enabled.resize(effect_number); ui_effects.resize(effect_number); @@ -1207,7 +1206,7 @@ void Courtroom::load_free_blocks() delete_widget(widget); // And create new free block buttons - int free_block_number = ao_app->read_theme_ini_int("free_block_number", INI_CONFIG); + int free_block_number = ao_app->read_theme_ini_int("free_block_number", COURTROOM_CONFIG_INI); free_blocks_enabled.resize(free_block_number); ui_free_blocks.resize(free_block_number); @@ -1239,7 +1238,7 @@ void Courtroom::load_shouts() delete_widget(widget); // And create new shouts - int shout_number = ao_app->read_theme_ini_int("shout_number", INI_CONFIG); + int shout_number = ao_app->read_theme_ini_int("shout_number", COURTROOM_CONFIG_INI); shouts_enabled.resize(shout_number); ui_shouts.resize(shout_number); @@ -1281,7 +1280,7 @@ void Courtroom::load_wtce() delete_widget(widget); // And create new wtce buttons - const int l_wtce_count = ao_app->read_theme_ini_int("wtce_number", INI_CONFIG); + const int l_wtce_count = ao_app->read_theme_ini_int("wtce_number", COURTROOM_CONFIG_INI); wtce_enabled.resize(l_wtce_count); ui_wtce.clear(); @@ -1359,7 +1358,7 @@ void Courtroom::set_judge_wtce() wtce->hide(); // check if we use a single wtce or multiple - const bool is_single_wtce = ao_app->read_theme_ini_bool("enable_single_wtce", INI_CONFIG); + const bool is_single_wtce = ao_app->read_theme_ini_bool("enable_single_wtce", COURTROOM_CONFIG_INI); // update visibility for next/previous ui_wtce_up->setVisible(is_judge && is_single_wtce); @@ -1414,21 +1413,21 @@ void Courtroom::set_dropdowns() void Courtroom::set_fonts() { - set_drtextedit_font(ui_vp_showname, "showname", INI_FONTS, ao_app); - set_drtextedit_font(ui_vp_message, "message", INI_FONTS, ao_app); - set_drtextedit_font(ui_ic_chatlog, "ic_chatlog", INI_FONTS, ao_app); + set_drtextedit_font(ui_vp_showname, "showname", COURTROOM_FONTS_INI, ao_app); + set_drtextedit_font(ui_vp_message, "message", COURTROOM_FONTS_INI, ao_app); + set_drtextedit_font(ui_ic_chatlog, "ic_chatlog", COURTROOM_FONTS_INI, ao_app); // Chatlog does not support drtextedit because html - set_font(ui_ooc_chatlog, "server_chatlog", INI_FONTS, ao_app); - set_font(ui_music_list, "music_list", INI_FONTS, ao_app); - set_font(ui_area_list, "area_list", INI_FONTS, ao_app); - set_font(ui_sfx_list, "sfx_list", INI_FONTS, ao_app); - set_drtextedit_font(ui_vp_music_name, "music_name", INI_FONTS, ao_app); - set_drtextedit_font(ui_vp_notepad, "notepad", INI_FONTS, ao_app); + set_font(ui_ooc_chatlog, "server_chatlog", COURTROOM_FONTS_INI, ao_app); + set_font(ui_music_list, "music_list", COURTROOM_FONTS_INI, ao_app); + set_font(ui_area_list, "area_list", COURTROOM_FONTS_INI, ao_app); + set_font(ui_sfx_list, "sfx_list", COURTROOM_FONTS_INI, ao_app); + set_drtextedit_font(ui_vp_music_name, "music_name", COURTROOM_FONTS_INI, ao_app); + set_drtextedit_font(ui_vp_notepad, "notepad", COURTROOM_FONTS_INI, ao_app); for (int i = 0; i < ui_timers.length(); ++i) { AOTimer *i_timer = ui_timers.at(i); - set_drtextedit_font(i_timer, QString("timer_%1").arg(i), INI_FONTS, ao_app); + set_drtextedit_font(i_timer, QString("timer_%1").arg(i), COURTROOM_FONTS_INI, ao_app); } } diff --git a/src/emotes.cpp b/src/emotes.cpp index 77324c12f..0d739066e 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -4,6 +4,7 @@ #include "aobutton.h" #include "aoconfig.h" #include "aoemotebutton.h" +#include "commondefs.h" #include "theme.h" #include @@ -38,9 +39,9 @@ void Courtroom::reconstruct_emotes() delete ui_emote_list.takeLast(); // resize and move - set_size_and_pos(ui_emotes, "emotes", INI_DESIGN, ao_app); + set_size_and_pos(ui_emotes, "emotes", COURTROOM_DESIGN_INI, ao_app); - QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", "courtroom_design.ini"); + QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", COURTROOM_DESIGN_INI); const int button_width = 40; int x_spacing = f_spacing.x(); diff --git a/src/evidence.cpp b/src/evidence.cpp index 940834bbf..80ce78805 100644 --- a/src/evidence.cpp +++ b/src/evidence.cpp @@ -6,6 +6,7 @@ #include "aoevidencedescription.h" #include "aoimagedisplay.h" #include "aolineedit.h" +#include "commondefs.h" #include "drpacket.h" #include "theme.h" @@ -40,10 +41,10 @@ void Courtroom::construct_evidence() ui_evidence_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "color: white;"); - set_size_and_pos(ui_evidence, "evidence_background", INI_DESIGN, ao_app); - set_size_and_pos(ui_evidence_buttons, "evidence_buttons", INI_DESIGN, ao_app); + set_size_and_pos(ui_evidence, "evidence_background", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_evidence_buttons, "evidence_buttons", COURTROOM_DESIGN_INI, ao_app); - QPoint f_spacing = ao_app->get_button_spacing("evidence_button_spacing", "courtroom_design.ini"); + QPoint f_spacing = ao_app->get_button_spacing("evidence_button_spacing", COURTROOM_DESIGN_INI); const int button_width = 70; int x_spacing = f_spacing.x(); diff --git a/src/lobby.cpp b/src/lobby.cpp index ca7080bf1..1bb69b7d0 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -5,6 +5,7 @@ #include "aoconfig.h" #include "aoimagedisplay.h" #include "aotextarea.h" +#include "commondefs.h" #include "debug_functions.h" #include "drpacket.h" #include "drpather.h" @@ -20,9 +21,6 @@ #include #include -const QString Lobby::INI_DESIGN = "lobby_design.ini"; -const QString Lobby::INI_FONTS = "lobby_fonts.ini"; - Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; @@ -86,11 +84,11 @@ bool Lobby::is_public_server() const // sets images, position and size void Lobby::set_widgets() { - pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", INI_DESIGN); + pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", LOBBY_DESIGN_INI); if (f_lobby.width < 0 || f_lobby.height < 0) { - qDebug() << "W: did not find lobby width or height in " << INI_DESIGN; + qDebug() << "W: did not find lobby width or height in " << LOBBY_DESIGN_INI; // Most common symptom of bad config files, missing assets, or misnamed // theme folder @@ -113,62 +111,62 @@ void Lobby::set_widgets() this->resize(f_lobby.width, f_lobby.height); } - set_size_and_pos(ui_background, "lobby", INI_DESIGN, ao_app); + set_size_and_pos(ui_background, "lobby", LOBBY_DESIGN_INI, ao_app); ui_background->set_image("lobbybackground.png"); - set_size_and_pos(ui_public_servers, "public_servers", INI_DESIGN, ao_app); + set_size_and_pos(ui_public_servers, "public_servers", LOBBY_DESIGN_INI, ao_app); ui_public_servers->set_image("publicservers_selected.png"); - set_size_and_pos(ui_favorites, "favorites", INI_DESIGN, ao_app); + set_size_and_pos(ui_favorites, "favorites", LOBBY_DESIGN_INI, ao_app); ui_favorites->set_image("favorites.png"); - set_size_and_pos(ui_refresh, "refresh", INI_DESIGN, ao_app); + set_size_and_pos(ui_refresh, "refresh", LOBBY_DESIGN_INI, ao_app); ui_refresh->set_image("refresh.png"); - set_size_and_pos(ui_add_to_fav, "add_to_fav", INI_DESIGN, ao_app); + set_size_and_pos(ui_add_to_fav, "add_to_fav", LOBBY_DESIGN_INI, ao_app); ui_add_to_fav->set_image("addtofav.png"); - set_size_and_pos(ui_connect, "connect", INI_DESIGN, ao_app); + set_size_and_pos(ui_connect, "connect", LOBBY_DESIGN_INI, ao_app); ui_connect->set_image("connect.png"); - set_size_and_pos(ui_version, "version", INI_DESIGN, ao_app); + set_size_and_pos(ui_version, "version", LOBBY_DESIGN_INI, ao_app); ui_version->setText("Version: " + get_version_string()); - set_size_and_pos(ui_about, "about", INI_DESIGN, ao_app); + set_size_and_pos(ui_about, "about", LOBBY_DESIGN_INI, ao_app); ui_about->set_image("about.png"); - set_size_and_pos(ui_server_list, "server_list", INI_DESIGN, ao_app); + set_size_and_pos(ui_server_list, "server_list", LOBBY_DESIGN_INI, ao_app); ui_server_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "font: bold;"); - set_size_and_pos(ui_player_count, "player_count", INI_DESIGN, ao_app); + set_size_and_pos(ui_player_count, "player_count", LOBBY_DESIGN_INI, ao_app); ui_player_count->setStyleSheet("font: bold;" "color: white;" "qproperty-alignment: AlignCenter;"); - set_size_and_pos(ui_description, "description", INI_DESIGN, ao_app); + set_size_and_pos(ui_description, "description", LOBBY_DESIGN_INI, ao_app); ui_description->setReadOnly(true); ui_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "color: white;"); - set_size_and_pos(ui_chatbox, "chatbox", INI_DESIGN, ao_app); + set_size_and_pos(ui_chatbox, "chatbox", LOBBY_DESIGN_INI, ao_app); ui_chatbox->setReadOnly(true); ui_chatbox->setStyleSheet("QTextBrowser{background-color: rgba(0, 0, 0, 0);}"); - set_size_and_pos(ui_chatname, "chatname", INI_DESIGN, ao_app); - set_text_alignment(ui_chatname, "chatname", INI_FONTS, ao_app); + set_size_and_pos(ui_chatname, "chatname", LOBBY_DESIGN_INI, ao_app); + set_text_alignment(ui_chatname, "chatname", LOBBY_FONTS_INI, ao_app); ui_chatname->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "selection-background-color: rgba(0, 0, 0, 0);"); - set_size_and_pos(ui_chatmessage, "chatmessage", INI_DESIGN, ao_app); - set_text_alignment(ui_chatmessage, "chatmessage", INI_FONTS, ao_app); + set_size_and_pos(ui_chatmessage, "chatmessage", LOBBY_DESIGN_INI, ao_app); + set_text_alignment(ui_chatmessage, "chatmessage", LOBBY_FONTS_INI, ao_app); ui_chatmessage->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "selection-background-color: rgba(0, 0, 0, 0);"); ui_loading_background->resize(this->width(), this->height()); ui_loading_background->set_image("loadingbackground.png"); - set_size_and_pos(ui_loading_text, "loading_label", INI_DESIGN, ao_app); + set_size_and_pos(ui_loading_text, "loading_label", LOBBY_DESIGN_INI, ao_app); ui_loading_text->setFont(QFont("Arial", 20, QFont::Bold)); ui_loading_text->setReadOnly(true); ui_loading_text->setAlignment(Qt::AlignCenter); @@ -177,8 +175,8 @@ void Lobby::set_widgets() "color: rgba(255, 128, 0, 255);"); ui_loading_text->append("Loading"); - set_size_and_pos(ui_progress_bar, "progress_bar", INI_DESIGN, ao_app); - set_size_and_pos(ui_cancel, "cancel", INI_DESIGN, ao_app); + set_size_and_pos(ui_progress_bar, "progress_bar", LOBBY_DESIGN_INI, ao_app); + set_size_and_pos(ui_cancel, "cancel", LOBBY_DESIGN_INI, ao_app); ui_cancel->setText("Cancel"); ui_loading_background->hide(); @@ -190,13 +188,13 @@ void Lobby::set_widgets() void Lobby::set_fonts() { - set_drtextedit_font(ui_player_count, "player_count", INI_FONTS, ao_app); - set_font(ui_description, "description", INI_FONTS, ao_app); - set_font(ui_chatbox, "chatbox", INI_FONTS, ao_app); - set_font(ui_chatname, "chatname", INI_FONTS, ao_app); - set_font(ui_chatmessage, "chatmessage", INI_FONTS, ao_app); - set_drtextedit_font(ui_loading_text, "loading_text", INI_FONTS, ao_app); - set_font(ui_server_list, "server_list", INI_FONTS, ao_app); + set_drtextedit_font(ui_player_count, "player_count", LOBBY_FONTS_INI, ao_app); + set_font(ui_description, "description", LOBBY_FONTS_INI, ao_app); + set_font(ui_chatbox, "chatbox", LOBBY_FONTS_INI, ao_app); + set_font(ui_chatname, "chatname", LOBBY_FONTS_INI, ao_app); + set_font(ui_chatmessage, "chatmessage", LOBBY_FONTS_INI, ao_app); + set_drtextedit_font(ui_loading_text, "loading_text", LOBBY_FONTS_INI, ao_app); + set_font(ui_server_list, "server_list", LOBBY_FONTS_INI, ao_app); } void Lobby::set_stylesheet(QWidget *widget, QString target_tag) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 5373b131e..164c8cdc5 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -1,6 +1,7 @@ #include "aoapplication.h" #include "aoconfig.h" +#include "commondefs.h" #include "file_functions.h" #include @@ -237,7 +238,7 @@ QString AOApplication::get_font_name(QString p_identifier, QString p_file) QString AOApplication::get_sfx(QString p_identifier) { - return read_theme_ini(p_identifier, "courtroom_sounds.ini"); + return read_theme_ini(p_identifier, COURTROOM_SOUNDS_INI); } QString AOApplication::get_stylesheet(QString target_tag, QString p_file) @@ -284,16 +285,14 @@ QMap AOApplication::get_chatmessage_colors() QMap color_map = DR::get_default_color_map(); // File lookup order - // 1. In the theme folder (gamemode-timeofday/main/default), look for - // "courtroom_text_colors.ini". + // 1. In the theme folder (gamemode-timeofday/main/default) - const QString file_name = "courtroom_text_colors.ini"; - QString path = find_theme_asset_path(file_name); + QString path = find_theme_asset_path(COURTROOM_TEXT_COLOR_INI); if (path.isEmpty()) { qInfo().noquote() << QString("[color] theme %1 is missing file: %2, using default colors instead") .arg(ao_config->theme()) - .arg(file_name); + .arg(COURTROOM_TEXT_COLOR_INI); return color_map; } qInfo().noquote() << QString("[color] loading colors for theme %1").arg(ao_config->theme()); @@ -339,9 +338,9 @@ QVector AOApplication::get_highlight_colors() { // File lookup order // 1. In the theme folder (gamemode-timeofday/main/default), look for - // "courtroom_config.ini". + // COURTROOM_INI_CONFIG. - QString path = find_theme_asset_path("courtroom_config.ini"); + QString path = find_theme_asset_path(COURTROOM_CONFIG_INI); if (path.isEmpty()) return QVector(); @@ -390,9 +389,9 @@ QString AOApplication::get_spbutton(QString p_tag, int index) { // File lookup order // 1. In the theme folder (gamemode-timeofday/main/default), look for - // "courtroom_config.ini". + // COURTROOM_INI_CONFIG. - QString path = find_theme_asset_path("courtroom_config.ini"); + QString path = find_theme_asset_path(COURTROOM_CONFIG_INI); if (path.isEmpty()) return ""; @@ -433,9 +432,9 @@ QStringList AOApplication::get_effect(int index) { // File lookup order // 1. In the theme folder (gamemode-timeofday/main/default), look for - // "courtroom_config.ini". + // COURTROOM_INI_CONFIG. - QString path = find_theme_asset_path("courtroom_config.ini"); + QString path = find_theme_asset_path(COURTROOM_CONFIG_INI); if (path.isEmpty()) return QStringList(); @@ -480,9 +479,9 @@ QStringList AOApplication::get_sfx_list() QStringList r_sfx_list; QStringList l_file_list; - l_file_list.append(get_base_path() + "configs/sounds.ini"); + l_file_list.append(get_base_path() + CONFIG_SOUNDS_INI); for (const QString &i_chr : get_char_include_tree(get_current_char())) - l_file_list.append(get_character_path(i_chr, "sounds.ini")); + l_file_list.append(get_character_path(i_chr, CHARACTER_SOUNDS_INI)); for (const QString &i_file_path : qAsConst(l_file_list)) { @@ -512,7 +511,7 @@ QString drLookupKey(const QStringList &keyList, const QString &targetKey) // be found QVariant AOApplication::read_char_ini(QString p_chr, QString p_group, QString p_key, QVariant p_def) { - QSettings s(get_character_path(p_chr, "char.ini"), QSettings::IniFormat); + QSettings s(get_character_path(p_chr, CHARACTER_CHAR_INI), QSettings::IniFormat); s.setIniCodec("UTF-8"); s.beginGroup(drLookupKey(s.childGroups(), p_group)); return s.value(drLookupKey(s.childKeys(), p_key), p_def); @@ -597,7 +596,7 @@ QVector AOApplication::get_emote_list(QString p_chr) qDebug().noquote() << QString("Adding <%1>").arg(i_chr); #endif - QSettings l_chrini(get_character_path(i_chr, "char.ini"), QSettings::IniFormat); + QSettings l_chrini(get_character_path(i_chr, CHARACTER_CHAR_INI), QSettings::IniFormat); l_chrini.setIniCodec("UTF-8"); QStringList l_keys; From 7836f0bc03c1cd07606793ca86b743cbc10c889f Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 31 May 2021 18:42:16 +0200 Subject: [PATCH 422/842] Fixed wrong function call order --- src/courtroom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index c8ffc516d..65f53f9e2 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -76,13 +76,13 @@ void Courtroom::set_character_list(QVector p_chr_list) void Courtroom::set_area_list(QStringList p_area_list) { m_area_list = p_area_list; - list_music(); + list_areas(); } void Courtroom::set_music_list(QStringList p_music_list) { m_music_list = p_music_list; - list_areas(); + list_music(); } void Courtroom::enter_courtroom(int p_cid) From 2a7f82229408209b48705397054cea4686164a6d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 1 Jun 2021 22:44:10 +0200 Subject: [PATCH 423/842] Fix crash from empty lines inside a character's sounds.ini --- src/text_file_functions.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 164c8cdc5..20e52b0d1 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -494,6 +494,15 @@ QStringList AOApplication::get_sfx_list() } } + // remove blanks + for (int i = 0; i < r_sfx_list.length(); ++i) + { + const QString &l_line = r_sfx_list.at(i); + if (!l_line.trimmed().isEmpty()) + continue; + r_sfx_list.removeAt(i--); + } + return r_sfx_list; } From 00becd2d5afd0d288230daae123ad407c07734b0 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 2 Jun 2021 00:05:19 +0200 Subject: [PATCH 424/842] chat_tick_rate packet's value is no longer optional --- src/courtroom.cpp | 9 +++++++-- src/server_socket.cpp | 7 +++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 65f53f9e2..6b5aeece4 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -372,9 +372,14 @@ void Courtroom::set_background(QString p_background) current_background = p_background; } -void Courtroom::set_tick_rate(const std::optional &tick_rate) +void Courtroom::set_tick_rate(const int p_tick_rate) { - m_server_tick_rate = tick_rate; + if (p_tick_rate < 0) + { + m_server_tick_rate.reset(); + return; + } + m_server_tick_rate = p_tick_rate; } void Courtroom::handle_music_anim() diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 763ac89f7..018debcb8 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -272,8 +272,11 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) } else if (l_header == "chat_tick_rate") { - if (is_courtroom_constructed) - m_courtroom->set_tick_rate(l_content.isEmpty() ? std::nullopt : std::optional(l_content.at(0).toInt())); + if (l_content.size() < 1) + return; + if (!is_courtroom_constructed) + return; + m_courtroom->set_tick_rate(l_content.at(0).toInt()); } // server accepting char request(CC) packet else if (l_header == "PV") From e19e8a1544a09bd9864e377c6bc826734c8fc367 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 2 Jun 2021 00:07:42 +0200 Subject: [PATCH 425/842] Missing change --- include/courtroom.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/courtroom.h b/include/courtroom.h index f4a41c072..34045dcf1 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -81,7 +81,7 @@ class Courtroom : public QMainWindow // it's a legacy bg void set_background(QString p_background); - void set_tick_rate(const std::optional &tick_rate); + void set_tick_rate(const int tick_rate); // sets the evidence list member variable to argument void set_evidence_list(QVector &p_evi_list); From 6e565b8c9c5884079a44cef4fb52ab316260fa6d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 6 Jun 2021 01:16:14 +0200 Subject: [PATCH 426/842] Clear buffer after disconnecting --- include/drserversocket.h | 4 ++-- src/drserversocket.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/drserversocket.h b/include/drserversocket.h index 912d7de7f..b9b51bb9d 100644 --- a/include/drserversocket.h +++ b/include/drserversocket.h @@ -1,7 +1,7 @@ #pragma once -#include "drpacket.h" #include "datatypes.h" +#include "drpacket.h" #include #include @@ -38,7 +38,7 @@ public slots: bool m_is_connected = false; QTimer *m_reconnect_timer = nullptr; bool is_reconnectable = false; - QString m_data; + QString m_buffer; private slots: void _p_update_state(QAbstractSocket::SocketState); diff --git a/src/drserversocket.cpp b/src/drserversocket.cpp index ffae72b6e..3acf5622e 100644 --- a/src/drserversocket.cpp +++ b/src/drserversocket.cpp @@ -49,6 +49,7 @@ void DRServerSocket::disconnect_from_server() { m_socket->close(); m_socket->abort(); + m_buffer.clear(); } void DRServerSocket::send_packet(DRPacket p_packet) @@ -103,9 +104,9 @@ void DRServerSocket::_p_check_socket_error() void DRServerSocket::_p_read_socket() { - m_data += QString::fromUtf8(m_socket->readAll()); - QStringList l_raw_packet_list = m_data.split("#%", DR::KeepEmptyParts); - m_data = l_raw_packet_list.takeLast(); + m_buffer += QString::fromUtf8(m_socket->readAll()); + QStringList l_raw_packet_list = m_buffer.split("#%", DR::KeepEmptyParts); + m_buffer = l_raw_packet_list.takeLast(); for (const QString &i_raw_packet : l_raw_packet_list) { QStringList l_raw_data_list = i_raw_packet.split("#"); From f7053a2addac7163d11637f14539e70fd66db0d0 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 7 Jun 2021 18:57:08 +0200 Subject: [PATCH 427/842] Slight rework for chat ticks --- include/courtroom.h | 2 ++ src/courtroom.cpp | 24 ++++++++++++++++++++---- src/courtroom_widgets.cpp | 2 +- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 34045dcf1..ca535429a 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -583,6 +583,8 @@ private slots: void play_sfx(); void start_chat_timer(); + void stop_chat_timer(); + void calculate_chat_tick_interval(); void next_chat_letter(); void post_chat(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6b5aeece4..3d2f7f418 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -902,8 +902,8 @@ void Courtroom::handle_chatmessage(QStringList p_contents) text_state = 0; anim_state = 0; + stop_chat_timer(); ui_vp_objection->stop(); - m_tick_timer->stop(); ui_vp_evidence_display->reset(); // reset effect @@ -1174,6 +1174,7 @@ void Courtroom::handle_chatmessage_3() } } + calculate_chat_tick_interval(); start_chat_timer(); } @@ -1499,12 +1500,26 @@ void Courtroom::setup_chat() } void Courtroom::start_chat_timer() +{ + if (m_tick_timer->isActive()) + return; + m_tick_timer->start(); +} + +void Courtroom::stop_chat_timer() +{ + if (!m_tick_timer->isActive()) + return; + m_tick_timer->stop(); +} + +void Courtroom::calculate_chat_tick_interval() { double l_tick_rate = ao_config->chat_tick_interval(); if (m_server_tick_rate.has_value()) l_tick_rate = qMax(m_server_tick_rate.value(), 0); l_tick_rate = qBound(0.0, l_tick_rate * (1.0 - qBound(-1.0, 0.4 * m_tick_speed, 1.0)), l_tick_rate * 2.0); - m_tick_timer->start(l_tick_rate); + m_tick_timer->setInterval(l_tick_rate); } void Courtroom::next_chat_letter() @@ -1537,7 +1552,9 @@ void Courtroom::next_chat_letter() ++m_tick_step; const bool is_positive = f_character == Qt::Key_BraceRight; m_tick_speed = qBound(-3, m_tick_speed + (is_positive ? 1 : -1), 3); + calculate_chat_tick_interval(); next_chat_letter(); + start_chat_timer(); return; } else if (f_character == Qt::Key_Space) @@ -1652,14 +1669,13 @@ void Courtroom::next_chat_letter() ++m_tick_step; is_ignore_next_letter = false; - start_chat_timer(); } void Courtroom::post_chat() { text_state = 2; - m_tick_timer->stop(); anim_state = 3; + stop_chat_timer(); if (m_msg_is_first_person == false) { diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 237514f68..12ed70c34 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -41,7 +41,7 @@ void Courtroom::create_widgets() m_keepalive_timer->start(60000); m_tick_timer = new QTimer(this); - m_tick_timer->setSingleShot(true); + m_tick_timer->setSingleShot(false); m_tick_timer->setTimerType(Qt::PreciseTimer); // Prevents drift m_sound_timer = new QTimer(this); From 164b6636d4dec3ca54f26f00d6b40229d80f014a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 8 Jun 2021 03:56:19 +0200 Subject: [PATCH 428/842] Start of #187 --- include/aoapplication.h | 10 +++++----- src/aoapplication.cpp | 36 ++++++++++++++++-------------------- src/main.cpp | 1 + 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index 6505aa07f..fdc66a7c1 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -1,8 +1,8 @@ #ifndef AOAPPLICATION_H #define AOAPPLICATION_H -#include "drpacket.h" #include "datatypes.h" +#include "drpacket.h" class AOConfig; class AOConfigPanel; @@ -191,6 +191,9 @@ class AOApplication : public QApplication // Returns overlay at p_effect in char_path/overlay QStringList get_overlay(QString p_char, int p_effect); + ////// Functions for fonts handling ////// + void load_fonts(); + public slots: void loading_cancelled(); @@ -244,10 +247,7 @@ private slots: void _p_handle_server_packet(DRPacket); void on_courtroom_closing(); void on_courtroom_destroyed(); - void on_config_theme_changed(); - void on_config_reload_theme_requested(); - void on_config_gamemode_changed(); - void on_config_timeofday_changed(); + void handle_theme_modification(); }; #endif // AOAPPLICATION_H diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 6ac1f9d16..b83c9b6cd 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -9,6 +9,8 @@ #include "drserversocket.h" #include "lobby.h" +#include +#include #include #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) @@ -30,12 +32,12 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) m_master_socket = new DRServerSocket(this); m_server_socket = new DRServerSocket(this); - connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(on_config_theme_changed())); - connect(ao_config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_config_gamemode_changed())); - connect(ao_config, SIGNAL(timeofday_changed(QString)), this, SLOT(on_config_timeofday_changed())); + connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(gamemode_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(timeofday_changed(QString)), this, SLOT(handle_theme_modification())); - connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(on_config_reload_theme_requested())); - connect(this, SIGNAL(reload_theme()), ao_config_panel, SLOT(on_config_reload_theme_requested())); + connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(handle_theme_modification())); + connect(this, SIGNAL(reload_theme()), ao_config_panel, SLOT(handle_theme_modification())); ao_config_panel->hide(); dr_discord->set_presence(ao_config->discord_presence()); @@ -192,23 +194,10 @@ bool AOApplication::has_chat_speed_feature() const return feature_chat_speed; } -void AOApplication::on_config_theme_changed() +void AOApplication::handle_theme_modification() { - Q_EMIT reload_theme(); -} - -void AOApplication::on_config_reload_theme_requested() -{ - Q_EMIT reload_theme(); -} - -void AOApplication::on_config_gamemode_changed() -{ - Q_EMIT reload_theme(); -} + load_fonts(); -void AOApplication::on_config_timeofday_changed() -{ Q_EMIT reload_theme(); } @@ -296,6 +285,13 @@ QVector &AOApplication::get_server_list() return m_server_list; } +void AOApplication::load_fonts() +{ + QFontDatabase l_database; + for (const QFileInfo &fileInfo : QDir(get_case_sensitive_path(get_base_path() + "fonts")).entryInfoList()) + l_database.addApplicationFont(fileInfo.absoluteFilePath()); +} + void AOApplication::loading_cancelled() { destruct_courtroom(); diff --git a/src/main.cpp b/src/main.cpp index ef7d3dd07..f50e21a17 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ int main(int argc, char *argv[]) AOApplication app(argc, argv); + app.load_fonts(); app.construct_lobby(); app.get_lobby()->show(); From 6a3d5f5a5c2a9bfd72037954321fe5a30b515fba Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 8 Jun 2021 11:47:29 -0400 Subject: [PATCH 429/842] Make OOC message and name have gray background like other textboxes --- src/courtroom_widgets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 237514f68..716dda3b5 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -673,11 +673,11 @@ void Courtroom::set_widgets() set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_FONTS_INI, ao_app); - ui_ooc_chat_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); + ui_ooc_chat_message->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_ooc_chat_name, "ooc_chat_name", COURTROOM_FONTS_INI, ao_app); - ui_ooc_chat_name->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); + ui_ooc_chat_name->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); set_size_and_pos(ui_music_search, "music_search", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_music_search, "music_search", COURTROOM_FONTS_INI, ao_app); From 95d470dd245fecf4a2a2b266e1a34be94793f22e Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 8 Jun 2021 18:06:02 -0400 Subject: [PATCH 430/842] Hide vertical scrollbar for note selector+Make widget resizable --- src/aonotearea.cpp | 2 +- src/courtroom_widgets.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp index 164825e8f..1b2231764 100644 --- a/src/aonotearea.cpp +++ b/src/aonotearea.cpp @@ -43,7 +43,7 @@ void Courtroom::on_add_button_clicked() f_line->setReadOnly(true); - f_layout->setSizeConstraint(QLayout::SetFixedSize); + f_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); f_layout->addWidget(f_hover); f_layout->addWidget(f_line); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 716dda3b5..1dc48ff07 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -150,11 +150,11 @@ void Courtroom::create_widgets() ui_note_area = new AONoteArea(this, ao_app); ui_note_area->add_button = new AOButton(ui_note_area, ao_app); ui_note_area->m_layout = new QVBoxLayout(ui_note_area); - ui_note_scroll_area = new QScrollArea(this); - ui_note_scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + ui_note_scroll_area = new QScrollArea(this); + ui_note_scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_note_scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_note_scroll_area->setWidgetResizable(false); + ui_note_scroll_area->setWidgetResizable(true); ui_set_notes = new AOButton(this, ao_app); From 4bd210eb5feb847de3a27751d2dfdaf31d756590 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 10 Jun 2021 13:24:01 -0400 Subject: [PATCH 431/842] Rename "chatbox_self" to "chatmed_self" to be consistent --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6b5aeece4..d54fb71ba 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -987,7 +987,7 @@ void Courtroom::handle_chatmessage_2() // handles IC if (ao_config->log_display_self_highlight_enabled() && m_chatmessage[CMChrId].toInt() == m_chr_id) { - const QString l_chatbox_self_name = "chatbox_self.png"; + const QString l_chatbox_self_name = "chatmed_self.png"; if (file_exists(ao_app->find_theme_asset_path(l_chatbox_self_name))) l_chatbox_name = l_chatbox_self_name; } From cd367935f6602ab78e90121cca635253494a1030 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 14 Jun 2021 17:09:25 -0400 Subject: [PATCH 432/842] All actions together --- .github/workflows/build-all.yml | 335 ++++++++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 .github/workflows/build-all.yml diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml new file mode 100644 index 000000000..b8e98ee77 --- /dev/null +++ b/.github/workflows/build-all.yml @@ -0,0 +1,335 @@ +# Original code from skyedeving and oldmud0 +# at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml +name: build-all + +on: push + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + ############################################################################### + # WINDOWS # + ############################################################################### + + windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup workspace + shell: bash + working-directory: ${{github.workspace}} + run: | + echo "parentworkspace=D:\\a\\DRO-Client\\" >> $GITHUB_ENV + mkdir "3rd" + mkdir "3rd/include" + mkdir "3rd/x86" + + - name: Fetch Discord external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o discord_rpc_win.zip + unzip discord_rpc_win.zip + cp ./discord-rpc/win32-dynamic/include/discord_register.h ./DRO-Client/3rd/include + cp ./discord-rpc/win32-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include + cp ./discord-rpc/win32-dynamic/bin/discord-rpc.dll ./DRO-Client/3rd/x86 + cp ./discord-rpc/win32-dynamic/lib/discord-rpc.lib ./DRO-Client/3rd/x86 + + - name: Fetch Bass external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bass24.zip -o bass.zip + unzip bass.zip + cp ./c/bass.h ./DRO-Client/3rd/include + cp ./bass.dll ./DRO-Client/3rd/x86 + cp ./c/bass.lib ./DRO-Client/3rd/x86 + + - name: Fetch QtApng external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-mingw73_32-5.14.1.zip -o apng.zip + unzip apng.zip + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: '5.14.2' + arch: 'win32_mingw73' + + - name: Install AQt + shell: bash + working-directory: ${{env.parentworkspace}} + run: pip install aqtinstall + + - name: Install MinGW + shell: bash + working-directory: ${{env.parentworkspace}} + run: aqt tool -O ./Qt windows tools_mingw 7.3.0-1-202004170606 qt.tools.win32_mingw730 + + - name: Setup MinGW + shell: bash + working-directory: ${{env.parentworkspace}} + # For whatever reason, qmake insists on using the MinGW64 installation that came with the host machine rather than the newly installed MinGW32 + # I could not find any way of making it use that MinGW32 folder, it always insisted on using MinGW64. + # Therefore, I just brought the MinGW32 folder to the MinGW64 folder. + # Tom Scott would be proud of this bodge + run: | + export PATH=/d/a/DRO-Client/Qt/Tools/mingw730_32/bin:$PATH + rm -r /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ + cp -r /d/a/DRO-Client/Qt/Tools/mingw730_32/ /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ + + - name: Run qmake + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + ./Qt/5.14.2/mingw73_32/bin/qmake.exe -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec win32-g++ "CONFIG+=qtquickcompiler" + + - name: Run make + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + ./Qt/Tools/mingw730_32/bin/mingw32-make.exe -f ./Makefile qmake_all + ./Qt/Tools/mingw730_32/bin/mingw32-make.exe -j8 + + - name: Set up deploy folder + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + mkdir "Danganronpa_Online" + cp ./release/dro-client.exe "./Danganronpa_Online/dro-client.exe" + + - name: Deploy + working-directory: ${{env.parentworkspace}}/Danganronpa_Online + shell: bash + run: | + windeployqt.exe --quick . + cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . + cp ../bass.dll . + cp ../mingw73_32/plugins/imageformats/qapng.dll ./imageformats/ + + - name: Make ZIP File + working-directory: ${{env.parentworkspace}} + shell: bash + run: | + zip -r "Danganronpa Online.zip" "Danganronpa Online" + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: "Danganronpa Online (Windows).zip" + path: "${{env.parentworkspace}}/Danganronpa Online.zip" + + - name: Upload Artifact (just .exe) + uses: actions/upload-artifact@v2 + with: + name: dro-client.exe + path: ${{env.parentworkspace}}/Danganronpa_Online/dro-client.exe + + ############################################################################### + # MACOS # + ############################################################################### + + macos: + runs-on: macos-10.15 + + steps: + - uses: actions/checkout@v2 + + - name: Setup workspace + shell: bash + working-directory: ${{github.workspace}} + run: | + echo "parentworkspace=/Users/runner/work/DRO-Client/" >> $GITHUB_ENV + mkdir "3rd" + mkdir "3rd/include" + mkdir "3rd/x86_64" + + - name: Fetch Discord external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip -o discord_rpc_osx.zip + unzip discord_rpc_osx.zip + cp ./discord-rpc/osx-dynamic/include/discord_register.h ./DRO-Client/3rd/include + cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include + cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./DRO-Client/3rd/x86_64 + + - name: Fetch Bass external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bass24-osx.zip -o bass.zip + unzip bass.zip + cp ./bass.h ./DRO-Client/3rd/include + cp ./libbass.dylib ./DRO-Client/3rd/x86_64 + + - name: Fetch QtApng external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-clang_64-5.14.1.tar.xz -o qtapng.tar.xz + tar -xvf qtapng.tar.xz + + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: '5.14.2' + + - name: Run qmake + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + ./Qt/5.14.2/clang_64/bin/qmake -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec macx-clang "CONFIG+=x86_64" "CONFIG+=qtquickcompiler" + + - name: Run Make + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + /usr/bin/make -f ./Makefile qmake_all + /usr/bin/make -j12 + + - name: Set up deploy folder + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + mkdir "Danganronpa Online" + cp -R ./dro-client.app "./Danganronpa Online" + mv "./Danganronpa Online/dro-client.app" "./Danganronpa Online/Danganronpa Online.app" + + - name: Deploy + working-directory: "${{env.parentworkspace}}/Danganronpa Online" + shell: bash + run: | + ../Qt/5.14.2/clang_64/bin/macdeployqt "Danganronpa Online.app" + cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/Frameworks/" + cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/MacOS/" + cp ../libbass.dylib "./Danganronpa Online.app/Contents/Frameworks/" + cp ../libbass.dylib "./Danganronpa Online.app/Contents/MacOS/" + cp -R ../clang_64/plugins/imageformats "./Danganronpa Online.app/Contents/MacOS/" + + - name: Make DMG + working-directory: ${{env.parentworkspace}} + shell: bash + run: | + hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online" -ov -format UDZO "Danganronpa Online.dmg" + + - name: Make ZIP File + working-directory: ${{env.parentworkspace}} + shell: bash + run: | + zip -r "Danganronpa Online.zip" "Danganronpa Online.dmg" + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: "Danganronpa Online (MacOS).zip" + path: "${{env.parentworkspace}}/Danganronpa Online.zip" + + ############################################################################### + # UBUNTU # + ############################################################################### + + ubuntu: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - name: Setup workspace + shell: bash + working-directory: ${{github.workspace}} + run: | + echo "parentworkspace=/home/runner/work/DRO-Client/" >> $GITHUB_ENV + mkdir "3rd" + mkdir "3rd/include" + mkdir "3rd/x86_64" + + - name: Fetch Discord external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip + unzip discord_rpc_linux.zip + cp ./discord-rpc/linux-dynamic/include/discord_register.h ./DRO-Client/3rd/include/discord_register.h + cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include/discord_rpc.h + cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./DRO-Client/3rd/x86_64/libdiscord-rpc.so + + - name: Fetch Bass external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip + unzip bass.zip + cp ./bass.h ./DRO-Client/3rd/include/bass.h + cp ./x64/libbass.so ./DRO-Client/3rd/x86_64/libbass.so + + - name: Fetch QtApng external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://cdn.discordapp.com/attachments/422857337133596692/845396654642954260/libqapng.so -o libqapng.so + + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: '5.14.2' + + - name: Run qmake + shell: bash + working-directory: ${{github.workspace}} + run: | + qmake + + - name: Run Make + shell: bash + working-directory: ${{github.workspace}} + run: | + make + + - name: Set up deploy folder + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + mkdir "Danganronpa Online" + cp -R ./DRO-Client/dro-client "./dro-client" + + - name: Deploy + working-directory: "${{env.parentworkspace}}/Danganronpa Online" + shell: bash + run: | + mkdir depends + cp ../discord-rpc/linux-dynamic/lib/libdiscord-rpc.so "./depends" + cp ../x64/libbass.so "./depends" + mkdir imageformats + cp -R ../libqapng.so "./imageformats" + echo "LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client" > "dro-client.sh" + echo "Installation instructions + + 1. Download the Full Pack for Windows and extract on the same folder as this folder. + + 2. On your terminal, run + sudo apt install qt5-default + + 3. Change directory to this folder, and run + chmod +x dro-client.sh + chmod +x dro-client + + 4. To launch, run + ./dro-client.sh" > "Readme (Ubuntu).txt" + + - name: Make ZIP File + working-directory: ${{env.parentworkspace}} + shell: bash + run: | + zip -r "Danganronpa Online.zip" "Danganronpa Online" + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: "Danganronpa Online (Ubuntu).zip" + path: "${{env.parentworkspace}}/Danganronpa Online.zip" From 242d193de909f0ccf41402c1b1b5d37b91826a9e Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 14 Jun 2021 17:34:03 -0400 Subject: [PATCH 433/842] Fix zip utility for Windows --- .github/workflows/build-all.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index b8e98ee77..8dda3d6e4 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -55,6 +55,7 @@ jobs: run: | curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-mingw73_32-5.14.1.zip -o apng.zip unzip apng.zip + - name: Install Qt uses: jurplel/install-qt-action@v2 with: @@ -89,7 +90,7 @@ jobs: run: | ./Qt/5.14.2/mingw73_32/bin/qmake.exe -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec win32-g++ "CONFIG+=qtquickcompiler" - - name: Run make + - name: Run Make shell: bash working-directory: ${{env.parentworkspace}} run: | @@ -116,7 +117,7 @@ jobs: working-directory: ${{env.parentworkspace}} shell: bash run: | - zip -r "Danganronpa Online.zip" "Danganronpa Online" + powershell Compress-Archive -Path "${{env.parentworkspace}}/Danganronpa_Online" -DestinationPath "${{env.parentworkspace}}/Danganronpa Online.zip" - name: Upload Artifact uses: actions/upload-artifact@v2 From 8406261c432e4293e51a0fd47aee4c9318f7b565 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 14 Jun 2021 17:44:40 -0400 Subject: [PATCH 434/842] Remove double zip --- .github/workflows/build-all.yml | 56 +++++++++++++-------------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 8dda3d6e4..33e75b3c5 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -113,23 +113,17 @@ jobs: cp ../bass.dll . cp ../mingw73_32/plugins/imageformats/qapng.dll ./imageformats/ - - name: Make ZIP File - working-directory: ${{env.parentworkspace}} - shell: bash - run: | - powershell Compress-Archive -Path "${{env.parentworkspace}}/Danganronpa_Online" -DestinationPath "${{env.parentworkspace}}/Danganronpa Online.zip" - - name: Upload Artifact uses: actions/upload-artifact@v2 with: - name: "Danganronpa Online (Windows).zip" - path: "${{env.parentworkspace}}/Danganronpa Online.zip" + name: "Danganronpa Online (Windows)" + path: "${{env.parentworkspace}}/Danganronpa_Online" - name: Upload Artifact (just .exe) uses: actions/upload-artifact@v2 with: - name: dro-client.exe - path: ${{env.parentworkspace}}/Danganronpa_Online/dro-client.exe + name: "dro-client.exe" + path: "${{env.parentworkspace}}/Danganronpa_Online/dro-client.exe" ############################################################################### # MACOS # @@ -212,24 +206,24 @@ jobs: cp ../libbass.dylib "./Danganronpa Online.app/Contents/Frameworks/" cp ../libbass.dylib "./Danganronpa Online.app/Contents/MacOS/" cp -R ../clang_64/plugins/imageformats "./Danganronpa Online.app/Contents/MacOS/" - - - name: Make DMG - working-directory: ${{env.parentworkspace}} - shell: bash - run: | - hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online" -ov -format UDZO "Danganronpa Online.dmg" - - - name: Make ZIP File - working-directory: ${{env.parentworkspace}} - shell: bash - run: | - zip -r "Danganronpa Online.zip" "Danganronpa Online.dmg" - + - name: Upload Artifact uses: actions/upload-artifact@v2 with: - name: "Danganronpa Online (MacOS).zip" - path: "${{env.parentworkspace}}/Danganronpa Online.zip" + name: "Danganronpa Online (MacOS)" + path: "${{env.parentworkspace}}/Danganronpa Online.app" + + # - name: Make DMG + # working-directory: ${{env.parentworkspace}} + # shell: bash + # run: | + # hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online" -ov -format UDZO "Danganronpa Online.dmg" + # + # - name: Upload Artifact + # uses: actions/upload-artifact@v2 + # with: + # name: "Danganronpa Online (MacOS)" + # path: "${{env.parentworkspace}}/Danganronpa Online.dmg" ############################################################################### # UBUNTU # @@ -297,7 +291,7 @@ jobs: working-directory: ${{env.parentworkspace}} run: | mkdir "Danganronpa Online" - cp -R ./DRO-Client/dro-client "./dro-client" + cp -R ./DRO-Client/dro-client "./Danganronpa Online/dro-client" - name: Deploy working-directory: "${{env.parentworkspace}}/Danganronpa Online" @@ -322,15 +316,9 @@ jobs: 4. To launch, run ./dro-client.sh" > "Readme (Ubuntu).txt" - - - name: Make ZIP File - working-directory: ${{env.parentworkspace}} - shell: bash - run: | - zip -r "Danganronpa Online.zip" "Danganronpa Online" - name: Upload Artifact uses: actions/upload-artifact@v2 with: - name: "Danganronpa Online (Ubuntu).zip" - path: "${{env.parentworkspace}}/Danganronpa Online.zip" + name: "Danganronpa Online (Ubuntu)" + path: "${{env.parentworkspace}}/Danganronpa Online" From d4b7bdaeaef840e09d13b8f40775064c812c7d1c Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 14 Jun 2021 17:59:30 -0400 Subject: [PATCH 435/842] Fix MacOS artifact --- .github/workflows/build-all.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 33e75b3c5..9083499c5 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -211,7 +211,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: "Danganronpa Online (MacOS)" - path: "${{env.parentworkspace}}/Danganronpa Online.app" + path: "${{env.parentworkspace}}/Danganronpa Online/Danganronpa Online.app" # - name: Make DMG # working-directory: ${{env.parentworkspace}} From 1fdbc91875c280d3ad631f53193a180c7c01d82c Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 14 Jun 2021 18:05:30 -0400 Subject: [PATCH 436/842] Remove no longer needed file (1) --- .github/workflows/build-mac.yml | 100 -------------------------------- 1 file changed, 100 deletions(-) delete mode 100644 .github/workflows/build-mac.yml diff --git a/.github/workflows/build-mac.yml b/.github/workflows/build-mac.yml deleted file mode 100644 index b023ff720..000000000 --- a/.github/workflows/build-mac.yml +++ /dev/null @@ -1,100 +0,0 @@ -# Original code from skyedeving and oldmud0 -# at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml -name: build-mac - -on: push - -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release - -jobs: - macos: - runs-on: macos-10.15 - - steps: - - uses: actions/checkout@v2 - - - name: Setup workspace - shell: bash - working-directory: ${{github.workspace}} - run: | - echo "parentworkspace=/Users/runner/work/DRO-Client/" >> $GITHUB_ENV - mkdir "3rd" - mkdir "3rd/include" - mkdir "3rd/x86_64" - - - name: Fetch Discord external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip -o discord_rpc_osx.zip - unzip discord_rpc_osx.zip - cp ./discord-rpc/osx-dynamic/include/discord_register.h ./DRO-Client/3rd/include - cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include - cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./DRO-Client/3rd/x86_64 - - - name: Fetch Bass external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bass24-osx.zip -o bass.zip - unzip bass.zip - cp ./bass.h ./DRO-Client/3rd/include - cp ./libbass.dylib ./DRO-Client/3rd/x86_64 - - - name: Fetch QtApng external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-clang_64-5.14.1.tar.xz -o qtapng.tar.xz - tar -xvf qtapng.tar.xz - ls -R - - - name: Install Qt - uses: jurplel/install-qt-action@v2 - with: - version: '5.14.2' - - - name: Run qmake - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - ./Qt/5.14.2/clang_64/bin/qmake -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec macx-clang "CONFIG+=x86_64" "CONFIG+=qtquickcompiler" - - - name: Run Make - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - /usr/bin/make -f ./Makefile qmake_all - /usr/bin/make -j12 - - - name: Set up deploy folder - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - mkdir "Danganronpa Online" - cp -R ./dro-client.app "./Danganronpa Online" - mv "./Danganronpa Online/dro-client.app" "./Danganronpa Online/Danganronpa Online.app" - - - name: Deploy - working-directory: "${{env.parentworkspace}}/Danganronpa Online" - shell: bash - run: | - ../Qt/5.14.2/clang_64/bin/macdeployqt "Danganronpa Online.app" - cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/Frameworks/" - cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/MacOS/" - cp ../libbass.dylib "./Danganronpa Online.app/Contents/Frameworks/" - cp ../libbass.dylib "./Danganronpa Online.app/Contents/MacOS/" - cp -R ../clang_64/plugins/imageformats "./Danganronpa Online.app/Contents/MacOS/" - - - name: Make DMG - working-directory: ${{env.parentworkspace}} - shell: bash - run: hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online" -ov -format UDZO "Danganronpa Online.dmg" - - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - name: "Danganronpa Online.dmg" - path: "${{env.parentworkspace}}/Danganronpa Online.dmg" From 1eaa92794daf5753684a2269958e6e80a01c6265 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 14 Jun 2021 18:05:38 -0400 Subject: [PATCH 437/842] Remove no longer needed file (2) --- .github/workflows/build-windows.yml | 123 ---------------------------- 1 file changed, 123 deletions(-) delete mode 100644 .github/workflows/build-windows.yml diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml deleted file mode 100644 index ce7b4f02e..000000000 --- a/.github/workflows/build-windows.yml +++ /dev/null @@ -1,123 +0,0 @@ -# Original code from skyedeving and oldmud0 -# at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml -name: build-windows - -on: push - -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release - -jobs: - windows: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v2 - - - name: Setup workspace - shell: bash - working-directory: ${{github.workspace}} - run: | - echo "parentworkspace=D:\\a\\DRO-Client\\" >> $GITHUB_ENV - mkdir "3rd" - mkdir "3rd/include" - mkdir "3rd/x86" - - - name: Fetch Discord external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o discord_rpc_win.zip - unzip discord_rpc_win.zip - cp ./discord-rpc/win32-dynamic/include/discord_register.h ./DRO-Client/3rd/include - cp ./discord-rpc/win32-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include - cp ./discord-rpc/win32-dynamic/bin/discord-rpc.dll ./DRO-Client/3rd/x86 - cp ./discord-rpc/win32-dynamic/lib/discord-rpc.lib ./DRO-Client/3rd/x86 - - - name: Fetch Bass external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bass24.zip -o bass.zip - unzip bass.zip - cp ./c/bass.h ./DRO-Client/3rd/include - cp ./bass.dll ./DRO-Client/3rd/x86 - cp ./c/bass.lib ./DRO-Client/3rd/x86 - ls ./DRO-Client/3rd -R - - - name: Fetch QtApng external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-mingw73_32-5.14.1.zip -o apng.zip - unzip apng.zip - - - name: Install Qt - uses: jurplel/install-qt-action@v2 - with: - version: '5.14.2' - arch: 'win32_mingw73' - - - name: Install AQt - shell: bash - working-directory: ${{env.parentworkspace}} - run: pip install aqtinstall - - - name: Install MinGW - shell: bash - working-directory: ${{env.parentworkspace}} - run: aqt tool -O ./Qt windows tools_mingw 7.3.0-1-202004170606 qt.tools.win32_mingw730 - - - name: Setup MinGW - shell: bash - working-directory: ${{env.parentworkspace}} - # For whatever reason, qmake insists on using the MinGW64 installation that came with the host machine rather than the newly installed MinGW32 - # I could not find any way of making it use that MinGW32 folder, it always insisted on using MinGW64. - # Therefore, I just brought the MinGW32 folder to the MinGW64 folder. - # Tom Scott would be proud of this bodge - run: | - export PATH=/d/a/DRO-Client/Qt/Tools/mingw730_32/bin:$PATH - rm -r /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ - cp -r /d/a/DRO-Client/Qt/Tools/mingw730_32/ /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ - - - name: Run qmake - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - ./Qt/5.14.2/mingw73_32/bin/qmake.exe -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec win32-g++ "CONFIG+=qtquickcompiler" - - - name: Run make - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - ./Qt/Tools/mingw730_32/bin/mingw32-make.exe -f ./Makefile qmake_all - ./Qt/Tools/mingw730_32/bin/mingw32-make.exe -j8 - - - name: Set up deploy folder - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - mkdir "Danganronpa_Online" - cp ./release/dro-client.exe "./Danganronpa_Online/Danganronpa_Online.exe" - - - name: Deploy - working-directory: ${{env.parentworkspace}}/Danganronpa_Online - shell: bash - run: | - windeployqt.exe --quick . - cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . - cp ../bass.dll . - cp ../mingw73_32/plugins/imageformats/qapng.dll ./imageformats/ - - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - name: Danganronpa_Online - path: ${{env.parentworkspace}}/Danganronpa_Online - - - name: Upload Artifact (just .exe) - uses: actions/upload-artifact@v2 - with: - name: dro-client.exe - path: ${{env.parentworkspace}}/Danganronpa_Online/Danganronpa_Online.exe From 8e852e16735edddd4db8754a756357bae6b45e2a Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 14 Jun 2021 19:10:01 -0400 Subject: [PATCH 438/842] Maintain directory hierarchy --- .github/workflows/build-all.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 9083499c5..07a4764cc 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -117,7 +117,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: "Danganronpa Online (Windows)" - path: "${{env.parentworkspace}}/Danganronpa_Online" + path: "${{env.parentworkspace}}/*/Danganronpa_Online" - name: Upload Artifact (just .exe) uses: actions/upload-artifact@v2 @@ -211,7 +211,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: "Danganronpa Online (MacOS)" - path: "${{env.parentworkspace}}/Danganronpa Online/Danganronpa Online.app" + path: "${{env.parentworkspace}}/Danganronpa Online/*/Danganronpa Online.app" # - name: Make DMG # working-directory: ${{env.parentworkspace}} @@ -321,4 +321,4 @@ jobs: uses: actions/upload-artifact@v2 with: name: "Danganronpa Online (Ubuntu)" - path: "${{env.parentworkspace}}/Danganronpa Online" + path: "${{env.parentworkspace}}/*/Danganronpa Online" From 4802d1b0a08e0e07004c6b14b1e7b6d9cd113164 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 14 Jun 2021 19:16:19 -0400 Subject: [PATCH 439/842] Update build-all.yml --- .github/workflows/build-all.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 07a4764cc..91dad753a 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -117,7 +117,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: "Danganronpa Online (Windows)" - path: "${{env.parentworkspace}}/*/Danganronpa_Online" + path: "${{env.parentworkspace}}/**/Danganronpa_Online" - name: Upload Artifact (just .exe) uses: actions/upload-artifact@v2 @@ -211,7 +211,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: "Danganronpa Online (MacOS)" - path: "${{env.parentworkspace}}/Danganronpa Online/*/Danganronpa Online.app" + path: "${{env.parentworkspace}}/Danganronpa Online/**/Danganronpa Online.app" # - name: Make DMG # working-directory: ${{env.parentworkspace}} @@ -321,4 +321,4 @@ jobs: uses: actions/upload-artifact@v2 with: name: "Danganronpa Online (Ubuntu)" - path: "${{env.parentworkspace}}/*/Danganronpa Online" + path: "${{env.parentworkspace}}/**/Danganronpa Online" From 8042840ea698bb8a6c9e0b46285f135823bfe940 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 14 Jun 2021 19:28:10 -0400 Subject: [PATCH 440/842] Fix Windows folder name --- .github/workflows/build-all.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 91dad753a..a62f473c1 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -101,11 +101,11 @@ jobs: shell: bash working-directory: ${{env.parentworkspace}} run: | - mkdir "Danganronpa_Online" - cp ./release/dro-client.exe "./Danganronpa_Online/dro-client.exe" + mkdir "Danganronpa Online" + cp ./release/dro-client.exe "./Danganronpa Online/dro-client.exe" - name: Deploy - working-directory: ${{env.parentworkspace}}/Danganronpa_Online + working-directory: "${{env.parentworkspace}}/Danganronpa Online" shell: bash run: | windeployqt.exe --quick . @@ -117,13 +117,13 @@ jobs: uses: actions/upload-artifact@v2 with: name: "Danganronpa Online (Windows)" - path: "${{env.parentworkspace}}/**/Danganronpa_Online" + path: "${{env.parentworkspace}}/**/Danganronpa Online" - name: Upload Artifact (just .exe) uses: actions/upload-artifact@v2 with: name: "dro-client.exe" - path: "${{env.parentworkspace}}/Danganronpa_Online/dro-client.exe" + path: "${{env.parentworkspace}}/Danganronpa Online/dro-client.exe" ############################################################################### # MACOS # From 4a3cecedace49db4e06e6c9284df5061e1580824 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 15 Jun 2021 16:25:52 -0400 Subject: [PATCH 441/842] Fix Qt version for Ubuntu --- .github/workflows/build-all.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index a62f473c1..b2149ae36 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -272,7 +272,7 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v2 with: - version: '5.14.2' + version: '5.12.8' - name: Run qmake shell: bash From 9f0dff8526f4fd1c049c1427661f66f13b765fa6 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 15 Jun 2021 16:49:38 -0400 Subject: [PATCH 442/842] Change Qt installation format to match dev syntax --- .github/workflows/build-all.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index b2149ae36..e26799ac1 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -270,9 +270,14 @@ jobs: curl -L https://cdn.discordapp.com/attachments/422857337133596692/845396654642954260/libqapng.so -o libqapng.so - name: Install Qt - uses: jurplel/install-qt-action@v2 - with: - version: '5.12.8' + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + sudo apt-get install build-essential + sudo apt-get install qt5-default qttools5-dev + sudo apt-get install libqt5designer5 + sudo apt-get install git + sudo snap install discord - name: Run qmake shell: bash From ac63c037ccb476bf486932495735fd14d002df46 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 16 Jun 2021 13:34:10 +0200 Subject: [PATCH 443/842] #190 (start) * Fixed spectator button refreshing the interface twice when pressing it * Removed `MU` and `UM` packet, removed mute list (deprecated) * Removed `IL` packet (deprecated) (ip packet list) * Moved character specific function its own source file --- dronline-client.pro | 1 + include/courtroom.h | 23 ----------- src/courtroom.cpp | 76 ++----------------------------------- src/courtroom_character.cpp | 18 +++++++++ src/courtroom_widgets.cpp | 42 -------------------- src/server_socket.cpp | 15 -------- 6 files changed, 23 insertions(+), 152 deletions(-) create mode 100644 src/courtroom_character.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 4d40c8035..6b825cba2 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -99,6 +99,7 @@ SOURCES += \ src/charselect.cpp \ src/commondefs.cpp \ src/courtroom.cpp \ + src/courtroom_character.cpp \ src/courtroom_widgets.cpp \ src/datatypes.cpp \ src/debug_functions.cpp \ diff --git a/include/courtroom.h b/include/courtroom.h index 34045dcf1..65f170664 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -97,23 +97,12 @@ class Courtroom : public QMainWindow // called when a DONE#% from the server was received void done_received(); - // sets the local mute list based on characters available on the server - void set_mute_list(); - // sets desk and bg based on pos in chatmessage void set_scene(); // sets text color based on text color in chatmessage void set_text_color(); - // takes in serverD-formatted IP list as prints a converted version to server - // OOC admittedly poorly named - void set_ip_list(QString p_list); - - // disables chat if current cid matches second argument - // enables if p_muted is false - void set_mute(bool p_muted, int p_cid); - // send a message that the player is banned and quits the server void set_ban(int p_cid); @@ -285,11 +274,6 @@ class Courtroom : public QMainWindow QString m_message_color_name; QStack m_message_color_stack; - // char id, muted or not - QMap mute_map; - - // QVector muted_cids; - bool is_client_muted = false; // state of animation, 0 = objecting, 1 = preanim, 2 = talking, 3 = idle @@ -396,7 +380,6 @@ class Courtroom : public QMainWindow AOTextArea *ui_ooc_chatlog = nullptr; - QListWidget *ui_mute_list = nullptr; QListWidget *ui_area_list = nullptr; QListWidget *ui_music_list = nullptr; @@ -490,8 +473,6 @@ class Courtroom : public QMainWindow AOButton *ui_effect_flash = nullptr; AOButton *ui_effect_gloom = nullptr; - AOButton *ui_mute = nullptr; - AOButton *ui_defense_plus = nullptr; AOButton *ui_defense_minus = nullptr; @@ -586,8 +567,6 @@ private slots: void next_chat_letter(); void post_chat(); - void on_mute_list_item_changed(QListWidgetItem *p_item); - void on_showname_changed(QString); void on_showname_placeholder_changed(QString); void on_character_ini_changed(QString); @@ -656,8 +635,6 @@ private slots: void on_effect_button_clicked(const bool); void on_effect_button_toggled(const bool); - void on_mute_clicked(); - void on_defense_minus_clicked(); void on_defense_plus_clicked(); void on_prosecution_minus_clicked(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6b5aeece4..38dbc4efd 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -259,8 +259,6 @@ void Courtroom::done_received() set_char_select_page(); - set_mute_list(); - set_char_select(); show(); @@ -850,9 +848,6 @@ void Courtroom::handle_chatmessage(QStringList p_contents) if (f_char_id < 0 || f_char_id >= m_chr_list.size()) return; - if (mute_map.value(m_chatmessage[CMChrId].toInt())) - return; - const QString l_message = QString(m_chatmessage[CMMessage]) .remove(QRegularExpression("(?destruct_courtroom(); } -int Courtroom::get_character_id() -{ - return m_chr_id; -} - -QString Courtroom::get_base_character() -{ - return is_spectating() ? nullptr : m_chr_list.at(m_chr_id).name; -} - -QString Courtroom::get_current_character() -{ - return ao_config->character_ini(get_base_character()); -} - void Courtroom::handle_song(QStringList p_contents) { if (p_contents.size() < 2) @@ -1812,13 +1792,10 @@ void Courtroom::handle_song(QStringList p_contents) str_char = f_showname; } - if (!mute_map.value(l_chr_id)) - { - append_ic_text(str_char, "has played a song: " + f_song, false, true, l_chr_id == m_chr_id); - if (ao_config->log_is_recording_enabled()) - save_textlog(str_char + " has played a song: " + f_song); - m_music_player->play(f_song); - } + append_ic_text(str_char, "has played a song: " + f_song, false, true, l_chr_id == m_chr_id); + if (ao_config->log_is_recording_enabled()) + save_textlog(str_char + " has played a song: " + f_song); + m_music_player->play(f_song); } int pos = f_song.lastIndexOf(QChar('.')); @@ -2057,32 +2034,6 @@ void Courtroom::on_pos_dropdown_changed(int p_index) // ao_app->send_server_packet(DRPacket("SP", {f_pos})); } -void Courtroom::on_mute_list_item_changed(QListWidgetItem *p_item) -{ - int f_cid = -1; - - for (int n_char = 0; n_char < m_chr_list.size(); n_char++) - { - if (m_chr_list.at(n_char).name == p_item->text()) - f_cid = n_char; - } - - if (f_cid < 0 || f_cid >= m_chr_list.size()) - { - qDebug() << "W: " << p_item->text() << " not present in char_list"; - return; - } - - if (Qt::CheckState::Checked == p_item->checkState()) - { - mute_map.insert(f_cid, true); - } - else - { - mute_map.insert(f_cid, false); - } -} - void Courtroom::on_music_list_clicked() { ui_ic_chat_message->setFocus(); @@ -2287,20 +2238,6 @@ void Courtroom::on_effect_button_toggled(const bool p_checked) l_button->setText(l_name); } -void Courtroom::on_mute_clicked() -{ - if (ui_mute_list->isHidden()) - { - ui_mute_list->show(); - ui_mute->set_image("mute_pressed.png"); - } - else - { - ui_mute_list->hide(); - ui_mute->set_image("mute.png"); - } -} - void Courtroom::on_defense_minus_clicked() { int f_state = defense_bar_state - 1; @@ -2455,11 +2392,6 @@ void Courtroom::on_char_select_right_clicked() void Courtroom::on_spectator_clicked() { ao_app->send_server_packet(DRPacket("CC", {QString::number(ao_app->get_client_id()), "-1", get_hdid()})); - enter_courtroom(-1); - - ui_emotes->hide(); - - ui_char_select_background->hide(); } void Courtroom::on_call_mod_clicked() diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp new file mode 100644 index 000000000..8fe4a15e1 --- /dev/null +++ b/src/courtroom_character.cpp @@ -0,0 +1,18 @@ +#include "courtroom.h" + +#include "aoconfig.h" + +int Courtroom::get_character_id() +{ + return m_chr_id; +} + +QString Courtroom::get_base_character() +{ + return is_spectating() ? nullptr : m_chr_list.at(m_chr_id).name; +} + +QString Courtroom::get_current_character() +{ + return ao_config->character_ini(get_base_character()); +} diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 237514f68..6c76cc68e 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -117,7 +117,6 @@ void Courtroom::create_widgets() ui_ooc_chatlog->setReadOnly(true); ui_ooc_chatlog->setOpenExternalLinks(true); - ui_mute_list = new QListWidget(this); ui_area_list = new QListWidget(this); ui_music_list = new QListWidget(this); ui_sfx_list = new QListWidget(this); @@ -211,8 +210,6 @@ void Courtroom::create_widgets() ui_checks.push_back(ui_flip); ui_checks.push_back(ui_hidden); - ui_mute = new AOButton(this, ao_app); - ui_defense_plus = new AOButton(this, ao_app); ui_defense_minus = new AOButton(this, ao_app); @@ -270,9 +267,6 @@ void Courtroom::connect_widgets() connect(ui_iniswap_dropdown, SIGNAL(activated(int)), this, SLOT(on_iniswap_dropdown_changed(int))); connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); - connect(ui_mute_list, SIGNAL(itemChanged(QListWidgetItem *)), this, - SLOT(on_mute_list_item_changed(QListWidgetItem *))); - connect(ao_config, SIGNAL(showname_changed(QString)), this, SLOT(on_showname_changed(QString))); connect(ao_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); @@ -300,8 +294,6 @@ void Courtroom::connect_widgets() connect(ui_wtce_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_wtce_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - connect(ui_mute, SIGNAL(clicked()), this, SLOT(on_mute_clicked())); - connect(ui_defense_minus, SIGNAL(clicked()), this, SLOT(on_defense_minus_clicked())); connect(ui_defense_plus, SIGNAL(clicked()), this, SLOT(on_defense_plus_clicked())); connect(ui_prosecution_minus, SIGNAL(clicked()), this, SLOT(on_prosecution_minus_clicked())); @@ -369,7 +361,6 @@ void Courtroom::reset_widget_names() {"vp_objection", ui_vp_objection}, {"ic_chatlog", ui_ic_chatlog}, {"server_chatlog", ui_ooc_chatlog}, - {"mute_list", ui_mute_list}, {"area_list", ui_area_list}, {"music_list", ui_music_list}, {"sfx_list", ui_sfx_list}, @@ -411,7 +402,6 @@ void Courtroom::reset_widget_names() {"pre", ui_pre}, {"flip", ui_flip}, {"hidden", ui_hidden}, - {"mute_button", ui_mute}, {"defense_plus", ui_defense_plus}, {"defense_minus", ui_defense_minus}, {"prosecution_plus", ui_prosecution_plus}, @@ -630,9 +620,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_ooc_chatlog, "server_chatlog", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_mute_list, "mute_list", COURTROOM_DESIGN_INI, ao_app); - ui_mute_list->hide(); - set_size_and_pos(ui_music_list, "music_list", COURTROOM_DESIGN_INI, ao_app); set_size_and_pos(ui_area_list, "area_list", COURTROOM_DESIGN_INI, ao_app); if (ui_music_list->isVisible()) @@ -901,9 +888,6 @@ void Courtroom::set_widgets() } } - set_size_and_pos(ui_mute, "mute_button", COURTROOM_DESIGN_INI, ao_app); - ui_mute->set_image("mute.png"); - set_size_and_pos(ui_defense_plus, "defense_plus", COURTROOM_DESIGN_INI, ao_app); ui_defense_plus->set_image("defplus.png"); @@ -1406,7 +1390,6 @@ void Courtroom::set_dropdowns() set_dropdown(ui_emote_dropdown, "[EMOTE DROPDOWN]"); set_dropdown(ui_iniswap_dropdown, "[INISWAP DROPDOWN]"); set_dropdown(ui_pos_dropdown, "[POS DROPDOWN]"); - set_dropdown(ui_mute_list, "[MUTE LIST]"); set_dropdown(ui_ic_chat_message, "[IC LINE]"); set_dropdown(ui_ooc_chat_message, "[OOC LINE]"); } @@ -1430,28 +1413,3 @@ void Courtroom::set_fonts() set_drtextedit_font(i_timer, QString("timer_%1").arg(i), COURTROOM_FONTS_INI, ao_app); } } - -void Courtroom::set_mute_list() -{ - mute_map.clear(); - - // maps which characters are muted based on cid, none are muted by default - for (int n_cid = 0; n_cid < m_chr_list.size(); n_cid++) - { - mute_map.insert(n_cid, false); - } - - QStringList sorted_mute_list; - - for (const char_type &i_char : qAsConst(m_chr_list)) - sorted_mute_list.append(i_char.name); - - sorted_mute_list.sort(); - - for (const QString &i_chr_name : sorted_mute_list) - { - QListWidgetItem *i_item = new QListWidgetItem(i_chr_name, ui_mute_list); - i_item->setFlags(i_item->flags() | Qt::ItemFlag::ItemIsUserCheckable); - i_item->setCheckState(Qt::Unchecked); - } -} diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 018debcb8..3b01ead80 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -338,21 +338,6 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) m_courtroom->set_evidence_list(f_evi_list); } } - else if (l_header == "IL") - { - if (is_courtroom_constructed && l_content.size() > 0) - m_courtroom->set_ip_list(l_content.at(0)); - } - else if (l_header == "MU") - { - if (is_courtroom_constructed && l_content.size() > 0) - m_courtroom->set_mute(true, l_content.at(0).toInt()); - } - else if (l_header == "UM") - { - if (is_courtroom_constructed && l_content.size() > 0) - m_courtroom->set_mute(false, l_content.at(0).toInt()); - } else if (l_header == "KK") { if (is_courtroom_constructed && l_content.size() > 0) From 4aa82175554b8cc6a0f83c5dc1e92e6f3ad6088d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 16 Jun 2021 15:44:25 +0200 Subject: [PATCH 444/842] Separated widget/character loading * Separated widget loading from character loading --- include/courtroom.h | 35 ++++++++-- src/aoapplication.cpp | 1 - src/aoconfigpanel.cpp | 7 +- src/courtroom.cpp | 123 +++++++++++++----------------------- src/courtroom_character.cpp | 64 +++++++++++++++++++ src/courtroom_widgets.cpp | 2 +- 6 files changed, 143 insertions(+), 89 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 65f170664..f7b34711a 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -103,20 +103,28 @@ class Courtroom : public QMainWindow // sets text color based on text color in chatmessage void set_text_color(); + // disables chat if current cid matches second argument + // enables if p_muted is false + void set_muted(bool p_muted, int p_cid); + // send a message that the player is banned and quits the server void set_ban(int p_cid); // implementations in path_functions.cpp QString get_background_path(QString p_file); - // cid = character id, returns the cid of the currently selected character - int get_character_id(); +public: QString get_base_character(); QString get_current_character(); + void update_iniswap_list(); + void update_default_iniswap_item(); // Set the showname of the client void set_showname(QString p_showname); + // sets up widgets + void setup_courtroom(); + // properly sets up some varibles: resets user state void enter_courtroom(int p_cid); @@ -127,7 +135,7 @@ class Courtroom : public QMainWindow void list_areas(); QString current_sfx_file(); - void update_sfx_list(); + void load_character_sfx_list(); void update_sfx_widget_list(); void select_default_sfx(); void clear_sfx_selection(); @@ -283,8 +291,6 @@ class Courtroom : public QMainWindow // ticking done int text_state = 2; - // character id, which index of the char_list the player is - int m_chr_id = -1; // if enabled, disable showing our own sprites when we talk in ic bool m_msg_is_first_person = false; @@ -681,6 +687,25 @@ private slots: void ping_server(); + // character + // =========================================================================== +public: + using CharacterId = int; + enum : CharacterId + { + SpectatorId = -1, + }; + + int get_character_id(); +public slots: + void set_character_id(const int); +signals: + void character_id_changed(int); + +private: + // character id, which index of the char_list the player is + CharacterId m_chr_id = SpectatorId; + /*! * ============================================================================= * AUDIO SYSTEM diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index b83c9b6cd..aecda6046 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -37,7 +37,6 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(ao_config, SIGNAL(timeofday_changed(QString)), this, SLOT(handle_theme_modification())); connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(handle_theme_modification())); - connect(this, SIGNAL(reload_theme()), ao_config_panel, SLOT(handle_theme_modification())); ao_config_panel->hide(); dr_discord->set_presence(ao_config->discord_presence()); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 4e6092ac5..11d1b9c34 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -127,7 +127,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(log_format_use_newline_changed(bool)), ui_log_format_use_newline, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_display_empty_messages_changed(bool)), ui_log_display_empty_messages, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_display_music_switch_changed(bool)), ui_log_display_music_switch, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_music_switch_changed(bool)), ui_log_display_music_switch, + SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); connect(m_config, SIGNAL(log_is_recording_changed(bool)), ui_log_is_recording, SLOT(setChecked(bool))); @@ -140,7 +141,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(effect_ignore_suppression_changed(bool)), ui_effect_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(music_volume_changed(int)), ui_music, SLOT(setValue(int))); - connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), ui_music_ignore_suppression, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), ui_music_ignore_suppression, + SLOT(setChecked(bool))); connect(m_config, SIGNAL(blip_volume_changed(int)), ui_blip, SLOT(setValue(int))); connect(m_config, SIGNAL(blip_ignore_suppression_changed(bool)), ui_blip_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(blip_rate_changed(int)), ui_blip_rate, SLOT(setValue(int))); @@ -404,7 +406,6 @@ void AOConfigPanel::update_audio_device_list() void AOConfigPanel::on_reload_theme_clicked() { - qDebug() << "reload theme clicked"; Q_EMIT reload_theme(); } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 38dbc4efd..4ea7f9706 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -61,6 +61,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() set_widgets(); set_char_select(); set_widget_names(); + setup_courtroom(); } Courtroom::~Courtroom() @@ -85,30 +86,18 @@ void Courtroom::set_music_list(QStringList p_music_list) list_music(); } -void Courtroom::enter_courtroom(int p_cid) +void Courtroom::setup_courtroom() { - qDebug() << "enter_courtroom"; - - // unmute audio - suppress_audio(false); + load_shouts(); + load_effects(); + load_wtce(); + load_free_blocks(); - const int l_prev_emote_id = m_emote_id; - const int l_prev_emote_page = m_current_emote_page; + // setup chat + update_ic_log(true); - // widgets =================================================================== current_evidence_page = 0; current_evidence = 0; - - m_shout_state = 0; - m_shout_current = 0; - m_effect_state = 0; - m_effect_current = 0; - m_wtce_current = 0; - reset_wtce_buttons(); - - // setup chat - on_chat_config_changed(); - set_evidence_page(); // Update widgets first, then check if everything is valid @@ -118,14 +107,22 @@ void Courtroom::enter_courtroom(int p_cid) set_widgets(); + update_iniswap_list(); + + m_shout_state = 0; + m_shout_current = 0; check_shouts(); if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) cycle_shout(1); + m_effect_state = 0; + m_effect_current = 0; check_effects(); if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) cycle_effect(1); + m_wtce_current = 0; + reset_wtce_buttons(); check_wtce(); if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) cycle_wtce(1); @@ -144,47 +141,31 @@ void Courtroom::enter_courtroom(int p_cid) for (AOTimer *i_timer : qAsConst(ui_timers)) i_timer->redraw(); +} - ui_char_select_background->hide(); +void Courtroom::enter_courtroom(int p_cid) +{ + qDebug() << "enter_courtroom"; + + // unmute audio + suppress_audio(false); + + const int l_prev_emote_id = m_emote_id; + const int l_prev_emote_page = m_current_emote_page; // character ================================================================= const bool l_changed_chr_id = (m_chr_id != p_cid); m_chr_id = p_cid; + if (l_changed_chr_id) + update_default_iniswap_item(); + QLineEdit *l_current_field = ui_ic_chat_message; if (ui_ooc_chat_message->hasFocus()) l_current_field = ui_ooc_chat_message; const int l_current_cursor_pos = l_current_field->cursorPosition(); const QString l_chr_name = get_current_character(); - { // repopulate ini-swapper - QSignalBlocker b_ini_list(ui_iniswap_dropdown); - ui_iniswap_dropdown->clear(); - - QStringList l_name_list{"Default"}; - const QString l_path = ao_app->get_base_path() + "/characters"; - const QFileInfoList &l_info_list = QDir(l_path).entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); - for (const QFileInfo &i_info : l_info_list) - { - const QString l_name = i_info.fileName(); - if (get_base_character() == l_name) - continue; - if (!file_exists(ao_app->get_character_path(l_name, CHARACTER_CHAR_INI))) - continue; - l_name_list.append(l_name); - } - - QPixmap l_blank_image(64, 64); - l_blank_image.fill(Qt::transparent); - for (int i = 0; i < l_name_list.length(); ++i) - { - const QString &i_name = l_name_list.at(i); - const QString l_real_name = i == 0 ? get_base_character() : i_name; - const QString l_icon_file = ao_app->get_character_path(l_real_name, "char_icon.png"); - ui_iniswap_dropdown->addItem(file_exists(l_icon_file) ? QIcon(l_icon_file) : QIcon(l_blank_image), i_name); - } - ui_iniswap_dropdown->setCurrentText(get_current_character()); - } if (is_spectating()) { @@ -207,6 +188,9 @@ void Courtroom::enter_courtroom(int p_cid) const bool l_changed_chr = l_chr_name != get_current_character(); QString l_current_chr = l_chr_name; + if (l_changed_chr) + set_character_position(ao_app->get_char_side(l_chr_name)); + const int l_prev_emote_count = m_emote_list.count(); m_emote_list = ao_app->get_emote_list(l_current_chr); @@ -225,16 +209,7 @@ void Courtroom::enter_courtroom(int p_cid) } set_emote_page(); - // Refresh character position. If the character was changed, use the new - // position, otherwise use the old one. Even if the else is useless now (it - // can be omitted), I am keeping it in case we expand set_character_position - // to do more. - if (l_changed_chr_id) - set_character_position(ao_app->get_char_side(l_chr_name)); - else - set_character_position(ui_pos_dropdown->currentText()); - - update_sfx_list(); + load_character_sfx_list(); select_default_sfx(); ui_emotes->setHidden(is_spectating()); @@ -249,11 +224,13 @@ void Courtroom::enter_courtroom(int p_cid) const QString l_showname = ao_config->showname(); if (!l_showname.isEmpty() && !is_first_showname_sent) send_showname_packet(l_showname); + + ui_char_select_background->hide(); } void Courtroom::done_received() { - m_chr_id = -1; + m_chr_id = SpectatorId; suppress_audio(true); @@ -469,7 +446,7 @@ QString Courtroom::current_sfx_file() return l_file == m_sfx_default_file ? get_current_emote().sound_file : l_file; } -void Courtroom::update_sfx_list() +void Courtroom::load_character_sfx_list() { // colors m_sfx_color_found = ao_app->get_color("found_song_color", COURTROOM_DESIGN_INI); @@ -528,7 +505,7 @@ void Courtroom::clear_sfx_selection() void Courtroom::on_sfx_search_editing_finished() { - update_sfx_list(); + load_character_sfx_list(); } void Courtroom::on_sfx_widget_list_row_changed() @@ -672,7 +649,7 @@ void Courtroom::on_showname_changed(QString p_showname) bool Courtroom::is_spectating() { - return m_chr_id == -1; + return m_chr_id == SpectatorId; } void Courtroom::on_showname_placeholder_changed(QString p_showname_placeholder) @@ -836,7 +813,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) int f_char_id = m_chatmessage[CMChrId].toInt(); - if (f_char_id == -1) + if (f_char_id == SpectatorId) { is_system_speaking = true; m_chatmessage[CMChrId] = "0"; @@ -1703,16 +1680,9 @@ void Courtroom::set_text_color() m_message_color.setNamedColor(color_code); } -void Courtroom::set_ip_list(QString p_list) -{ - QString f_list = p_list.replace("|", ":").replace("*", "\n"); - - ui_ooc_chatlog->append(f_list); -} - -void Courtroom::set_mute(bool p_muted, int p_cid) +void Courtroom::set_muted(bool p_muted, int p_cid) { - if (p_cid != m_chr_id && p_cid != -1) + if (p_cid != m_chr_id && p_cid != SpectatorId) return; if (p_muted) @@ -1732,7 +1702,7 @@ void Courtroom::set_mute(bool p_muted, int p_cid) void Courtroom::set_ban(int p_cid) { - if (p_cid != m_chr_id && p_cid != -1) + if (p_cid != m_chr_id && p_cid != SpectatorId) return; call_notice("You have been banned."); @@ -2355,15 +2325,10 @@ void Courtroom::on_app_reload_theme_requested() return; } - // Otherwise carry on - load_shouts(); - load_effects(); - load_wtce(); - load_free_blocks(); + setup_courtroom(); // to update status on the background set_background(current_background); - enter_courtroom(m_chr_id); } void Courtroom::on_back_to_lobby_clicked() diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index 8fe4a15e1..9edf8cd55 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -1,12 +1,28 @@ #include "courtroom.h" +#include "aoapplication.h" #include "aoconfig.h" +#include "commondefs.h" +#include "file_functions.h" + +#include +#include +#include +#include int Courtroom::get_character_id() { return m_chr_id; } +void Courtroom::set_character_id(const int p_chr_id) +{ + if (m_chr_id == p_chr_id) + return; + m_chr_id = p_chr_id; + Q_EMIT character_id_changed(m_chr_id); +} + QString Courtroom::get_base_character() { return is_spectating() ? nullptr : m_chr_list.at(m_chr_id).name; @@ -16,3 +32,51 @@ QString Courtroom::get_current_character() { return ao_config->character_ini(get_base_character()); } + +namespace +{ +void drSetItemIcon(QComboBox *p_widget, const int p_index, const QString &p_chr_name, AOApplication *ao_app) +{ + static const QIcon s_blank_icon = []() -> QIcon { + QPixmap l_blank_texture(64, 64); + l_blank_texture.fill(Qt::transparent); + return QIcon(l_blank_texture); + }(); + + const QString l_icon_file = ao_app->get_character_path(p_chr_name, "char_icon.png"); + p_widget->setItemIcon(p_index, file_exists(l_icon_file) ? QIcon(l_icon_file) : s_blank_icon); +} +} // namespace + +void Courtroom::update_iniswap_list() +{ + QSignalBlocker b_ini_list(ui_iniswap_dropdown); + ui_iniswap_dropdown->clear(); + + QStringList l_name_list{"Default"}; + const QString l_path = ao_app->get_base_path() + "/characters"; + const QFileInfoList l_info_list = QDir(l_path).entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); + for (const QFileInfo &i_info : l_info_list) + { + const QString l_name = i_info.fileName(); + if (!file_exists(ao_app->get_character_path(l_name, CHARACTER_CHAR_INI))) + continue; + l_name_list.append(l_name); + } + + for (int i = 0; i < l_name_list.length(); ++i) + { + const QString &i_name = l_name_list.at(i); + ui_iniswap_dropdown->addItem(i_name); + if (i == 0) + continue; + drSetItemIcon(ui_iniswap_dropdown, i, i_name, ao_app); + } + update_default_iniswap_item(); + ui_iniswap_dropdown->setCurrentText(get_current_character()); +} + +void Courtroom::update_default_iniswap_item() +{ + drSetItemIcon(ui_iniswap_dropdown, 0, get_base_character(), ao_app); +} diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 6c76cc68e..89a424e1f 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -825,7 +825,7 @@ void Courtroom::set_widgets() // behavior will occur if the button is hidden due to 'config_panel' not being // found in courtroom_design.ini This is to assist with people who switch to // incompatible and/or smaller themes and have the button disappear - if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || !ui_config_panel->isVisible()) + if (ui_config_panel->x() > width() || ui_config_panel->y() > height()) { ui_config_panel->setVisible(true); ui_config_panel->move(0, 0); From 840a19e05025f3c2ec9616a7e04117cb718fa125 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 16 Jun 2021 15:49:11 +0200 Subject: [PATCH 445/842] Remove needless client assumption and remove unused parameter --- include/courtroom.h | 2 +- src/charselect.cpp | 11 ++--------- src/courtroom.cpp | 3 +-- src/courtroom_widgets.cpp | 2 +- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index f7b34711a..2eff2f713 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -575,7 +575,7 @@ private slots: void on_showname_changed(QString); void on_showname_placeholder_changed(QString); - void on_character_ini_changed(QString); + void on_character_ini_changed(); void on_ic_showname_editing_finished(); void on_ic_message_return_pressed(); void on_chat_config_changed(); diff --git a/src/charselect.cpp b/src/charselect.cpp index 6b8d4905c..df28e47cc 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -168,15 +168,8 @@ void Courtroom::char_clicked(int n_char) return; } - if (n_real_char == m_chr_id) - { - enter_courtroom(m_chr_id); - } - else - { - ao_app->send_server_packet( - DRPacket("CC", {QString::number(ao_app->get_client_id()), QString::number(n_real_char), get_hdid()})); - } + ao_app->send_server_packet( + DRPacket("CC", {QString::number(ao_app->get_client_id()), QString::number(n_real_char), get_hdid()})); } void Courtroom::char_mouse_entered(AOCharButton *p_caller) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 4ea7f9706..9a59c8a3a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -659,9 +659,8 @@ void Courtroom::on_showname_placeholder_changed(QString p_showname_placeholder) ui_ic_chat_showname->setToolTip(l_showname); } -void Courtroom::on_character_ini_changed(QString p_base_chr) +void Courtroom::on_character_ini_changed() { - Q_UNUSED(p_base_chr) enter_courtroom(m_chr_id); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 89a424e1f..cffb587d1 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -270,7 +270,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(showname_changed(QString)), this, SLOT(on_showname_changed(QString))); connect(ao_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); - connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(on_character_ini_changed(QString))); + connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(on_character_ini_changed())); connect(ui_ic_chat_showname, SIGNAL(editingFinished()), this, SLOT(on_ic_showname_editing_finished())); connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ic_message_return_pressed())); From 9bc947bfa254e74f10bb6c2dba4de03ee537663e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 16 Jun 2021 18:09:42 +0200 Subject: [PATCH 446/842] Deploy compiler libraries alongside Qt's --- .github/workflows/build-all.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index e26799ac1..4deb886f8 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -108,7 +108,7 @@ jobs: working-directory: "${{env.parentworkspace}}/Danganronpa Online" shell: bash run: | - windeployqt.exe --quick . + windeployqt.exe --quick --compiler-runtime . cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . cp ../bass.dll . cp ../mingw73_32/plugins/imageformats/qapng.dll ./imageformats/ From 4a973e671d2a46cc49ce039d6a9854b774a8d6aa Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Wed, 16 Jun 2021 12:54:58 -0400 Subject: [PATCH 447/842] Add missing libraries --- .github/workflows/build-all.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index e26799ac1..68444d5fb 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -112,6 +112,9 @@ jobs: cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . cp ../bass.dll . cp ../mingw73_32/plugins/imageformats/qapng.dll ./imageformats/ + cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libgcc_s_dw2-1.dll . + cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libwinpthread-1.dll . + cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libstdc++-6.dll . - name: Upload Artifact uses: actions/upload-artifact@v2 From 7675f3d99f2cdeff1a6730c20d895ffd4ea95266 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 16 Jun 2021 19:36:08 +0200 Subject: [PATCH 448/842] Revert "Separated widget/character loading" This reverts commit 4aa82175554b8cc6a0f83c5dc1e92e6f3ad6088d. --- include/courtroom.h | 35 ++-------- src/aoapplication.cpp | 1 + src/aoconfigpanel.cpp | 7 +- src/courtroom.cpp | 123 +++++++++++++++++++++++------------- src/courtroom_character.cpp | 64 ------------------- src/courtroom_widgets.cpp | 2 +- 6 files changed, 89 insertions(+), 143 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 0cf8c8648..24434cb69 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -103,28 +103,20 @@ class Courtroom : public QMainWindow // sets text color based on text color in chatmessage void set_text_color(); - // disables chat if current cid matches second argument - // enables if p_muted is false - void set_muted(bool p_muted, int p_cid); - // send a message that the player is banned and quits the server void set_ban(int p_cid); // implementations in path_functions.cpp QString get_background_path(QString p_file); -public: + // cid = character id, returns the cid of the currently selected character + int get_character_id(); QString get_base_character(); QString get_current_character(); - void update_iniswap_list(); - void update_default_iniswap_item(); // Set the showname of the client void set_showname(QString p_showname); - // sets up widgets - void setup_courtroom(); - // properly sets up some varibles: resets user state void enter_courtroom(int p_cid); @@ -135,7 +127,7 @@ class Courtroom : public QMainWindow void list_areas(); QString current_sfx_file(); - void load_character_sfx_list(); + void update_sfx_list(); void update_sfx_widget_list(); void select_default_sfx(); void clear_sfx_selection(); @@ -291,6 +283,8 @@ class Courtroom : public QMainWindow // ticking done int text_state = 2; + // character id, which index of the char_list the player is + int m_chr_id = -1; // if enabled, disable showing our own sprites when we talk in ic bool m_msg_is_first_person = false; @@ -689,25 +683,6 @@ private slots: void ping_server(); - // character - // =========================================================================== -public: - using CharacterId = int; - enum : CharacterId - { - SpectatorId = -1, - }; - - int get_character_id(); -public slots: - void set_character_id(const int); -signals: - void character_id_changed(int); - -private: - // character id, which index of the char_list the player is - CharacterId m_chr_id = SpectatorId; - /*! * ============================================================================= * AUDIO SYSTEM diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index aecda6046..b83c9b6cd 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -37,6 +37,7 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(ao_config, SIGNAL(timeofday_changed(QString)), this, SLOT(handle_theme_modification())); connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(handle_theme_modification())); + connect(this, SIGNAL(reload_theme()), ao_config_panel, SLOT(handle_theme_modification())); ao_config_panel->hide(); dr_discord->set_presence(ao_config->discord_presence()); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 11d1b9c34..4e6092ac5 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -127,8 +127,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(log_format_use_newline_changed(bool)), ui_log_format_use_newline, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_display_empty_messages_changed(bool)), ui_log_display_empty_messages, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_display_music_switch_changed(bool)), ui_log_display_music_switch, - SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_music_switch_changed(bool)), ui_log_display_music_switch, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); connect(m_config, SIGNAL(log_is_recording_changed(bool)), ui_log_is_recording, SLOT(setChecked(bool))); @@ -141,8 +140,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(effect_ignore_suppression_changed(bool)), ui_effect_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(music_volume_changed(int)), ui_music, SLOT(setValue(int))); - connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), ui_music_ignore_suppression, - SLOT(setChecked(bool))); + connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), ui_music_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(blip_volume_changed(int)), ui_blip, SLOT(setValue(int))); connect(m_config, SIGNAL(blip_ignore_suppression_changed(bool)), ui_blip_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(blip_rate_changed(int)), ui_blip_rate, SLOT(setValue(int))); @@ -406,6 +404,7 @@ void AOConfigPanel::update_audio_device_list() void AOConfigPanel::on_reload_theme_clicked() { + qDebug() << "reload theme clicked"; Q_EMIT reload_theme(); } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 79f983a2f..618c626e1 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -61,7 +61,6 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() set_widgets(); set_char_select(); set_widget_names(); - setup_courtroom(); } Courtroom::~Courtroom() @@ -86,18 +85,30 @@ void Courtroom::set_music_list(QStringList p_music_list) list_music(); } -void Courtroom::setup_courtroom() +void Courtroom::enter_courtroom(int p_cid) { - load_shouts(); - load_effects(); - load_wtce(); - load_free_blocks(); + qDebug() << "enter_courtroom"; - // setup chat - update_ic_log(true); + // unmute audio + suppress_audio(false); + + const int l_prev_emote_id = m_emote_id; + const int l_prev_emote_page = m_current_emote_page; + // widgets =================================================================== current_evidence_page = 0; current_evidence = 0; + + m_shout_state = 0; + m_shout_current = 0; + m_effect_state = 0; + m_effect_current = 0; + m_wtce_current = 0; + reset_wtce_buttons(); + + // setup chat + on_chat_config_changed(); + set_evidence_page(); // Update widgets first, then check if everything is valid @@ -107,22 +118,14 @@ void Courtroom::setup_courtroom() set_widgets(); - update_iniswap_list(); - - m_shout_state = 0; - m_shout_current = 0; check_shouts(); if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) cycle_shout(1); - m_effect_state = 0; - m_effect_current = 0; check_effects(); if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) cycle_effect(1); - m_wtce_current = 0; - reset_wtce_buttons(); check_wtce(); if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) cycle_wtce(1); @@ -141,31 +144,47 @@ void Courtroom::setup_courtroom() for (AOTimer *i_timer : qAsConst(ui_timers)) i_timer->redraw(); -} - -void Courtroom::enter_courtroom(int p_cid) -{ - qDebug() << "enter_courtroom"; - - // unmute audio - suppress_audio(false); - const int l_prev_emote_id = m_emote_id; - const int l_prev_emote_page = m_current_emote_page; + ui_char_select_background->hide(); // character ================================================================= const bool l_changed_chr_id = (m_chr_id != p_cid); m_chr_id = p_cid; - if (l_changed_chr_id) - update_default_iniswap_item(); - QLineEdit *l_current_field = ui_ic_chat_message; if (ui_ooc_chat_message->hasFocus()) l_current_field = ui_ooc_chat_message; const int l_current_cursor_pos = l_current_field->cursorPosition(); const QString l_chr_name = get_current_character(); + { // repopulate ini-swapper + QSignalBlocker b_ini_list(ui_iniswap_dropdown); + ui_iniswap_dropdown->clear(); + + QStringList l_name_list{"Default"}; + const QString l_path = ao_app->get_base_path() + "/characters"; + const QFileInfoList &l_info_list = QDir(l_path).entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); + for (const QFileInfo &i_info : l_info_list) + { + const QString l_name = i_info.fileName(); + if (get_base_character() == l_name) + continue; + if (!file_exists(ao_app->get_character_path(l_name, CHARACTER_CHAR_INI))) + continue; + l_name_list.append(l_name); + } + + QPixmap l_blank_image(64, 64); + l_blank_image.fill(Qt::transparent); + for (int i = 0; i < l_name_list.length(); ++i) + { + const QString &i_name = l_name_list.at(i); + const QString l_real_name = i == 0 ? get_base_character() : i_name; + const QString l_icon_file = ao_app->get_character_path(l_real_name, "char_icon.png"); + ui_iniswap_dropdown->addItem(file_exists(l_icon_file) ? QIcon(l_icon_file) : QIcon(l_blank_image), i_name); + } + ui_iniswap_dropdown->setCurrentText(get_current_character()); + } if (is_spectating()) { @@ -188,9 +207,6 @@ void Courtroom::enter_courtroom(int p_cid) const bool l_changed_chr = l_chr_name != get_current_character(); QString l_current_chr = l_chr_name; - if (l_changed_chr) - set_character_position(ao_app->get_char_side(l_chr_name)); - const int l_prev_emote_count = m_emote_list.count(); m_emote_list = ao_app->get_emote_list(l_current_chr); @@ -209,7 +225,16 @@ void Courtroom::enter_courtroom(int p_cid) } set_emote_page(); - load_character_sfx_list(); + // Refresh character position. If the character was changed, use the new + // position, otherwise use the old one. Even if the else is useless now (it + // can be omitted), I am keeping it in case we expand set_character_position + // to do more. + if (l_changed_chr_id) + set_character_position(ao_app->get_char_side(l_chr_name)); + else + set_character_position(ui_pos_dropdown->currentText()); + + update_sfx_list(); select_default_sfx(); ui_emotes->setHidden(is_spectating()); @@ -224,13 +249,11 @@ void Courtroom::enter_courtroom(int p_cid) const QString l_showname = ao_config->showname(); if (!l_showname.isEmpty() && !is_first_showname_sent) send_showname_packet(l_showname); - - ui_char_select_background->hide(); } void Courtroom::done_received() { - m_chr_id = SpectatorId; + m_chr_id = -1; suppress_audio(true); @@ -446,7 +469,7 @@ QString Courtroom::current_sfx_file() return l_file == m_sfx_default_file ? get_current_emote().sound_file : l_file; } -void Courtroom::load_character_sfx_list() +void Courtroom::update_sfx_list() { // colors m_sfx_color_found = ao_app->get_color("found_song_color", COURTROOM_DESIGN_INI); @@ -505,7 +528,7 @@ void Courtroom::clear_sfx_selection() void Courtroom::on_sfx_search_editing_finished() { - load_character_sfx_list(); + update_sfx_list(); } void Courtroom::on_sfx_widget_list_row_changed() @@ -649,7 +672,7 @@ void Courtroom::on_showname_changed(QString p_showname) bool Courtroom::is_spectating() { - return m_chr_id == SpectatorId; + return m_chr_id == -1; } void Courtroom::on_showname_placeholder_changed(QString p_showname_placeholder) @@ -813,7 +836,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) int f_char_id = m_chatmessage[CMChrId].toInt(); - if (f_char_id == SpectatorId) + if (f_char_id == -1) { is_system_speaking = true; m_chatmessage[CMChrId] = "0"; @@ -1696,9 +1719,16 @@ void Courtroom::set_text_color() m_message_color.setNamedColor(color_code); } -void Courtroom::set_muted(bool p_muted, int p_cid) +void Courtroom::set_ip_list(QString p_list) +{ + QString f_list = p_list.replace("|", ":").replace("*", "\n"); + + ui_ooc_chatlog->append(f_list); +} + +void Courtroom::set_mute(bool p_muted, int p_cid) { - if (p_cid != m_chr_id && p_cid != SpectatorId) + if (p_cid != m_chr_id && p_cid != -1) return; if (p_muted) @@ -1718,7 +1748,7 @@ void Courtroom::set_muted(bool p_muted, int p_cid) void Courtroom::set_ban(int p_cid) { - if (p_cid != m_chr_id && p_cid != SpectatorId) + if (p_cid != m_chr_id && p_cid != -1) return; call_notice("You have been banned."); @@ -2341,10 +2371,15 @@ void Courtroom::on_app_reload_theme_requested() return; } - setup_courtroom(); + // Otherwise carry on + load_shouts(); + load_effects(); + load_wtce(); + load_free_blocks(); // to update status on the background set_background(current_background); + enter_courtroom(m_chr_id); } void Courtroom::on_back_to_lobby_clicked() diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index 9edf8cd55..8fe4a15e1 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -1,28 +1,12 @@ #include "courtroom.h" -#include "aoapplication.h" #include "aoconfig.h" -#include "commondefs.h" -#include "file_functions.h" - -#include -#include -#include -#include int Courtroom::get_character_id() { return m_chr_id; } -void Courtroom::set_character_id(const int p_chr_id) -{ - if (m_chr_id == p_chr_id) - return; - m_chr_id = p_chr_id; - Q_EMIT character_id_changed(m_chr_id); -} - QString Courtroom::get_base_character() { return is_spectating() ? nullptr : m_chr_list.at(m_chr_id).name; @@ -32,51 +16,3 @@ QString Courtroom::get_current_character() { return ao_config->character_ini(get_base_character()); } - -namespace -{ -void drSetItemIcon(QComboBox *p_widget, const int p_index, const QString &p_chr_name, AOApplication *ao_app) -{ - static const QIcon s_blank_icon = []() -> QIcon { - QPixmap l_blank_texture(64, 64); - l_blank_texture.fill(Qt::transparent); - return QIcon(l_blank_texture); - }(); - - const QString l_icon_file = ao_app->get_character_path(p_chr_name, "char_icon.png"); - p_widget->setItemIcon(p_index, file_exists(l_icon_file) ? QIcon(l_icon_file) : s_blank_icon); -} -} // namespace - -void Courtroom::update_iniswap_list() -{ - QSignalBlocker b_ini_list(ui_iniswap_dropdown); - ui_iniswap_dropdown->clear(); - - QStringList l_name_list{"Default"}; - const QString l_path = ao_app->get_base_path() + "/characters"; - const QFileInfoList l_info_list = QDir(l_path).entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); - for (const QFileInfo &i_info : l_info_list) - { - const QString l_name = i_info.fileName(); - if (!file_exists(ao_app->get_character_path(l_name, CHARACTER_CHAR_INI))) - continue; - l_name_list.append(l_name); - } - - for (int i = 0; i < l_name_list.length(); ++i) - { - const QString &i_name = l_name_list.at(i); - ui_iniswap_dropdown->addItem(i_name); - if (i == 0) - continue; - drSetItemIcon(ui_iniswap_dropdown, i, i_name, ao_app); - } - update_default_iniswap_item(); - ui_iniswap_dropdown->setCurrentText(get_current_character()); -} - -void Courtroom::update_default_iniswap_item() -{ - drSetItemIcon(ui_iniswap_dropdown, 0, get_base_character(), ao_app); -} diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 8e101b8cb..3993b2717 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -825,7 +825,7 @@ void Courtroom::set_widgets() // behavior will occur if the button is hidden due to 'config_panel' not being // found in courtroom_design.ini This is to assist with people who switch to // incompatible and/or smaller themes and have the button disappear - if (ui_config_panel->x() > width() || ui_config_panel->y() > height()) + if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || !ui_config_panel->isVisible()) { ui_config_panel->setVisible(true); ui_config_panel->move(0, 0); From 89c1d2c4639c3aedd78b218c6cabf51381d9f79b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 16 Jun 2021 19:36:12 +0200 Subject: [PATCH 449/842] Revert "#190 (start)" This reverts commit ac63c037ccb476bf486932495735fd14d002df46. --- dronline-client.pro | 1 - include/courtroom.h | 23 +++++++++++ src/courtroom.cpp | 76 +++++++++++++++++++++++++++++++++++-- src/courtroom_character.cpp | 18 --------- src/courtroom_widgets.cpp | 42 ++++++++++++++++++++ src/server_socket.cpp | 15 ++++++++ 6 files changed, 152 insertions(+), 23 deletions(-) delete mode 100644 src/courtroom_character.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 6b825cba2..4d40c8035 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -99,7 +99,6 @@ SOURCES += \ src/charselect.cpp \ src/commondefs.cpp \ src/courtroom.cpp \ - src/courtroom_character.cpp \ src/courtroom_widgets.cpp \ src/datatypes.cpp \ src/debug_functions.cpp \ diff --git a/include/courtroom.h b/include/courtroom.h index 24434cb69..ca535429a 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -97,12 +97,23 @@ class Courtroom : public QMainWindow // called when a DONE#% from the server was received void done_received(); + // sets the local mute list based on characters available on the server + void set_mute_list(); + // sets desk and bg based on pos in chatmessage void set_scene(); // sets text color based on text color in chatmessage void set_text_color(); + // takes in serverD-formatted IP list as prints a converted version to server + // OOC admittedly poorly named + void set_ip_list(QString p_list); + + // disables chat if current cid matches second argument + // enables if p_muted is false + void set_mute(bool p_muted, int p_cid); + // send a message that the player is banned and quits the server void set_ban(int p_cid); @@ -274,6 +285,11 @@ class Courtroom : public QMainWindow QString m_message_color_name; QStack m_message_color_stack; + // char id, muted or not + QMap mute_map; + + // QVector muted_cids; + bool is_client_muted = false; // state of animation, 0 = objecting, 1 = preanim, 2 = talking, 3 = idle @@ -380,6 +396,7 @@ class Courtroom : public QMainWindow AOTextArea *ui_ooc_chatlog = nullptr; + QListWidget *ui_mute_list = nullptr; QListWidget *ui_area_list = nullptr; QListWidget *ui_music_list = nullptr; @@ -473,6 +490,8 @@ class Courtroom : public QMainWindow AOButton *ui_effect_flash = nullptr; AOButton *ui_effect_gloom = nullptr; + AOButton *ui_mute = nullptr; + AOButton *ui_defense_plus = nullptr; AOButton *ui_defense_minus = nullptr; @@ -569,6 +588,8 @@ private slots: void next_chat_letter(); void post_chat(); + void on_mute_list_item_changed(QListWidgetItem *p_item); + void on_showname_changed(QString); void on_showname_placeholder_changed(QString); void on_character_ini_changed(QString); @@ -637,6 +658,8 @@ private slots: void on_effect_button_clicked(const bool); void on_effect_button_toggled(const bool); + void on_mute_clicked(); + void on_defense_minus_clicked(); void on_defense_plus_clicked(); void on_prosecution_minus_clicked(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 618c626e1..3d2f7f418 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -259,6 +259,8 @@ void Courtroom::done_received() set_char_select_page(); + set_mute_list(); + set_char_select(); show(); @@ -848,6 +850,9 @@ void Courtroom::handle_chatmessage(QStringList p_contents) if (f_char_id < 0 || f_char_id >= m_chr_list.size()) return; + if (mute_map.value(m_chatmessage[CMChrId].toInt())) + return; + const QString l_message = QString(m_chatmessage[CMMessage]) .remove(QRegularExpression("(?destruct_courtroom(); } +int Courtroom::get_character_id() +{ + return m_chr_id; +} + +QString Courtroom::get_base_character() +{ + return is_spectating() ? nullptr : m_chr_list.at(m_chr_id).name; +} + +QString Courtroom::get_current_character() +{ + return ao_config->character_ini(get_base_character()); +} + void Courtroom::handle_song(QStringList p_contents) { if (p_contents.size() < 2) @@ -1808,10 +1828,13 @@ void Courtroom::handle_song(QStringList p_contents) str_char = f_showname; } - append_ic_text(str_char, "has played a song: " + f_song, false, true, l_chr_id == m_chr_id); - if (ao_config->log_is_recording_enabled()) - save_textlog(str_char + " has played a song: " + f_song); - m_music_player->play(f_song); + if (!mute_map.value(l_chr_id)) + { + append_ic_text(str_char, "has played a song: " + f_song, false, true, l_chr_id == m_chr_id); + if (ao_config->log_is_recording_enabled()) + save_textlog(str_char + " has played a song: " + f_song); + m_music_player->play(f_song); + } } int pos = f_song.lastIndexOf(QChar('.')); @@ -2050,6 +2073,32 @@ void Courtroom::on_pos_dropdown_changed(int p_index) // ao_app->send_server_packet(DRPacket("SP", {f_pos})); } +void Courtroom::on_mute_list_item_changed(QListWidgetItem *p_item) +{ + int f_cid = -1; + + for (int n_char = 0; n_char < m_chr_list.size(); n_char++) + { + if (m_chr_list.at(n_char).name == p_item->text()) + f_cid = n_char; + } + + if (f_cid < 0 || f_cid >= m_chr_list.size()) + { + qDebug() << "W: " << p_item->text() << " not present in char_list"; + return; + } + + if (Qt::CheckState::Checked == p_item->checkState()) + { + mute_map.insert(f_cid, true); + } + else + { + mute_map.insert(f_cid, false); + } +} + void Courtroom::on_music_list_clicked() { ui_ic_chat_message->setFocus(); @@ -2254,6 +2303,20 @@ void Courtroom::on_effect_button_toggled(const bool p_checked) l_button->setText(l_name); } +void Courtroom::on_mute_clicked() +{ + if (ui_mute_list->isHidden()) + { + ui_mute_list->show(); + ui_mute->set_image("mute_pressed.png"); + } + else + { + ui_mute_list->hide(); + ui_mute->set_image("mute.png"); + } +} + void Courtroom::on_defense_minus_clicked() { int f_state = defense_bar_state - 1; @@ -2408,6 +2471,11 @@ void Courtroom::on_char_select_right_clicked() void Courtroom::on_spectator_clicked() { ao_app->send_server_packet(DRPacket("CC", {QString::number(ao_app->get_client_id()), "-1", get_hdid()})); + enter_courtroom(-1); + + ui_emotes->hide(); + + ui_char_select_background->hide(); } void Courtroom::on_call_mod_clicked() diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp deleted file mode 100644 index 8fe4a15e1..000000000 --- a/src/courtroom_character.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "courtroom.h" - -#include "aoconfig.h" - -int Courtroom::get_character_id() -{ - return m_chr_id; -} - -QString Courtroom::get_base_character() -{ - return is_spectating() ? nullptr : m_chr_list.at(m_chr_id).name; -} - -QString Courtroom::get_current_character() -{ - return ao_config->character_ini(get_base_character()); -} diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 3993b2717..12ed70c34 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -117,6 +117,7 @@ void Courtroom::create_widgets() ui_ooc_chatlog->setReadOnly(true); ui_ooc_chatlog->setOpenExternalLinks(true); + ui_mute_list = new QListWidget(this); ui_area_list = new QListWidget(this); ui_music_list = new QListWidget(this); ui_sfx_list = new QListWidget(this); @@ -210,6 +211,8 @@ void Courtroom::create_widgets() ui_checks.push_back(ui_flip); ui_checks.push_back(ui_hidden); + ui_mute = new AOButton(this, ao_app); + ui_defense_plus = new AOButton(this, ao_app); ui_defense_minus = new AOButton(this, ao_app); @@ -267,6 +270,9 @@ void Courtroom::connect_widgets() connect(ui_iniswap_dropdown, SIGNAL(activated(int)), this, SLOT(on_iniswap_dropdown_changed(int))); connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); + connect(ui_mute_list, SIGNAL(itemChanged(QListWidgetItem *)), this, + SLOT(on_mute_list_item_changed(QListWidgetItem *))); + connect(ao_config, SIGNAL(showname_changed(QString)), this, SLOT(on_showname_changed(QString))); connect(ao_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); @@ -294,6 +300,8 @@ void Courtroom::connect_widgets() connect(ui_wtce_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_wtce_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + connect(ui_mute, SIGNAL(clicked()), this, SLOT(on_mute_clicked())); + connect(ui_defense_minus, SIGNAL(clicked()), this, SLOT(on_defense_minus_clicked())); connect(ui_defense_plus, SIGNAL(clicked()), this, SLOT(on_defense_plus_clicked())); connect(ui_prosecution_minus, SIGNAL(clicked()), this, SLOT(on_prosecution_minus_clicked())); @@ -361,6 +369,7 @@ void Courtroom::reset_widget_names() {"vp_objection", ui_vp_objection}, {"ic_chatlog", ui_ic_chatlog}, {"server_chatlog", ui_ooc_chatlog}, + {"mute_list", ui_mute_list}, {"area_list", ui_area_list}, {"music_list", ui_music_list}, {"sfx_list", ui_sfx_list}, @@ -402,6 +411,7 @@ void Courtroom::reset_widget_names() {"pre", ui_pre}, {"flip", ui_flip}, {"hidden", ui_hidden}, + {"mute_button", ui_mute}, {"defense_plus", ui_defense_plus}, {"defense_minus", ui_defense_minus}, {"prosecution_plus", ui_prosecution_plus}, @@ -620,6 +630,9 @@ void Courtroom::set_widgets() set_size_and_pos(ui_ooc_chatlog, "server_chatlog", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_mute_list, "mute_list", COURTROOM_DESIGN_INI, ao_app); + ui_mute_list->hide(); + set_size_and_pos(ui_music_list, "music_list", COURTROOM_DESIGN_INI, ao_app); set_size_and_pos(ui_area_list, "area_list", COURTROOM_DESIGN_INI, ao_app); if (ui_music_list->isVisible()) @@ -888,6 +901,9 @@ void Courtroom::set_widgets() } } + set_size_and_pos(ui_mute, "mute_button", COURTROOM_DESIGN_INI, ao_app); + ui_mute->set_image("mute.png"); + set_size_and_pos(ui_defense_plus, "defense_plus", COURTROOM_DESIGN_INI, ao_app); ui_defense_plus->set_image("defplus.png"); @@ -1390,6 +1406,7 @@ void Courtroom::set_dropdowns() set_dropdown(ui_emote_dropdown, "[EMOTE DROPDOWN]"); set_dropdown(ui_iniswap_dropdown, "[INISWAP DROPDOWN]"); set_dropdown(ui_pos_dropdown, "[POS DROPDOWN]"); + set_dropdown(ui_mute_list, "[MUTE LIST]"); set_dropdown(ui_ic_chat_message, "[IC LINE]"); set_dropdown(ui_ooc_chat_message, "[OOC LINE]"); } @@ -1413,3 +1430,28 @@ void Courtroom::set_fonts() set_drtextedit_font(i_timer, QString("timer_%1").arg(i), COURTROOM_FONTS_INI, ao_app); } } + +void Courtroom::set_mute_list() +{ + mute_map.clear(); + + // maps which characters are muted based on cid, none are muted by default + for (int n_cid = 0; n_cid < m_chr_list.size(); n_cid++) + { + mute_map.insert(n_cid, false); + } + + QStringList sorted_mute_list; + + for (const char_type &i_char : qAsConst(m_chr_list)) + sorted_mute_list.append(i_char.name); + + sorted_mute_list.sort(); + + for (const QString &i_chr_name : sorted_mute_list) + { + QListWidgetItem *i_item = new QListWidgetItem(i_chr_name, ui_mute_list); + i_item->setFlags(i_item->flags() | Qt::ItemFlag::ItemIsUserCheckable); + i_item->setCheckState(Qt::Unchecked); + } +} diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 3b01ead80..018debcb8 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -338,6 +338,21 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) m_courtroom->set_evidence_list(f_evi_list); } } + else if (l_header == "IL") + { + if (is_courtroom_constructed && l_content.size() > 0) + m_courtroom->set_ip_list(l_content.at(0)); + } + else if (l_header == "MU") + { + if (is_courtroom_constructed && l_content.size() > 0) + m_courtroom->set_mute(true, l_content.at(0).toInt()); + } + else if (l_header == "UM") + { + if (is_courtroom_constructed && l_content.size() > 0) + m_courtroom->set_mute(false, l_content.at(0).toInt()); + } else if (l_header == "KK") { if (is_courtroom_constructed && l_content.size() > 0) From 865f8d65d354ed5c8f4d4d5f598e0e7f5492bcb2 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 16 Jun 2021 22:08:14 +0200 Subject: [PATCH 450/842] Fixed config panel's theme fields * Theme fields now properly update when changed, this include the following fields: `theme`, `gamemode` and `timeofday` --- icon-filled.png | Bin 0 -> 35320 bytes include/aoconfigpanel.h | 3 +++ src/aoconfigpanel.cpp | 30 +++++++++++++++++++++++++++--- 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 icon-filled.png diff --git a/icon-filled.png b/icon-filled.png new file mode 100644 index 0000000000000000000000000000000000000000..40119dddfb6a1ac8122e556832698be22b96e007 GIT binary patch literal 35320 zcmc$`bySqm+ciA&h{T|XGz=w3cQ=SgNjFFg-Q6IJprnF=G=kEhbR!}q-QCg*A>Hrs zSMT$E>v>~+|9q~+VrDpV?sMPgI@j6P-uw9GxvBy_E;%j)0>M{&DyIQ~pn;cY5GXqM zH3F052EXpOJk@uHKEmK%VUO^jHAh(6I*HTmHMG#t+FFX!>GG>^skq1@Y;2$Uxgj+DRJAPp>@9>X=_Dm+ z#e76S2aX62Gg==<2PbzCA91>W^eX~h|NWSgj`kl-JnY5kr2k%!R!`+Qt*o;ff>wZo zi`{~YOMq5Tn1fq@PmrINjh2Uthntg&hm)J1ots;PpGSn7m-b)(=)l$7EUiQ|8o5u(0soK6rT8K?`+N zJl!1radArvPJ{!(5#i+F4%%}6r>%>Pvxl?0jr0Ga>;L@u|8W8EXjN4HbB+IYERK%< zxrDoiycf8Qe?5@@wza#KuM2`x1L5xM>1KhD_X5MD|9dwsBC>7>GY@AsEoWzke^1o& ze-D|KhlhigmRZ-<$ARfe@zyv&IGHi=9VM3(Oifp9nu6D;Kv27uP?V zsyJKPTKWE`DK|Tp06VvU77u7DB*M$h%Ej|Pn}U$BH1ja~KQ^|s5V3N0b2I}tZ0l%d zjo@@~vZkZ`&tnvkb#`!e0~ZFPN}tR74b=+&#>kED(xv;&kBQaM;>fiU{)Xn(-mH5$u-2mb~l&W_-NtX6BaM>;l|; z0(?Te{CvDT0{{NLoU?`J-w^ow{eP|oOJ@tv$NwB2pD>S^kU7GVonHtc#4c#YXU;Cf z&o9i5Kv?A$^Y2wov`K7KA?{=Z@Wk4O@EYU>W>*!N!xNfY7vuU8JXwEqYm5i^Uwi$a{v z;%_t{Eb0FBvF(5JJpQk){IBnO+aN%v|C=cJ=hfYvtvtNV+z`^%;Mx5jqmc7|HF$S3 zumAnf|L>;qpJV#Z(EnFa`~PL=|9UJIHfB!N2#{ep>Hgjf=iglP&q?9@f6v!H{`Oz; z*1wyBWcc^xf3h%m^PfD9Z~|Ssfh;Z7;|z9!uo6W%X)T{LRJx}Ru>!?+YpY|v-Dja# z=r0#{Rp*TJ@4nKox%>Vx@v{$!^);qVU)Xm!Uc#X8w=nOT!>Z)HxL4amO-M|e?y|rO zKJNy9fB(b&#>2;yLRNGepA%RgcoaBiC8%;RrlSvbKTyeoc(}=rut3s{hCFbk9_jM^ z|MKzvf}A?Bej?|w@zvcd&SQL!;hPuHfsIXYq#2_7N(2 z%&Gy^v2AL66_7d4ZGi7AkXmGKCoe4rr5mkV%T!qZxle;BLrO%whFTRy{HSHQ-;vzA zX3@}T9m0aT5K3afBN1~tazX@l3=QN~=ul*7qY zHeJM=^vLJ~X6$tIeN$Ojd+p?8vgV^29|qYGR-@4@e%QvXeyS4t-$2 zPWKPFB`00!dcCic&hs^R_WO;iHBUv*)o~mc^+bxYLChNN6r*=3gbJ&J6ykF50ar2J zxzWl>Ic~Q^e5iBAeX3UH+jViXVIY-)(MRQve0*y`tb}h z&`!5`lZ;RBRwA=c#yQ#ba>!ShSWTw~)}kjnWBXU~4UF>Y9wKvP3jx%mFqb9WnM{|n z70zY}za62Ta=bR9Gb~Qoo1PzY(p}|j&+c{T<&8ZFe|5YwRAQPrnK4)YD&p0JkAEFC z88K-V^RMe$d6Zifkt{YLnb3>Ag>A{}iXOqRxHoFhgcvnvQSqjx{ixf$U=UB?4xL^`!o}s zSraur+1-{Lk6BolAOB)yR(`6yEVN5ETXG-z!rn1!m)CMIq)hT+F`=ya{G>CJis0os z=a%j^tq4|@M0vIlB?0~b;nsrlS|v+P1mWS~vF)ypem^Ihi0{@>!4WT+{uI_L`7wTe zf(Ti4t~M^Q0b@Q{7#xX6e*JYY_7a-0p5b@&>78r@Ti_u|bJO}rWUggSb=nr|0!J~P zG8DG4Cu!E3Z*Qf%O)=Au92WKLSsb-!hR=@A*5=WVW%G)58R#dL$XwgVdOmKf$m|V| zY15YTY-44qK8tF-dHw5yf#9p|LAo$I%57%9$jjiwhfO z$hVceWS92|_9Q>PeV49jtcd}AL7I|lo31l6+TJb9)$tJ)&E5JDCe^<{BL#tDg7L#b zVTHG^1_SjqhqM3BkkkJ(&ZU;U5?rT@{EO-~;d zhK_M{?P0(^;EoBA2iX)3nM8SEu|lLmzxfe`oB70s!RT}{WPPozEiat5t00oGNwKm?$!1=6q(v4B80x4xc_eia_&XeLl8>!MjqR2ASdVcA zU$q~mZC_up*4{gXkXoe9?e1;+Z^ul`1+CyyQ+I@ST3W3a&(-=Ko={=EYLCf%ZYIN^ z7|-xVrfa6B$X+X{`CF5Pf4f|GgfA&RZpR00ZN@LF=U71?; zVW($psL$1D#cktaYm>r6C|c=P>kd#`Ji((7H}}!9l#7t;a}T&ZEORcq(R=qG@mlaa zx=aY|>l8|?)0o#{8pC^mjB@npcje`8D=oKGUY!|%o=o=#T#wV9ky@y~x6~is_MFSk z3CE-4@{tNidhG%=GjnyFPL3WO*-V}i2KT^Jp-(J_{ZEQi(P?|iX(Kf|;`}eVJHcYw zZc0r~Kb#U=aGb9O%Qxo9ipY_?`aOAWF0L2DliU-%AIXVHUH)iIU$7ezQmGc_7h}TQ z*p$>g+VpQ|avn+K{mi!-z?Oo(E2LE~j2uloLm%5^cufbP9b5>WTzsj?6umbhnwwv1 z*;kK0ZxXk|MTTCx0^|lK>81uZ zE9fsOro_a>G6R0UG<2>loj0uV^fbNQ%gYQh)wJf>tTdSTwC4gPT>;6>sbw?VHRs8A zvhuco<5Z=izqA=QBxGgMFX37;S4|%+WRS09$Og|ZJt;3QVravR&(F_q!_xpHmJ~D? z3@lhLAxcjsO-~g^M`L4zwzgw>w_OEGq>MUo4;OpsBNzk?j!rAfC9>|IL);FBLP9u* zmJ&MS8vHJZ({(^HZ8p6*V`Zlg%ViEXqa=8loo^M{8J?_>8?9-voLT)5hI|I@7ng?E;FHH5899^@#9y9}g@ zH+b9k&^z6cS2qc`j#;?5GAx@gttJS)&4s*bmd?-wzt9m^2vjwhZ=eaPcbsra z$ua03mvP+BU1FUPm^d9r8$)Gk zdnV%k09DnI(QOfctl6ol;k75~v+>ki(UweJld@-gMuQa=K8-qEUq31;65m;E8kx97 z4&{U+SY-49yi~p;0bGDcWtwn%&%2FunaOHus0ALi>vn3Y^6;tfM$cIZk{;C<1*4k0 zXUAht_b@H4hW~t!yuHbj4oP#k*m&UsOoO)ZTmFlsWWMmlfEcfqxILYg5aOJOSGmH`$?hfs z(lAT;znfFDs8cGR(@rbBg2dHT5x@|I!EJU`@=uWBCyUp*YN^w^rf3#d%jFMRO3!;- zhAQZ1ms*%0BvD^XjTR6}BwlZX25_`A461iZylYU?eeP+R1++*#$7)FMt%CKOXyBip z7j4P&5@549#>^L_9b^2` zkgtOtY8B@nDiZ=#Jh!3>uva=lvK)iZ&JEaK-HCXhJr4G2`2rAA5qmAx8(C#X7lr$%G?&Hy{{JZ?tBjlVftxTWg;H96*bU5vDwcn z<2?7x{#>*uQmN;Nqf8VNP1>W4pO>cYc7C|L+*>C&&4uqxuI+A`$>%3sSUA|xG`xNt zzu&k9nscJ)IC}o1cv47Q|4IE3AMe-TB`UvZc|H|gxvdnL#yQT@E6@_cFAaYboq>sN zBFu^%O8c~2%wsS_I8OZD#@_AHy6H__5Pj>%6``oHE!67qS=i}9&kR8BOj(szf3CXD$5FMhuXYHm(iWaXDu)SsH631|#DB*_rv z+4Le3^>L>D(K3zw#DUnJmOo`Egec&w+2163D{Q zwxK5bBh5I8quJR!?Do@^S+NxCrl3D(-%K(x?kL1#Dt_Lq;0b|zf(UJ(63WJ6i%TsB zzJQ!pKUu8%f#j5XLc823L-moY-2ESed@>tnZxT|g9rvc1W*Q$+Tj&cMOu9xtDA7sq zVzQX&p97T_id02z4=p`iRQfS}Hi^rT68Uqz{RQuab=XhObKd!JmQhL^d@0UJh4)&`CT3i4)?90qy z1~FEXSubb2QQXOWrE7Cawdhw-+O*IRSr{I6TWVR8n8Uu*M9sL<^wl(l^NfR6-jtjU zu{6x7?q=HBO0wB;BICne;B#Zmm9#k&0ZwPRpo#w&0C{5p=$~0bX|=Q`v~+ZnKIo2) zj)KjSsbXr$CBK`4Pi5Ovgp-hvlXD>Jdx%tUaImDQ_bmGE-{;%!9)%mFw1t(aZzIxN zy_TNPd5zV2d|crVK?V^LT+?MMahDDK!bQh~yr`OL6cbw;@HHQv{Kh(Od_0qB@0%eD zO=eF7+eK6PhVUtkQWOIdRj|W6x?=qFscmIUa1kUwXQ8carRjmv%0QgMB|u2?H;eR< zx#>Zy5we4J{88(Y;$EY#Cv=*xPs3GI4c~5YZZFgf@Ty4&WCcrlOORV!o!2ZpqLNfM zFfq|KcC47fQuhdV{87=i$%8S;(t(iL+Kv1!FJA%xU*q}# z&@k!G%nWq?vZ3cbcljtDy*~-fMHTOv@XZRxP%!o@X2>5swDle1iN3UEm-$&#!zrj2 zsT$foEeTbOkyDY?U9CPe_iwN45yybs7__)9^^}c8iPte0?LK}xxAZ&*vDL%@4ULF6 z0P$s{APKiz^`{9qJJ+S`99kE6O;W^PsPwaQuX%IREWom#7l-f>`H{$RXehG`gu3>! zU-zLT&8`=RFw&)O`Z&1Q*xDOf&PI#YkOxasD?Lp~>N|5aEBN?5Pmxf-X# z#6X<;y(CdBnR{3U*iB817sqer>fQ5bUlSh>Xqw;Q7`BP?_lYTDn-I`R>&fb}U}ZH| z@wCPZr;AC1BV}GArhX?B02Eq#+_7ESV7IoEqoCBoisu)I&>l;qYWN`|JKZBQUEwb% zO-x9w`kTWuBDTi>LP=l-z;HG*h$&q%nEa3pU$Nhc=fn_a_n-JLtT(4U&VF-K2P#l* zL$;O9=!=b#4+^nVnTI>sTPX%}!V@3HtnL!v8Qlyo8zTj%Ae(_vO`bqasgtL~C8QPnL&=zrKZWs>KP2(R_dew|ss3wr|F} za1*xQA-86-r#s+lSS*KJJ9Xhzjk2cNlw@gTjDX;~%2s&)tr{F#P zrZ8Y>mEPSoyjdAa>oE1LXQXX1FMa%FE5@=~UZ!xu2hVq`ouey%9zZJE{l+T0Sxo~s zeSUR=&9|SufI*QQ|91Be0>hY(L_hIiQ@ID2FzoB8gTmavIsZ;aPR@Xnwt<#_!zMzT z6rC8tEhnGJMd`f~y=fPHZ|IMApDk`LS5LKR0xynlgUCVY*7htcs98cGA;I14aJpJO z95&3?S6(~^XP{D>|LSSzm|mbeue(0_#Iz z;URul9mxY@PLvJVj@5o7qYKpQZzjjuoE%}z; zYY(sXk`{EEPg}huuT$Qq8Cmlb82rjQyTRXko4dh2<87Sv1q%OJUd*6)=Z%8*?i>v< z>EK%0hqKz|I|>Pr&z^0SSW$N5;Zc*MP#bGcjcQ<_ZJ1Z67PGM~R&8Qqs6b!Qs*K>= zkNy37mDBfxEN2MRlkk*mvXuEumr1LNI2Yd=$0ubIo~U6&TVhGc$n3b9 zQ(cSl{ptpLz~G3o zep_4C=e{WM#XYD zq?W#R@+|UQM4ZleaC}J5w>;H68i|z37ySvwbZs?z(_ye>A&w;*4M*d-{G~RIfYI<* z;V+_3X`a<;p)NQ?(Vp`@oi{zN&x`1ktymYKBv?zEH?Nq<1bkS7bJNuEB4Zi5X@yf$ zMcNw%#n;*Mf!9^@+~NSZyx}EF29%KRS6LV!&z2rhr>r*0Sb6oQ?ggKXU(#eeRe1%; z_ryLOjDnoE@j@oxPQU7v9A*SKtc`XP_GY9eB<-=4 zI)5{8ZazslR})|I+b^$~zur8RGvrqI~< zOE`n^F-Mh<0;4JVU3 z$Ih2@F1~j{+U?SXoO-jZ*Ej+X)B9$;gdRBq0@hyd-EIz5^!={lNoSaQbC=- z*7I71rr2nAZ_j%#M@?sJ)Q11|=VL?1YMi~g?d}Mg=eM)|i-T7WO{Qx-MMK}N*IFgI zA)g~WkH(2S049h#!w3zP(^X~aPe1FQPeoLoW{oH+05sc=Rx&bC}aQAmMXg{Rd0bsbyz!qET;=b zyLPNb6;5s2&P_#21LIE5iLopiw_*_RR_Rl5k%sDOz-IOrR$4_J*C&sd>TwZ5UU&=r zrD-F*bqg{UN%AO>&plOCz$}T^dpe$#susW2^aJ7Y!G>)ev| z2M3Q0t0S`~_lv%f=569*>+hQC&MeF0my3PN7BkFK6C~A(CQA`8EKaRK)$aAr;2iB0 z^;1!HQi-Jj>ftB9p+c1}gW|>2%wF)#N}19wDK!uf^8BEmX2ED{(5B8==uw6N-UxN-d}nwgtP<2Maf6J#j5JrIHV+077Pp zrE1zpuIs1KfFXqk*q;sbj@tF_I`xtC1bWY&EH-)f`I)QwStY9GdEOKwVe0Qq{Flzr zu9t(ZP;n2FN&#`}IH@lK?XyyPAte=Hv0535jv?q^Yq{XNM2mDFP;d&m=`Bbw9DCP3 zFyOtO5eNw9nEnDhxUfi54a14!_~R`kouv`Nm#9pf6@$&tKqrV{^X%?`1Q0FmCB)kE zH_F0JXhg(vIj6{i}A)A-(VQBrqnaabt&d_{{F*bQ9<%4m;?I#V__s^`fg=w zmw7L@vPqD1kn~bfRHo>M#2_YK!@LoliP1C(cdLufclzwZLTUeEk$QOrT`g@YMUf8E zVIio?JFM?cHH1_`F5qTI$>w@S+_!$-F@wVa9bTFtuv%dKaP=XH>pC%oI#K3e`q`Zx3g!RY#?cA}7*jCDHM9HhB{qQ;NN3+Qu&cQJ4Dg z^Bpo{ZP($AXV&zYXejnJ|ANt(*<45cUi;B9IqQ1VIo?a0N8bD-b+X7MT!=n%L zxD_WT!pF66?hpu;b>HJDEaYkJ$@{pjFQb>JA{0N$F0xf>=zL?}#-y0&8>{q8g6JOc zpOfJiIsu#vefSnW(SHtu%dMJ>?a_y_5Fy+Y2ZEc`XN zqSsB1~KSRl3C{O(=+8oNFoSj=>G8GT?R#?au44oy9@|K zni!|^w5+hy*YfZSOSuTaWMFQTm&4o65J;lfW%Qy81S%Zs z?>@E_UAz;XcN)71$P+$fNH((~aZ+hO9#9A-`afD#P0ik#p!($v(6~CtO)7T2w9jW` zs8Fr!t^1FSvH&poNG-1~aG6MVb-idW_-ZqbU-iL)`L|iXO?l7x9R|krl@^rPZ-{?_ zZIO>5+Qw0&tSE$04y>@abeC#`zFvtQp)M1OUb8ca;7JwD4rZ2DUw!a^K7!!R{;B(I zaa!XQ*osr9k_VB$H;w{Ts`@$=M%$F){S10j(L=eAcrt)QdcHigo|&PkX_)#xK&EKu zV_za~vOaIFYBme5#ZFqPPi)lgy*j565(uQH-1i*oCaC#z)~g5bd_p4Mk&8?Z6@hB9 z-6v?|-*=R_&2bDU)r%tQpB+DiXpAnBdta5Yd4~TKrj$`WbpoZ+#Si;38j<*XM>gB^ z?0leoOB1K(n6f|jul_9|VaWHXinWZn2l=+(ey3nS)JiQV_1fMjQhj?64x4uHl9XJ~ zX@^gYk2_A)HIS9yq0`c7^||pXMHwE`%20U`Do!swl)C?8*aO6?uly>=kQIPiDJ*;p zSf_-fPIs|3F82)M-|O2HWYoDCq?Dw3nugvCo1~rPuC_ctnE4N3-@kL-fXK3#;2lC9==&x-{dOhCdI z;jFkS>f{-Kn)XZ2SQmz7vGsU0$PzQ6zfSdKCzjXN(os$4`!qCge0peHV_UJzvtC92 z5hkx*n$F#K9TgQ{=`}(!ymJjE%)Sa^%T9)}L=Te|^36&UtCP{!udx5+{enD@^yYl} zv;nt3h_X%GEQcfL7JP$x|F@+fP2#uPKh_1Am%#2Ral zjU_R#E`nR@vue1`rC?Gcod|d5t#EHXKd{zM9IKmk_$GERSbSDn+quU6-0-o0Um~+2 zK}6X4IrUtFN;20CNct^%v4(p<1e$TKKNzPdZCctYDovK~{Pptyo*S|CxH%{f09e*%cxC=XM<<8LQkob``gP>%+Uwlyb@6k+6VLMdoehFai+y(K zOB!Pk6TU7~@?^z~54;Dl?g)5wKr>00cfp!Wbd59&v^7T>Nngk~2wd{3^!^m`Uadr1}G(K#gO?fL5M`l3Z$QKmT4%l@kqEH>(lNji#Bf5#$i$Cdc=*;-$ruIa_u=oN%71_r7fnKQ6SC z|E5_2Fffr|C6@FgZTm6aeb2otqo{@g|Tg^03Xjx zuLhl*BI4MX(kk4$nk|ASVP~?LHW`V zO+Lh4k1`nFv;Hh^X zkciCnYd`uZ?D#8nugt42`7MGBeSg_~K#ceDAhwHPYpkNvx@i7Q+_wFMP*ub&t)oGu?uM-dQ1^xu`G z2z=?ImXPVnGYu|!fn^~EN-OWZncyS6a$b48cmy(km6Nk>bJ~7-FLC?+;@&B!rU1IB z$>78g1J!9f(Bh^x^sg_EQ8NwK);uy#i^zxdipNu?4FoeK<)1QkQXLhVqIH*0V1%@v z73|=p0M#jc{eCPHO4w!N0LNM}{g7MV4_h{E(C)@3xJop3cPYQR^reeQxh@Y ztI3nPx!i-JeV@x?UBAvFPy;Q_F6VSG?M!=T4QVnX0UTFx+H-`!KWnGs7J{hV-ZI)| z6d+`HQC3D{(?s%_r#9EmK>y8$Tr>z?TL4&0Als0SwqzDfceu|E1Q)v+WA4_TKMBvh z11HZ|s70i<+cklh8@Ka&D5G!(XfIFVNXOTj?U8StgU-nA%Rqe3Zu|uu=DnC$qr}(o z5)rlsp}q+nF^(;ljs!H$do_v8_lRMOA_bbnRlb+8xs_>eRyvTFSp0ShcD%43U9Y)L z<3PFX%tT%ZXX^k;W0j)@)7bjHi&J$T0}F6iq$UPg&6JAA_0|(MC}ZNa{eD1PI#)Y& z{_FeAHFn3THxyl79n}5y!}P_pWVw%fIWcs#(JLDGj%6C;wiWZwjcjt5|3W#iW{huZB@V8a zjjTOl5y9sxoO$^8GCtSZykA4dYQ~Fa7EvgEtDjJR96ciPFS3|Y@t>X4)vqrEsLyu@ z7H*EBikUwyUhXvpZuc8c3D<22OaOL=QuN{*&aQJys%O%tFl@1##cQw2DH?1zK;G3m zlVhU?02Sws*m65%mV9N71XcvkCm*Vy@ZUBXZc(w?%CA-P@ComJcz}gY3-=fuDRT7A z4;m1?`v)B}0uSC(zG=#i0L|Qs zqiWflE}AJC)rj(V4S{A0IURKgC}4ihP?db^LJA)(QaC-Ul`y^Czu#8=7q$T}#>0V3 z?*bX?y@r))V8;<%nIKD?`)2K|=~M;`u=rx87|k$C{S0p3@=MddmNa(yoc2L2C`4y} z@Fm75t0ppy%P*wb2?Gjw8M;!xOotMN+QA}g?}Kqs3xp6qOoB_a6l}^>q!1uSP zmk7&#v6?&;SM!*uIZzrZF6v#Klo!Fq3W^dlo{I8>*hhk%SDw6i7{dhh#mtdI#`VgX zvynPXz{ewC$hO;?1H`MxJakbQ$<{_-V3~XUrldJ_k*VPQgv+Li=PYN7KTP!bDrQu zZQWxr`Kqq={xd*$nq?DJ0U$gf5pCaGx4pc1?$aVeng56_u;xZEM-s!e@se`fPs7L= zCzn3z=-B0frkIPVG{G5SW=7AYLIPBYG-h61)j8!HzVF)q3S@^Zo!a_(E5NDn<|;4q zJGHwS(Ne)7!~5{C>EbUyFe=I}e-^)Lo4}-{qfIT5^3y#nn>iyRqyI;EQX&Aj@RPK* z`1RWBJ9qf2VkRCA+3}4qslbqTQ@Yu7w$FxgO1vA$HU&e8Hvu;Y!r{PFJ+4+|XC%rENRT*LYg77D#wA)%t z*39$q_p22CDGx)w&HDaopw}~4hDiRPfhPDxW-5LqE0cp82=s+FL8H8#S)*sQ_Vm$JN(~KAeDH|1{yjD~l;0ld~2TGR2b7SM$g; z;8#{ao|aP*kxv&X#8syUJ!ZewG7y2#mZ~{~WZnXG(kCN#4$FPi`^xQumvv9b}nW54v`F0eLzH@fRMIZgEq*LCD8twLb^^_QxV*--da zB8Om0RzX-s8vs3*GUythauNZ{Ektlk{ZNc z3LYmw;Z-C@aV^)b3pr{TVxIMmf}57WCDevPsL?|xhXH9bPZs5qd1x67qs_ zp^US$JR%|1m2KNa2p|G-@>$E~c;?8viH(2O>+!L}n%i-iq3|%1%y;RBgPMn4sJVse zOAohoc0rlfPzYX+!%UrFB87LuFrT>l7V)Vy53mu8*vtbNRmU*^4U$@oI&+^3uV%zR zg8){SO9zJL6Z_&Ov#m4r93vX^9v=78=)?7tiW|?o-uH>p zccs)f!i%gQN20@tG0nhora}kSX|UWJ=2_W#%E#=|d0b~FXiByyW{R}E&8wzJny(BH z+MX1+8ZfC*{qcd$O9xg}2xkD+b5_onlH#{Sm=LMgUOQ8wmd_id%e++xN=plb@i zk6eEJNjAmqa{v&T3*#j0^&SqXkcPiQtez*CD%FKte@A}Hq5mPnsJJZTH1JY(tpoQ_ zp^>PCAFlw=OLE6-NF;bq=T?;h0|N{^2}xIy5=(><(}vePo{w3);M!n8LP720F3LjP zR>T@pj)ec#TYEa}n$tST|9DGmfEEt)^z>l)@Cb0Q#m^;6H77ylrLJQy7$qP2I(Kt9ez@`K$}9}0)+s_SQQY;< z`w?a35f2a>@eI&Fn2r5a1ocpO$=I4JJXr>tnKf*46t)HSc<7+xh;zglQKJ85f`)mD6bw7nT-Gee*Zf zQ~+{h_ev+wW-cx-`hmrtrupiq)ql^0Z={s|x$ICQ(Nh8FqA2$PnLPe_RCc_Jbu7dJ9eg~=Uo$Il|} zNHvh@k7ZLO>rdHkT8}$T{=@gb4&-q?1D(W-cVjKi4nBJM5kr$3b$P?X4ql>=tl$NS z^H;C5$N0rMzp>3!Sg)|dNyB};c6Z)u<^db(^p`>|nWyhRBb=KKuk&cg4J_2P$@m>M z6f%RqFypEW8G7wBuB=g{;Lg=~t>Kx{ERbn3RSD}8L!N6JS6FWT0nRcZe+E-1UYm~( zDH{zcSJy; zW-H=OR`$hu6=>mKlfWe)<8Sbi7RlCQS7p#-qM%Mp`+cC(8rbloOpU!x>D6XEPK*6% z0^QmY9r4w~J_J2s*P*5uIn*+!r|TiLP$P`%%FS5_2yDzu7oE|^y7;jq%pp$ier#=8 zy)>KO_1bAjJ)Xgp(*GX_Y~>X=$qxYeUj1?IU9xMIJXOj4pJ_lm1=D$#=9<(Z{mIG- z3Jdbq@{UEZ$+xEP)Qg#)DJs9FtkyKZX)1{(|Au*C`4L9M>X%hSziz#EOa~?ggh5et zplgSOs@;uB!wTEONTV^E+3XXX96!_a`NrmwG+awvzs`P7Lbpso+H43s=BmBLPb0S0 z%qioxRqu$Fzy|X=+fx3{(Wz$e$6QiDyWi{+ruN?qX0|gt4Nz*qSH29JKK5#IP>>-w zyLpgP=uy*~@Uqr_|37fvrCT_1#FM@HAhPk(${@8RZ z&Edv|&_+H}QHsJN6tx!sl|~4RCzajz{8j%-gSGyoy3>(jERHoc<4p+=1`xn@~?uCo(28mjghDv-17_M$aE$8YnW)ybR`cajr{il$GBMf=1SvYCvmBiCtf|Ufj}#ppAL|S9TI%W1 zEkrELY?nudUSD60Zro{Td&!Lry9ePz+4;ql8;DuzzXUd`-eSrylXcYUYUQph#8SV% z_vVpFli>t&iB1%Et_^@aQh8%wDSEyY%*w$Gfeka#WtZ1aTk;frv4z4#ngkexx_p%Z zC8|@jBupT~+~X#v_mSZlm`FYqGjLMNpcKPtaQk6@YEJdCZPLuR&DrObM>l#6rB#nk zc82-}CUoCDNHP2K=a8_*R&oxHveVb!Z`oZOedaC}Yrha{YM*Lv{u0fOpdc5Pxt2BG{gbUKk| z5EznZ$?TPUJAt&=_6yywNf8l6sl1|s3(1v*%tCZx#fA0L=2gI=9|Bjw`CiB4BXuQS zvoIYSGcFW+4f1Zvvp;sYoP=>3`A-!SX(6~B6si3%8w&D_;LUR^!Z~+c-Y+vV5I94< z(&vj?Po@NeHgxnwc#ZWDT9hlYk4g45j4!!wjK9j5j?M_DGJIlZy5_bTSfws!m&Y;# z>u#Te{~JT*DO^j}x{x=Sp8m&;%3~Nj#OmYQ#jMb5^MKNtPoWQjO-=K*aqnwr7+k0k za!O^>(ScMp`qeD_Qy9f|*9>#qY_QsGbKrDQe6&I9UZ-iWGwMx~|B040f9whYl`31A zO{0r>)YJ0W_qq7V-kf7^qEs zy(_g-etC6t&`E>U}BdY zz8-wNy$94eo%9DIIjQxYIV^>Zj<4eMhkBWz$iGppMF z^Qs5ZF*(v!v0e8dtFRth<|!oW&Oy^xv9)kuzkUnWBK-5$d}cl&Xu4?y(T^wOCel!Z zJPSMfU0ghumJW-ejki&SAJn6t$ol$zsSx3Y|E$VZbTm$%a?okvg(EHWB~3Gd8iG02 z0Vf|5!iRiEik&VOiF)lhuH6U6`NRSi#8FkpTD*b5m-dc55%q(NbTWA(=_Tb))KhEr zJc9d0yy#ZP1n28ln{iC^57r7QkAPDZxC3TKTh+w)whI(!>Rrg*TC#|-{nU4^Y|KW7 z%hCSkJ|UW^6S4j}*MCnL5&|V5Njf_}&u5f26eF@Q($FCxUL4?}x<@oBjh^M>>!$(L z*3rJE-@rU;81*VCE%1bf){E03ZTQIX2a1FtA7SZFiX@XJ)wPV$+_sK@`QM4@7X;$6 zM^zI`%2V4U065>MisL*z(;vz}ijK90V;a}hQvCTG1yV4Lz{QS9OJ zFp-w#{Xj@zKDY_8t#+QcOqcmLhp8c%AANuZS>p_10S5+&7Jo@)&Kwh4~bLOA!LOkiV(8* zJj!~P*&};pL`e3|R?5uY`+M*G`6s?zx^%hZC+9pL&&T8bxZiKL z>rD?OJ|T8(OfJSP{1r|*3Tl)L_KE4bHz;i#U3&rANQEI&_gf~*xs=TvlSaA}1ta0T zJzEr*TI*tM9X+vwj)aHmq@>beZFU$Gxiod0`$EgdR5iU%nM2D^8vEziwjmIxY1XIv zt)x})MuFK&m2`rYN{|cPP;%sNKCci-L~_-Vx++6G-YW5PP>>E|z)bh@ z!HoZ^tLtOsvMGF;*H&$_eL}^3<2D6l;aA^|zXB7{)h9oe_fzfn${gOY5TRvrdo43< z4NJx>guXAJgY#GI4B`fCyapMp_E*fBrc00rCIs~!e*N^i3$iyYE}MN!KYeAwaFOp8 zHQlbLV%*xwR&YPXABRn^TYM3+6bR{(A!x~Q2NcgDlNRFWic^F=R2uxL9Kja`wUCy2 zPHlja%fmp|L~H6>d}50>L;vc7pXM1LW<&+Iuj#=W>X~pqAr(lpKp<*%;*zO)(m1-F z+V}jWh;$bBe`Et!6e0K=5;qpVQ<~h&M;c9)^6o@b{(}0k{9jV z*i`53-MDwWYWmC{r@cm}juFT5bm>j6)KITw?hY^Jfu}NuholoO4$n98kjJacSA)`r zmc!@@IAr^+%PmyE$9OK_i@**0l75|4(Tx%j=Uy~wDVUs6Ald$-Ye*}G1xvBYehKQI z*byBydv?_!5K0XcbR|8}29`jN?>4Vu9QUbrDN*@`M$n0)x}RSI`&ALC*!dqY#UA6I zoOl9uOQ-`?UwG`$dy1ZQ!vtvZbaBRIuN!97giLOHPNs2xKq(3H7-2J!*i`#n#UD z$RqdlhyAPU;kb7%|Jwxi>BWC^v(rXe|El4AH00q~q5jZdxcNLGULX7@u zyF%7avvN3Ay`tV)wuw+6nfO)f_w8Lm9_02DafSaHpGUuAQKU?rI(=r4cM-9&R~Kni z6|k@BDq`Fxe&i+ZwRAb|hwfW?@+f-8BMmr73?s8m3rT`qw1&K6?m*ABUr2(MEf`P6 z7%o*@w-7xE_m=nK&C=QOz4xL~Ejv^0eA!}VBHp4!`S~H4Y*#X<%U`Y`?FkX|?Fd|C z1~W$)Cmf;&-RX#Hh1TyqgZXMKKHDR~UauH)UTZGP7XI`GeGH4ph!qTTK7|L3+zw)* zz=QiMLR!t#KMT8IShUdE*Em())Y&;Q)^Rc}QoeQhHc>yt$f?38W$NPc&Y2ht^OPlj zChbhThC2PyuVXYizzNPZ)BJnOLa*_dxr?XgLB#Y=YVn8CX7LX7axY)@ z8dys^z6}{8#7ptzq~?fW8G2RUsZR}#3$Li#H<)m4P!Zk71~HMVy!TOSz#6NkX;s%g zj+XysSDs%gaT(J_VaIY#hL%TqZ|zaSWr=uJheoLG201=;NN0Cv9ux$N4`Emq{`4#s z%vHB`s!~*`)U^L;Q+KZNmi&}DIj#rgD%FPhX#$qR%r|kdMnO+X78$fzY~hOEcS;^#!}G8 zes#qX!_HA+a|ao0HD0zI-d55$(>--GoJG3#cXUry{1P%_Wo4r9J-sJM2r3YNm9muw z+Ne(czM?+AH~Z)2thOK=6`w&5Jws~RxLEun&zA=R|_e6;|GdlD_4#7?ak1^k9v*N|9_VuJ@X%`WF(aBE{WEh;AmTOOder-Yx1O_$ z98nPt!oSGNEmJZfD3mlZ!zRg`T41Yn58-YwC)_*KN=<=kPPNa4TP^Zx&<`^5ek|@B z3NQTpF*GYEF;6Ic;;c+!f#hNkr?WZ;hpTMPVBhbB#jZv1E2=nXOM`_-PpJoLwq=5A zHxFhfo17URhYpR(qVv;V&i(dz5R=QM)H?Y`->L8X`7Kx1{XKxDoa~0%Hl~rc@_@u} zt0mpT~mbzI}}#f0(h%k6EqETh1y2yTox zb7KLRIegeTxhWhf6_i_595wG1B{q3I(mY{U%eLdK)@wSNKz`N9wS8F2raIF#m!B`z zV*0vAY$g(Tq_Me~WrHcfboJBduBUP@t*Gk&c#+uh5`hTr-&MRuJMnBaW5{mId1gXI zEXe0;$DUunpVb*tQ+N>PTIx0JH*pcrCC8eCVuIWC-qlm4Px%aYm}jvgk?3G^>W~bM z0()b|cR`fU9tT%;a0uSX(J?60+$(bLYA0o?SeKuiSRX7WNFeY8>rFu9x%j9CjaSB5 z?}TR(?;HFurL(a2&*RI1*@WTYFvk63J8u|LBTI#T-s(1L;S?=!I^y=D}ncvEr&|A?`B^}lZx}zDb z_r0T9Q9g=;AHvJBi~H{;*x;wo0f$AOlS=O*dqv#+oINa)NeRU)8y2!iCJX%>)R{6K zLe}P9BF*)_>$2b0U49r-JW-Dhz@10Qu58hsr%^w_V7nJfVtTU1maEo(>^Bd+jF{yI%pde7fKwz9WBYo9*zo3dqVFLm&6f?~5eGZ-4hvmJ~{ zIvZ`Ejj*pWM9Go_hYV|DCMJrSRpfYrslr2oQL+`Z#9f~jb`B81s4-^mAUV)Fpk!pj zM7eZzbv(aH)?^h*TSs8Beu_l7`cPUKcvL<;JUJHb5DorjDR}cLO(gzlUqs~ZZvxS3 z1!th8uG*LYZIz=MwMIO5a?ziWEErK@U9y_UF|g+)t<}>t3Ek$)QOllw;5TSn*>t)! z_A|?|L;`E5+Xy35xJJAOKnkr)#dCW?0$??XzuGO8%N+mGC`(rLE+yy1duXvHO56u6FFa??J4@+@jJMs1EV9@ttyKck(#9WErZ~@qAn^~+-tyYtGm@7H zMv=?H`ua-}I?Y=B;l^^Ve)@#y+n~~pqu6QQc79d+O4^!O86+~ZZv2opxAj^_j7&zfF~5eJ$~PtB*^h;H8nLSZ016CverC07P7_I^HdUf zdcg#l601=#;=j`xoimioiJ737Jv+6xH4sr9VwN4y?W))w95v0TeE0OT9I+Xi1$Zf$ znVO=r#=6|*pQ?)01j5Mv{id6zfsKH+l7CuMz1AmHL~k}Y@b%N`E;SZ&1vcnHX^(_gpa(FQUqD$Ja>S zcG8p6|6X8vj#!|g2*D$`M?{P)1x^fGFB^xGQHM1^F%?Bv*;R36Oh`&J=9*hcaidmmVyB-W|Q5E-o0qNr3^cNo;H^htf@wIKrGP zT||8=*3^K8oML!;0E$+&t5%D@k9~YsdtbbKys~?+u;{I*IVpIFAM-M^=CIOARmUb- zs-E)7B??TXevBZ8*qB{)Wev8Kx_SvkE$F2Yzv&_*7Pu*Xuyx&RvA)9CIRtDLf(|qJ zx~;LOI&SWqtek+oj;^s4Hd!91xPh?7!68(VQT$+tzoS&V4=%_8B(i%+moMW{P+jM^ zQLecBX?v+mQ$wGEmcF~cLTo`GJex2gLL`b1`m+=-n15G%p=J_x;^=msWJVz7L||Aw z*LLeT;K-{!nJ0a8&90Qj-p(*dEL^PbNoW*3mva?Y8fY_lJSyf=E5oywzpo_0VCGZX zO;$yJ7zA5kWKa|A`2mhSI#y0Eyo{Ly!|(Ll*vL}6I`i|RN%C0m79wr`kvn#B=(R{i zkxsH1^dCtf2{D}ErY;(RqYY2*@?)TDk-#+yf;GiB+gF7AhJ>oK$!XWDDyxdDC~@pA zO8g3>&N2rsO{q{fQ?Ks%kXpE=SRm1Pwzd^?4S$A<2gHn4w50sDKzyvmzRIS7`>ND)iEN`O#er{p891D5Vde7e;lH*JbL+F zg(g$*LQOPfe`n)-O#Aw34uf~@<7z5VA~tGYAL`@{McCw1W8Zewj2XYtCA=+IzwHJd zE~(<^

    _VD>VoXe$zZvO%Q}OO&_|NR4!=W;%xAh1nL47i#zRd`~p1{xinNfG<5j<2@jKay*R@b5j`(yy-)lRO<#kU=U+q)^cfz0I~k2Bu`H ztwtgOm#tXe-cfRNJUd6CuV#S7KjqEHg9qhb(#;N42yfv3AD+&?T}d+0&}7JXC5VH* z1IF||*&kb8a%I1)Z7EYRf3gLl`O&5N;MbHJjb>U#UHuhd8n0W=Q1T1qH=<%SdkOC1IR&+q%!0gATl&(NP}d*4I=AI%2e1 zT4fs~Bu3JIU1LaHG1|G;cX%aec=UcVkBXz?TBcr{A?@n#nMW;%cHw*CpO2!2U%UM9 z%eoWXZ&&F!J|p|{Yw1k|(EHUg)4s$Rm6fy0yGO1tcioG4lK*8%ngYSp?UbCHc-J(L`NM#X+J{ub{n7f!#>rLu&DPL+n&-bR(Ha2)GEO$Hda1p#_e(s{T}Og%y7<|?%rZIlS6aW8`OUDxlFreh0* z;85*jY$~?Rdzk`1GI{i}(6fA*ZJsrqTyJ`aKG@ht7LS9i# zRXKWjAaYT})B1jRCP3nLws#2jH*PE5i+qAE=^0L`Y%n%5eCB97@WPE}Y<16m)bVgX z$BeeIZD*?G7CAmoJAOm){juU6W05WWr!bx_D62Av>-^rS>d>P4)3W`YqLZWqWz_s2 z-#w|bl{8sH!Q`_)f1IJj3(2NOVtzP)M7g<5(;tCgSBSQY`v{!Ie?&=G%OfKK>r3Hf znTJQm_?Pin(o`^x;a6NjNxH8nAaA9q5R(w??rlBM3bHa#6OR-3dMJ)TBLgF|$s0a5 z*P7YOC>WAPjL+y6u;EF!?riuUK9PNN(?3`8@iqJvZrn1?P<(>W7yM%-wg7{3WYfCa z^*uXIlzne`nnUnb``Mry0+FJ7(pu8QC>76muy9fkog$n!U_*fHOOAj9NMM~kk6F8(y2T@t;BlAHWJ`!;)edGTZmAd^(}bAAsLUhBm5{f{Gn$(b#B70xId;X8R7p^w))wvHV6bH$K5tHJ$CN3=NUb-_j{-Y# zBti?tng>kc5c8xuYi~^3xm>8pTal^YdC;kaCbI_uGzqDiES{mWHHk#apk|+KT$0^% z4k$i5I*`(V^Cy#`r;{FQ;ys`sg(oe&l$&!sqLi}>Ot&y}IU&8_v|wJB$);qS_T&5a z{9iGe;VYpFh5u`R&nryInTP#UNJ1 zF7F7Jk`ycYM-??1f#`F6(blGHYBa+`t)@!7xye&UA{^Wg`n#msxe%^N0MtzOe}0pl zH(0i9ZReY(LNC@nJ65gJmL@=Z{(HIND8kHiuk$f|2Q#vVU!uI?_ZQ=8&6CMD+RyK& zD!`%+{{`w=PF-bP4-&b0R)Av?heaq$q`(^^WO#V9dM*d@c~BlP*ECS-np0cD>d`HT&1NIS926Az@=8#e$d8y= zMVF*K2Wf%Ix0-=0a`LxuM-r1X!C;?#Zg}M5ru;x&!I?_h6lm%px- zINjD~wRal0kbsv3#A&d@!5+b)tkUr9EqRZ2;*Ep}{~YbD2Es_JfTNK$28V#WXmZ6d$u zQ+VL(P?m6K877<2Wt#n9i%%U3mxUgueHJAM^IA%ko!t zoG&^mbxu56?sX@27yf>$s=KF|c<;%3&yn|V<|%VxLPxYsP6tSFa;+1l8~xvB zek-G=qNl%3R}X%_Q3)#riH}Cw!!yqb#N2kDM=&8GnZq^Rw0~bOm_3Nx;P#J>1OzZY5P`Zb1u|G!-bk)ybv|E>a**ijX^b#lUT=Hq~pI+cHdv?$i z;@nVoSW8!uIzIfeK=Qn>rsj9ch?c+&x3%`39!4q#T2U|Q&&j1vaAj#R-aD4tKfaT2 zy8jTLJi`S?m}*!nS|ydIxbU<1k!61JaWqr796s@DE0H5hyJTV_r>C}f2n3U&Cdj;U zo~cvweYcI*PIf)3^`Ecg%PFY*Wf?q*_aYEqVDt*YG-lWId+8O*Kh{aMliE20Hj;F(s_otZ*njdXPu(Vo|x;1tjsrmd|PZfRyj)<=ooglxxL4mjOdk~3ey$7vv zUz3(9fM+f*Z>irB?%SfPa@gD1qXCzL9Ev}@tfPbb{^SGcGOg=%r8Uh;LynuI8c25P zrW!Yld8rZEy;|=h<=JPUnHu$$OaH*Aji6OA6o2}){|iVddKR~h*hIfNT`b8Dep*pZ zKN42l+?F)ooug>`i3%Zl1+&A6PgZneno23*|8{@imvlHzRHe8*t7`d3hWICKA5E!$ zUjhg}Vj9#2(P^e_e$59r=(GU~;O#%5=8e`@a%|9{O20T+#Nr<)f+26jCR;VBUnTWJV)jX*oupVQweL%h zhPf&7GuH<-xFc)ajjN+TVwlj<)K#cox6ebPjnn`8a^r5|)vsGqjnA*> z+)dFdPL&+dX*0K32xvMLC8!!ORmQXy{bz5(!l|0Yo)& zWw6OPt!dQxv+!iRa?O>v%b}k<<`)8X{ZpIlCMfhgNoAK-8U2$)pUB34Lt%? z6|d|yW3>;Sm1fs*N8hN;tLaz6uII|xjR+<8fbk7T2}1>sqeD6hXFu;Xc39uedGAm% z;9RBe^oAalkMdf%K5#dEVmH)HlkBbMt;o8|9y6FVXykUCv3a< zV}UUpOM!g{R@X>IzLoNlCX6b7tz8(?RoGxk_!40p+lV|SMWnaJC-^|m3H$Xb2`OO% zya3s1^c$jSCVYMqz$mg4G}7*q>e~s8V#$7NJ>$9P2&IUVWrAkZg(T-AQ)}0e%U2K# zF}o|vZ=c)c&ql7Bw(N)+c&w?hB7-Jkh;Wf)zeRe-I_e)Etiuo~;hwp%MD!l$0{R+{ zoF;oEC|(Rd(!3s4$mUURv3>Z2Tk_ylw5Q*slN=PefJAtB@uk(UsC|U(F7pRzFB8Rq zp~U6JOyu3YCB3xG#WyB@!v@*Qn68$uH_m_U9ER<|u*j%SmFvpPo=)IXb>Ey_JtJ|*J z?_#KA_ViE-bQtdZ50Q=(luqS6y}b!oUEI5S{hm+1b@}F$H0i#| zqt;DcD?gacE8y!fIt(%`sqWvcWW|)EDd9MsZmyKi{$@Nh*B;{&ZBWn^zWO3GWOsSG z0DSmZ6K3ciRM2wa$j7k2Oc`qsYozfy#%Be%3Gb(#DaH-26J0-?zJewRy@clX82$!` zq>%=D*DG-dgkqeK)A;_#fz*eT&S4v`#Y}m`CoPK5Eq7K-^n&G4c#s%r273~1f@2L6 zo^;6O`T6r0@dp0zM^*Z|4W%kMBa*@=wkFpa--Z16yU#7@6+Qo>p20_DKDB4JrPhlB z?swV6#cfIsK!D0oP1@XH3=r*ltwFIjb3e`hVmb6|{y6(X>Zt8pNMO3XQg~<`c0|tP zYIa;aNlD(ZAm^{e*PBY=ps!GqNC75I01r{tz$R^K4%^jhoXDUa>+AAlsOC>Zt^L=X zR=-kD-XKZJ1~Zb&fn#+P4R7JmJeTfjRf@0wxkV3f7(fJ}c1j{ohJ>S9A}RS}Rk_pJ z14rRsmxJ+wSwNi3kN%vwd6;&NUsQLVCWC%yIwNXOB}V_8;Ojqua4!L)yOgE|s_h?= z;s*c2b3$&D2QNCI#|1LFItz zw+a`BdnA$b8y_5PR`Il~k;hAWMt@?FXkTQOCu1k zc!M`Xxg~9}1YX`p-=TlhnisEJ_IK@1?S9ThL)CVX)R^<|svA`j;7$B=P1Opl+1>d+ ztP)EFhd6JM7Fto8Vwd*2s=+i8!(yPzZ>S1ac2EneF-R3F%fDsS;b{lL2LUPLc*BLX z(uytE{Ywk^8p9Av=_o{cb>4baW{|BFD}8%&L&k>aI)}mfhk^O99KG<+`^5@+T|DHn zuMv1i43Y(rTF(-q@t?+jufMQNSl5>3&W&aUEr<~mFz#wbn@WX-&{>W5qA&>M8t^CD)Csu;ne8@X%|>t z_hty_gXXjYV9A|%&iK!oRGYbWr3lvFxJMMaRek?jodK8QYaE1hmD+)k%M#T^Gqb7O zwHATVnPF7`vs=+%-^J%VhEEUHNFv62HGt8JWB{l*^lO1t0YKneIqv~aCXn0{mMI+P z?j&f5yg~61`|fZei7tW=SV(+di!wHjDZ{;bWSrCdBpac{@H8qO{ejmUsSsRosCos# zEQ_0I9Y@J6<{s0kq{T>#^J8`XM1IyQ1ciwKAF`R0-$ke1?6aL#buL{k_eX+8slGn@ z#Cm1|Ywbpkf<|=P%9Gt~RDiV?jyhQ`=woi7jkb5$p6N*Q-#yan4Sj%o!?L_sA|>T5 z>bO#E#q7V4TE;FADBez}0a)w9L?LBd(Wt`xkq6@fKhWfwgEaE%F2cuO_s`ddMxw>{y8jEl2>=Jj z@YftG>cKd|KY!Nta6fE{c8*fGtr7Mm0gYFwFHDQTGlvr{CzL=HoA6Lkm#5uM%hFxX z(NkDnF@`k}k!hVdK=Fru7Y{*$CSyhfmaqXuIz=BVSXMW9^1d?tNX&(V7bm0DbSC-q z5~9Dk$MqKV>SM$Jd#1uVb6%eDQ)jt z-mcq0mt{hCTn=Nv@8U_PRR7i@_gh04WPr;bazKMTjIszgKQ7goBSr7B&iMc7t?2P~SZH^{g*9Ey5X`0#LP zEs|ba^Ai|gKLuS89%CZK^(wv*RX+ByfKEoIk`>7t{66S89?0ycH}k&(TJK9#ImTcr zZQHf7uIC)b==2hw8$OLJN%dkeKH_0kuBTR%dOn*y8-nfC&A##MzRBt+P7#r)Q~tW(DCQkU37;c$ad=x()%Qm)wX7`tzPW(TSR93FA?K<^ z{thJuFEEgePF0LeX|E|?W(-fZl9lHLKu-8E1N@jF3RNtiDBY8C*l;smy$*gI$o59| zYA`M4%2Q$d(Gg=a<6RBY{hPH0eBYG-RD*ATzfSBovhLs9;i;oteUMqdw!2eeK}}^5 zH|Ig>M8=jMKTdDvX?n(tlrGjiP;rpDf(}O&^z46cc}(ncR?gk{y|nN|O%9-C2V3`n zw=Q|{s~|8is`tX~TN}v>jR6~wVmLOQwXbGn)$zMh%i5g(?fBOw#9)Ub$z;F%M5UT+ zZhJPXPt?3zUvwm4I9(2QF z?a1mLJlg_&kMe-IiRL-&=h%?2j_}6VlxQR6h7Vem9tVWHrLmkjT9)N)PuE^8Lrq!( zVpW(an;ns1O6|3W*Mz1w7fu*;wRKERml=)kfC#cIf$LvF%=a-XJJ*O@jUZ1=eOo4ea3x`JD;XL2Zx=ifdbP1+qKrgq zPJYL1+iN{P*p;LoC_w9iycyN~u*IT{sZ+sVu(J#sci2vqJ30ZYb3#BSgAO<7ssJti zH7XAJ8fwX#6O}{I4-6El+ZX?}Z1vizo=N_`yZ2V@QAyL8>B_PjY1WMDSp?W{w%^}p zymN>B>`%1RWZT&w{@qA8Q7R1A;!^6JBz+wE1F23QkxscT&O8rQ%Sjoq={>$r z-7fAA#ngb7zl8Yjoidk3!iYl=ew7lovdm9JJELWKK&?BNcMhQ56+Q4|1i1xVU$1pU zCRW?LY5&(ZBu;d=G&{sLW zhO|I5M@U#`5i{HvRQi5-a5TUuB45dBc~{wIztOjOE={iGFU7}rtC8C-1L?e1$|v7f zSG0~@tSvEo&P0H-xPy>zVT=Cc*Q)a-{bJ(^_}6F_j&jmOW856N_EVffB0TWoA71+# zY`!#gdJ0r{7@s6d-D6`y$W$yg%RGJri`lyRA9K`ZhCaE+LwGp>z%l%O-zFaef2N}? zg=zAJ`%?VV)0N!fuDT8uZyzk4OU;>34Ob9)_~Fr)&-ChwK?oK)iN&^dMT*QT;2}Gh zbw9ZwG%LLNQiPH+w)fq2fjQ&IQ^%DuXu8gbIW^c#yvw2D1Ql-Ww`_I2yC2~>G6jRj z1Kg+%*vo&%ZVR&R0T8K}Pdd_S38-+utI{*`lH|I1Q{tv_M}I7*Z}*Tr@YAYV4ez+I zt@K|fJ4W+AQ&dTlkZeRE8U-3__c8Lh1tUJr{&#Vilyd^goGIYOFU@2N<F<^zG$?dT zOf1Uu+oKsqPTwFi4*mW9(X~nqQ?v*>%3x5K$c$P?B;I><&-Qt)&qsrtEGIu( zAzGtK&2!@6dkt)_sj>T!tcLROGt`KqZL}$dHc}0ntDLfw>3>>IV~c9OKaX#@yeo<+ z9lrq*U^Bl8yPo67e=J$BQARn3GO1AW9mit&WRN0pDfi6Z%b`daRWpsnXLSqqLhOs{ z4x)?}xr}3C-?mmZmdKtwUA_!A9X2I9!wJxr(iJBF#oMF4)%H>(lJpX4-F=)$8flv} zQR^A7k&p_PVGFntK*mg{Mz(3d#6x-`1OKCvOZgLTi-o!)B8}*%yUANj84fs^DfILJ z2l>0R@oZ=hzajI-jVNGAJ4>F=0Tn6UG(fIEgj-n)`wp%okv9Znivn@c^t_gzazeMH zTgWh2nFN^SDtEcYy`9Ym?z%j;?EK4%^*WkY|Hajn7@LuRN@A2ugH^9u461k`a z#5P}!%F}!aXp~V>QKboce^2BuQlN(hq3hRA=;~Rovv$WxM~_gF7CxUd02?l?C>ZPF6myul~E4$ZG?2Q(*(H!seYEvica0eMsOztN zmkY{^vvi1J)bva~8HXR1b6dWz( z7NDb}s^hK-nFjyKg%a#X?|^xMG%WlT^*x*BnMN8>&9kez{Oq%L)z*YS*Kk^?Oa);H%7Z@ALk8R(ElyO5|<2L_O%ANRco!4VnS=D>Z z9w49GDuQS&5_6D#0*GdJC&=c-|Mvk`td0f?a8(gFpCS^p2^ZH7meF}MM?IHS7Fiqf`W+% z5Ik3dbBNnjasHdhq_*HSo7*73@#3zVj3>kT7}TkR$Yucq2~NO95f{YwKkax$&1b7n>lr;(Qd4BbbDk~N%Nf}ZU(OpnltvOW-t&T41XaYJ zr#}#ZU8?Y`_*Df~WGtz(%kq!ijuXk`LZ5AREGb6*58@cGZ689fNr6q9S6xRZc*?Y!VIj-XI(prN;seGpQ%fk?QK1_+oB$`_y=!U z|50AzypaGl1|L1@e(t-%Y%V}a0%+N12pS5wus8A2|CU`eIcsuO8nQU~vFus>rZYKw z+Ze2Md1B_u^)N@sr^{G|&i`+q!oL@~&ClXB`vp&t!W|jAnbdRsA7{H?U(YXo^fLbZ z6;gFiqRsrMim@^7@7Bw9l~pU}pwwu2(eQ|G7ytYz@u&+DS9Kuby0{+z9(v492Z1j( zwCWaF9L0}GRLOR%_walfwnR>Y-I)G|{h^@_I2+MBJ0F|49@pr9xKs*9fyFECYk%(l zF}nr7WAN0P3Jw|&588&ujdE8GroAUYtqacs;x$*O4HazSOD=4m^R_!|$FLv*b+LkY zc(q!*IEX+*GirT*FIr&i#rPUm+T3SWMAad|GX}CEt-C`m691z#R2{Dg0nUUmai<$> z>BWsRA^rSos9<1>pPU@E?JntVOwXlDOMlz;)J;@bSXlvo5Vv(3f#>1D6>0mQ22~ZG zA5CX-!4!E>^8CRQ#pTW+TO!O8cyO!GXegsT5KM0QyD!hZVO$jTM4c-GNk%rYw#sOx zca24OO5`$bx^zv&{Sxw2&mFD1x5_wSBBQk4%8EX)e=6jq6s{kZvkg^t)O;6D< z)F;*1T2L?|T9a)jCyjbyB=zx1spc?)S<~UfgtU+)0@vmH!_yjN{^?O>)*GQcPGhV8 zN@>5j^?J~`j2mqP$RlkrdW6AD^7RL6*I8|snsJJwHRAqIZWJnYef!(*(d4)gjzYK; z8`;Y~8vHM~wR+m??1s>E0>IA`Q(Bq6UV5D~QbbBL_^_K?Gku`0NyrMxJJd}gbzy3) zmD@UV`Y8laNPKl>e$~_9#qG{A2mphT|0xjIB7IulFH$*9*WS+=s`98(Ko0UZ95l<6 zxBa3W++Lw`K07~{P>$XD!K@dK2rM_ktt$L{x)GV&(9p0TwI}cLaIW+k`>nT%a#*|! z;C35^IIc~pTzizY46#cYB)G@b) zTL~9ONiO7GIF=8xg}1m)r3lR~Z7ddiyV`YcBVhYT@8SeoGGroYo#tZ<)NR zYqoTd#`)!N#a3&^ct2dNAmyS(IiPsDxla3*BUcQ|O}tsdAS!D=FK#_GcZzdLtjl?ZuRlG?p~+&U>$trweH__lputb;~xp zZPW0%jF=GQ!PTVnJ_^n?6$8DHznRIO9x(OyKEtci#=JQq%!T$N`HNf_ zg53kGW@wtv!rYsioULrs)pxLmF)nRI2sN7|d<27{(nOhsUZ0c6qWHec*wS6-n8FYi z*&_IZLtF1j{(wbwiP?u9T5Bn1Wdx1}>X?hAaDv#=hybk^GrbNw!PeA}y2N>Z;a7`} zw1BMR-nl1Tk%kU#y3J1b>_t@RlH_&?hA-lPx5hduM>O*b)b^QXB|a&Q-Xy z!nPa5Ao+KIV4d*F)$iXsi*j=j>0H)gRP%Omlhi1itfj_m!~OSP?sLUfLR}J*E1k(U zv9|wha}(mLK0oZe23`{wTb=K0*vlI2vTLIw*a!RTJ{tvQI^Ba028Twk&?+ag%jl}kjEw;Bi zZjla}a&l9nzoL=)Ux1|FaJE1PgS@w?Z?>T2$)Ki^$>#QR`CsnMoqO9^)wBIJhxZq{ zrBeJCG-D5J`BHFQ7(7BR$qECj*>|I+5=JgB6*~eI_v zeb|J=zM4a<0WHm*hnuHdho0jbt8p}vzJnw@x(V`Js&iqcm<|7NZz}wN-MOmLZb!z zjf1~Zhh0fcYN?{cOb+oT?Cn47Dos2`UI8I?UBYGQDBe`Ss8BrM<w`A512S*;&X7MtOYok%3OR)6hcXWYu-bcpm;CQ@i6%34v_dvwEQooI2f79)>T#=uLl6ZYaOrtF-{8x7JmP^ zA(`a1tN&EN!^1H}@eO6TEWfH2#fbJOqpj9c3AB7qbBrYoM?xmMvwB}K)&!u4bQH5v zcbHooDj5_-t9)nAvpUp=M@I*Urt?M5xH9u4ZiW#1pMAWJKJay=o=fue@u+*?wL0mO^wh8gf z7dXsc&+TK+ePA((rL%>zbOO}{X9ucy#D^deCK!kkFt4S_SkXZ-~6l%!T%tNx27vb9rBuVM0s+)Vi#caugPks$Y%{e6zKDrIdlF)D2IG}X^0gX)b0;Z z;32NTnK8YdTU5YNQV4(U8F1cu3&Eg9MY`CS;k+c-uuR*7g3d>;bT&*MX!L`Iwxr(9 zrzBP4{RR~U<*di-bHVxsESV++ob@1DI6fUxC%(bI36YrRb3@z?g7ph#Hp|YbYB)dIWs4rv^38{o%IJ=14T0ZS?ag3Xg$;D^JQt+Z- zx(Qk^8nLH`z`14aqr90Hn#l&HoXPTwhYjz!-P``LQa;~iBqqFacGy+YboBFaee2BU z?A?-V$aXJ6$y#FGuqx_O#92MZJ+-iS6hguvEZi6%e{G%tppI7T!Cn;C^j1Ogczlej$3^+Y#1FwLpi2UXgSe0QxLx@Z`o-bL@>p_tG_&~M-el)G@c880 z+nX8JJ%ZwtM5db`S)pv>Zc{WPit}*d?6Ve5N?nauI5hj-t5JY-!@@HOw{cS5f>hit{E7JK)jhq zP708JLYXuuD6da~_QENF-`@dp+)fw+_909UPB=&{W1RUbh{-`1-=nw;{Rm8OWj{?3 z29y9`Y-eErDb_P)G47J8UOXI20|i$n=G(!bQ&)dwVCz#>jgaNI4dkg50$d2qX~3> z6b)?6pD4mE{Y23g{-$)YAenLGb>fo1Lgkcg7O?z+{aSnMySTIcu?ZNqK;DcT!Yg&` zVsh}hSdbb{jpZi2@7yLC5EHKGTDGmb{4bQ;0m56>KTSC3MvORALf0%*P9rM>CEx2} z?^))I4yK-e9(qm4c{Zae&w^$@d}6-p!DF_ste3#Hoynqzkj4#5VoPsV5|d*_X{LO; zZuy2np3GwEQNnJl;^I}8PNrZUB3H7QE8J-wobl)sE!o?8{3*Eoos_CM2)C!0A$CNH i@c?Y1|G)ltC&0pMPN*WW))%g02t|yVe6j4qp#KLdN&mP2 literal 0 HcmV?d00001 diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 94c04d784..b9580a48a 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -42,6 +42,9 @@ public slots: private slots: void on_reload_theme_clicked(); + void on_theme_changed(QString); + void on_gamemode_changed(QString); + void on_timeofday_changed(QString); void on_gamemode_index_changed(QString p_text); void on_timeofday_index_changed(QString p_text); void on_showname_placeholder_changed(QString p_text); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 11d1b9c34..8039f4ff2 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -108,10 +108,10 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(discord_hide_character_changed(bool)), ui_discord_hide_character, SLOT(setChecked(bool))); // game - connect(m_config, SIGNAL(theme_changed(QString)), ui_theme, SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(gamemode_changed(QString)), ui_gamemode, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(theme_changed(QString)), this, SLOT(on_theme_changed(QString))); + connect(m_config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_gamemode_changed(QString))); connect(m_config, SIGNAL(manual_gamemode_changed(bool)), ui_manual_gamemode, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(timeofday_changed(QString)), ui_timeofday, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(timeofday_changed(QString)), this, SLOT(on_theme_changed(QString))); connect(m_config, SIGNAL(manual_timeofday_changed(bool)), ui_manual_timeofday, SLOT(setChecked(bool))); connect(m_config, SIGNAL(showname_changed(QString)), ui_showname, SLOT(setText(QString))); connect(m_config, SIGNAL(showname_placeholder_changed(QString)), this, @@ -409,6 +409,30 @@ void AOConfigPanel::on_reload_theme_clicked() Q_EMIT reload_theme(); } +void AOConfigPanel::on_theme_changed(QString p_name) +{ + refresh_theme_list(); + refresh_gamemode_list(); + refresh_timeofday_list(); + ui_theme->setCurrentText(p_name); +} + +void AOConfigPanel::on_gamemode_changed(QString p_name) +{ + refresh_theme_list(); + refresh_gamemode_list(); + refresh_timeofday_list(); + ui_gamemode->setCurrentText(p_name); +} + +void AOConfigPanel::on_timeofday_changed(QString p_name) +{ + refresh_theme_list(); + refresh_gamemode_list(); + refresh_timeofday_list(); + ui_timeofday->setCurrentText(p_name); +} + void AOConfigPanel::on_gamemode_index_changed(QString p_text) { Q_UNUSED(p_text); From 49b2a8fd78b7f001c38cab0a255b297d4ad1dd81 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 17 Jun 2021 06:32:55 +0200 Subject: [PATCH 451/842] Restore character select visibility after reloading a theme --- src/courtroom.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 1164c60a6..9c0e79d91 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -105,7 +105,9 @@ void Courtroom::setup_courtroom() // and cycling through them if the buttons that are supposed to be displayed // do not exist It will also take care of free blocks + const bool l_chr_select_visible = ui_char_select_background->isVisible(); set_widgets(); + ui_char_select_background->setVisible(l_chr_select_visible); update_iniswap_list(); From 5c7bceb17516547254a6d462a7e47fc33c9a5a69 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 17 Jun 2021 21:14:55 -0400 Subject: [PATCH 452/842] Optimize effect/shout button path checking --- include/aobutton.h | 1 + src/aobutton.cpp | 5 +++++ src/courtroom.cpp | 10 +++++----- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/include/aobutton.h b/include/aobutton.h index 9c2daa093..f37426735 100644 --- a/include/aobutton.h +++ b/include/aobutton.h @@ -13,6 +13,7 @@ class AOButton : public QPushButton AOButton(QWidget *parent, AOApplication *p_ao_app); QString get_image(); + bool has_image(); void set_image(QString p_image); private: diff --git a/src/aobutton.cpp b/src/aobutton.cpp index 5f8e74b0f..bbea80f4b 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -16,6 +16,11 @@ QString AOButton::get_image() return m_image; } +bool AOButton::has_image() +{ + return (!m_image.isEmpty()); +} + void AOButton::set_image(QString p_image) { m_image = ao_app->find_theme_asset_path(p_image); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d54fb71ba..cd0c87b7c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2158,7 +2158,7 @@ void Courtroom::on_shout_button_toggled(const bool p_checked) const QString l_image_name(QString("%1%2.png").arg(l_name, QString(p_checked ? "_selected" : nullptr))); l_button->set_image(l_image_name); - if (ao_app->find_theme_asset_path(l_image_name).isEmpty()) + if (!l_button->has_image()) l_button->setText(l_name); } @@ -2238,8 +2238,8 @@ void Courtroom::cycle_wtce(int p_delta) * @brief Set the sprites of the effect buttons, and mark the currently * selected effect as such. * - * @details If a sprite cannot be found for a shout button, a regular - * push button is displayed for it with its shout name instead. + * @details If a sprite cannot be found for an effect button, a regular + * push button is displayed for it with its effect name instead. */ void Courtroom::reset_effect_buttons() { @@ -2283,7 +2283,7 @@ void Courtroom::on_effect_button_toggled(const bool p_checked) const QString l_image_name(QString("%1%2.png").arg(l_name, QString(p_checked ? "_pressed" : nullptr))); l_button->set_image(l_image_name); - if (ao_app->find_theme_asset_path(l_image_name).isEmpty()) + if (!l_button->has_image()) l_button->setText(l_name); } @@ -2373,7 +2373,7 @@ void Courtroom::reset_wtce_buttons() const QString l_file = l_name + ".png"; AOButton *l_button = ui_wtce.at(i); l_button->set_image(l_file); - l_button->setText(ao_app->find_theme_asset_path(l_file).isEmpty() ? l_name : nullptr); + l_button->setText(!l_button->has_image() ? l_name : nullptr); } m_wtce_current = 0; From b8ae0eb9b0719b9faaf8371d2c6d3149739010c9 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 18 Jun 2021 09:51:05 +0200 Subject: [PATCH 453/842] Fixes for sfx list and emote list, tweaks for emote list * Fixed sfx color not updating on theme reload * Improved sfx recolor performance * Fixed current emote selection resetting on theme reload * Tweaked emote list to scroll to the currently selected emote on theme reload --- dronline-client.pro | 1 + include/courtroom.h | 35 ++++++---- src/courtroom.cpp | 122 +++------------------------------ src/courtroom_character.cpp | 20 +++++- src/courtroom_sfx.cpp | 131 ++++++++++++++++++++++++++++++++++++ src/courtroom_widgets.cpp | 7 +- src/emotes.cpp | 43 +++++------- 7 files changed, 206 insertions(+), 153 deletions(-) create mode 100644 src/courtroom_sfx.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 6b825cba2..d87c753f1 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -100,6 +100,7 @@ SOURCES += \ src/commondefs.cpp \ src/courtroom.cpp \ src/courtroom_character.cpp \ + src/courtroom_sfx.cpp \ src/courtroom_widgets.cpp \ src/datatypes.cpp \ src/debug_functions.cpp \ diff --git a/include/courtroom.h b/include/courtroom.h index 76db92bef..9e977f549 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -118,6 +118,7 @@ class Courtroom : public QMainWindow QString get_current_character(); void update_iniswap_list(); void update_default_iniswap_item(); + void select_base_character_iniswap(); // Set the showname of the client void set_showname(QString p_showname); @@ -134,12 +135,6 @@ class Courtroom : public QMainWindow void list_areas(); - QString current_sfx_file(); - void load_character_sfx_list(); - void update_sfx_widget_list(); - void select_default_sfx(); - void clear_sfx_selection(); - void list_note_files(); void set_note_files(); @@ -538,10 +533,10 @@ class Courtroom : public QMainWindow void set_char_select_page(); void construct_emotes(); - void reconstruct_emotes(); + void construct_emote_page_layout(); void reset_emote_page(); - void set_emote_page(); - void set_emote_dropdown(); + void refresh_emote_page(const bool scroll_to_current_emote = false); + void fill_emote_dropdown(); DREmote get_emote(const int id); DREmote get_current_emote(); @@ -591,9 +586,6 @@ private slots: void on_music_list_double_clicked(QModelIndex p_model); void on_area_list_double_clicked(QModelIndex p_model); - void on_sfx_search_editing_finished(); - void on_sfx_widget_list_row_changed(); - void select_emote(int p_id); void on_emote_clicked(int p_id); @@ -708,6 +700,25 @@ public slots: // character id, which index of the char_list the player is CharacterId m_chr_id = SpectatorId; + // sfx +public: + std::optional current_sfx(); + QString current_sfx_file(); + void load_current_character_sfx_list(); + void load_sfx_list_theme(); + void select_default_sfx(); + void clear_sfx_selection(); + void update_all_sfx_item_color(); + +public slots: + void filter_sfx_list(); + +private: + void set_sfx_item_color(QListWidgetItem *item); + +private slots: + void _p_sfxCurrentItemChanged(QListWidgetItem *current_item, QListWidgetItem *previous_item); + /*! * ============================================================================= * AUDIO SYSTEM diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 9c0e79d91..d63b52403 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -92,6 +92,7 @@ void Courtroom::setup_courtroom() load_effects(); load_wtce(); load_free_blocks(); + load_sfx_list_theme(); // setup chat update_ic_log(true); @@ -152,8 +153,7 @@ void Courtroom::enter_courtroom(int p_cid) // unmute audio suppress_audio(false); - const int l_prev_emote_id = m_emote_id; - const int l_prev_emote_page = m_current_emote_page; + const QString l_prev_chr_name = get_current_character(); // character ================================================================= const bool l_changed_chr_id = (m_chr_id != p_cid); @@ -168,7 +168,6 @@ void Courtroom::enter_courtroom(int p_cid) const int l_current_cursor_pos = l_current_field->cursorPosition(); const QString l_chr_name = get_current_character(); - if (is_spectating()) { ao_app->get_discord()->clear_character_name(); @@ -187,31 +186,30 @@ void Courtroom::enter_courtroom(int p_cid) ao_app->send_server_packet(DRPacket("chrini", l_content)); } } - const bool l_changed_chr = l_chr_name != get_current_character(); - QString l_current_chr = l_chr_name; - + const bool l_changed_chr = l_chr_name != l_prev_chr_name; if (l_changed_chr) set_character_position(ao_app->get_char_side(l_chr_name)); + select_base_character_iniswap(); const int l_prev_emote_count = m_emote_list.count(); - m_emote_list = ao_app->get_emote_list(l_current_chr); + m_emote_list = ao_app->get_emote_list(l_chr_name); const QString l_prev_emote = ui_emote_dropdown->currentText(); - set_emote_dropdown(); + fill_emote_dropdown(); if (l_changed_chr || l_prev_emote_count != m_emote_list.count()) { + m_emote_id = 0; + m_current_emote_page = 0; ui_pre->setChecked(ui_pre->isChecked() || ao_config->always_pre_enabled()); } else { - m_emote_id = l_prev_emote_id; - m_current_emote_page = l_prev_emote_page; ui_emote_dropdown->setCurrentText(l_prev_emote); } - set_emote_page(); + refresh_emote_page(); - load_character_sfx_list(); + load_current_character_sfx_list(); select_default_sfx(); ui_emotes->setHidden(is_spectating()); @@ -439,106 +437,6 @@ void Courtroom::list_areas() } } -QString Courtroom::current_sfx_file() -{ - QListWidgetItem *l_item = ui_sfx_list->currentItem(); - if (l_item == nullptr) - return nullptr; - const QString l_file = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).file; - return l_file == m_sfx_default_file ? get_current_emote().sound_file : l_file; -} - -void Courtroom::load_character_sfx_list() -{ - // colors - m_sfx_color_found = ao_app->get_color("found_song_color", COURTROOM_DESIGN_INI); - m_sfx_color_missing = ao_app->get_color("missing_song_color", COURTROOM_DESIGN_INI); - - // items - m_sfx_list.clear(); - m_sfx_list.append(DRSfx("Default", m_sfx_default_file)); - m_sfx_list.append(DRSfx("Silence", nullptr)); - - const QStringList l_sfx_list = ao_app->get_sfx_list(); - for (const QString &i_sfx_line : l_sfx_list) - { - const QStringList l_sfx_entry = i_sfx_line.split("=", DR::SkipEmptyParts); - - const QString l_name = l_sfx_entry.at(l_sfx_entry.size() - 1).trimmed(); - const QString l_file = QString(l_sfx_entry.size() >= 2 ? l_sfx_entry.at(0) : nullptr).trimmed(); - const bool l_is_found = !ao_app->find_asset_path({ao_app->get_sounds_path(l_file)}, audio_extensions()).isEmpty(); - m_sfx_list.append(DRSfx(l_name, l_file, l_is_found)); - } - - update_sfx_widget_list(); -} - -void Courtroom::update_sfx_widget_list() -{ - QSignalBlocker l_blocker(ui_sfx_list); - ui_sfx_list->clear(); - - const QString l_name_filter = ui_sfx_search->text(); - for (int i = 0; i < m_sfx_list.length(); ++i) - { - const DRSfx &i_sfx = m_sfx_list.at(i); - if (!i_sfx.name.contains(l_name_filter, Qt::CaseInsensitive)) - continue; - QListWidgetItem *l_item = new QListWidgetItem; - l_item->setText(i_sfx.name); - l_item->setData(Qt::UserRole, i); - ui_sfx_list->addItem(l_item); - } - - on_sfx_widget_list_row_changed(); -} - -void Courtroom::select_default_sfx() -{ - if (ui_sfx_list->count() == 0) - return; - ui_sfx_list->setCurrentRow(0); -} - -void Courtroom::clear_sfx_selection() -{ - ui_sfx_list->setCurrentRow(-1); -} - -void Courtroom::on_sfx_search_editing_finished() -{ - load_character_sfx_list(); -} - -void Courtroom::on_sfx_widget_list_row_changed() -{ - const int p_current_row = ui_sfx_list->currentRow(); - - for (int i = 0; i < ui_sfx_list->count(); ++i) - { - QListWidgetItem *l_item = ui_sfx_list->item(i); - const bool l_is_found = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).is_found; - - QColor i_color = l_is_found ? m_sfx_color_found : m_sfx_color_missing; - if (i == p_current_row) - { - ui_pre->setChecked(ui_pre->isChecked() || l_is_found); - - // Calculate the amount of lightness it would take to light up the row. We - // also limit it to 1.0, as giving lightness values above 1.0 to QColor does - // nothing. +0.4 is just an arbitrarily chosen number. - const double l_final_lightness = qMin(1.0, i_color.lightnessF() + 0.4); - - // This is just the reverse of the above, basically. We set the colour, and we - // set the brush to have that colour. - i_color.setHslF(i_color.hueF(), i_color.saturationF(), l_final_lightness); - } - - l_item->setBackground(i_color); - } - ui_ic_chat_message->setFocus(); -} - void Courtroom::list_note_files() { QString f_config = ao_app->get_base_path() + CONFIG_FILESABSTRACT_INI; diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index 9edf8cd55..502cc63df 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -73,10 +73,28 @@ void Courtroom::update_iniswap_list() drSetItemIcon(ui_iniswap_dropdown, i, i_name, ao_app); } update_default_iniswap_item(); - ui_iniswap_dropdown->setCurrentText(get_current_character()); + + select_base_character_iniswap(); } void Courtroom::update_default_iniswap_item() { drSetItemIcon(ui_iniswap_dropdown, 0, get_base_character(), ao_app); } + +void Courtroom::select_base_character_iniswap() +{ + const QString l_current_chr = get_current_character(); + if (get_base_character() == l_current_chr) + { + ui_iniswap_dropdown->setCurrentIndex(0); + return; + } + ui_iniswap_dropdown->setCurrentText(l_current_chr); +} + +void Courtroom::on_iniswap_dropdown_changed(int p_index) +{ + ao_config->set_character_ini(get_base_character(), + p_index == 0 ? get_base_character() : ui_iniswap_dropdown->itemText(p_index)); +} diff --git a/src/courtroom_sfx.cpp b/src/courtroom_sfx.cpp new file mode 100644 index 000000000..7cf4a140d --- /dev/null +++ b/src/courtroom_sfx.cpp @@ -0,0 +1,131 @@ +#include "courtroom.h" + +#include "aoapplication.h" +#include "commondefs.h" +#include "file_functions.h" + +#include +#include +#include +#include + +#include + +std::optional Courtroom::current_sfx() +{ + QListWidgetItem *l_item = ui_sfx_list->currentItem(); + if (l_item == nullptr) + return std::nullopt; + return m_sfx_list.at(l_item->data(Qt::UserRole).toInt()); +} + +QString Courtroom::current_sfx_file() +{ + const QString l_current_emote_file = get_current_emote().sound_file; + const std::optional l_optional_sfx = current_sfx(); + if (!l_optional_sfx.has_value()) + return l_current_emote_file; + const QString l_file = l_optional_sfx.value().file; + return l_file == m_sfx_default_file ? l_current_emote_file : l_file; +} + +void Courtroom::load_sfx_list_theme() +{ + m_sfx_color_found = ao_app->get_color("found_song_color", COURTROOM_DESIGN_INI); + m_sfx_color_missing = ao_app->get_color("missing_song_color", COURTROOM_DESIGN_INI); + update_all_sfx_item_color(); +} + +void Courtroom::load_current_character_sfx_list() +{ + QSignalBlocker l_blocker(ui_sfx_list); + ui_sfx_list->clear(); + + // items + m_sfx_list.clear(); + m_sfx_list.append(DRSfx("Default", m_sfx_default_file)); + m_sfx_list.append(DRSfx("Silence", nullptr)); + + const QStringList l_sfx_list = ao_app->get_sfx_list(); + for (const QString &i_sfx_line : l_sfx_list) + { + const QStringList l_sfx_entry = i_sfx_line.split("=", DR::SkipEmptyParts); + + const QString l_name = l_sfx_entry.at(l_sfx_entry.size() - 1).trimmed(); + const QString l_file = QString(l_sfx_entry.size() >= 2 ? l_sfx_entry.at(0) : nullptr).trimmed(); + const bool l_is_found = !ao_app->find_asset_path({ao_app->get_sounds_path(l_file)}, audio_extensions()).isEmpty(); + m_sfx_list.append(DRSfx(l_name, l_file, l_is_found)); + } + + // create items + QList l_item_list; + for (int i = 0; i < m_sfx_list.length(); ++i) + { + const DRSfx &i_sfx = m_sfx_list.at(i); + QListWidgetItem *l_item = new QListWidgetItem(ui_sfx_list); + l_item_list.append(l_item); + l_item->setText(i_sfx.name); + l_item->setData(Qt::UserRole, i); + set_sfx_item_color(l_item); + } + + filter_sfx_list(); +} + +void Courtroom::filter_sfx_list() +{ + const QString l_filter = ui_sfx_search->text(); + for (int i = 0; i < ui_sfx_list->count(); ++i) + { + QListWidgetItem *i_item = ui_sfx_list->item(i); + i_item->setHidden(!i_item->text().contains(l_filter, Qt::CaseInsensitive)); + } +} + +void Courtroom::select_default_sfx() +{ + if (ui_sfx_list->count() == 0) + return; + ui_sfx_list->setCurrentRow(0); +} + +void Courtroom::clear_sfx_selection() +{ + ui_sfx_list->setCurrentRow(-1); +} + +void Courtroom::set_sfx_item_color(QListWidgetItem *p_item) +{ + if (p_item == nullptr) + return; + const bool l_is_found = m_sfx_list.at(p_item->data(Qt::UserRole).toInt()).is_found; + QColor l_color = l_is_found ? m_sfx_color_found : m_sfx_color_missing; + + if (p_item == ui_sfx_list->currentItem()) + { + // Calculate the amount of lightness it would take to light up the row. We + // also limit it to 1.0, as giving lightness values above 1.0 to QColor does + // nothing. +0.4 is just an arbitrarily chosen number. + const double l_final_lightness = qMin(1.0, l_color.lightnessF() + 0.4); + + // This is just the reverse of the above, basically. We set the colour, and we + // set the brush to have that colour. + l_color.setHslF(l_color.hueF(), l_color.saturationF(), l_final_lightness); + } + + p_item->setBackground(l_color); +} + +void Courtroom::update_all_sfx_item_color() +{ + for (int i = 0; i < m_sfx_list.length(); ++i) + set_sfx_item_color(ui_sfx_list->item(i)); +} + +void Courtroom::_p_sfxCurrentItemChanged(QListWidgetItem *p_current_item, QListWidgetItem *p_previous_item) +{ + set_sfx_item_color(p_current_item); + set_sfx_item_color(p_previous_item); + ui_pre->setChecked(ui_pre->isChecked() || current_sfx().has_value()); + ui_ic_chat_message->setFocus(); +} diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index b95575805..83b3ebe0e 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -310,7 +310,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited())); - connect(ui_sfx_search, SIGNAL(editingFinished()), this, SLOT(on_sfx_search_editing_finished())); + connect(ui_sfx_search, SIGNAL(editingFinished()), this, SLOT(filter_sfx_list())); connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); @@ -326,7 +326,8 @@ void Courtroom::connect_widgets() connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); - connect(ui_sfx_list, SIGNAL(currentRowChanged(int)), this, SLOT(on_sfx_widget_list_row_changed())); + connect(ui_sfx_list, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, + SLOT(_p_sfxCurrentItemChanged(QListWidgetItem *, QListWidgetItem *))); connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); @@ -677,7 +678,7 @@ void Courtroom::set_widgets() // emotes set_size_and_pos(ui_emotes, "emotes", COURTROOM_DESIGN_INI, ao_app); - reconstruct_emotes(); + construct_emote_page_layout(); set_size_and_pos(ui_emote_left, "emote_left", COURTROOM_DESIGN_INI, ao_app); ui_emote_left->set_image("arrow_left.png"); diff --git a/src/emotes.cpp b/src/emotes.cpp index 0d739066e..eaa24907f 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -29,10 +29,10 @@ void Courtroom::construct_emotes() ui_pos_dropdown->addItem("hld", "hld"); ui_pos_dropdown->addItem("hlp", "hlp"); - reconstruct_emotes(); + construct_emote_page_layout(); } -void Courtroom::reconstruct_emotes() +void Courtroom::construct_emote_page_layout() { // delete previous buttons while (!ui_emote_list.isEmpty()) @@ -78,24 +78,19 @@ void Courtroom::reconstruct_emotes() } } - reset_emote_page(); + refresh_emote_page(true); } void Courtroom::reset_emote_page() { - m_current_emote_page = 0; m_emote_id = 0; - - if (is_spectating()) - ui_emotes->hide(); - else - ui_emotes->show(); - - set_emote_page(); - set_emote_dropdown(); + m_current_emote_page = 0; + if (ui_emote_dropdown->count()) + ui_emote_dropdown->setCurrentIndex(m_emote_id); + refresh_emote_page(true); } -void Courtroom::set_emote_page() +void Courtroom::refresh_emote_page(const bool p_scroll_to_current_emote) { ui_emote_left->hide(); ui_emote_right->hide(); @@ -109,7 +104,11 @@ void Courtroom::set_emote_page() const int l_page_count = qFloor(l_emote_count / m_page_max_emote_count) + bool(l_emote_count % m_page_max_emote_count); + + if (p_scroll_to_current_emote) + m_current_emote_page = m_emote_id / m_page_max_emote_count; m_current_emote_page = qBound(0, m_current_emote_page, l_page_count - 1); + const int l_current_page_emote_count = qBound(0, l_emote_count - m_current_emote_page * m_page_max_emote_count, m_page_max_emote_count); @@ -128,7 +127,7 @@ void Courtroom::set_emote_page() } } -void Courtroom::set_emote_dropdown() +void Courtroom::fill_emote_dropdown() { QSignalBlocker l_blocker(ui_emote_dropdown); ui_emote_dropdown->clear(); @@ -139,11 +138,11 @@ void Courtroom::set_emote_dropdown() ui_emote_dropdown->addItems(l_emote_list); } -DREmote Courtroom::get_emote(const int id) +DREmote Courtroom::get_emote(const int p_emote_id) { - if (id < 0 || id >= m_emote_list.length()) + if (p_emote_id < 0 || p_emote_id >= m_emote_list.length()) return DREmote(); - return m_emote_list.at(id); + return m_emote_list.at(p_emote_id); } DREmote Courtroom::get_current_emote() @@ -189,7 +188,7 @@ void Courtroom::on_emote_left_clicked() { --m_current_emote_page; - set_emote_page(); + refresh_emote_page(); ui_ic_chat_message->setFocus(); } @@ -198,7 +197,7 @@ void Courtroom::on_emote_right_clicked() { ++m_current_emote_page; - set_emote_page(); + refresh_emote_page(); ui_ic_chat_message->setFocus(); } @@ -207,9 +206,3 @@ void Courtroom::on_emote_dropdown_changed(int p_index) { select_emote(p_index); } - -void Courtroom::on_iniswap_dropdown_changed(int p_index) -{ - ao_config->set_character_ini(get_base_character(), - p_index == 0 ? get_base_character() : ui_iniswap_dropdown->itemText(p_index)); -} From 2d77ff3255bf353e80084d2d36f7a67c031e7174 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 18 Jun 2021 16:33:16 +0200 Subject: [PATCH 454/842] Merge #190 * Pull Request #192 --- dronline-client.pro | 2 + include/aoconfigpanel.h | 3 + include/courtroom.h | 87 +++++----- src/aoapplication.cpp | 1 - src/aoconfigpanel.cpp | 37 ++++- src/charselect.cpp | 11 +- src/courtroom.cpp | 314 +++++++----------------------------- src/courtroom_character.cpp | 100 ++++++++++++ src/courtroom_sfx.cpp | 131 +++++++++++++++ src/courtroom_widgets.cpp | 53 +----- src/emotes.cpp | 43 +++-- src/server_socket.cpp | 15 -- 12 files changed, 398 insertions(+), 399 deletions(-) create mode 100644 src/courtroom_character.cpp create mode 100644 src/courtroom_sfx.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 4d40c8035..d87c753f1 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -99,6 +99,8 @@ SOURCES += \ src/charselect.cpp \ src/commondefs.cpp \ src/courtroom.cpp \ + src/courtroom_character.cpp \ + src/courtroom_sfx.cpp \ src/courtroom_widgets.cpp \ src/datatypes.cpp \ src/debug_functions.cpp \ diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 94c04d784..b9580a48a 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -42,6 +42,9 @@ public slots: private slots: void on_reload_theme_clicked(); + void on_theme_changed(QString); + void on_gamemode_changed(QString); + void on_timeofday_changed(QString); void on_gamemode_index_changed(QString p_text); void on_timeofday_index_changed(QString p_text); void on_showname_placeholder_changed(QString p_text); diff --git a/include/courtroom.h b/include/courtroom.h index ca535429a..9e977f549 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -97,22 +97,15 @@ class Courtroom : public QMainWindow // called when a DONE#% from the server was received void done_received(); - // sets the local mute list based on characters available on the server - void set_mute_list(); - // sets desk and bg based on pos in chatmessage void set_scene(); // sets text color based on text color in chatmessage void set_text_color(); - // takes in serverD-formatted IP list as prints a converted version to server - // OOC admittedly poorly named - void set_ip_list(QString p_list); - // disables chat if current cid matches second argument // enables if p_muted is false - void set_mute(bool p_muted, int p_cid); + void set_muted(bool p_muted, int p_cid); // send a message that the player is banned and quits the server void set_ban(int p_cid); @@ -120,14 +113,19 @@ class Courtroom : public QMainWindow // implementations in path_functions.cpp QString get_background_path(QString p_file); - // cid = character id, returns the cid of the currently selected character - int get_character_id(); +public: QString get_base_character(); QString get_current_character(); + void update_iniswap_list(); + void update_default_iniswap_item(); + void select_base_character_iniswap(); // Set the showname of the client void set_showname(QString p_showname); + // sets up widgets + void setup_courtroom(); + // properly sets up some varibles: resets user state void enter_courtroom(int p_cid); @@ -137,12 +135,6 @@ class Courtroom : public QMainWindow void list_areas(); - QString current_sfx_file(); - void update_sfx_list(); - void update_sfx_widget_list(); - void select_default_sfx(); - void clear_sfx_selection(); - void list_note_files(); void set_note_files(); @@ -285,11 +277,6 @@ class Courtroom : public QMainWindow QString m_message_color_name; QStack m_message_color_stack; - // char id, muted or not - QMap mute_map; - - // QVector muted_cids; - bool is_client_muted = false; // state of animation, 0 = objecting, 1 = preanim, 2 = talking, 3 = idle @@ -299,8 +286,6 @@ class Courtroom : public QMainWindow // ticking done int text_state = 2; - // character id, which index of the char_list the player is - int m_chr_id = -1; // if enabled, disable showing our own sprites when we talk in ic bool m_msg_is_first_person = false; @@ -396,7 +381,6 @@ class Courtroom : public QMainWindow AOTextArea *ui_ooc_chatlog = nullptr; - QListWidget *ui_mute_list = nullptr; QListWidget *ui_area_list = nullptr; QListWidget *ui_music_list = nullptr; @@ -490,8 +474,6 @@ class Courtroom : public QMainWindow AOButton *ui_effect_flash = nullptr; AOButton *ui_effect_gloom = nullptr; - AOButton *ui_mute = nullptr; - AOButton *ui_defense_plus = nullptr; AOButton *ui_defense_minus = nullptr; @@ -551,10 +533,10 @@ class Courtroom : public QMainWindow void set_char_select_page(); void construct_emotes(); - void reconstruct_emotes(); + void construct_emote_page_layout(); void reset_emote_page(); - void set_emote_page(); - void set_emote_dropdown(); + void refresh_emote_page(const bool scroll_to_current_emote = false); + void fill_emote_dropdown(); DREmote get_emote(const int id); DREmote get_current_emote(); @@ -588,11 +570,9 @@ private slots: void next_chat_letter(); void post_chat(); - void on_mute_list_item_changed(QListWidgetItem *p_item); - void on_showname_changed(QString); void on_showname_placeholder_changed(QString); - void on_character_ini_changed(QString); + void on_character_ini_changed(); void on_ic_showname_editing_finished(); void on_ic_message_return_pressed(); void on_chat_config_changed(); @@ -606,9 +586,6 @@ private slots: void on_music_list_double_clicked(QModelIndex p_model); void on_area_list_double_clicked(QModelIndex p_model); - void on_sfx_search_editing_finished(); - void on_sfx_widget_list_row_changed(); - void select_emote(int p_id); void on_emote_clicked(int p_id); @@ -658,8 +635,6 @@ private slots: void on_effect_button_clicked(const bool); void on_effect_button_toggled(const bool); - void on_mute_clicked(); - void on_defense_minus_clicked(); void on_defense_plus_clicked(); void on_prosecution_minus_clicked(); @@ -706,6 +681,44 @@ private slots: void ping_server(); + // character + // =========================================================================== +public: + using CharacterId = int; + enum : CharacterId + { + SpectatorId = -1, + }; + + int get_character_id(); +public slots: + void set_character_id(const int); +signals: + void character_id_changed(int); + +private: + // character id, which index of the char_list the player is + CharacterId m_chr_id = SpectatorId; + + // sfx +public: + std::optional current_sfx(); + QString current_sfx_file(); + void load_current_character_sfx_list(); + void load_sfx_list_theme(); + void select_default_sfx(); + void clear_sfx_selection(); + void update_all_sfx_item_color(); + +public slots: + void filter_sfx_list(); + +private: + void set_sfx_item_color(QListWidgetItem *item); + +private slots: + void _p_sfxCurrentItemChanged(QListWidgetItem *current_item, QListWidgetItem *previous_item); + /*! * ============================================================================= * AUDIO SYSTEM diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index b83c9b6cd..aecda6046 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -37,7 +37,6 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(ao_config, SIGNAL(timeofday_changed(QString)), this, SLOT(handle_theme_modification())); connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(handle_theme_modification())); - connect(this, SIGNAL(reload_theme()), ao_config_panel, SLOT(handle_theme_modification())); ao_config_panel->hide(); dr_discord->set_presence(ao_config->discord_presence()); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 4e6092ac5..8039f4ff2 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -108,10 +108,10 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(discord_hide_character_changed(bool)), ui_discord_hide_character, SLOT(setChecked(bool))); // game - connect(m_config, SIGNAL(theme_changed(QString)), ui_theme, SLOT(setCurrentText(QString))); - connect(m_config, SIGNAL(gamemode_changed(QString)), ui_gamemode, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(theme_changed(QString)), this, SLOT(on_theme_changed(QString))); + connect(m_config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_gamemode_changed(QString))); connect(m_config, SIGNAL(manual_gamemode_changed(bool)), ui_manual_gamemode, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(timeofday_changed(QString)), ui_timeofday, SLOT(setCurrentText(QString))); + connect(m_config, SIGNAL(timeofday_changed(QString)), this, SLOT(on_theme_changed(QString))); connect(m_config, SIGNAL(manual_timeofday_changed(bool)), ui_manual_timeofday, SLOT(setChecked(bool))); connect(m_config, SIGNAL(showname_changed(QString)), ui_showname, SLOT(setText(QString))); connect(m_config, SIGNAL(showname_placeholder_changed(QString)), this, @@ -127,7 +127,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(log_format_use_newline_changed(bool)), ui_log_format_use_newline, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_display_empty_messages_changed(bool)), ui_log_display_empty_messages, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_display_music_switch_changed(bool)), ui_log_display_music_switch, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_music_switch_changed(bool)), ui_log_display_music_switch, + SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); connect(m_config, SIGNAL(log_is_recording_changed(bool)), ui_log_is_recording, SLOT(setChecked(bool))); @@ -140,7 +141,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(effect_ignore_suppression_changed(bool)), ui_effect_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(music_volume_changed(int)), ui_music, SLOT(setValue(int))); - connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), ui_music_ignore_suppression, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), ui_music_ignore_suppression, + SLOT(setChecked(bool))); connect(m_config, SIGNAL(blip_volume_changed(int)), ui_blip, SLOT(setValue(int))); connect(m_config, SIGNAL(blip_ignore_suppression_changed(bool)), ui_blip_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(blip_rate_changed(int)), ui_blip_rate, SLOT(setValue(int))); @@ -404,10 +406,33 @@ void AOConfigPanel::update_audio_device_list() void AOConfigPanel::on_reload_theme_clicked() { - qDebug() << "reload theme clicked"; Q_EMIT reload_theme(); } +void AOConfigPanel::on_theme_changed(QString p_name) +{ + refresh_theme_list(); + refresh_gamemode_list(); + refresh_timeofday_list(); + ui_theme->setCurrentText(p_name); +} + +void AOConfigPanel::on_gamemode_changed(QString p_name) +{ + refresh_theme_list(); + refresh_gamemode_list(); + refresh_timeofday_list(); + ui_gamemode->setCurrentText(p_name); +} + +void AOConfigPanel::on_timeofday_changed(QString p_name) +{ + refresh_theme_list(); + refresh_gamemode_list(); + refresh_timeofday_list(); + ui_timeofday->setCurrentText(p_name); +} + void AOConfigPanel::on_gamemode_index_changed(QString p_text) { Q_UNUSED(p_text); diff --git a/src/charselect.cpp b/src/charselect.cpp index 6b8d4905c..df28e47cc 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -168,15 +168,8 @@ void Courtroom::char_clicked(int n_char) return; } - if (n_real_char == m_chr_id) - { - enter_courtroom(m_chr_id); - } - else - { - ao_app->send_server_packet( - DRPacket("CC", {QString::number(ao_app->get_client_id()), QString::number(n_real_char), get_hdid()})); - } + ao_app->send_server_packet( + DRPacket("CC", {QString::number(ao_app->get_client_id()), QString::number(n_real_char), get_hdid()})); } void Courtroom::char_mouse_entered(AOCharButton *p_caller) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 3d2f7f418..d63b52403 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -61,6 +61,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() set_widgets(); set_char_select(); set_widget_names(); + setup_courtroom(); } Courtroom::~Courtroom() @@ -85,30 +86,19 @@ void Courtroom::set_music_list(QStringList p_music_list) list_music(); } -void Courtroom::enter_courtroom(int p_cid) +void Courtroom::setup_courtroom() { - qDebug() << "enter_courtroom"; - - // unmute audio - suppress_audio(false); + load_shouts(); + load_effects(); + load_wtce(); + load_free_blocks(); + load_sfx_list_theme(); - const int l_prev_emote_id = m_emote_id; - const int l_prev_emote_page = m_current_emote_page; + // setup chat + update_ic_log(true); - // widgets =================================================================== current_evidence_page = 0; current_evidence = 0; - - m_shout_state = 0; - m_shout_current = 0; - m_effect_state = 0; - m_effect_current = 0; - m_wtce_current = 0; - reset_wtce_buttons(); - - // setup chat - on_chat_config_changed(); - set_evidence_page(); // Update widgets first, then check if everything is valid @@ -116,16 +106,26 @@ void Courtroom::enter_courtroom(int p_cid) // and cycling through them if the buttons that are supposed to be displayed // do not exist It will also take care of free blocks + const bool l_chr_select_visible = ui_char_select_background->isVisible(); set_widgets(); + ui_char_select_background->setVisible(l_chr_select_visible); + + update_iniswap_list(); + m_shout_state = 0; + m_shout_current = 0; check_shouts(); if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) cycle_shout(1); + m_effect_state = 0; + m_effect_current = 0; check_effects(); if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) cycle_effect(1); + m_wtce_current = 0; + reset_wtce_buttons(); check_wtce(); if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) cycle_wtce(1); @@ -144,48 +144,30 @@ void Courtroom::enter_courtroom(int p_cid) for (AOTimer *i_timer : qAsConst(ui_timers)) i_timer->redraw(); +} - ui_char_select_background->hide(); +void Courtroom::enter_courtroom(int p_cid) +{ + qDebug() << "enter_courtroom"; + + // unmute audio + suppress_audio(false); + + const QString l_prev_chr_name = get_current_character(); // character ================================================================= const bool l_changed_chr_id = (m_chr_id != p_cid); m_chr_id = p_cid; + if (l_changed_chr_id) + update_default_iniswap_item(); + QLineEdit *l_current_field = ui_ic_chat_message; if (ui_ooc_chat_message->hasFocus()) l_current_field = ui_ooc_chat_message; const int l_current_cursor_pos = l_current_field->cursorPosition(); const QString l_chr_name = get_current_character(); - { // repopulate ini-swapper - QSignalBlocker b_ini_list(ui_iniswap_dropdown); - ui_iniswap_dropdown->clear(); - - QStringList l_name_list{"Default"}; - const QString l_path = ao_app->get_base_path() + "/characters"; - const QFileInfoList &l_info_list = QDir(l_path).entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); - for (const QFileInfo &i_info : l_info_list) - { - const QString l_name = i_info.fileName(); - if (get_base_character() == l_name) - continue; - if (!file_exists(ao_app->get_character_path(l_name, CHARACTER_CHAR_INI))) - continue; - l_name_list.append(l_name); - } - - QPixmap l_blank_image(64, 64); - l_blank_image.fill(Qt::transparent); - for (int i = 0; i < l_name_list.length(); ++i) - { - const QString &i_name = l_name_list.at(i); - const QString l_real_name = i == 0 ? get_base_character() : i_name; - const QString l_icon_file = ao_app->get_character_path(l_real_name, "char_icon.png"); - ui_iniswap_dropdown->addItem(file_exists(l_icon_file) ? QIcon(l_icon_file) : QIcon(l_blank_image), i_name); - } - ui_iniswap_dropdown->setCurrentText(get_current_character()); - } - if (is_spectating()) { ao_app->get_discord()->clear_character_name(); @@ -204,37 +186,30 @@ void Courtroom::enter_courtroom(int p_cid) ao_app->send_server_packet(DRPacket("chrini", l_content)); } } - const bool l_changed_chr = l_chr_name != get_current_character(); - QString l_current_chr = l_chr_name; + const bool l_changed_chr = l_chr_name != l_prev_chr_name; + if (l_changed_chr) + set_character_position(ao_app->get_char_side(l_chr_name)); + select_base_character_iniswap(); const int l_prev_emote_count = m_emote_list.count(); - m_emote_list = ao_app->get_emote_list(l_current_chr); + m_emote_list = ao_app->get_emote_list(l_chr_name); const QString l_prev_emote = ui_emote_dropdown->currentText(); - set_emote_dropdown(); + fill_emote_dropdown(); if (l_changed_chr || l_prev_emote_count != m_emote_list.count()) { + m_emote_id = 0; + m_current_emote_page = 0; ui_pre->setChecked(ui_pre->isChecked() || ao_config->always_pre_enabled()); } else { - m_emote_id = l_prev_emote_id; - m_current_emote_page = l_prev_emote_page; ui_emote_dropdown->setCurrentText(l_prev_emote); } - set_emote_page(); + refresh_emote_page(); - // Refresh character position. If the character was changed, use the new - // position, otherwise use the old one. Even if the else is useless now (it - // can be omitted), I am keeping it in case we expand set_character_position - // to do more. - if (l_changed_chr_id) - set_character_position(ao_app->get_char_side(l_chr_name)); - else - set_character_position(ui_pos_dropdown->currentText()); - - update_sfx_list(); + load_current_character_sfx_list(); select_default_sfx(); ui_emotes->setHidden(is_spectating()); @@ -249,18 +224,18 @@ void Courtroom::enter_courtroom(int p_cid) const QString l_showname = ao_config->showname(); if (!l_showname.isEmpty() && !is_first_showname_sent) send_showname_packet(l_showname); + + ui_char_select_background->hide(); } void Courtroom::done_received() { - m_chr_id = -1; + m_chr_id = SpectatorId; suppress_audio(true); set_char_select_page(); - set_mute_list(); - set_char_select(); show(); @@ -462,106 +437,6 @@ void Courtroom::list_areas() } } -QString Courtroom::current_sfx_file() -{ - QListWidgetItem *l_item = ui_sfx_list->currentItem(); - if (l_item == nullptr) - return nullptr; - const QString l_file = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).file; - return l_file == m_sfx_default_file ? get_current_emote().sound_file : l_file; -} - -void Courtroom::update_sfx_list() -{ - // colors - m_sfx_color_found = ao_app->get_color("found_song_color", COURTROOM_DESIGN_INI); - m_sfx_color_missing = ao_app->get_color("missing_song_color", COURTROOM_DESIGN_INI); - - // items - m_sfx_list.clear(); - m_sfx_list.append(DRSfx("Default", m_sfx_default_file)); - m_sfx_list.append(DRSfx("Silence", nullptr)); - - const QStringList l_sfx_list = ao_app->get_sfx_list(); - for (const QString &i_sfx_line : l_sfx_list) - { - const QStringList l_sfx_entry = i_sfx_line.split("=", DR::SkipEmptyParts); - - const QString l_name = l_sfx_entry.at(l_sfx_entry.size() - 1).trimmed(); - const QString l_file = QString(l_sfx_entry.size() >= 2 ? l_sfx_entry.at(0) : nullptr).trimmed(); - const bool l_is_found = !ao_app->find_asset_path({ao_app->get_sounds_path(l_file)}, audio_extensions()).isEmpty(); - m_sfx_list.append(DRSfx(l_name, l_file, l_is_found)); - } - - update_sfx_widget_list(); -} - -void Courtroom::update_sfx_widget_list() -{ - QSignalBlocker l_blocker(ui_sfx_list); - ui_sfx_list->clear(); - - const QString l_name_filter = ui_sfx_search->text(); - for (int i = 0; i < m_sfx_list.length(); ++i) - { - const DRSfx &i_sfx = m_sfx_list.at(i); - if (!i_sfx.name.contains(l_name_filter, Qt::CaseInsensitive)) - continue; - QListWidgetItem *l_item = new QListWidgetItem; - l_item->setText(i_sfx.name); - l_item->setData(Qt::UserRole, i); - ui_sfx_list->addItem(l_item); - } - - on_sfx_widget_list_row_changed(); -} - -void Courtroom::select_default_sfx() -{ - if (ui_sfx_list->count() == 0) - return; - ui_sfx_list->setCurrentRow(0); -} - -void Courtroom::clear_sfx_selection() -{ - ui_sfx_list->setCurrentRow(-1); -} - -void Courtroom::on_sfx_search_editing_finished() -{ - update_sfx_list(); -} - -void Courtroom::on_sfx_widget_list_row_changed() -{ - const int p_current_row = ui_sfx_list->currentRow(); - - for (int i = 0; i < ui_sfx_list->count(); ++i) - { - QListWidgetItem *l_item = ui_sfx_list->item(i); - const bool l_is_found = m_sfx_list.at(l_item->data(Qt::UserRole).toInt()).is_found; - - QColor i_color = l_is_found ? m_sfx_color_found : m_sfx_color_missing; - if (i == p_current_row) - { - ui_pre->setChecked(ui_pre->isChecked() || l_is_found); - - // Calculate the amount of lightness it would take to light up the row. We - // also limit it to 1.0, as giving lightness values above 1.0 to QColor does - // nothing. +0.4 is just an arbitrarily chosen number. - const double l_final_lightness = qMin(1.0, i_color.lightnessF() + 0.4); - - // This is just the reverse of the above, basically. We set the colour, and we - // set the brush to have that colour. - i_color.setHslF(i_color.hueF(), i_color.saturationF(), l_final_lightness); - } - - l_item->setBackground(i_color); - } - ui_ic_chat_message->setFocus(); -} - void Courtroom::list_note_files() { QString f_config = ao_app->get_base_path() + CONFIG_FILESABSTRACT_INI; @@ -674,7 +549,7 @@ void Courtroom::on_showname_changed(QString p_showname) bool Courtroom::is_spectating() { - return m_chr_id == -1; + return m_chr_id == SpectatorId; } void Courtroom::on_showname_placeholder_changed(QString p_showname_placeholder) @@ -684,9 +559,8 @@ void Courtroom::on_showname_placeholder_changed(QString p_showname_placeholder) ui_ic_chat_showname->setToolTip(l_showname); } -void Courtroom::on_character_ini_changed(QString p_base_chr) +void Courtroom::on_character_ini_changed() { - Q_UNUSED(p_base_chr) enter_courtroom(m_chr_id); } @@ -838,7 +712,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) int f_char_id = m_chatmessage[CMChrId].toInt(); - if (f_char_id == -1) + if (f_char_id == SpectatorId) { is_system_speaking = true; m_chatmessage[CMChrId] = "0"; @@ -850,9 +724,6 @@ void Courtroom::handle_chatmessage(QStringList p_contents) if (f_char_id < 0 || f_char_id >= m_chr_list.size()) return; - if (mute_map.value(m_chatmessage[CMChrId].toInt())) - return; - const QString l_message = QString(m_chatmessage[CMMessage]) .remove(QRegularExpression("(?append(f_list); -} - -void Courtroom::set_mute(bool p_muted, int p_cid) +void Courtroom::set_muted(bool p_muted, int p_cid) { - if (p_cid != m_chr_id && p_cid != -1) + if (p_cid != m_chr_id && p_cid != SpectatorId) return; if (p_muted) @@ -1753,7 +1617,7 @@ void Courtroom::set_mute(bool p_muted, int p_cid) void Courtroom::set_ban(int p_cid) { - if (p_cid != m_chr_id && p_cid != -1) + if (p_cid != m_chr_id && p_cid != SpectatorId) return; call_notice("You have been banned."); @@ -1762,21 +1626,6 @@ void Courtroom::set_ban(int p_cid) ao_app->destruct_courtroom(); } -int Courtroom::get_character_id() -{ - return m_chr_id; -} - -QString Courtroom::get_base_character() -{ - return is_spectating() ? nullptr : m_chr_list.at(m_chr_id).name; -} - -QString Courtroom::get_current_character() -{ - return ao_config->character_ini(get_base_character()); -} - void Courtroom::handle_song(QStringList p_contents) { if (p_contents.size() < 2) @@ -1828,13 +1677,10 @@ void Courtroom::handle_song(QStringList p_contents) str_char = f_showname; } - if (!mute_map.value(l_chr_id)) - { - append_ic_text(str_char, "has played a song: " + f_song, false, true, l_chr_id == m_chr_id); - if (ao_config->log_is_recording_enabled()) - save_textlog(str_char + " has played a song: " + f_song); - m_music_player->play(f_song); - } + append_ic_text(str_char, "has played a song: " + f_song, false, true, l_chr_id == m_chr_id); + if (ao_config->log_is_recording_enabled()) + save_textlog(str_char + " has played a song: " + f_song); + m_music_player->play(f_song); } int pos = f_song.lastIndexOf(QChar('.')); @@ -2073,32 +1919,6 @@ void Courtroom::on_pos_dropdown_changed(int p_index) // ao_app->send_server_packet(DRPacket("SP", {f_pos})); } -void Courtroom::on_mute_list_item_changed(QListWidgetItem *p_item) -{ - int f_cid = -1; - - for (int n_char = 0; n_char < m_chr_list.size(); n_char++) - { - if (m_chr_list.at(n_char).name == p_item->text()) - f_cid = n_char; - } - - if (f_cid < 0 || f_cid >= m_chr_list.size()) - { - qDebug() << "W: " << p_item->text() << " not present in char_list"; - return; - } - - if (Qt::CheckState::Checked == p_item->checkState()) - { - mute_map.insert(f_cid, true); - } - else - { - mute_map.insert(f_cid, false); - } -} - void Courtroom::on_music_list_clicked() { ui_ic_chat_message->setFocus(); @@ -2303,20 +2123,6 @@ void Courtroom::on_effect_button_toggled(const bool p_checked) l_button->setText(l_name); } -void Courtroom::on_mute_clicked() -{ - if (ui_mute_list->isHidden()) - { - ui_mute_list->show(); - ui_mute->set_image("mute_pressed.png"); - } - else - { - ui_mute_list->hide(); - ui_mute->set_image("mute.png"); - } -} - void Courtroom::on_defense_minus_clicked() { int f_state = defense_bar_state - 1; @@ -2434,15 +2240,10 @@ void Courtroom::on_app_reload_theme_requested() return; } - // Otherwise carry on - load_shouts(); - load_effects(); - load_wtce(); - load_free_blocks(); + setup_courtroom(); // to update status on the background set_background(current_background); - enter_courtroom(m_chr_id); } void Courtroom::on_back_to_lobby_clicked() @@ -2471,11 +2272,6 @@ void Courtroom::on_char_select_right_clicked() void Courtroom::on_spectator_clicked() { ao_app->send_server_packet(DRPacket("CC", {QString::number(ao_app->get_client_id()), "-1", get_hdid()})); - enter_courtroom(-1); - - ui_emotes->hide(); - - ui_char_select_background->hide(); } void Courtroom::on_call_mod_clicked() diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp new file mode 100644 index 000000000..502cc63df --- /dev/null +++ b/src/courtroom_character.cpp @@ -0,0 +1,100 @@ +#include "courtroom.h" + +#include "aoapplication.h" +#include "aoconfig.h" +#include "commondefs.h" +#include "file_functions.h" + +#include +#include +#include +#include + +int Courtroom::get_character_id() +{ + return m_chr_id; +} + +void Courtroom::set_character_id(const int p_chr_id) +{ + if (m_chr_id == p_chr_id) + return; + m_chr_id = p_chr_id; + Q_EMIT character_id_changed(m_chr_id); +} + +QString Courtroom::get_base_character() +{ + return is_spectating() ? nullptr : m_chr_list.at(m_chr_id).name; +} + +QString Courtroom::get_current_character() +{ + return ao_config->character_ini(get_base_character()); +} + +namespace +{ +void drSetItemIcon(QComboBox *p_widget, const int p_index, const QString &p_chr_name, AOApplication *ao_app) +{ + static const QIcon s_blank_icon = []() -> QIcon { + QPixmap l_blank_texture(64, 64); + l_blank_texture.fill(Qt::transparent); + return QIcon(l_blank_texture); + }(); + + const QString l_icon_file = ao_app->get_character_path(p_chr_name, "char_icon.png"); + p_widget->setItemIcon(p_index, file_exists(l_icon_file) ? QIcon(l_icon_file) : s_blank_icon); +} +} // namespace + +void Courtroom::update_iniswap_list() +{ + QSignalBlocker b_ini_list(ui_iniswap_dropdown); + ui_iniswap_dropdown->clear(); + + QStringList l_name_list{"Default"}; + const QString l_path = ao_app->get_base_path() + "/characters"; + const QFileInfoList l_info_list = QDir(l_path).entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); + for (const QFileInfo &i_info : l_info_list) + { + const QString l_name = i_info.fileName(); + if (!file_exists(ao_app->get_character_path(l_name, CHARACTER_CHAR_INI))) + continue; + l_name_list.append(l_name); + } + + for (int i = 0; i < l_name_list.length(); ++i) + { + const QString &i_name = l_name_list.at(i); + ui_iniswap_dropdown->addItem(i_name); + if (i == 0) + continue; + drSetItemIcon(ui_iniswap_dropdown, i, i_name, ao_app); + } + update_default_iniswap_item(); + + select_base_character_iniswap(); +} + +void Courtroom::update_default_iniswap_item() +{ + drSetItemIcon(ui_iniswap_dropdown, 0, get_base_character(), ao_app); +} + +void Courtroom::select_base_character_iniswap() +{ + const QString l_current_chr = get_current_character(); + if (get_base_character() == l_current_chr) + { + ui_iniswap_dropdown->setCurrentIndex(0); + return; + } + ui_iniswap_dropdown->setCurrentText(l_current_chr); +} + +void Courtroom::on_iniswap_dropdown_changed(int p_index) +{ + ao_config->set_character_ini(get_base_character(), + p_index == 0 ? get_base_character() : ui_iniswap_dropdown->itemText(p_index)); +} diff --git a/src/courtroom_sfx.cpp b/src/courtroom_sfx.cpp new file mode 100644 index 000000000..7cf4a140d --- /dev/null +++ b/src/courtroom_sfx.cpp @@ -0,0 +1,131 @@ +#include "courtroom.h" + +#include "aoapplication.h" +#include "commondefs.h" +#include "file_functions.h" + +#include +#include +#include +#include + +#include + +std::optional Courtroom::current_sfx() +{ + QListWidgetItem *l_item = ui_sfx_list->currentItem(); + if (l_item == nullptr) + return std::nullopt; + return m_sfx_list.at(l_item->data(Qt::UserRole).toInt()); +} + +QString Courtroom::current_sfx_file() +{ + const QString l_current_emote_file = get_current_emote().sound_file; + const std::optional l_optional_sfx = current_sfx(); + if (!l_optional_sfx.has_value()) + return l_current_emote_file; + const QString l_file = l_optional_sfx.value().file; + return l_file == m_sfx_default_file ? l_current_emote_file : l_file; +} + +void Courtroom::load_sfx_list_theme() +{ + m_sfx_color_found = ao_app->get_color("found_song_color", COURTROOM_DESIGN_INI); + m_sfx_color_missing = ao_app->get_color("missing_song_color", COURTROOM_DESIGN_INI); + update_all_sfx_item_color(); +} + +void Courtroom::load_current_character_sfx_list() +{ + QSignalBlocker l_blocker(ui_sfx_list); + ui_sfx_list->clear(); + + // items + m_sfx_list.clear(); + m_sfx_list.append(DRSfx("Default", m_sfx_default_file)); + m_sfx_list.append(DRSfx("Silence", nullptr)); + + const QStringList l_sfx_list = ao_app->get_sfx_list(); + for (const QString &i_sfx_line : l_sfx_list) + { + const QStringList l_sfx_entry = i_sfx_line.split("=", DR::SkipEmptyParts); + + const QString l_name = l_sfx_entry.at(l_sfx_entry.size() - 1).trimmed(); + const QString l_file = QString(l_sfx_entry.size() >= 2 ? l_sfx_entry.at(0) : nullptr).trimmed(); + const bool l_is_found = !ao_app->find_asset_path({ao_app->get_sounds_path(l_file)}, audio_extensions()).isEmpty(); + m_sfx_list.append(DRSfx(l_name, l_file, l_is_found)); + } + + // create items + QList l_item_list; + for (int i = 0; i < m_sfx_list.length(); ++i) + { + const DRSfx &i_sfx = m_sfx_list.at(i); + QListWidgetItem *l_item = new QListWidgetItem(ui_sfx_list); + l_item_list.append(l_item); + l_item->setText(i_sfx.name); + l_item->setData(Qt::UserRole, i); + set_sfx_item_color(l_item); + } + + filter_sfx_list(); +} + +void Courtroom::filter_sfx_list() +{ + const QString l_filter = ui_sfx_search->text(); + for (int i = 0; i < ui_sfx_list->count(); ++i) + { + QListWidgetItem *i_item = ui_sfx_list->item(i); + i_item->setHidden(!i_item->text().contains(l_filter, Qt::CaseInsensitive)); + } +} + +void Courtroom::select_default_sfx() +{ + if (ui_sfx_list->count() == 0) + return; + ui_sfx_list->setCurrentRow(0); +} + +void Courtroom::clear_sfx_selection() +{ + ui_sfx_list->setCurrentRow(-1); +} + +void Courtroom::set_sfx_item_color(QListWidgetItem *p_item) +{ + if (p_item == nullptr) + return; + const bool l_is_found = m_sfx_list.at(p_item->data(Qt::UserRole).toInt()).is_found; + QColor l_color = l_is_found ? m_sfx_color_found : m_sfx_color_missing; + + if (p_item == ui_sfx_list->currentItem()) + { + // Calculate the amount of lightness it would take to light up the row. We + // also limit it to 1.0, as giving lightness values above 1.0 to QColor does + // nothing. +0.4 is just an arbitrarily chosen number. + const double l_final_lightness = qMin(1.0, l_color.lightnessF() + 0.4); + + // This is just the reverse of the above, basically. We set the colour, and we + // set the brush to have that colour. + l_color.setHslF(l_color.hueF(), l_color.saturationF(), l_final_lightness); + } + + p_item->setBackground(l_color); +} + +void Courtroom::update_all_sfx_item_color() +{ + for (int i = 0; i < m_sfx_list.length(); ++i) + set_sfx_item_color(ui_sfx_list->item(i)); +} + +void Courtroom::_p_sfxCurrentItemChanged(QListWidgetItem *p_current_item, QListWidgetItem *p_previous_item) +{ + set_sfx_item_color(p_current_item); + set_sfx_item_color(p_previous_item); + ui_pre->setChecked(ui_pre->isChecked() || current_sfx().has_value()); + ui_ic_chat_message->setFocus(); +} diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 12ed70c34..83b3ebe0e 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -117,7 +117,6 @@ void Courtroom::create_widgets() ui_ooc_chatlog->setReadOnly(true); ui_ooc_chatlog->setOpenExternalLinks(true); - ui_mute_list = new QListWidget(this); ui_area_list = new QListWidget(this); ui_music_list = new QListWidget(this); ui_sfx_list = new QListWidget(this); @@ -211,8 +210,6 @@ void Courtroom::create_widgets() ui_checks.push_back(ui_flip); ui_checks.push_back(ui_hidden); - ui_mute = new AOButton(this, ao_app); - ui_defense_plus = new AOButton(this, ao_app); ui_defense_minus = new AOButton(this, ao_app); @@ -270,13 +267,10 @@ void Courtroom::connect_widgets() connect(ui_iniswap_dropdown, SIGNAL(activated(int)), this, SLOT(on_iniswap_dropdown_changed(int))); connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); - connect(ui_mute_list, SIGNAL(itemChanged(QListWidgetItem *)), this, - SLOT(on_mute_list_item_changed(QListWidgetItem *))); - connect(ao_config, SIGNAL(showname_changed(QString)), this, SLOT(on_showname_changed(QString))); connect(ao_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); - connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(on_character_ini_changed(QString))); + connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(on_character_ini_changed())); connect(ui_ic_chat_showname, SIGNAL(editingFinished()), this, SLOT(on_ic_showname_editing_finished())); connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ic_message_return_pressed())); @@ -300,8 +294,6 @@ void Courtroom::connect_widgets() connect(ui_wtce_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); connect(ui_wtce_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - connect(ui_mute, SIGNAL(clicked()), this, SLOT(on_mute_clicked())); - connect(ui_defense_minus, SIGNAL(clicked()), this, SLOT(on_defense_minus_clicked())); connect(ui_defense_plus, SIGNAL(clicked()), this, SLOT(on_defense_plus_clicked())); connect(ui_prosecution_minus, SIGNAL(clicked()), this, SLOT(on_prosecution_minus_clicked())); @@ -318,7 +310,7 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited())); - connect(ui_sfx_search, SIGNAL(editingFinished()), this, SLOT(on_sfx_search_editing_finished())); + connect(ui_sfx_search, SIGNAL(editingFinished()), this, SLOT(filter_sfx_list())); connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); @@ -334,7 +326,8 @@ void Courtroom::connect_widgets() connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); - connect(ui_sfx_list, SIGNAL(currentRowChanged(int)), this, SLOT(on_sfx_widget_list_row_changed())); + connect(ui_sfx_list, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, + SLOT(_p_sfxCurrentItemChanged(QListWidgetItem *, QListWidgetItem *))); connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); @@ -369,7 +362,6 @@ void Courtroom::reset_widget_names() {"vp_objection", ui_vp_objection}, {"ic_chatlog", ui_ic_chatlog}, {"server_chatlog", ui_ooc_chatlog}, - {"mute_list", ui_mute_list}, {"area_list", ui_area_list}, {"music_list", ui_music_list}, {"sfx_list", ui_sfx_list}, @@ -411,7 +403,6 @@ void Courtroom::reset_widget_names() {"pre", ui_pre}, {"flip", ui_flip}, {"hidden", ui_hidden}, - {"mute_button", ui_mute}, {"defense_plus", ui_defense_plus}, {"defense_minus", ui_defense_minus}, {"prosecution_plus", ui_prosecution_plus}, @@ -630,9 +621,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_ooc_chatlog, "server_chatlog", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_mute_list, "mute_list", COURTROOM_DESIGN_INI, ao_app); - ui_mute_list->hide(); - set_size_and_pos(ui_music_list, "music_list", COURTROOM_DESIGN_INI, ao_app); set_size_and_pos(ui_area_list, "area_list", COURTROOM_DESIGN_INI, ao_app); if (ui_music_list->isVisible()) @@ -690,7 +678,7 @@ void Courtroom::set_widgets() // emotes set_size_and_pos(ui_emotes, "emotes", COURTROOM_DESIGN_INI, ao_app); - reconstruct_emotes(); + construct_emote_page_layout(); set_size_and_pos(ui_emote_left, "emote_left", COURTROOM_DESIGN_INI, ao_app); ui_emote_left->set_image("arrow_left.png"); @@ -838,7 +826,7 @@ void Courtroom::set_widgets() // behavior will occur if the button is hidden due to 'config_panel' not being // found in courtroom_design.ini This is to assist with people who switch to // incompatible and/or smaller themes and have the button disappear - if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || !ui_config_panel->isVisible()) + if (ui_config_panel->x() > width() || ui_config_panel->y() > height()) { ui_config_panel->setVisible(true); ui_config_panel->move(0, 0); @@ -901,9 +889,6 @@ void Courtroom::set_widgets() } } - set_size_and_pos(ui_mute, "mute_button", COURTROOM_DESIGN_INI, ao_app); - ui_mute->set_image("mute.png"); - set_size_and_pos(ui_defense_plus, "defense_plus", COURTROOM_DESIGN_INI, ao_app); ui_defense_plus->set_image("defplus.png"); @@ -1406,7 +1391,6 @@ void Courtroom::set_dropdowns() set_dropdown(ui_emote_dropdown, "[EMOTE DROPDOWN]"); set_dropdown(ui_iniswap_dropdown, "[INISWAP DROPDOWN]"); set_dropdown(ui_pos_dropdown, "[POS DROPDOWN]"); - set_dropdown(ui_mute_list, "[MUTE LIST]"); set_dropdown(ui_ic_chat_message, "[IC LINE]"); set_dropdown(ui_ooc_chat_message, "[OOC LINE]"); } @@ -1430,28 +1414,3 @@ void Courtroom::set_fonts() set_drtextedit_font(i_timer, QString("timer_%1").arg(i), COURTROOM_FONTS_INI, ao_app); } } - -void Courtroom::set_mute_list() -{ - mute_map.clear(); - - // maps which characters are muted based on cid, none are muted by default - for (int n_cid = 0; n_cid < m_chr_list.size(); n_cid++) - { - mute_map.insert(n_cid, false); - } - - QStringList sorted_mute_list; - - for (const char_type &i_char : qAsConst(m_chr_list)) - sorted_mute_list.append(i_char.name); - - sorted_mute_list.sort(); - - for (const QString &i_chr_name : sorted_mute_list) - { - QListWidgetItem *i_item = new QListWidgetItem(i_chr_name, ui_mute_list); - i_item->setFlags(i_item->flags() | Qt::ItemFlag::ItemIsUserCheckable); - i_item->setCheckState(Qt::Unchecked); - } -} diff --git a/src/emotes.cpp b/src/emotes.cpp index 0d739066e..eaa24907f 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -29,10 +29,10 @@ void Courtroom::construct_emotes() ui_pos_dropdown->addItem("hld", "hld"); ui_pos_dropdown->addItem("hlp", "hlp"); - reconstruct_emotes(); + construct_emote_page_layout(); } -void Courtroom::reconstruct_emotes() +void Courtroom::construct_emote_page_layout() { // delete previous buttons while (!ui_emote_list.isEmpty()) @@ -78,24 +78,19 @@ void Courtroom::reconstruct_emotes() } } - reset_emote_page(); + refresh_emote_page(true); } void Courtroom::reset_emote_page() { - m_current_emote_page = 0; m_emote_id = 0; - - if (is_spectating()) - ui_emotes->hide(); - else - ui_emotes->show(); - - set_emote_page(); - set_emote_dropdown(); + m_current_emote_page = 0; + if (ui_emote_dropdown->count()) + ui_emote_dropdown->setCurrentIndex(m_emote_id); + refresh_emote_page(true); } -void Courtroom::set_emote_page() +void Courtroom::refresh_emote_page(const bool p_scroll_to_current_emote) { ui_emote_left->hide(); ui_emote_right->hide(); @@ -109,7 +104,11 @@ void Courtroom::set_emote_page() const int l_page_count = qFloor(l_emote_count / m_page_max_emote_count) + bool(l_emote_count % m_page_max_emote_count); + + if (p_scroll_to_current_emote) + m_current_emote_page = m_emote_id / m_page_max_emote_count; m_current_emote_page = qBound(0, m_current_emote_page, l_page_count - 1); + const int l_current_page_emote_count = qBound(0, l_emote_count - m_current_emote_page * m_page_max_emote_count, m_page_max_emote_count); @@ -128,7 +127,7 @@ void Courtroom::set_emote_page() } } -void Courtroom::set_emote_dropdown() +void Courtroom::fill_emote_dropdown() { QSignalBlocker l_blocker(ui_emote_dropdown); ui_emote_dropdown->clear(); @@ -139,11 +138,11 @@ void Courtroom::set_emote_dropdown() ui_emote_dropdown->addItems(l_emote_list); } -DREmote Courtroom::get_emote(const int id) +DREmote Courtroom::get_emote(const int p_emote_id) { - if (id < 0 || id >= m_emote_list.length()) + if (p_emote_id < 0 || p_emote_id >= m_emote_list.length()) return DREmote(); - return m_emote_list.at(id); + return m_emote_list.at(p_emote_id); } DREmote Courtroom::get_current_emote() @@ -189,7 +188,7 @@ void Courtroom::on_emote_left_clicked() { --m_current_emote_page; - set_emote_page(); + refresh_emote_page(); ui_ic_chat_message->setFocus(); } @@ -198,7 +197,7 @@ void Courtroom::on_emote_right_clicked() { ++m_current_emote_page; - set_emote_page(); + refresh_emote_page(); ui_ic_chat_message->setFocus(); } @@ -207,9 +206,3 @@ void Courtroom::on_emote_dropdown_changed(int p_index) { select_emote(p_index); } - -void Courtroom::on_iniswap_dropdown_changed(int p_index) -{ - ao_config->set_character_ini(get_base_character(), - p_index == 0 ? get_base_character() : ui_iniswap_dropdown->itemText(p_index)); -} diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 018debcb8..3b01ead80 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -338,21 +338,6 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) m_courtroom->set_evidence_list(f_evi_list); } } - else if (l_header == "IL") - { - if (is_courtroom_constructed && l_content.size() > 0) - m_courtroom->set_ip_list(l_content.at(0)); - } - else if (l_header == "MU") - { - if (is_courtroom_constructed && l_content.size() > 0) - m_courtroom->set_mute(true, l_content.at(0).toInt()); - } - else if (l_header == "UM") - { - if (is_courtroom_constructed && l_content.size() > 0) - m_courtroom->set_mute(false, l_content.at(0).toInt()); - } else if (l_header == "KK") { if (is_courtroom_constructed && l_content.size() > 0) From d7f80f5e8c524876e3dbfdc3b50f9f8f3fbc0b80 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 18 Jun 2021 10:51:01 -0400 Subject: [PATCH 455/842] Don't hide widgets if not in courtroom_design, instead make them size 0, 0 --- src/courtroom_widgets.cpp | 16 +++++++++------- src/theme.cpp | 4 +++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 1dc48ff07..1e8584547 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -833,12 +833,12 @@ void Courtroom::set_widgets() ui_note_button->setText("Notes"); } - // The config panel has a special property. If it is displayed beyond the - // right or lower limit of the window, it will be moved to 0, 0 A similar - // behavior will occur if the button is hidden due to 'config_panel' not being - // found in courtroom_design.ini This is to assist with people who switch to - // incompatible and/or smaller themes and have the button disappear - if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || !ui_config_panel->isVisible()) + // The config panel has a special property. If it is displayed beyond the right or lower limit of the window, it will + // be moved to 0, 0 A similar behavior will occur if the button is resized to 0, 0 due to 'config_panel' not being + // found in courtroom_design.ini This is to assist with people who switch to incompatible and/or smaller themes and + // have the button disappear + if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || ui_config_panel->width() <= 0 + || ui_config_panel->height() <= 0) { ui_config_panel->setVisible(true); ui_config_panel->move(0, 0); @@ -1005,7 +1005,9 @@ void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) if (design_ini_result.width < 0 || design_ini_result.height < 0) { qDebug() << "W: could not find \"" << p_identifier << "\" in " << filename; - p_widget->hide(); + // Don't hide, as some widgets don't have a built-in way of reappearing again. + p_widget->move(0, 0); + p_widget->resize(0, 0); } else { diff --git a/src/theme.cpp b/src/theme.cpp index f7545a6df..736b7792f 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -18,7 +18,9 @@ void set_size_and_pos(QWidget *p_widget, QString p_identifier, QString p_ini_fil if (design_ini_result.width < 0 || design_ini_result.height < 0) { qDebug() << "W: could not find \"" << p_identifier << "\" in " << p_ini_file; - p_widget->hide(); + // Don't hide, as some widgets don't have a built-in way of reappearing again. + p_widget->move(0, 0); + p_widget->resize(0, 0); } else { From 0b0218963868f3a77493f62426ca8eb2cc2fcb55 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 19 Jun 2021 05:33:13 +0200 Subject: [PATCH 456/842] Ini-swap charselect tweaks * Ini-swapped character icon now shows in the bottom-right of the original character * Clicking on the character we already have selected will reload the character itself * Added tooltip to character icon --- include/aocharbutton.h | 11 +++++++--- include/courtroom.h | 5 +++-- src/aoapplication.cpp | 2 +- src/aocharbutton.cpp | 34 ++++++++++++++++++++++-------- src/charselect.cpp | 41 +++++++++++++++++++++++++++++++------ src/courtroom.cpp | 20 ++++-------------- src/courtroom_character.cpp | 16 +++++++-------- src/courtroom_widgets.cpp | 8 ++++---- 8 files changed, 88 insertions(+), 49 deletions(-) diff --git a/include/aocharbutton.h b/include/aocharbutton.h index b3ac2e19c..b491651c7 100644 --- a/include/aocharbutton.h +++ b/include/aocharbutton.h @@ -6,6 +6,8 @@ class AOImageDisplay; #include +class QLabel; + class AOCharButton : public QPushButton { Q_OBJECT @@ -13,9 +15,9 @@ class AOCharButton : public QPushButton public: AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos); - void reset(); - void set_taken(); - void set_image(QString p_character); + QString character(); + void set_character(QString character, QString ini_character); + void set_taken(const bool); signals: void mouse_entered(AOCharButton *p_caller); @@ -28,6 +30,9 @@ class AOCharButton : public QPushButton private: AOApplication *ao_app = nullptr; + QString m_character; + + QLabel *ui_character = nullptr; AOImageDisplay *ui_taken = nullptr; }; diff --git a/include/courtroom.h b/include/courtroom.h index 9e977f549..2a5b2e591 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -114,8 +114,8 @@ class Courtroom : public QMainWindow QString get_background_path(QString p_file); public: - QString get_base_character(); - QString get_current_character(); + QString get_character(); + QString get_character_ini(); void update_iniswap_list(); void update_default_iniswap_item(); void select_base_character_iniswap(); @@ -673,6 +673,7 @@ private slots: void on_char_select_left_clicked(); void on_char_select_right_clicked(); + void update_character_icon(QString character); void char_clicked(int n_char); void char_mouse_entered(AOCharButton *p_caller); void char_mouse_left(); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index aecda6046..830b39eff 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -214,7 +214,7 @@ QString AOApplication::get_current_char() { if (!is_courtroom_constructed) return nullptr; - return m_courtroom->get_current_character(); + return m_courtroom->get_character_ini(); } /** diff --git a/src/aocharbutton.cpp b/src/aocharbutton.cpp index 92912cb0d..d50c1bc16 100644 --- a/src/aocharbutton.cpp +++ b/src/aocharbutton.cpp @@ -5,6 +5,7 @@ #include "file_functions.h" #include +#include AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos) : QPushButton(parent) { @@ -13,6 +14,11 @@ AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, this->resize(60, 60); this->move(x_pos, y_pos); + ui_character = new QLabel(this); + ui_character->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_character->resize(30, 30); + ui_character->move(28, 28); + ui_taken = new AOImageDisplay(this, ao_app); ui_taken->resize(60, 60); ui_taken->set_image("char_taken.png"); @@ -20,22 +26,32 @@ AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, ui_taken->hide(); } -void AOCharButton::reset() +QString AOCharButton::character() { - ui_taken->hide(); + return m_character; } -void AOCharButton::set_taken() +void AOCharButton::set_character(QString p_character, QString p_character_ini) { - ui_taken->show(); + m_character = p_character; + const QString l_icon_path = ao_app->get_character_path(m_character, "char_icon.png"); + const bool l_file_exist = file_exists(l_icon_path); + setStyleSheet(l_file_exist ? QString("AOCharButton { border-image: url(\"%1\"); }").arg(l_icon_path) : nullptr); + const QString l_final_character = QString(m_character).replace("&", "&&"); + setText(l_file_exist ? nullptr : l_final_character); + + const bool l_is_different_chr = m_character != p_character_ini; + if (l_is_different_chr) + ui_character->setStyleSheet( + QString("border-image: url(\"%1\");").arg(ao_app->get_character_path(p_character_ini, "char_icon.png"))); + ui_character->setVisible(l_is_different_chr); + setToolTip(l_is_different_chr ? QString("%1 as %2").arg(m_character, QString(p_character_ini).replace("&", "&&")) + : l_final_character); } -void AOCharButton::set_image(QString p_character) +void AOCharButton::set_taken(const bool p_enabled) { - const QString l_image = ao_app->get_character_path(p_character, "char_icon.png"); - const bool l_file_exist = file_exists(l_image); - setStyleSheet(l_file_exist ? QString("border-image: url(\"%1\");").arg(l_image) : nullptr); - setText(l_file_exist ? nullptr : p_character.replace("&", "&&")); + ui_taken->setVisible(p_enabled); } void AOCharButton::enterEvent(QEvent *e) diff --git a/src/charselect.cpp b/src/charselect.cpp index df28e47cc..850e97832 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -3,6 +3,7 @@ #include "aoapplication.h" #include "aobutton.h" #include "aocharbutton.h" +#include "aoconfig.h" #include "aoimagedisplay.h" #include "commondefs.h" #include "debug_functions.h" @@ -39,6 +40,8 @@ void Courtroom::construct_char_select() connect(ui_chr_select_left, SIGNAL(clicked()), this, SLOT(on_char_select_left_clicked())); connect(ui_chr_select_right, SIGNAL(clicked()), this, SLOT(on_char_select_right_clicked())); + connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(update_character_icon(QString))); + connect(ui_spectator, SIGNAL(clicked()), this, SLOT(on_spectator_clicked())); reconstruct_char_select(); @@ -125,10 +128,7 @@ void Courtroom::set_char_select_page() ui_chr_select_right->hide(); for (AOCharButton *button : qAsConst(ui_char_button_list)) - { - button->reset(); button->hide(); - } const int l_item_count = m_chr_list.length(); const int l_page_count = qFloor(l_item_count / m_page_max_chr_count) + bool(l_item_count % m_page_max_chr_count); @@ -147,15 +147,44 @@ void Courtroom::set_char_select_page() { int l_real_i = i + m_current_chr_page * m_page_max_chr_count; AOCharButton *l_button = ui_char_button_list.at(i); - l_button->set_image(m_chr_list.at(l_real_i).name); + const QString l_base_chr = m_chr_list.at(l_real_i).name; + l_button->set_character(l_base_chr, ao_config->character_ini(l_base_chr)); + l_button->set_taken((m_chr_list.at(l_real_i).taken)); l_button->show(); - if (m_chr_list.at(l_real_i).taken) - l_button->set_taken(); + } +} + +void Courtroom::on_char_select_left_clicked() +{ + --m_current_chr_page; + set_char_select_page(); +} + +void Courtroom::on_char_select_right_clicked() +{ + ++m_current_chr_page; + set_char_select_page(); +} + +void Courtroom::update_character_icon(QString p_character) +{ + for (AOCharButton *i_button : qAsConst(ui_char_button_list)) + { + if (i_button->character() != p_character) + continue; + i_button->set_character(p_character, ao_config->character_ini(p_character)); + break; } } void Courtroom::char_clicked(int n_char) { + if (get_character() == ui_char_button_list.at(n_char)->character()) + { + enter_courtroom(get_character_id()); + return; + } + int n_real_char = n_char + m_current_chr_page * m_page_max_chr_count; QString char_ini_path = ao_app->get_character_path(m_chr_list.at(n_real_char).name, CHARACTER_CHAR_INI); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d63b52403..0a51bb7aa 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -153,7 +153,7 @@ void Courtroom::enter_courtroom(int p_cid) // unmute audio suppress_audio(false); - const QString l_prev_chr_name = get_current_character(); + const QString l_prev_chr_name = get_character_ini(); // character ================================================================= const bool l_changed_chr_id = (m_chr_id != p_cid); @@ -167,7 +167,7 @@ void Courtroom::enter_courtroom(int p_cid) l_current_field = ui_ooc_chat_message; const int l_current_cursor_pos = l_current_field->cursorPosition(); - const QString l_chr_name = get_current_character(); + const QString l_chr_name = get_character_ini(); if (is_spectating()) { ao_app->get_discord()->clear_character_name(); @@ -603,7 +603,7 @@ void Courtroom::on_ic_message_return_pressed() packet_contents.append(l_emote.anim); - packet_contents.append(get_current_character()); + packet_contents.append(get_character_ini()); if (ui_hidden->isChecked()) packet_contents.append("../../misc/blank"); @@ -612,7 +612,7 @@ void Courtroom::on_ic_message_return_pressed() packet_contents.append(ui_ic_chat_message->text()); - const QString l_side = ao_app->get_char_side(get_current_character()); + const QString l_side = ao_app->get_char_side(get_character_ini()); packet_contents.append(l_side); // sfx file @@ -2257,18 +2257,6 @@ void Courtroom::on_back_to_lobby_clicked() ao_app->destruct_courtroom(); } -void Courtroom::on_char_select_left_clicked() -{ - --m_current_chr_page; - set_char_select_page(); -} - -void Courtroom::on_char_select_right_clicked() -{ - ++m_current_chr_page; - set_char_select_page(); -} - void Courtroom::on_spectator_clicked() { ao_app->send_server_packet(DRPacket("CC", {QString::number(ao_app->get_client_id()), "-1", get_hdid()})); diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index 502cc63df..e062d7775 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -23,14 +23,14 @@ void Courtroom::set_character_id(const int p_chr_id) Q_EMIT character_id_changed(m_chr_id); } -QString Courtroom::get_base_character() +QString Courtroom::get_character() { return is_spectating() ? nullptr : m_chr_list.at(m_chr_id).name; } -QString Courtroom::get_current_character() +QString Courtroom::get_character_ini() { - return ao_config->character_ini(get_base_character()); + return ao_config->character_ini(get_character()); } namespace @@ -79,13 +79,13 @@ void Courtroom::update_iniswap_list() void Courtroom::update_default_iniswap_item() { - drSetItemIcon(ui_iniswap_dropdown, 0, get_base_character(), ao_app); + drSetItemIcon(ui_iniswap_dropdown, 0, get_character(), ao_app); } void Courtroom::select_base_character_iniswap() { - const QString l_current_chr = get_current_character(); - if (get_base_character() == l_current_chr) + const QString l_current_chr = get_character_ini(); + if (get_character() == l_current_chr) { ui_iniswap_dropdown->setCurrentIndex(0); return; @@ -95,6 +95,6 @@ void Courtroom::select_base_character_iniswap() void Courtroom::on_iniswap_dropdown_changed(int p_index) { - ao_config->set_character_ini(get_base_character(), - p_index == 0 ? get_base_character() : ui_iniswap_dropdown->itemText(p_index)); + ao_config->set_character_ini(get_character(), + p_index == 0 ? get_character() : ui_iniswap_dropdown->itemText(p_index)); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 83b3ebe0e..3d4f21a30 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1058,7 +1058,7 @@ void Courtroom::check_effects() for (int i = 0; i < ui_effects.size(); ++i) { - QString path = ao_app->find_asset_path({ao_app->get_character_path(get_current_character(), effect_names.at(i))}, + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), effect_names.at(i))}, animated_extensions()); if (path.isEmpty()) path = ao_app->find_theme_asset_path(effect_names.at(i), animated_extensions()); @@ -1078,7 +1078,7 @@ void Courtroom::check_free_blocks() for (int i = 0; i < ui_free_blocks.size(); ++i) { QString path = ao_app->find_asset_path( - {ao_app->get_character_path(get_current_character(), free_block_names.at(i))}, animated_extensions()); + {ao_app->get_character_path(get_character_ini(), free_block_names.at(i))}, animated_extensions()); if (path.isEmpty()) path = ao_app->find_theme_asset_path(free_block_names.at(i), animated_extensions()); free_blocks_enabled[i] = (!path.isEmpty()); @@ -1096,7 +1096,7 @@ void Courtroom::check_shouts() for (int i = 0; i < ui_shouts.size(); ++i) { - QString path = ao_app->find_asset_path({ao_app->get_character_path(get_current_character(), shout_names.at(i))}, + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), shout_names.at(i))}, animated_extensions()); if (path.isEmpty()) @@ -1117,7 +1117,7 @@ void Courtroom::check_wtce() for (int i = 0; i < ui_wtce.size(); ++i) { - QString path = ao_app->find_asset_path({ao_app->get_character_path(get_current_character(), wtce_names.at(i))}, + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), wtce_names.at(i))}, animated_extensions()); if (path.isEmpty()) path = ao_app->find_theme_asset_path(wtce_names.at(i), animated_extensions()); From 1777c6cee5868ac0b63af06add06d7c8d8800a64 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 19 Jun 2021 20:11:33 +0200 Subject: [PATCH 457/842] Improved IC chatlog performance * Changed the minimum log line count to 1 * Disabled vertical text alignment * Extra block no longer needed * Cache text char formats for ic chatlog * Added maybe_color for reading a color from ini --- include/aoapplication.h | 5 + include/courtroom.h | 12 ++ res/ui/config_panel.ui | 6 +- src/aoconfig.cpp | 2 +- src/courtroom.cpp | 249 ++++++++++++------------------------ src/courtroom_widgets.cpp | 13 +- src/text_file_functions.cpp | 34 +++-- 7 files changed, 135 insertions(+), 186 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index fdc66a7c1..a8af96202 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -14,6 +14,8 @@ class Lobby; #include #include +#include + class AOApplication : public QApplication { Q_OBJECT @@ -132,6 +134,9 @@ class AOApplication : public QApplication // Returns the name of the font with p_identifier from p_file QString get_font_name(QString p_identifier, QString p_file); + // Returns the color with p_identifier from p_file + std::optional maybe_color(QString p_identifier, QString p_file); + // Returns the color with p_identifier from p_file QColor get_color(QString p_identifier, QString p_file); diff --git a/include/courtroom.h b/include/courtroom.h index 2a5b2e591..8e53b9cc7 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -33,6 +33,7 @@ class DRTextEdit; #include #include #include +#include class QCheckBox; class QComboBox; @@ -163,6 +164,16 @@ class Courtroom : public QMainWindow void handle_chatmessage_2(); void handle_chatmessage_3(); + struct IcLogTextFormat + { + QTextCharFormat text; + QTextCharFormat name; + QTextCharFormat selfname; + QTextCharFormat system; + }; + IcLogTextFormat m_ic_log_format; + void load_ic_text_format(); + // adds text to the IC chatlog. p_name first as bold then p_text then a newlin // this function keeps the chatlog scrolled to the top unless there's text // selected @@ -211,6 +222,7 @@ class Courtroom : public QMainWindow int adapt_numbered_items(QVector &item_vector, QString config_item_number, QString item_name); signals: + void loaded_theme(); void closing(); private: diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index fadfa4e41..1e343a925 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 1 + 2 @@ -344,10 +344,10 @@ lines - 20 + 1 - 20000 + 10000 200 diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index b6cdf177b..52ab67a42 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -130,7 +130,7 @@ void AOConfigPrivate::read_file() manual_timeofday = cfg.value("manual_timeofday", false).toBool(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); - log_max_lines = cfg.value("chatlog_limit", 200).toInt(); + log_max_lines = cfg.value("chatlog_limit", 100).toInt(); log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); log_display_timestamp = cfg.value("chatlog_display_timestamp", true).toBool(); log_display_self_highlight = cfg.value("chatlog_display_self_highlight", true).toBool(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 3e7a0b9b0..ba6e197eb 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -94,9 +94,6 @@ void Courtroom::setup_courtroom() load_free_blocks(); load_sfx_list_theme(); - // setup chat - update_ic_log(true); - current_evidence_page = 0; current_evidence = 0; set_evidence_page(); @@ -1054,200 +1051,124 @@ void Courtroom::on_chat_config_changed() update_ic_log(true); } -void Courtroom::update_ic_log(bool p_reset_log) +void Courtroom::load_ic_text_format() { - // resize if needed - const int record_count = m_ic_record_list.length() + m_ic_record_queue.length(); - if (record_count > ao_config->log_max_lines()) - m_ic_record_list = m_ic_record_list.mid(record_count - ao_config->log_max_lines()); + ui_ic_chatlog->ensurePolished(); + m_ic_log_format.text = QTextCharFormat(); + m_ic_log_format.text.setFont(ui_ic_chatlog->font()); + m_ic_log_format.text.setForeground(ui_ic_chatlog->palette().color(ui_ic_chatlog->foregroundRole())); - if (p_reset_log) - { - // Turn off auto align. That is because we are going to be performing a lot of text change operations - // but we don't necessarily care the intermediate states are not aligned - ui_ic_chatlog->set_auto_align(false); - // we need all recordings - QQueue new_queue; - while (!m_ic_record_list.isEmpty()) - new_queue.append(m_ic_record_list.takeFirst()); - new_queue.append(m_ic_record_queue); - m_ic_record_queue = std::move(new_queue); + auto set_format_color = [this](const QString &f_identifier, QTextCharFormat &f_format) { + if (const std::optional l_color = + ao_app->maybe_color(QString("ic_chatlog_%1_color").arg(f_identifier), COURTROOM_FONTS_INI); + l_color.has_value()) + f_format.setForeground(l_color.value()); - // clear log - ui_ic_chatlog->clear(); - ui_ic_chatlog->realign_text(); - } + if (const bool l_is_bold = + ao_app->get_font_property(QString("ic_chatlog_%1_bold").arg(f_identifier), COURTROOM_FONTS_INI)) + f_format.setFontWeight(QFont::Bold); + }; - // prepare the formats we need - // default color - QColor default_color = ao_app->get_color("ic_chatlog_color", COURTROOM_FONTS_INI); - QColor not_found_color = QColor(255, 255, 255); - - QTextCharFormat name_format = ui_ic_chatlog->currentCharFormat(); - if (ao_app->get_font_property("ic_chatlog_bold", COURTROOM_FONTS_INI)) - name_format.setFontWeight(QFont::Bold); - else - name_format.setFontWeight(QFont::Normal); - - QColor showname_color = ao_app->get_color("ic_chatlog_showname_color", COURTROOM_FONTS_INI); - if (showname_color == not_found_color) - showname_color = default_color; - name_format.setForeground(showname_color); - - QTextCharFormat selfname_format = name_format; + m_ic_log_format.name = m_ic_log_format.text; + set_format_color("showname", m_ic_log_format.name); + m_ic_log_format.selfname = m_ic_log_format.name; if (ao_config->log_display_self_highlight_enabled()) - { - QColor selfname_color = ao_app->get_color("ic_chatlog_selfname_color", COURTROOM_FONTS_INI); - if (selfname_color == not_found_color) - selfname_color = showname_color; - selfname_format.setForeground(selfname_color); - } + set_format_color("selfname", m_ic_log_format.selfname); - QTextCharFormat line_format = ui_ic_chatlog->currentCharFormat(); - line_format.setFontWeight(QFont::Normal); - QColor message_color = ao_app->get_color("ic_chatlog_message_color", COURTROOM_FONTS_INI); - if (message_color == not_found_color) - message_color = default_color; - line_format.setForeground(message_color); + m_ic_log_format.system = m_ic_log_format.text; + set_format_color("system", m_ic_log_format.system); +} - QTextCharFormat system_format = ui_ic_chatlog->currentCharFormat(); - system_format.setFontWeight(QFont::Normal); - QColor system_color = ao_app->get_color("ic_chatlog_system_color", COURTROOM_FONTS_INI); - if (system_color == not_found_color) - system_color = not_found_color; - system_format.setForeground(system_color); +void Courtroom::update_ic_log(bool p_reset_log) +{ + if (const int l_record_count = m_ic_record_list.length() + m_ic_record_queue.length(); + l_record_count > ao_config->log_max_lines()) + m_ic_record_list = m_ic_record_list.mid(l_record_count - ao_config->log_max_lines()); - // need vscroll bar for cache - QScrollBar *vscrollbar = ui_ic_chatlog->verticalScrollBar(); + if (p_reset_log || ao_config->log_max_lines() == 1) + { + if (p_reset_log) + { + load_ic_text_format(); + QQueue l_new_queue; + dynamic_cast &>(l_new_queue) = std::move(m_ic_record_list); + l_new_queue.append(std::move(m_ic_record_queue)); + m_ic_record_queue = std::move(l_new_queue); + } - // format values - const bool chatlog_scrolldown = ao_config->log_is_topdown_enabled(); - const bool chatlog_newline = ao_config->log_format_use_newline_enabled(); + ui_ic_chatlog->clear(); + ui_ic_chatlog->setAlignment(ui_ic_chatlog->get_text_alignment()); + } + bool l_log_is_empty = m_ic_record_list.length() == 0; - // cache previous values - const QTextCursor prev_cursor = ui_ic_chatlog->textCursor(); - const int scroll_pos = vscrollbar->value(); - const bool is_scrolled = - chatlog_scrolldown ? scroll_pos == vscrollbar->maximum() : scroll_pos == vscrollbar->minimum(); + const bool l_topdown_orientation = ao_config->log_is_topdown_enabled(); + const bool l_use_newline = ao_config->log_format_use_newline_enabled(); + QTextCursor l_cursor = ui_ic_chatlog->textCursor(); + const QTextCursor::MoveOperation move_type = l_topdown_orientation ? QTextCursor::End : QTextCursor::Start; - // recover cursor - QTextCursor cursor = ui_ic_chatlog->textCursor(); - // figure out if we need to move up or down - const QTextCursor::MoveOperation move_type = chatlog_scrolldown ? QTextCursor::End : QTextCursor::Start; + const QTextCharFormat &l_text_format = m_ic_log_format.text; + const QTextCharFormat &l_name_format = m_ic_log_format.name; + const QTextCharFormat &l_selfname_format = m_ic_log_format.selfname; + const QTextCharFormat &l_system_format = m_ic_log_format.system; while (!m_ic_record_queue.isEmpty()) { - DRChatRecord record = m_ic_record_queue.takeFirst(); - m_ic_record_list.append(record); - const QTextCharFormat l_record_name_format = record.is_self() ? selfname_format : name_format; + const DRChatRecord l_record = m_ic_record_queue.takeFirst(); + m_ic_record_list.append(l_record); - if (record.get_message().trimmed().isEmpty() && !ao_config->log_display_empty_messages_enabled()) + if (!ao_config->log_display_empty_messages_enabled() && l_record.get_message().trimmed().isEmpty()) continue; - if (record.is_music() && !ao_config->log_display_music_switch_enabled()) + if (!ao_config->log_display_music_switch_enabled() && l_record.is_music()) continue; - // move cursor - cursor.movePosition(move_type); + l_cursor.movePosition(move_type); - const QString record_end = (QString(QChar::LineFeed) + (chatlog_newline ? QString(QChar::LineFeed) : "")); + const QString l_linefeed(QChar::LineFeed); + if (!l_log_is_empty) + l_cursor.insertText(l_linefeed + QString(l_use_newline ? l_linefeed : nullptr), l_text_format); + l_log_is_empty = false; + + if (!ao_config->log_is_topdown_enabled()) + l_cursor.movePosition(move_type); + + // self-highlight check + const QTextCharFormat &l_target_name_format = + (l_record.is_self() && ao_config->log_display_self_highlight_enabled()) ? l_selfname_format : l_name_format; if (ao_config->log_display_timestamp_enabled()) - cursor.insertText(QString("[%1] ").arg(record.get_timestamp().toString("hh:mm")), l_record_name_format); + l_cursor.insertText(QString("[%1] ").arg(l_record.get_timestamp().toString("hh:mm")), l_target_name_format); - if (record.is_system()) + if (l_record.is_system()) { - cursor.insertText(record.get_message() + record_end, system_format); + l_cursor.insertText(l_record.get_message(), l_system_format); } else { - QString separator; - if (chatlog_newline) - separator = QString(QChar::LineFeed); - else if (!record.is_music()) - separator = ": "; + QString l_separator; + if (l_use_newline) + l_separator = QString(QChar::LineFeed); + else if (!l_record.is_music()) + l_separator = ": "; else - separator = " "; - cursor.insertText(record.get_name() + separator, l_record_name_format); - cursor.insertText(record.get_message() + record_end, line_format); + l_separator = " "; + l_cursor.insertText(l_record.get_name() + l_separator, l_target_name_format); + l_cursor.insertText(l_record.get_message(), l_text_format); } } - // figure out the number of blocks we need overall - // this is always going to amount to at least the current length of records - int block_count = m_ic_record_list.length() + 1; // there's always one extra block - // to do that, we need to go through the records - for (DRChatRecord &record : m_ic_record_list) - if (!record.is_system()) - if (chatlog_newline) - block_count += 2; // if newline is actived, it always inserts two extra - // newlines; therefor two paragraphs - - // there's always one extra block count, so deduce one from block_count - int blocks_to_delete = ui_ic_chatlog->document()->blockCount() - block_count; - - // the orientation at which we need to delete from - const QTextCursor::MoveOperation start_location = chatlog_scrolldown ? QTextCursor::Start : QTextCursor::End; - const QTextCursor::MoveOperation block_orientation = - chatlog_scrolldown ? QTextCursor::NextBlock : QTextCursor::PreviousBlock; - - /* Blocks appear like this - * textQChar(0x2029) - * additionaltextQChar(0x2029) - * moretextQChar(0x2029) - * where QChar(0x2029) is the paragraph break block. - * Do note that the above example has FOUR blocks: text, additionaltext, - * moretext, and an empty block. That is because QTextCursor separates blocks - * by paragraph break block (which is why the above code has a -1) and does - * not consider this break character as part of the block (which is why we - * move Left in the loop, to 'be in the block'). Finally, BlockUnderCursor - * does NOT select the break character, so we deleteChar after removing the - * selection to remove the straggling newline. - * */ - - // move our cursor at the start - cursor.movePosition(start_location); - - // move the cursor around, depending on the orientation we need - for (int i = 0; i < blocks_to_delete; ++i) - cursor.movePosition(block_orientation, QTextCursor::KeepAnchor); - - // now that everything is selected, delete it - cursor.removeSelectedText(); - - /* - * However, if we do this, we also remove the last newline of the last block - * that remains, which will make it difficult to append new blocks to - * it/figure out the amount of blocks if we have a scroll up log, so we add it - * again if we removed any break characters at all - * */ - if (!chatlog_scrolldown && blocks_to_delete > 0) - cursor.insertBlock(); - - /* - * Unfortunately, the simplest alternative, that is, move cursor to the last - * block, remove the block under it and delete the last char does not work, as - * this also removes the last character of the block that remains. That's why - * we have to do this whole complicated process. - * */ - if (prev_cursor.hasSelection() || !is_scrolled) - { - // restore previous selection and vscrollbar - ui_ic_chatlog->setTextCursor(prev_cursor); - vscrollbar->setValue(scroll_pos); - } - // scroll up/down depending on context - else - { - ui_ic_chatlog->moveCursor(move_type); - vscrollbar->setValue(chatlog_scrolldown ? vscrollbar->maximum() : vscrollbar->minimum()); - } + { // remove unneeded blocks + const int l_max_block_count = m_ic_record_list.length() * (1 + l_use_newline) + + (l_use_newline * (m_ic_record_list.length() - 1)) + !l_topdown_orientation; + const QTextCursor::MoveOperation l_orientation = l_topdown_orientation ? QTextCursor::Start : QTextCursor::End; - if (p_reset_log) - { - // We are done updating the IC chat log, now do all alignment computations - ui_ic_chatlog->set_auto_align(true); + l_cursor.movePosition(l_orientation); + const QTextCursor::MoveOperation l_block_orientation = + l_topdown_orientation ? QTextCursor::NextBlock : QTextCursor::PreviousBlock; + for (int i = 0, i_max = ui_ic_chatlog->document()->blockCount() - l_max_block_count; i < i_max; ++i) + l_cursor.movePosition(l_block_orientation, QTextCursor::KeepAnchor); + l_cursor.removeSelectedText(); } } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 8af058361..e8420e822 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -112,6 +112,7 @@ void Courtroom::create_widgets() ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); + ui_ic_chatlog->set_auto_align(false); ui_ooc_chatlog = new AOTextArea(this); ui_ooc_chatlog->setReadOnly(true); @@ -301,6 +302,7 @@ void Courtroom::connect_widgets() connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, SLOT(on_text_color_changed(int))); + connect(this, SIGNAL(loaded_theme()), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_display_timestamp_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_display_self_highlight_changed(bool)), this, SLOT(on_chat_config_changed())); @@ -825,8 +827,8 @@ void Courtroom::set_widgets() // be moved to 0, 0 A similar behavior will occur if the button is resized to 0, 0 due to 'config_panel' not being // found in courtroom_design.ini This is to assist with people who switch to incompatible and/or smaller themes and // have the button disappear - if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || ui_config_panel->width() <= 0 - || ui_config_panel->height() <= 0) + if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || ui_config_panel->width() <= 0 || + ui_config_panel->height() <= 0) { ui_config_panel->setVisible(true); ui_config_panel->move(0, 0); @@ -979,6 +981,8 @@ void Courtroom::set_widgets() adapt_numbered_items(ui_timers, "timer_number", "timer"); set_dropdowns(); set_fonts(); + + Q_EMIT loaded_theme(); } void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) @@ -1079,8 +1083,8 @@ void Courtroom::check_free_blocks() for (int i = 0; i < ui_free_blocks.size(); ++i) { - QString path = ao_app->find_asset_path( - {ao_app->get_character_path(get_character_ini(), free_block_names.at(i))}, animated_extensions()); + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), free_block_names.at(i))}, + animated_extensions()); if (path.isEmpty()) path = ao_app->find_theme_asset_path(free_block_names.at(i), animated_extensions()); free_blocks_enabled[i] = (!path.isEmpty()); @@ -1402,6 +1406,7 @@ void Courtroom::set_fonts() set_drtextedit_font(ui_vp_showname, "showname", COURTROOM_FONTS_INI, ao_app); set_drtextedit_font(ui_vp_message, "message", COURTROOM_FONTS_INI, ao_app); set_drtextedit_font(ui_ic_chatlog, "ic_chatlog", COURTROOM_FONTS_INI, ao_app); + // Chatlog does not support drtextedit because html set_font(ui_ooc_chatlog, "server_chatlog", COURTROOM_FONTS_INI, ao_app); set_font(ui_music_list, "music_list", COURTROOM_FONTS_INI, ao_app); diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 20e52b0d1..e836da671 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -206,24 +206,30 @@ int AOApplication::get_font_property(QString p_identifier, QString p_file) return f_result.toInt(); } -QColor AOApplication::get_color(QString p_identifier, QString p_file) +std::optional AOApplication::maybe_color(QString p_identifier, QString p_file) { - QColor return_color(255, 255, 255); - - QString f_result = read_theme_ini(p_identifier, p_file); - if (f_result.isEmpty()) - return return_color; + const QString l_raw_color = read_theme_ini(p_identifier, p_file); + if (l_raw_color.isEmpty()) + return std::nullopt; - QStringList color_list = f_result.split(","); + const QStringList l_raw_light_list = l_raw_color.split(","); + if (l_raw_light_list.length() < 3) + return std::nullopt; - if (color_list.size() < 3) - return return_color; - - return_color.setRed(color_list.at(0).toInt()); - return_color.setGreen(color_list.at(1).toInt()); - return_color.setBlue(color_list.at(2).toInt()); + QColor r_color; + r_color.setRed(l_raw_light_list.at(0).toInt()); + r_color.setGreen(l_raw_light_list.at(1).toInt()); + r_color.setBlue(l_raw_light_list.at(2).toInt()); + if (l_raw_light_list.length() == 4) + r_color.setAlpha(l_raw_light_list.at(3).toInt()); + if (!r_color.isValid()) + return std::nullopt; + return r_color; +} - return return_color; +QColor AOApplication::get_color(QString p_identifier, QString p_file) +{ + return maybe_color(p_identifier, p_file).value_or(QColor(255, 255, 255)); } QString AOApplication::get_font_name(QString p_identifier, QString p_file) From 25c504d7c0cbf0f09c2599acebce301a43155d66 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 19 Jun 2021 20:54:43 +0200 Subject: [PATCH 458/842] Fix needless block removal call * No longer attempt to remove blocks even if none are selected --- src/courtroom.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ba6e197eb..1d21d00e0 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1130,7 +1130,7 @@ void Courtroom::update_ic_log(bool p_reset_log) l_cursor.insertText(l_linefeed + QString(l_use_newline ? l_linefeed : nullptr), l_text_format); l_log_is_empty = false; - if (!ao_config->log_is_topdown_enabled()) + if (!l_topdown_orientation) l_cursor.movePosition(move_type); // self-highlight check @@ -1162,13 +1162,17 @@ void Courtroom::update_ic_log(bool p_reset_log) const int l_max_block_count = m_ic_record_list.length() * (1 + l_use_newline) + (l_use_newline * (m_ic_record_list.length() - 1)) + !l_topdown_orientation; const QTextCursor::MoveOperation l_orientation = l_topdown_orientation ? QTextCursor::Start : QTextCursor::End; - - l_cursor.movePosition(l_orientation); const QTextCursor::MoveOperation l_block_orientation = l_topdown_orientation ? QTextCursor::NextBlock : QTextCursor::PreviousBlock; - for (int i = 0, i_max = ui_ic_chatlog->document()->blockCount() - l_max_block_count; i < i_max; ++i) - l_cursor.movePosition(l_block_orientation, QTextCursor::KeepAnchor); - l_cursor.removeSelectedText(); + + const int l_remove_block_count = ui_ic_chatlog->document()->blockCount() - l_max_block_count; + if (l_remove_block_count > 0) + { + l_cursor.movePosition(l_orientation); + for (int i = 0; i < l_remove_block_count; ++i) + l_cursor.movePosition(l_block_orientation, QTextCursor::KeepAnchor); + l_cursor.removeSelectedText(); + } } } From cb4d6a0490042f207f784b2c67befd8f27fd18c7 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 19 Jun 2021 21:37:21 +0200 Subject: [PATCH 459/842] Added support alpha for text coloring of widgets --- src/theme.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/theme.cpp b/src/theme.cpp index 736b7792f..578dcfa4e 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -70,16 +70,12 @@ void set_font(QWidget *p_widget, QString p_identifier, QString ini_file, AOAppli } p_widget->setFont(QFont(font_name, f_weight)); - QString font_color = ao_app->read_theme_ini(p_identifier + "_color", ini_file); - if (font_color.isEmpty()) - font_color = "255, 255, 255"; - QString color = "rgba(" + font_color + ", 255)"; - + const QColor l_font_color = ao_app->get_color(p_identifier + "_color", ini_file); int bold = ao_app->get_font_property(p_identifier + "_bold", ini_file); QString is_bold = (bold == 1 ? "bold" : ""); - QString style_sheet_string = class_name + " { " + "background-color: rgba(0, 0, 0, 0);\n" + "color: " + color + - ";\n" + "font: " + is_bold + ";" + " }"; + QString style_sheet_string = class_name + " { " + "background-color: rgba(0, 0, 0, 0);\n" + + "color: " + l_font_color.name(QColor::HexArgb) + ";\n" + "font: " + is_bold + ";" + " }"; p_widget->setStyleSheet(style_sheet_string); } From 4858c49e4e4bcc514457f423d71c1acf313b5a56 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 19 Jun 2021 21:55:28 +0200 Subject: [PATCH 460/842] Restored scroll positioning --- src/courtroom.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 1d21d00e0..d8f2ae5a2 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1112,6 +1112,11 @@ void Courtroom::update_ic_log(bool p_reset_log) const QTextCharFormat &l_selfname_format = m_ic_log_format.selfname; const QTextCharFormat &l_system_format = m_ic_log_format.system; + QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); + const int l_scroll_pos = l_scrollbar->value(); + const bool l_is_end_scroll_pos = p_reset_log || (l_topdown_orientation ? l_scroll_pos == l_scrollbar->maximum() + : l_scroll_pos == l_scrollbar->minimum()); + while (!m_ic_record_queue.isEmpty()) { const DRChatRecord l_record = m_ic_record_queue.takeFirst(); @@ -1174,6 +1179,9 @@ void Courtroom::update_ic_log(bool p_reset_log) l_cursor.removeSelectedText(); } } + + if (l_is_end_scroll_pos) + l_scrollbar->setValue(l_topdown_orientation ? l_scrollbar->maximum() : l_scrollbar->minimum()); } void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, bool p_self) From 4aa88fcf852e7e84445d17489d3f0a087effa3be Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 19 Jun 2021 22:04:52 +0200 Subject: [PATCH 461/842] Removed multiple instances of theme reloading --- src/aoapplication.cpp | 1 - src/courtroom.cpp | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 830b39eff..fc060d065 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -35,7 +35,6 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(handle_theme_modification())); connect(ao_config, SIGNAL(gamemode_changed(QString)), this, SLOT(handle_theme_modification())); connect(ao_config, SIGNAL(timeofday_changed(QString)), this, SLOT(handle_theme_modification())); - connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(handle_theme_modification())); ao_config_panel->hide(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d8f2ae5a2..a287e9667 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -51,9 +51,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() m_reload_timer->setInterval(200); m_reload_timer->setSingleShot(true); connect(m_reload_timer, SIGNAL(timeout()), this, SLOT(on_app_reload_theme_requested())); - connect(ao_config, SIGNAL(theme_changed(QString)), m_reload_timer, SLOT(start())); - connect(ao_config, SIGNAL(gamemode_changed(QString)), m_reload_timer, SLOT(start())); - connect(ao_config, SIGNAL(timeofday_changed(QString)), m_reload_timer, SLOT(start())); + connect(ao_app, SIGNAL(reload_theme()), m_reload_timer, SLOT(start())); create_widgets(); connect_widgets(); From 1df301c895a71384bc9ce54010c0433065bbf824 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 19 Jun 2021 16:59:09 -0400 Subject: [PATCH 462/842] Make set note widgets reload with theme reload --- include/aobutton.h | 2 ++ include/courtroom.h | 2 -- src/aobutton.cpp | 6 ++++++ src/courtroom_widgets.cpp | 13 +++++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/include/aobutton.h b/include/aobutton.h index f37426735..ba4565c4d 100644 --- a/include/aobutton.h +++ b/include/aobutton.h @@ -15,10 +15,12 @@ class AOButton : public QPushButton QString get_image(); bool has_image(); void set_image(QString p_image); + void refresh_image(); private: AOApplication *ao_app = nullptr; QString m_image; + QString m_image_stem; }; #endif // AOBUTTON_H diff --git a/include/courtroom.h b/include/courtroom.h index 2a5b2e591..e75600ad7 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -132,11 +132,9 @@ class Courtroom : public QMainWindow // helper function that populates ui_music_list with the contents of // music_list void list_music(); - void list_areas(); void list_note_files(); - void set_note_files(); void move_widget(QWidget *p_widget, QString p_identifier); diff --git a/src/aobutton.cpp b/src/aobutton.cpp index bbea80f4b..4c7da1674 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -23,6 +23,7 @@ bool AOButton::has_image() void AOButton::set_image(QString p_image) { + m_image_stem = p_image; m_image = ao_app->find_theme_asset_path(p_image); // Get the path of the found image without the extension const QString l_image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); @@ -44,3 +45,8 @@ void AOButton::set_image(QString p_image) this->setStyleSheet("border-image:url(\"" + m_image + "\")"); } } + +void AOButton::refresh_image() +{ + set_image(m_image_stem); +} diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 8af058361..18e9b09b4 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -13,6 +13,7 @@ #include "aomovie.h" #include "aomusicplayer.h" #include "aonotearea.h" +#include "aonotepicker.h" #include "aoscene.h" #include "aosfxplayer.h" #include "aoshoutplayer.h" @@ -961,6 +962,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_note_area, "note_area", COURTROOM_DESIGN_INI, ao_app); set_size_and_pos(ui_note_scroll_area, "note_area", COURTROOM_DESIGN_INI, ao_app); ui_note_scroll_area->setWidget(ui_note_area); + ui_note_area->set_image("note_area.png"); ui_note_area->add_button->set_image("add_button.png"); ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); @@ -976,6 +978,17 @@ void Courtroom::set_widgets() contains_add_button = true; } + // This is used to force already existing notepicker elements to reset their image and theme setting + for (AONotePicker *notepicker : ui_note_area->findChildren()) + { + for (AOButton *button : notepicker->findChildren()) + { + button->refresh_image(); + } + QLineEdit *f_line = notepicker->findChild(); + set_dropdown(f_line, "[LINE EDIT]"); + } + adapt_numbered_items(ui_timers, "timer_number", "timer"); set_dropdowns(); set_fonts(); From fe4da486030ade28f08d7b980336e1d878c7066f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 19 Jun 2021 16:59:32 -0400 Subject: [PATCH 463/842] Fix emotes with explicit prefixes no longer working --- src/aocharmovie.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 1beda81aa..55dce1773 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -39,7 +39,7 @@ bool AOCharMovie::play(QString p_chr, QString p_emote, QString p_prefix, bool p_ for (const QString &i_chr : ao_app->get_char_include_tree(p_chr)) { if (!p_prefix.isEmpty()) - l_file_list.append(ao_app->get_character_path(i_chr, QString("%1%2").arg(p_emote, p_prefix))); + l_file_list.append(ao_app->get_character_path(i_chr, QString("%1%2").arg(p_prefix, p_emote))); l_file_list.append(ao_app->get_character_path(i_chr, p_emote)); } From 631e99caa993364388fd6085b6a80c14fbf51712 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 19 Jun 2021 23:23:53 +0200 Subject: [PATCH 464/842] Added message format for IcLog --- include/courtroom.h | 3 ++- src/courtroom.cpp | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 8e53b9cc7..1b498d170 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -166,9 +166,10 @@ class Courtroom : public QMainWindow struct IcLogTextFormat { - QTextCharFormat text; + QTextCharFormat base; QTextCharFormat name; QTextCharFormat selfname; + QTextCharFormat message; QTextCharFormat system; }; IcLogTextFormat m_ic_log_format; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a287e9667..2fed46988 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1052,9 +1052,9 @@ void Courtroom::on_chat_config_changed() void Courtroom::load_ic_text_format() { ui_ic_chatlog->ensurePolished(); - m_ic_log_format.text = QTextCharFormat(); - m_ic_log_format.text.setFont(ui_ic_chatlog->font()); - m_ic_log_format.text.setForeground(ui_ic_chatlog->palette().color(ui_ic_chatlog->foregroundRole())); + m_ic_log_format.base = QTextCharFormat(); + m_ic_log_format.base.setFont(ui_ic_chatlog->font()); + m_ic_log_format.base.setForeground(ui_ic_chatlog->palette().color(ui_ic_chatlog->foregroundRole())); auto set_format_color = [this](const QString &f_identifier, QTextCharFormat &f_format) { if (const std::optional l_color = @@ -1067,14 +1067,17 @@ void Courtroom::load_ic_text_format() f_format.setFontWeight(QFont::Bold); }; - m_ic_log_format.name = m_ic_log_format.text; + m_ic_log_format.name = m_ic_log_format.base; set_format_color("showname", m_ic_log_format.name); m_ic_log_format.selfname = m_ic_log_format.name; if (ao_config->log_display_self_highlight_enabled()) set_format_color("selfname", m_ic_log_format.selfname); - m_ic_log_format.system = m_ic_log_format.text; + m_ic_log_format.message = m_ic_log_format.base; + set_format_color("message", m_ic_log_format.message); + + m_ic_log_format.system = m_ic_log_format.base; set_format_color("system", m_ic_log_format.system); } @@ -1105,9 +1108,9 @@ void Courtroom::update_ic_log(bool p_reset_log) QTextCursor l_cursor = ui_ic_chatlog->textCursor(); const QTextCursor::MoveOperation move_type = l_topdown_orientation ? QTextCursor::End : QTextCursor::Start; - const QTextCharFormat &l_text_format = m_ic_log_format.text; const QTextCharFormat &l_name_format = m_ic_log_format.name; const QTextCharFormat &l_selfname_format = m_ic_log_format.selfname; + const QTextCharFormat &l_message_format = m_ic_log_format.message; const QTextCharFormat &l_system_format = m_ic_log_format.system; QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); @@ -1130,7 +1133,7 @@ void Courtroom::update_ic_log(bool p_reset_log) const QString l_linefeed(QChar::LineFeed); if (!l_log_is_empty) - l_cursor.insertText(l_linefeed + QString(l_use_newline ? l_linefeed : nullptr), l_text_format); + l_cursor.insertText(l_linefeed + QString(l_use_newline ? l_linefeed : nullptr), l_message_format); l_log_is_empty = false; if (!l_topdown_orientation) @@ -1157,7 +1160,7 @@ void Courtroom::update_ic_log(bool p_reset_log) else l_separator = " "; l_cursor.insertText(l_record.get_name() + l_separator, l_target_name_format); - l_cursor.insertText(l_record.get_message(), l_text_format); + l_cursor.insertText(l_record.get_message(), l_message_format); } } From 9032e15d1e83c02002670449b24f90bbd1f270d2 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 19 Jun 2021 18:54:22 -0400 Subject: [PATCH 465/842] Remove AO2 style testimony widgets --- include/courtroom.h | 23 +---------------- src/courtroom.cpp | 52 --------------------------------------- src/courtroom_widgets.cpp | 16 ------------ 3 files changed, 1 insertion(+), 90 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index e75600ad7..251f1ed8a 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -183,8 +183,7 @@ class Courtroom : public QMainWindow void play_preanim(); - // plays the witness testimony or cross examination animation based on - // argument + // plays a splash animation based on the argument void handle_wtce(QString p_wtce); // sets the hp bar of defense(p_bar 1) or pro(p_bar 2) @@ -253,15 +252,6 @@ class Courtroom : public QMainWindow // should be visible less than a second) QTimer *m_flash_timer = nullptr; - // times how long the blinking testimony should be shown(green one in the - // corner) - static const int TESTIMONY_SHOW_INTERVAL = 1500; - QTimer *m_testimony_show_timer = nullptr; - // times how long the blinking testimony should be hidden - static const int TESTIMONY_HIDE_INTERVAL = 500; - QTimer *m_testimony_hide_timer = nullptr; - bool is_testimony_in_progress = false; - // Generate a File Name based on the time you launched the client QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); @@ -355,7 +345,6 @@ class Courtroom : public QMainWindow AOImageDisplay *ui_vp_chatbox = nullptr; DRTextEdit *ui_vp_showname = nullptr; DRTextEdit *ui_vp_message = nullptr; - AOImageDisplay *ui_vp_testimony = nullptr; AOMovie *ui_vp_effect = nullptr; AOMovie *ui_vp_wtce = nullptr; AOMovie *ui_vp_objection = nullptr; @@ -447,11 +436,6 @@ class Courtroom : public QMainWindow QVector wtce_enabled; QVector free_blocks_enabled; - AOButton *ui_witness_testimony = nullptr; - AOButton *ui_cross_examination = nullptr; - AOButton *ui_investigation = nullptr; - AOButton *ui_nonstop = nullptr; - AOButton *ui_change_character = nullptr; AOButton *ui_call_mod = nullptr; AOButton *ui_switch_area_music = nullptr; @@ -553,9 +537,6 @@ public slots: void realization_done(); - void show_testimony(); - void hide_testimony(); - void mod_called(QString p_ip); private slots: @@ -640,8 +621,6 @@ private slots: void on_text_color_changed(int p_color); - void on_witness_testimony_clicked(); - void on_cross_examination_clicked(); void reset_wtce_buttons(); void on_wtce_clicked(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 3e7a0b9b0..e77deeeef 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -137,8 +137,6 @@ void Courtroom::setup_courtroom() list_music(); list_areas(); - is_testimony_in_progress = false; - set_widget_names(); set_widget_layers(); @@ -250,9 +248,6 @@ void Courtroom::set_window_title(QString p_title) void Courtroom::set_scene() { - if (is_testimony_in_progress) - show_testimony(); - // witness is default if pos is invalid QString f_background = "witnessempty"; QString f_desk_image = "stand"; @@ -343,7 +338,6 @@ void Courtroom::set_taken(int n_char, bool p_taken) void Courtroom::set_background(QString p_background) { - is_testimony_in_progress = false; current_background = p_background; } @@ -1557,26 +1551,6 @@ void Courtroom::post_chat() m_message_color_stack.clear(); } -void Courtroom::show_testimony() -{ - if (!is_testimony_in_progress || m_chatmessage[CMPosition] != "wit") - return; - - ui_vp_testimony->show(); - - m_testimony_show_timer->start(TESTIMONY_SHOW_INTERVAL); -} - -void Courtroom::hide_testimony() -{ - ui_vp_testimony->hide(); - - if (!is_testimony_in_progress) - return; - - m_testimony_hide_timer->start(TESTIMONY_HIDE_INTERVAL); -} - void Courtroom::play_sfx() { const QString l_effect = m_chatmessage[CMSoundName]; @@ -1701,12 +1675,6 @@ void Courtroom::handle_wtce(QString p_wtce) { m_effects_player->play_effect(ao_app->get_sfx(wtce_names[index - 1])); ui_vp_wtce->play(wtce_names[index - 1]); - if (index == 1) - { - is_testimony_in_progress = true; - } - else if (index == 2) - is_testimony_in_progress = false; } } } @@ -2161,26 +2129,6 @@ void Courtroom::on_text_color_changed(int p_color) ui_ic_chat_message->setFocus(); } -void Courtroom::on_witness_testimony_clicked() -{ - if (is_client_muted) - return; - - ao_app->send_server_packet(DRPacket("RT", {"testimony1"})); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_cross_examination_clicked() -{ - if (is_client_muted) - return; - - ao_app->send_server_packet(DRPacket("RT", {"testimony2"})); - - ui_ic_chat_message->setFocus(); -} - /** * @brief Set the sprites of the splash buttons. * diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 18e9b09b4..6fed79d9f 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -51,12 +51,6 @@ void Courtroom::create_widgets() m_flash_timer = new QTimer(this); m_flash_timer->setSingleShot(true); - m_testimony_show_timer = new QTimer(this); - m_testimony_show_timer->setSingleShot(true); - - m_testimony_hide_timer = new QTimer(this); - m_testimony_hide_timer->setSingleShot(true); - char_button_mapper = new QSignalMapper(this); m_system_player = new AOSystemPlayer(ao_app, this); @@ -104,7 +98,6 @@ void Courtroom::create_widgets() ui_vp_showname_image = new AOImageDisplay(this, ao_app); - ui_vp_testimony = new AOImageDisplay(this, ao_app); ui_vp_effect = new AOMovie(this, ao_app); ui_vp_wtce = new AOMovie(this, ao_app); ui_vp_objection = new AOMovie(this, ao_app); @@ -258,9 +251,6 @@ void Courtroom::connect_widgets() connect(m_flash_timer, SIGNAL(timeout()), this, SLOT(realization_done())); - connect(m_testimony_show_timer, SIGNAL(timeout()), this, SLOT(hide_testimony())); - connect(m_testimony_hide_timer, SIGNAL(timeout()), this, SLOT(show_testimony())); - connect(ui_emote_left, SIGNAL(clicked()), this, SLOT(on_emote_left_clicked())); connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); @@ -357,7 +347,6 @@ void Courtroom::reset_widget_names() {"showname", ui_vp_showname}, {"message", ui_vp_message}, {"showname_image", ui_vp_showname_image}, - {"vp_testimony", ui_vp_testimony}, {"vp_effect", ui_vp_effect}, {"vp_wtce", ui_vp_wtce}, {"vp_objection", ui_vp_objection}, @@ -603,11 +592,6 @@ void Courtroom::set_widgets() ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "color: white"); - ui_vp_testimony->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_testimony->resize(ui_viewport->width(), ui_viewport->height()); - ui_vp_testimony->set_image("testimony.png"); - ui_vp_testimony->hide(); - ui_vp_effect->move(ui_viewport->x(), ui_viewport->y()); ui_vp_effect->resize(ui_viewport->width(), ui_viewport->height()); ui_vp_effect->hide(); From bcfc14d21d7a67571735a04107b6ce060781a38c Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 19 Jun 2021 19:22:00 -0400 Subject: [PATCH 466/842] Remove speedlines --- include/courtroom.h | 1 - src/courtroom.cpp | 17 +---------------- src/courtroom_widgets.cpp | 6 ------ 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 251f1ed8a..a94ec39aa 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -332,7 +332,6 @@ class Courtroom : public QMainWindow QWidget *ui_viewport = nullptr; AOScene *ui_vp_background = nullptr; - AOMovie *ui_vp_speedlines = nullptr; AOCharMovie *ui_vp_player_char = nullptr; AOScene *ui_vp_desk = nullptr; AOEvidenceDisplay *ui_vp_evidence_display = nullptr; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index e77deeeef..b7c8cad02 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -814,10 +814,8 @@ void Courtroom::objection_done() void Courtroom::handle_chatmessage_2() // handles IC { - ui_vp_speedlines->stop(); - ui_vp_player_char->stop(); - qDebug() << "handle_chatmessage_2"; + ui_vp_player_char->stop(); if (shout_delayed_reload_theme) { @@ -910,19 +908,6 @@ void Courtroom::handle_chatmessage_3() ui_vp_evidence_display->show_evidence(f_image, is_left_side); } - int emote_mod = m_chatmessage[CMEmoteModifier].toInt(); - - if (emote_mod == 5 || emote_mod == 6) - { - QString side = m_chatmessage[CMPosition]; - ui_vp_desk->hide(); - - if (side == "pro" || side == "hlp" || side == "wit") - ui_vp_speedlines->play("prosecution_speedlines"); - else - ui_vp_speedlines->play("defense_speedlines"); - } - int f_anim_state = 0; // BLUE is from an enum in datatypes.h bool text_is_blue = m_chatmessage[CMTextColor].toInt() == DR::CBlue; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 6fed79d9f..c6cfb514a 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -63,8 +63,6 @@ void Courtroom::create_widgets() 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_desk = new AOScene(ui_viewport, ao_app); @@ -333,7 +331,6 @@ void Courtroom::reset_widget_names() {"courtroom", this}, {"viewport", ui_viewport}, {"background", ui_vp_background}, //* - {"speedlines", ui_vp_speedlines}, //* {"player_char", ui_vp_player_char}, //* {"desk", ui_vp_desk}, //* {"music_display_a", ui_vp_music_display_a}, @@ -562,9 +559,6 @@ void Courtroom::set_widgets() ui_vp_background->move(0, 0); ui_vp_background->combo_resize(ui_viewport->size()); - ui_vp_speedlines->move(0, 0); - ui_vp_speedlines->combo_resize(ui_viewport->width(), ui_viewport->height()); - ui_vp_player_char->move(0, 0); ui_vp_player_char->combo_resize(ui_viewport->size()); From c1d9b078f70af1f37f55bbb3730f8477ce8c7233 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 20 Jun 2021 14:49:27 -0400 Subject: [PATCH 467/842] Remove unused variable --- src/courtroom.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 7fb13af91..e31dd8405 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1041,8 +1041,7 @@ void Courtroom::load_ic_text_format() l_color.has_value()) f_format.setForeground(l_color.value()); - if (const bool l_is_bold = - ao_app->get_font_property(QString("ic_chatlog_%1_bold").arg(f_identifier), COURTROOM_FONTS_INI)) + if (ao_app->get_font_property(QString("ic_chatlog_%1_bold").arg(f_identifier), COURTROOM_FONTS_INI)) f_format.setFontWeight(QFont::Bold); }; From b5b2ae017273de0e38e8aa6af9e299ee4a8addcf Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 20 Jun 2021 15:30:14 -0400 Subject: [PATCH 468/842] Remove leftover labels for widgets --- include/courtroom.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/courtroom.h b/include/courtroom.h index 71e4cb5a6..f4b66ac9b 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -463,7 +463,7 @@ class Courtroom : public QMainWindow QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip QVector ui_label_images; - QVector label_images = {"Pre", "Flip", "Hidden", "Music", "SFX", "Blip"}; + QVector label_images = {"Pre", "Flip", "Hidden"}; AOButton *ui_effect_flash = nullptr; AOButton *ui_effect_gloom = nullptr; From 37faf6739c740c7645a5aefbd9de09fcb54077e4 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 20 Jun 2021 15:37:22 -0400 Subject: [PATCH 469/842] Remove leftover debug messages+Fix typo --- src/courtroom_widgets.cpp | 2 -- src/text_file_functions.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index bf4e52a4d..5ac36a991 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1244,7 +1244,6 @@ void Courtroom::load_shouts() QString name = ao_app->get_spbutton("[SHOUTS]", i).trimmed(); if (!name.isEmpty()) { - qDebug() << "SHOUT " << name << " " << ui_shouts[i - 1]; shout_names.append(name); AOButton *l_button = ui_shouts.at(i - 1); widget_names.insert(name, l_button); @@ -1253,7 +1252,6 @@ void Courtroom::load_shouts() Q_EMIT l_button->toggled(l_button->isChecked()); } } - qDebug() << widget_names; } void Courtroom::load_wtce() diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index e836da671..50dd3b0c1 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -237,7 +237,7 @@ QString AOApplication::get_font_name(QString p_identifier, QString p_file) QString f_result = read_theme_ini(p_identifier, p_file); if (f_result.isEmpty()) - qDebug() << "Failure retreiving font name"; + qDebug() << "Failure retrieving font name"; return f_result; } From ae629f8708d090046400b45cf33d0af630f72d8c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 21 Jun 2021 06:17:27 +0200 Subject: [PATCH 470/842] Do not position each widgets twice --- src/courtroom.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 7fb13af91..d48fdd1d3 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -56,10 +56,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() create_widgets(); connect_widgets(); - set_widgets(); - set_char_select(); - set_widget_names(); setup_courtroom(); + set_char_select(); } Courtroom::~Courtroom() From 287aeb67ec730328ca4ac798df6de2e68f8884d1 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 21 Jun 2021 11:13:30 -0400 Subject: [PATCH 471/842] Make MacOS build come in the form of a DMG --- .github/workflows/build-all.yml | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 7f7cd9571..b27cc374f 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -209,24 +209,18 @@ jobs: cp ../libbass.dylib "./Danganronpa Online.app/Contents/Frameworks/" cp ../libbass.dylib "./Danganronpa Online.app/Contents/MacOS/" cp -R ../clang_64/plugins/imageformats "./Danganronpa Online.app/Contents/MacOS/" - + + - name: Make DMG + working-directory: ${{env.parentworkspace}} + shell: bash + run: | + hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online" -ov -format UDZO "Danganronpa Online.dmg" + - name: Upload Artifact uses: actions/upload-artifact@v2 with: - name: "Danganronpa Online (MacOS)" - path: "${{env.parentworkspace}}/Danganronpa Online/**/Danganronpa Online.app" - - # - name: Make DMG - # working-directory: ${{env.parentworkspace}} - # shell: bash - # run: | - # hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online" -ov -format UDZO "Danganronpa Online.dmg" - # - # - name: Upload Artifact - # uses: actions/upload-artifact@v2 - # with: - # name: "Danganronpa Online (MacOS)" - # path: "${{env.parentworkspace}}/Danganronpa Online.dmg" + name: "Danganronpa Online (MacOS)" + path: "${{env.parentworkspace}}/Danganronpa Online.dmg" ############################################################################### # UBUNTU # From ab0400c4808f3042bb23ff805054e3dba1d01bdd Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 27 Jun 2021 16:56:03 +0200 Subject: [PATCH 472/842] Added emote preview, emote and iniswap visibility changes * Emote and iniswap dropdowns are now disabled instead of hidden when spectating --- include/aoemotebutton.h | 10 ++++++++-- include/courtroom.h | 7 ++++++- src/aoemotebutton.cpp | 20 ++++++++++++++++++++ src/courtroom.cpp | 6 +++--- src/courtroom_widgets.cpp | 14 +++++++++++--- src/emotes.cpp | 31 +++++++++++++++++++++++++++++++ 6 files changed, 79 insertions(+), 9 deletions(-) diff --git a/include/aoemotebutton.h b/include/aoemotebutton.h index cfb576cf8..3781aea07 100644 --- a/include/aoemotebutton.h +++ b/include/aoemotebutton.h @@ -19,11 +19,17 @@ class AOEmoteButton : public QPushButton AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y); int get_emote_number(); - void set_emote_number(int p_emote_number); + void set_emote_number(int emote_number); void set_image(DREmote emote, bool enabled); signals: - void emote_clicked(int p_emote_number); + void emote_clicked(int emote_number); + void tooltip_requested(int emote_number, QPoint global_pos); + void mouse_left(int emote_number); + + // QPushButton interface +protected: + bool event(QEvent *event) override; private: AOApplication *ao_app = nullptr; diff --git a/include/courtroom.h b/include/courtroom.h index f4b66ac9b..e01d11809 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -326,6 +326,7 @@ class Courtroom : public QMainWindow int emote_columns = 5; int emote_rows = 2; int m_page_max_emote_count = 10; + int m_emote_preview_id = -1; // inmchatlog_changed; @@ -403,6 +404,8 @@ class Courtroom : public QMainWindow QVector ui_emote_list; AOButton *ui_emote_left = nullptr; AOButton *ui_emote_right = nullptr; + AOImageDisplay *ui_emote_preview = nullptr; + AOCharMovie *ui_emote_preview_character = nullptr; QComboBox *ui_emote_dropdown = nullptr; QComboBox *ui_iniswap_dropdown = nullptr; @@ -579,7 +582,9 @@ private slots: void select_emote(int p_id); - void on_emote_clicked(int p_id); + void on_emote_clicked(int id); + void on_emote_tooltip_requested(int id, QPoint global_pos); + void on_emote_mouse_left(int id); void on_emote_left_clicked(); void on_emote_right_clicked(); diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index 1d535b4d5..72773383a 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -3,6 +3,7 @@ #include "aoapplication.h" #include "file_functions.h" +#include #include AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) @@ -77,3 +78,22 @@ void AOEmoteButton::on_clicked() { Q_EMIT emote_clicked(m_index); } + +bool AOEmoteButton::event(QEvent *event) +{ + switch (event->type()) + { + case QEvent::ToolTip: + Q_EMIT tooltip_requested(m_index, dynamic_cast(event)->globalPos()); + break; + + case QEvent::HoverLeave: + Q_EMIT mouse_left(m_index); + break; + + default: + break; + } + + return QPushButton::event(event); +} diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ff861c7a1..2cd6023b5 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -204,9 +204,9 @@ void Courtroom::enter_courtroom(int p_cid) select_default_sfx(); ui_emotes->setHidden(is_spectating()); - ui_emote_dropdown->setHidden(is_spectating()); - ui_iniswap_dropdown->setHidden(is_spectating()); - ui_ic_chat_message->setEnabled(!is_spectating()); + ui_emote_dropdown->setDisabled(is_spectating()); + ui_iniswap_dropdown->setDisabled(is_spectating()); + ui_ic_chat_message->setDisabled(is_spectating()); // restore line field focus l_current_field->setFocus(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 5ac36a991..0654f1a7b 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -667,6 +667,14 @@ void Courtroom::set_widgets() set_size_and_pos(ui_emote_right, "emote_right", COURTROOM_DESIGN_INI, ao_app); ui_emote_right->set_image("arrow_right.png"); + set_size_and_pos(ui_emote_right, "emote_right", COURTROOM_DESIGN_INI, ao_app); + ui_emote_right->set_image("arrow_right.png"); + + set_size_and_pos(ui_emote_preview, "emote_preview", COURTROOM_DESIGN_INI, ao_app); + ui_emote_preview->set_image("emote_preview.png"); + set_size_and_pos(ui_emote_preview_character, "emote_preview", COURTROOM_DESIGN_INI, ao_app); + ui_emote_preview_character->move(0, 0); + set_size_and_pos(ui_emote_dropdown, "emote_dropdown", COURTROOM_DESIGN_INI, ao_app); set_size_and_pos(ui_iniswap_dropdown, "iniswap_dropdown", COURTROOM_DESIGN_INI, ao_app); @@ -959,13 +967,13 @@ void Courtroom::set_widgets() } // This is used to force already existing notepicker elements to reset their image and theme setting - for (AONotePicker *notepicker : ui_note_area->findChildren()) + for (AONotePicker *notepicker : ui_note_area->findChildren()) { - for (AOButton *button : notepicker->findChildren()) + for (AOButton *button : notepicker->findChildren()) { button->refresh_image(); } - QLineEdit *f_line = notepicker->findChild(); + QLineEdit *f_line = notepicker->findChild(); set_dropdown(f_line, "[LINE EDIT]"); } diff --git a/src/emotes.cpp b/src/emotes.cpp index eaa24907f..e9ae95c9c 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -2,8 +2,10 @@ #include "aoapplication.h" #include "aobutton.h" +#include "aocharmovie.h" #include "aoconfig.h" #include "aoemotebutton.h" +#include "aoimagedisplay.h" #include "commondefs.h" #include "theme.h" @@ -20,6 +22,10 @@ void Courtroom::construct_emotes() ui_emote_left = new AOButton(this, ao_app); ui_emote_right = new AOButton(this, ao_app); + ui_emote_preview = new AOImageDisplay(nullptr, ao_app); + ui_emote_preview->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::BypassGraphicsProxyWidget); + ui_emote_preview_character = new AOCharMovie(ui_emote_preview, ao_app); + ui_emote_dropdown = new QComboBox(this); ui_pos_dropdown = new QComboBox(this); ui_pos_dropdown->addItem("wit", "wit"); @@ -68,6 +74,8 @@ void Courtroom::construct_emote_page_layout() f_emote->set_emote_number(n); connect(f_emote, SIGNAL(emote_clicked(int)), this, SLOT(on_emote_clicked(int))); + connect(f_emote, SIGNAL(tooltip_requested(int, QPoint)), this, SLOT(on_emote_tooltip_requested(int, QPoint))); + connect(f_emote, SIGNAL(mouse_left(int)), this, SLOT(on_emote_mouse_left(int))); ++x_mod_count; @@ -184,6 +192,29 @@ void Courtroom::on_emote_clicked(int p_id) select_emote(p_id + m_page_max_emote_count * m_current_emote_page); } +void Courtroom::on_emote_tooltip_requested(int p_id, QPoint p_global_pos) +{ + const int l_real_id = p_id + m_page_max_emote_count * m_current_emote_page; + if (m_emote_preview_id != -1 || m_emote_preview_id == l_real_id) + return; + m_emote_preview_id = l_real_id; + const DREmote &l_emote = m_emote_list.at(m_emote_preview_id); + ui_emote_preview_character->set_mirrored(ui_flip->isChecked()); + ui_emote_preview_character->play_idle(l_emote.character, l_emote.dialog); + ui_emote_preview->move(p_global_pos.x(), p_global_pos.y() + 8); + ui_emote_preview->show(); +} + +void Courtroom::on_emote_mouse_left(int p_id) +{ + const int l_real_id = p_id + m_page_max_emote_count * m_current_emote_page; + if (m_emote_preview_id == -1 || m_emote_preview_id != l_real_id) + return; + m_emote_preview_id = -1; + ui_emote_preview->hide(); + ui_emote_preview_character->stop(); +} + void Courtroom::on_emote_left_clicked() { --m_current_emote_page; From a11a1a6ddf7aff39f628020702591e30e3223fb0 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 27 Jun 2021 17:12:51 +0200 Subject: [PATCH 473/842] Added default emote preview size --- src/courtroom_widgets.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 0654f1a7b..1bf4cb247 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -670,10 +670,17 @@ void Courtroom::set_widgets() set_size_and_pos(ui_emote_right, "emote_right", COURTROOM_DESIGN_INI, ao_app); ui_emote_right->set_image("arrow_right.png"); - set_size_and_pos(ui_emote_preview, "emote_preview", COURTROOM_DESIGN_INI, ao_app); - ui_emote_preview->set_image("emote_preview.png"); - set_size_and_pos(ui_emote_preview_character, "emote_preview", COURTROOM_DESIGN_INI, ao_app); - ui_emote_preview_character->move(0, 0); + { // emote preview + pos_size_type l_emote_preview_size = ao_app->get_element_dimensions("emote_preview", COURTROOM_DESIGN_INI); + if (l_emote_preview_size.width <= 0 || l_emote_preview_size.height <= 0) + { + l_emote_preview_size.width = 320; + l_emote_preview_size.height = 192; + } + ui_emote_preview->resize(l_emote_preview_size.width, l_emote_preview_size.height); + ui_emote_preview->set_image("emote_preview.png"); + ui_emote_preview_character->resize(l_emote_preview_size.width, l_emote_preview_size.height); + } set_size_and_pos(ui_emote_dropdown, "emote_dropdown", COURTROOM_DESIGN_INI, ao_app); From e3b7a62fa922ed2b4d4ce2100abe0036b4922683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sun, 27 Jun 2021 17:32:04 +0200 Subject: [PATCH 474/842] Removed duplicate lines --- src/courtroom_widgets.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 1bf4cb247..d2db2f53a 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -667,9 +667,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_emote_right, "emote_right", COURTROOM_DESIGN_INI, ao_app); ui_emote_right->set_image("arrow_right.png"); - set_size_and_pos(ui_emote_right, "emote_right", COURTROOM_DESIGN_INI, ao_app); - ui_emote_right->set_image("arrow_right.png"); - { // emote preview pos_size_type l_emote_preview_size = ao_app->get_element_dimensions("emote_preview", COURTROOM_DESIGN_INI); if (l_emote_preview_size.width <= 0 || l_emote_preview_size.height <= 0) From 7dd2424ada0d3dde08c497c324b2bfc463ab461c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 27 Jun 2021 18:34:10 +0200 Subject: [PATCH 475/842] Changed previews to position themselves * Emote previews will no longer be positioned away from the screen --- src/emotes.cpp | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/emotes.cpp b/src/emotes.cpp index e9ae95c9c..eff1aedfa 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -11,10 +11,15 @@ #include #include -#include #include #include +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) +#include +#else +#include +#endif + void Courtroom::construct_emotes() { ui_emotes = new QWidget(this); @@ -24,7 +29,9 @@ void Courtroom::construct_emotes() ui_emote_preview = new AOImageDisplay(nullptr, ao_app); ui_emote_preview->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::BypassGraphicsProxyWidget); + ui_emote_preview->setAttribute(Qt::WA_TransparentForMouseEvents); ui_emote_preview_character = new AOCharMovie(ui_emote_preview, ao_app); + ui_emote_preview_character->setAttribute(Qt::WA_TransparentForMouseEvents); ui_emote_dropdown = new QComboBox(this); ui_pos_dropdown = new QComboBox(this); @@ -201,7 +208,27 @@ void Courtroom::on_emote_tooltip_requested(int p_id, QPoint p_global_pos) const DREmote &l_emote = m_emote_list.at(m_emote_preview_id); ui_emote_preview_character->set_mirrored(ui_flip->isChecked()); ui_emote_preview_character->play_idle(l_emote.character, l_emote.dialog); - ui_emote_preview->move(p_global_pos.x(), p_global_pos.y() + 8); + +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + QRect l_screen_geometry = QApplication::desktop()->screenGeometry(); +#else + QScreen *screen = QApplication::screenAt(p_global_pos); + if (screen == nullptr) + return; + QRect l_screen_geometry = screen->geometry(); +#endif + + // position below cursor + const int l_vertical_spacing = 8; + p_global_pos.setY(p_global_pos.y() + l_vertical_spacing); + + if (l_screen_geometry.width() < ui_emote_preview->width() + p_global_pos.x()) + p_global_pos.setX(p_global_pos.x() - ui_emote_preview->width()); + + if (l_screen_geometry.height() < ui_emote_preview->height() + p_global_pos.y() + l_vertical_spacing) + p_global_pos.setY(p_global_pos.y() - ui_emote_preview->height() - l_vertical_spacing); + + ui_emote_preview->move(p_global_pos); ui_emote_preview->show(); } From 721cc4851302f2835b1a8e2d2c6224e21ad1bcdc Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 27 Jun 2021 18:56:05 +0200 Subject: [PATCH 476/842] Fixed vertical spacing not being adjusted properly --- src/emotes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/emotes.cpp b/src/emotes.cpp index eff1aedfa..9c7cfaabb 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -225,8 +225,8 @@ void Courtroom::on_emote_tooltip_requested(int p_id, QPoint p_global_pos) if (l_screen_geometry.width() < ui_emote_preview->width() + p_global_pos.x()) p_global_pos.setX(p_global_pos.x() - ui_emote_preview->width()); - if (l_screen_geometry.height() < ui_emote_preview->height() + p_global_pos.y() + l_vertical_spacing) - p_global_pos.setY(p_global_pos.y() - ui_emote_preview->height() - l_vertical_spacing); + if (l_screen_geometry.height() < ui_emote_preview->height() + p_global_pos.y()) + p_global_pos.setY(p_global_pos.y() - ui_emote_preview->height() - l_vertical_spacing * 2); ui_emote_preview->move(p_global_pos); ui_emote_preview->show(); From da358a492562db1bab300e56595efe28a4cf8fdc Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 27 Jun 2021 19:01:53 +0200 Subject: [PATCH 477/842] Small refactor to improve maintainability --- src/emotes.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/emotes.cpp b/src/emotes.cpp index 9c7cfaabb..7594246f7 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -220,15 +220,15 @@ void Courtroom::on_emote_tooltip_requested(int p_id, QPoint p_global_pos) // position below cursor const int l_vertical_spacing = 8; - p_global_pos.setY(p_global_pos.y() + l_vertical_spacing); + QPoint l_final_global_pos(p_global_pos.x(), p_global_pos.y() + l_vertical_spacing); - if (l_screen_geometry.width() < ui_emote_preview->width() + p_global_pos.x()) - p_global_pos.setX(p_global_pos.x() - ui_emote_preview->width()); + if (l_screen_geometry.width() < ui_emote_preview->width() + l_final_global_pos.x()) + l_final_global_pos.setX(p_global_pos.x() - ui_emote_preview->width()); - if (l_screen_geometry.height() < ui_emote_preview->height() + p_global_pos.y()) - p_global_pos.setY(p_global_pos.y() - ui_emote_preview->height() - l_vertical_spacing * 2); + if (l_screen_geometry.height() < ui_emote_preview->height() + l_final_global_pos.y()) + l_final_global_pos.setY(p_global_pos.y() - ui_emote_preview->height() - l_vertical_spacing); - ui_emote_preview->move(p_global_pos); + ui_emote_preview->move(l_final_global_pos); ui_emote_preview->show(); } From 9b5ea007433eb0b101fc61e543c3d6f50fb96e65 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 27 Jun 2021 20:15:18 +0200 Subject: [PATCH 478/842] Small code refactor --- include/courtroom.h | 4 ++-- src/emotes.cpp | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index e01d11809..73eb0b116 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -583,8 +583,8 @@ private slots: void select_emote(int p_id); void on_emote_clicked(int id); - void on_emote_tooltip_requested(int id, QPoint global_pos); - void on_emote_mouse_left(int id); + void show_emote_tooltip(int id, QPoint global_pos); + void hide_emote_tooltip(int id); void on_emote_left_clicked(); void on_emote_right_clicked(); diff --git a/src/emotes.cpp b/src/emotes.cpp index 7594246f7..20dd3113c 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -81,8 +81,8 @@ void Courtroom::construct_emote_page_layout() f_emote->set_emote_number(n); connect(f_emote, SIGNAL(emote_clicked(int)), this, SLOT(on_emote_clicked(int))); - connect(f_emote, SIGNAL(tooltip_requested(int, QPoint)), this, SLOT(on_emote_tooltip_requested(int, QPoint))); - connect(f_emote, SIGNAL(mouse_left(int)), this, SLOT(on_emote_mouse_left(int))); + connect(f_emote, SIGNAL(tooltip_requested(int, QPoint)), this, SLOT(show_emote_tooltip(int, QPoint))); + connect(f_emote, SIGNAL(mouse_left(int)), this, SLOT(hide_emote_tooltip(int))); ++x_mod_count; @@ -116,6 +116,7 @@ void Courtroom::refresh_emote_page(const bool p_scroll_to_current_emote) const int l_emote_count = m_emote_list.length(); for (AOEmoteButton *i_button : qAsConst(ui_emote_list)) i_button->hide(); + hide_emote_tooltip(m_emote_preview_id); const int l_page_count = qFloor(l_emote_count / m_page_max_emote_count) + bool(l_emote_count % m_page_max_emote_count); @@ -199,13 +200,13 @@ void Courtroom::on_emote_clicked(int p_id) select_emote(p_id + m_page_max_emote_count * m_current_emote_page); } -void Courtroom::on_emote_tooltip_requested(int p_id, QPoint p_global_pos) +void Courtroom::show_emote_tooltip(int p_id, QPoint p_global_pos) { - const int l_real_id = p_id + m_page_max_emote_count * m_current_emote_page; - if (m_emote_preview_id != -1 || m_emote_preview_id == l_real_id) + if (m_emote_preview_id != -1 || m_emote_preview_id == p_id) return; - m_emote_preview_id = l_real_id; - const DREmote &l_emote = m_emote_list.at(m_emote_preview_id); + m_emote_preview_id = p_id; + const int l_real_id = p_id + m_page_max_emote_count * m_current_emote_page; + const DREmote &l_emote = m_emote_list.at(l_real_id); ui_emote_preview_character->set_mirrored(ui_flip->isChecked()); ui_emote_preview_character->play_idle(l_emote.character, l_emote.dialog); @@ -232,10 +233,9 @@ void Courtroom::on_emote_tooltip_requested(int p_id, QPoint p_global_pos) ui_emote_preview->show(); } -void Courtroom::on_emote_mouse_left(int p_id) +void Courtroom::hide_emote_tooltip(int p_id) { - const int l_real_id = p_id + m_page_max_emote_count * m_current_emote_page; - if (m_emote_preview_id == -1 || m_emote_preview_id != l_real_id) + if (m_emote_preview_id == -1 || m_emote_preview_id != p_id) return; m_emote_preview_id = -1; ui_emote_preview->hide(); From aacc444a3904c13d366e5afc516ef08d1ecbfd99 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 30 Jun 2021 08:00:29 -0400 Subject: [PATCH 479/842] Add chat arrow --- include/courtroom.h | 3 +++ src/courtroom.cpp | 10 ++++++++++ src/courtroom_widgets.cpp | 8 ++++++++ 3 files changed, 21 insertions(+) diff --git a/include/courtroom.h b/include/courtroom.h index 73eb0b116..57530090a 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -295,6 +295,8 @@ class Courtroom : public QMainWindow bool m_chatbox_message_enable_highlighting = false; QVector m_chatbox_message_highlight_colors; + bool m_chat_arrow_exists = false; + QString current_file; // if true, a reload theme order was delayed to be executed *after* a shout @@ -361,6 +363,7 @@ class Courtroom : public QMainWindow AOMovie *ui_vp_effect = nullptr; AOMovie *ui_vp_wtce = nullptr; AOMovie *ui_vp_objection = nullptr; + AOMovie *ui_vp_chat_arrow = nullptr; AOImageDisplay *ui_vp_music_display_a = nullptr; AOImageDisplay *ui_vp_music_display_b = nullptr; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2cd6023b5..4238de0db 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -756,6 +756,11 @@ void Courtroom::handle_chatmessage(QStringList p_contents) f_showname = m_chatmessage[CMShowName]; } + if (m_chat_arrow_exists) + { + ui_vp_chat_arrow->stop(); + } + m_effects_player->stop_all(); text_state = 0; @@ -1465,6 +1470,11 @@ void Courtroom::post_chat() m_message_color_name = ""; m_message_color_stack.clear(); + + if (m_chat_arrow_exists && !chatmessage_is_empty) + { + ui_vp_chat_arrow->play("chat_arrow"); + } } void Courtroom::play_sfx() diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index d2db2f53a..616fde8be 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -100,6 +100,9 @@ void Courtroom::create_widgets() ui_vp_wtce = new AOMovie(this, ao_app); ui_vp_objection = new AOMovie(this, ao_app); + ui_vp_chat_arrow = new AOMovie(this, ao_app); + ui_vp_chat_arrow->set_play_once(false); + ui_iniswap_dropdown = new QComboBox(this); ui_ic_chatlog = new DRTextEdit(this); @@ -349,6 +352,7 @@ void Courtroom::reset_widget_names() {"vp_effect", ui_vp_effect}, {"vp_wtce", ui_vp_wtce}, {"vp_objection", ui_vp_objection}, + {"chat_arrow", ui_vp_chat_arrow}, {"ic_chatlog", ui_ic_chatlog}, {"server_chatlog", ui_ooc_chatlog}, {"area_list", ui_area_list}, @@ -588,6 +592,10 @@ void Courtroom::set_widgets() ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "color: white"); + set_size_and_pos(ui_vp_chat_arrow, "chat_arrow", COURTROOM_DESIGN_INI, ao_app); + ui_vp_chat_arrow->hide(); + m_chat_arrow_exists = !ao_app->find_theme_asset_path("chat_arrow", animated_or_static_extensions()).isEmpty(); + ui_vp_effect->move(ui_viewport->x(), ui_viewport->y()); ui_vp_effect->resize(ui_viewport->width(), ui_viewport->height()); ui_vp_effect->hide(); From 185c12a1a5c224f188fc7b031bbf9fed572a6237 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 30 Jun 2021 21:01:53 +0200 Subject: [PATCH 480/842] Fixed chatlog having issues with URLs and HTML in general --- dronline-client.pro | 2 ++ include/courtroom.h | 3 +- include/drchatlog.h | 41 ++++++++++++++++++++++ include/lobby.h | 3 +- src/courtroom.cpp | 1 + src/courtroom_widgets.cpp | 3 +- src/drchatlog.cpp | 73 +++++++++++++++++++++++++++++++++++++++ src/lobby.cpp | 3 +- 8 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 include/drchatlog.h create mode 100644 src/drchatlog.cpp diff --git a/dronline-client.pro b/dronline-client.pro index d87c753f1..51e2876c7 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -54,6 +54,7 @@ HEADERS += \ include/draudioerror.h \ include/draudiostream.h \ include/draudiostreamfamily.h \ + include/drchatlog.h \ include/drpacket.h \ include/drpather.h \ include/drserversocket.h \ @@ -111,6 +112,7 @@ SOURCES += \ src/draudioerror.cpp \ src/draudiostream.cpp \ src/draudiostreamfamily.cpp \ + src/drchatlog.cpp \ src/drpacket.cpp \ src/drpather.cpp \ src/drserversocket.cpp \ diff --git a/include/courtroom.h b/include/courtroom.h index 73eb0b116..f7e72e20b 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -26,6 +26,7 @@ class AOShoutPlayer; class AOSystemPlayer; class AOTextArea; class AOTimer; +class DRChatLog; class DRTextEdit; #include @@ -379,7 +380,7 @@ class Courtroom : public QMainWindow QList m_ic_record_list; QQueue m_ic_record_queue; - AOTextArea *ui_ooc_chatlog = nullptr; + DRChatLog *ui_ooc_chatlog = nullptr; QListWidget *ui_area_list = nullptr; QListWidget *ui_music_list = nullptr; diff --git a/include/drchatlog.h b/include/drchatlog.h new file mode 100644 index 000000000..b699c2058 --- /dev/null +++ b/include/drchatlog.h @@ -0,0 +1,41 @@ +#pragma once + +class AOConfig; + +#include +#include +#include +#include + +class DRChatLog : public QTextBrowser +{ + Q_OBJECT + +public: + DRChatLog(QWidget *parent = nullptr); + + void append_chatmessage(QString name, QString text); + void append_error(QString text); + +signals: + void message_queued(); + +private: + AOConfig *dr_config = nullptr; + + class Message + { + public: + QDateTime timestamp; + QString name; + QString text; + }; + QList m_message_list; + QQueue m_message_queue; + + void queue_message(QString name, QString text); + +private slots: + void _p_write_message_queue(); + void _p_reset_log(); +}; diff --git a/include/lobby.h b/include/lobby.h index c2baab912..3263dabae 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -8,6 +8,7 @@ class AOButton; class AOConfig; class AOImageDisplay; class AOTextArea; +class DRChatLog; class DRTextEdit; #include @@ -61,7 +62,7 @@ class Lobby : public QMainWindow QListWidget *ui_server_list = nullptr; DRTextEdit *ui_player_count = nullptr; AOTextArea *ui_description = nullptr; - AOTextArea *ui_chatbox = nullptr; + DRChatLog *ui_chatbox = nullptr; QLineEdit *ui_chatname = nullptr; QLineEdit *ui_chatmessage = nullptr; AOImageDisplay *ui_loading_background = nullptr; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2cd6023b5..bad5d30fc 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -19,6 +19,7 @@ #include "aotimer.h" #include "commondefs.h" #include "debug_functions.h" +#include "drchatlog.h" #include "drdiscord.h" #include "drpacket.h" #include "file_functions.h" diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index d2db2f53a..130996ca0 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -21,6 +21,7 @@ #include "aotextarea.h" #include "aotimer.h" #include "commondefs.h" +#include "drchatlog.h" #include "drtextedit.h" #include "file_functions.h" #include "theme.h" @@ -106,7 +107,7 @@ void Courtroom::create_widgets() ui_ic_chatlog->setReadOnly(true); ui_ic_chatlog->set_auto_align(false); - ui_ooc_chatlog = new AOTextArea(this); + ui_ooc_chatlog = new DRChatLog(this); ui_ooc_chatlog->setReadOnly(true); ui_ooc_chatlog->setOpenExternalLinks(true); diff --git a/src/drchatlog.cpp b/src/drchatlog.cpp new file mode 100644 index 000000000..bef292653 --- /dev/null +++ b/src/drchatlog.cpp @@ -0,0 +1,73 @@ +#include "drchatlog.h" + +#include "aoconfig.h" + +#include +#include + +DRChatLog::DRChatLog(QWidget *parent) : QTextBrowser(parent), dr_config(new AOConfig(this)) +{ + connect(this, SIGNAL(message_queued()), this, SLOT(_p_write_message_queue())); + connect(dr_config, SIGNAL(loaded_theme()), this, SLOT(_p_reset_log())); +} + +void DRChatLog::append_chatmessage(QString p_name, QString p_text) +{ + queue_message(p_name.trimmed().isEmpty() ? "Anonymous" : p_name, p_text); +} + +void DRChatLog::append_error(QString p_text) +{ + queue_message(nullptr, p_text); +} + +void DRChatLog::queue_message(QString p_name, QString p_text) +{ + Message l_message; + l_message.timestamp = QDateTime::currentDateTime(); + l_message.name = p_name; + l_message.text = p_text; + m_message_queue.append(std::move(l_message)); + Q_EMIT message_queued(); +} + +void DRChatLog::_p_write_message_queue() +{ + QScrollBar *l_scrollbar = verticalScrollBar(); + const bool l_is_end_scroll_pos = l_scrollbar->value() == l_scrollbar->maximum(); + + QTextCursor l_cursor = textCursor(); + l_cursor.movePosition(QTextCursor::End); + + while (!m_message_queue.isEmpty()) + { + if (!m_message_list.isEmpty()) + l_cursor.insertText(QString(QChar::LineFeed)); + const Message l_message = m_message_queue.dequeue(); + m_message_list.append(l_message); + + l_cursor.insertText(QString(QString("[%1] ").arg(l_message.timestamp.toString("hh:mm")))); + + if (!l_message.name.isEmpty()) + l_cursor.insertHtml(QString("%1: ").arg(l_message.name.toHtmlEscaped())); + + QString l_text = l_message.text.toHtmlEscaped(); + const QRegExp l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); + if (l_text.contains(l_regex)) + l_text.replace(l_regex, "\\1"); + l_cursor.insertHtml(l_text.replace("\n", "
    ")); + } + + if (l_is_end_scroll_pos) + l_scrollbar->setValue(l_scrollbar->maximum()); +} + +void DRChatLog::_p_reset_log() +{ + clear(); + QQueue l_new_message_queue; + dynamic_cast &>(l_new_message_queue) = std::move(m_message_list); + l_new_message_queue.append(std::move(m_message_queue)); + m_message_queue = std::move(l_new_message_queue); + _p_write_message_queue(); +} diff --git a/src/lobby.cpp b/src/lobby.cpp index 1bb69b7d0..6215e810a 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -7,6 +7,7 @@ #include "aotextarea.h" #include "commondefs.h" #include "debug_functions.h" +#include "drchatlog.h" #include "drpacket.h" #include "drpather.h" #include "drtextedit.h" @@ -47,7 +48,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_player_count->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_player_count->setReadOnly(true); ui_description = new AOTextArea(this); - ui_chatbox = new AOTextArea(this); + ui_chatbox = new DRChatLog(this); ui_chatbox->setOpenExternalLinks(true); ui_chatname = new QLineEdit(this); ui_chatname->setPlaceholderText("Name"); From c5a6a1a1911428418952fd053dd498441b4bb573 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 30 Jun 2021 22:13:04 +0200 Subject: [PATCH 481/842] Removed deprecated AOTextArea --- dronline-client.pro | 2 -- include/aotextarea.h | 20 ------------ include/courtroom.h | 1 - include/lobby.h | 4 +-- src/aotextarea.cpp | 72 -------------------------------------------- src/lobby.cpp | 17 +++++------ 6 files changed, 10 insertions(+), 106 deletions(-) delete mode 100644 include/aotextarea.h delete mode 100644 src/aotextarea.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 51e2876c7..8fd003532 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -41,7 +41,6 @@ HEADERS += \ include/aosfxplayer.h \ include/aoshoutplayer.h \ include/aosystemplayer.h \ - include/aotextarea.h \ include/aotimer.h \ include/commondefs.h \ include/courtroom.h \ @@ -94,7 +93,6 @@ SOURCES += \ src/aosfxplayer.cpp \ src/aoshoutplayer.cpp \ src/aosystemplayer.cpp \ - src/aotextarea.cpp \ src/aotimer.cpp \ src/audio_functions.cpp \ src/charselect.cpp \ diff --git a/include/aotextarea.h b/include/aotextarea.h deleted file mode 100644 index ed675c2c7..000000000 --- a/include/aotextarea.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef AOTEXTAREA_H -#define AOTEXTAREA_H - -#include - -class AOTextArea : public QTextBrowser -{ -public: - AOTextArea(QWidget *p_parent = nullptr); - - void append_chatmessage(QString p_name, QString p_message); - void append_error(QString p_message); - -private: - static const QRegExp URL_REGEXP; - - void auto_scroll(QTextCursor old_cursor, int scrollbar_value, bool is_scrolled_down); -}; - -#endif // AOTEXTAREA_H diff --git a/include/courtroom.h b/include/courtroom.h index f7e72e20b..d28431580 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -24,7 +24,6 @@ class AOScene; class AOSfxPlayer; class AOShoutPlayer; class AOSystemPlayer; -class AOTextArea; class AOTimer; class DRChatLog; class DRTextEdit; diff --git a/include/lobby.h b/include/lobby.h index 3263dabae..02376768f 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -7,7 +7,6 @@ class AOApplication; class AOButton; class AOConfig; class AOImageDisplay; -class AOTextArea; class DRChatLog; class DRTextEdit; @@ -17,6 +16,7 @@ class DRTextEdit; class QListWidget; class QLineEdit; class QProgressBar; +class QTextBrowser; class Lobby : public QMainWindow { @@ -61,7 +61,7 @@ class Lobby : public QMainWindow AOButton *ui_about = nullptr; QListWidget *ui_server_list = nullptr; DRTextEdit *ui_player_count = nullptr; - AOTextArea *ui_description = nullptr; + QTextBrowser *ui_description = nullptr; DRChatLog *ui_chatbox = nullptr; QLineEdit *ui_chatname = nullptr; QLineEdit *ui_chatmessage = nullptr; diff --git a/src/aotextarea.cpp b/src/aotextarea.cpp deleted file mode 100644 index 00c6d4815..000000000 --- a/src/aotextarea.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "aotextarea.h" - -#include -#include -#include -#include -#include - -const QRegExp AOTextArea::URL_REGEXP = QRegExp("\\b(https?://\\S+\\.\\S+)\\b"); - -AOTextArea::AOTextArea(QWidget *p_parent) : QTextBrowser(p_parent) -{} - -void AOTextArea::append_chatmessage(QString p_name, QString p_message) -{ - const QTextCursor old_cursor = this->textCursor(); - const int old_scrollbar_value = this->verticalScrollBar()->value(); - const bool is_scrolled_down = old_scrollbar_value == this->verticalScrollBar()->maximum(); - - this->moveCursor(QTextCursor::End); - - this->append(""); - this->insertHtml(QString("[%1] ").arg(QDateTime::currentDateTime().toString("hh:mm"))); - this->insertHtml("" + p_name.toHtmlEscaped() + ": "); - - // cheap workarounds ahoy - p_message += " "; - QString result = p_message.toHtmlEscaped().replace("\n", "
    "); - result = result.replace(URL_REGEXP, "\\1"); - - this->insertHtml(result); - - this->auto_scroll(old_cursor, old_scrollbar_value, is_scrolled_down); -} - -void AOTextArea::append_error(QString p_message) -{ - const QTextCursor old_cursor = this->textCursor(); - const int old_scrollbar_value = this->verticalScrollBar()->value(); - const bool is_scrolled_down = old_scrollbar_value == this->verticalScrollBar()->maximum(); - - this->moveCursor(QTextCursor::End); - - this->append(""); - this->insertHtml(QString("[%1] ").arg(QDateTime::currentDateTime().toString("hh:mm"))); - - p_message += " "; - QString result = p_message.replace("\n", "
    "); - result = result.replace(URL_REGEXP, "\\1"); - - this->insertHtml("" + result + ""); - - this->auto_scroll(old_cursor, old_scrollbar_value, is_scrolled_down); -} - -void AOTextArea::auto_scroll(QTextCursor old_cursor, int old_scrollbar_value, bool is_scrolled_down) -{ - if (old_cursor.hasSelection() || !is_scrolled_down) - { - // The user has selected text or scrolled away from the bottom: maintain - // position. - this->setTextCursor(old_cursor); - this->verticalScrollBar()->setValue(old_scrollbar_value); - } - else - { - // The user hasn't selected any text and the scrollbar is at the bottom: - // scroll to the bottom. - this->moveCursor(QTextCursor::End); - this->verticalScrollBar()->setValue(this->verticalScrollBar()->maximum()); - } -} diff --git a/src/lobby.cpp b/src/lobby.cpp index 6215e810a..9bb046592 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -47,7 +47,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_player_count->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_player_count->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_player_count->setReadOnly(true); - ui_description = new AOTextArea(this); + ui_description = new QTextBrowser(this); ui_chatbox = new DRChatLog(this); ui_chatbox->setOpenExternalLinks(true); ui_chatname = new QLineEdit(this); @@ -146,9 +146,9 @@ void Lobby::set_widgets() "qproperty-alignment: AlignCenter;"); set_size_and_pos(ui_description, "description", LOBBY_DESIGN_INI, ao_app); - ui_description->setReadOnly(true); ui_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "color: white;"); + ui_description->setReadOnly(true); set_size_and_pos(ui_chatbox, "chatbox", LOBBY_DESIGN_INI, ao_app); ui_chatbox->setReadOnly(true); @@ -355,10 +355,7 @@ void Lobby::on_server_list_clicked(QModelIndex p_model) } ui_player_count->setText(nullptr); - ui_description->moveCursor(QTextCursor::Start); - ui_description->setText("Connecting to " + m_last_server.name + "...\n\n"); - ui_description->append(m_last_server.desc); - ui_description->ensureCursorVisible(); + ui_description->setText("Connecting to " + m_last_server.name + "..."); ao_app->connect_to_server(m_last_server); } @@ -423,7 +420,9 @@ void Lobby::set_player_count(int players_online, int max_players) ui_player_count->setText(f_string); ui_player_count->setAlignment(Qt::AlignHCenter); - ui_description->setText("Connected to " + m_last_server.name + "\n\n"); - ui_description->append(m_last_server.desc); - ui_description->ensureCursorVisible(); + QString l_text = m_last_server.desc.toHtmlEscaped(); + const QRegExp l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); + if (l_text.contains(l_regex)) + l_text.replace(l_regex, "\\1"); + ui_description->setText(l_text.replace("\n", "
    ")); } From 80a2ff502a925aff97a71165e5a1c4ae74084171 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 30 Jun 2021 22:23:25 +0200 Subject: [PATCH 482/842] Cleared headers --- src/lobby.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 9bb046592..d9f7f3b54 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -4,7 +4,6 @@ #include "aobutton.h" #include "aoconfig.h" #include "aoimagedisplay.h" -#include "aotextarea.h" #include "commondefs.h" #include "debug_functions.h" #include "drchatlog.h" From f745086e0f35e0461f66a168df65791e593c0557 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 30 Jun 2021 23:01:52 +0200 Subject: [PATCH 483/842] Cleared header (bis) * Previous changes didn't save. --- src/courtroom.cpp | 1 - src/courtroom_widgets.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index bad5d30fc..68a8a0b4f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -15,7 +15,6 @@ #include "aosfxplayer.h" #include "aoshoutplayer.h" #include "aosystemplayer.h" -#include "aotextarea.h" #include "aotimer.h" #include "commondefs.h" #include "debug_functions.h" diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 130996ca0..b6c370ec0 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -18,7 +18,6 @@ #include "aosfxplayer.h" #include "aoshoutplayer.h" #include "aosystemplayer.h" -#include "aotextarea.h" #include "aotimer.h" #include "commondefs.h" #include "drchatlog.h" From e2582db4b1f9ecdd5a05b314978b388cdf750f22 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 30 Jun 2021 23:07:02 +0200 Subject: [PATCH 484/842] Changed regular linefeed to html-type --- src/drchatlog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drchatlog.cpp b/src/drchatlog.cpp index bef292653..9947b6663 100644 --- a/src/drchatlog.cpp +++ b/src/drchatlog.cpp @@ -42,7 +42,7 @@ void DRChatLog::_p_write_message_queue() while (!m_message_queue.isEmpty()) { if (!m_message_list.isEmpty()) - l_cursor.insertText(QString(QChar::LineFeed)); + l_cursor.insertHtml(QString("
    ")); const Message l_message = m_message_queue.dequeue(); m_message_list.append(l_message); From 8ffe90a23cff6aa0db531c180630feb6920fd271 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 30 Jun 2021 23:23:36 +0200 Subject: [PATCH 485/842] Fixed links not properly opening --- src/lobby.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index d9f7f3b54..45a512720 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -47,8 +47,11 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_player_count->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_player_count->setReadOnly(true); ui_description = new QTextBrowser(this); + ui_description->setOpenExternalLinks(true); + ui_description->setReadOnly(true); ui_chatbox = new DRChatLog(this); ui_chatbox->setOpenExternalLinks(true); + ui_chatbox->setReadOnly(true); ui_chatname = new QLineEdit(this); ui_chatname->setPlaceholderText("Name"); ui_chatmessage = new QLineEdit(this); @@ -147,7 +150,6 @@ void Lobby::set_widgets() set_size_and_pos(ui_description, "description", LOBBY_DESIGN_INI, ao_app); ui_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "color: white;"); - ui_description->setReadOnly(true); set_size_and_pos(ui_chatbox, "chatbox", LOBBY_DESIGN_INI, ao_app); ui_chatbox->setReadOnly(true); From 9d50a87ff4e204c057fb02a5f409416260f3ead1 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 30 Jun 2021 23:29:05 +0200 Subject: [PATCH 486/842] Properly clear description to reset format (precaution) --- src/lobby.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lobby.cpp b/src/lobby.cpp index 45a512720..848a3a90e 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -356,6 +356,7 @@ void Lobby::on_server_list_clicked(QModelIndex p_model) } ui_player_count->setText(nullptr); + ui_description->clear(); ui_description->setText("Connecting to " + m_last_server.name + "..."); ao_app->connect_to_server(m_last_server); @@ -412,6 +413,7 @@ void Lobby::append_error(QString f_message) void Lobby::set_choose_a_server() { ui_player_count->setText(nullptr); + ui_description->clear(); ui_description->setText(tr("Choose a server.")); } From fa3be84b687fafdfb520301814fe19950ced550b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 30 Jun 2021 23:45:42 +0200 Subject: [PATCH 487/842] Fix fake links --- src/lobby.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 848a3a90e..23e1c5fc7 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -356,8 +356,7 @@ void Lobby::on_server_list_clicked(QModelIndex p_model) } ui_player_count->setText(nullptr); - ui_description->clear(); - ui_description->setText("Connecting to " + m_last_server.name + "..."); + ui_description->setHtml("Connecting to " + m_last_server.name + "..."); ao_app->connect_to_server(m_last_server); } @@ -413,8 +412,7 @@ void Lobby::append_error(QString f_message) void Lobby::set_choose_a_server() { ui_player_count->setText(nullptr); - ui_description->clear(); - ui_description->setText(tr("Choose a server.")); + ui_description->setHtml(tr("Choose a server.")); } void Lobby::set_player_count(int players_online, int max_players) @@ -427,5 +425,5 @@ void Lobby::set_player_count(int players_online, int max_players) const QRegExp l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); if (l_text.contains(l_regex)) l_text.replace(l_regex, "\\1"); - ui_description->setText(l_text.replace("\n", "
    ")); + ui_description->setHtml(l_text.replace("\n", "
    ")); } From ccf99a3ecd1a32f9afc34ee2168d0f7025575149 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 30 Jun 2021 21:43:56 -0400 Subject: [PATCH 488/842] Remove leftover exclamation mark notation --- src/aomovie.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 5584cd6f9..13e16d7d5 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -32,12 +32,6 @@ void AOMovie::play(QString p_file, QString p_char) m_movie->stop(); QString file_path = ""; - // Remove ! at the beginning of p_file if needed - // This is an indicator that the file is not selectable in the current theme - // (gamemode-timeofday) but is still usable by other people - if (p_file.length() > 0 && p_file.at(0) == "!") - p_file = p_file.remove(0, 1); - QString char_p_file; // FIXME: When looking in the character folder, append "_bubble" except for // custom We probably should drop this From 29218ef949727874204e15dc97621b83660bba95 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 30 Jun 2021 22:02:01 -0400 Subject: [PATCH 489/842] Optimize directory lookup for AOMovie --- src/aomovie.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 13e16d7d5..5c61a1c66 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -50,12 +50,18 @@ void AOMovie::play(QString p_file, QString p_char) // 4. In the theme folder (gamemode-timeofday/main/default), look for // "placeholder" + extensions in `exts` in order - file_path = ao_app->find_asset_path( - { - ao_app->get_character_path(p_char, char_p_file), - ao_app->get_character_path(p_char, "overlay/" + char_p_file), - }, - animated_or_static_extensions()); + // Small optimization. If p_char is empty, the function call would trivially return empty anyway + // Then, it only makes sense to call the IO function if p_char is not empty + if (!p_char.isEmpty()) + { + file_path = ao_app->find_asset_path( + { + ao_app->get_character_path(p_char, char_p_file), + ao_app->get_character_path(p_char, "overlay/" + char_p_file), + }, + animated_or_static_extensions()); + } + if (file_path.isEmpty()) { file_path = ao_app->find_theme_asset_path(p_file, animated_or_static_extensions()); From c54ef08e2fc37f15dadef7a057b74f8b4d733bd0 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 4 Jul 2021 17:54:36 +0200 Subject: [PATCH 490/842] Updated lobby connection messages --- src/lobby.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 23e1c5fc7..c951697b0 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -45,6 +45,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_player_count->setFrameStyle(QFrame::NoFrame); ui_player_count->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_player_count->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_player_count->setWordWrapMode(QTextOption::NoWrap); ui_player_count->setReadOnly(true); ui_description = new QTextBrowser(this); ui_description->setOpenExternalLinks(true); @@ -355,7 +356,7 @@ void Lobby::on_server_list_clicked(QModelIndex p_model) m_last_server = ao_app->get_favorite_list().at(p_model.row()); } - ui_player_count->setText(nullptr); + ui_player_count->setText("Connecting..."); ui_description->setHtml("Connecting to " + m_last_server.name + "..."); ao_app->connect_to_server(m_last_server); @@ -417,7 +418,8 @@ void Lobby::set_choose_a_server() void Lobby::set_player_count(int players_online, int max_players) { - QString f_string = "Online: " + QString::number(players_online) + "/" + QString::number(max_players); + const QString f_string = + "Connected: " + QString::number(players_online) + " user" + QString(players_online > 1 ? "s" : nullptr); ui_player_count->setText(f_string); ui_player_count->setAlignment(Qt::AlignHCenter); From ae5572ecdb80c6d23aa73f4ad0c7ca8710ca2ec4 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 4 Jul 2021 20:45:32 +0200 Subject: [PATCH 491/842] Added back the max player count * The max player count doesn't do anything but maybe it will in the future, who knows? --- src/lobby.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index c951697b0..4ea0e3dcc 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -418,8 +418,8 @@ void Lobby::set_choose_a_server() void Lobby::set_player_count(int players_online, int max_players) { - const QString f_string = - "Connected: " + QString::number(players_online) + " user" + QString(players_online > 1 ? "s" : nullptr); + const QString f_string = "Connected: " + QString::number(players_online) + "/" + QString::number(max_players) + + " user" + QString(players_online > 1 ? "s" : nullptr); ui_player_count->setText(f_string); ui_player_count->setAlignment(Qt::AlignHCenter); From 8a08cf053b9b18db94a984a3f60f181d4d8b4632 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 7 Jul 2021 00:56:19 +0200 Subject: [PATCH 492/842] Revert most changes (minus the online/connected title change) --- src/lobby.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 4ea0e3dcc..a5df9c597 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -418,8 +418,7 @@ void Lobby::set_choose_a_server() void Lobby::set_player_count(int players_online, int max_players) { - const QString f_string = "Connected: " + QString::number(players_online) + "/" + QString::number(max_players) + - " user" + QString(players_online > 1 ? "s" : nullptr); + const QString f_string = "Connected: " + QString::number(players_online) + "/" + QString::number(max_players); ui_player_count->setText(f_string); ui_player_count->setAlignment(Qt::AlignHCenter); From 11fff563c07895042ea3fa0915551fca469a965c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 7 Jul 2021 03:33:29 +0200 Subject: [PATCH 493/842] Removed multiple reloading of the chat arrow --- include/aomovie.h | 1 + include/courtroom.h | 2 -- src/aomovie.cpp | 14 +++++++++----- src/courtroom.cpp | 14 ++++---------- src/courtroom_widgets.cpp | 3 ++- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/include/aomovie.h b/include/aomovie.h index 8c49d86e4..170bdd661 100644 --- a/include/aomovie.h +++ b/include/aomovie.h @@ -17,6 +17,7 @@ class AOMovie : public QLabel void set_play_once(bool p_play_once); void play(QString p_file, QString p_char = ""); void play_interjection(QString p_char_name, QString p_interjection_name); + void restart(); void combo_resize(int w, int h); QMovie::MovieState state(); void stop(); diff --git a/include/courtroom.h b/include/courtroom.h index 57530090a..39a29a50d 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -295,8 +295,6 @@ class Courtroom : public QMainWindow bool m_chatbox_message_enable_highlighting = false; QVector m_chatbox_message_highlight_colors; - bool m_chat_arrow_exists = false; - QString current_file; // if true, a reload theme order was delayed to be executed *after* a shout diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 5584cd6f9..6e77da64d 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -119,6 +119,12 @@ void AOMovie::play_interjection(QString p_char_name, QString p_interjection_name m_movie->start(); } +void AOMovie::restart() +{ + m_movie->stop(); + m_movie->start(); +} + void AOMovie::stop() { m_movie->stop(); @@ -131,13 +137,11 @@ void AOMovie::stop() void AOMovie::frame_change(int n_frame) { - if (n_frame == (m_movie->frameCount() - 1) && is_play_once) + const int l_real_frame = n_frame + 1; + if (is_play_once && l_real_frame == m_movie->frameCount()) { - // we need this or else the last frame wont show delay(m_movie->nextFrameDelay()); - this->stop(); - - // signal connected to courtroom object, let it figure out what to do + m_movie->stop(); Q_EMIT done(); } } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 4238de0db..afa054b96 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -756,11 +756,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) f_showname = m_chatmessage[CMShowName]; } - if (m_chat_arrow_exists) - { - ui_vp_chat_arrow->stop(); - } - + ui_vp_chat_arrow->hide(); m_effects_player->stop_all(); text_state = 0; @@ -1470,11 +1466,9 @@ void Courtroom::post_chat() m_message_color_name = ""; m_message_color_stack.clear(); - - if (m_chat_arrow_exists && !chatmessage_is_empty) - { - ui_vp_chat_arrow->play("chat_arrow"); - } + if (!chatmessage_is_empty) + ui_vp_chat_arrow->restart(); + ui_vp_chat_arrow->setHidden(chatmessage_is_empty); } void Courtroom::play_sfx() diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 616fde8be..a99608c03 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -593,8 +593,9 @@ void Courtroom::set_widgets() "color: white"); set_size_and_pos(ui_vp_chat_arrow, "chat_arrow", COURTROOM_DESIGN_INI, ao_app); + if (!ao_app->find_theme_asset_path("chat_arrow", animated_or_static_extensions()).isEmpty()) + ui_vp_chat_arrow->play("chat_arrow"); ui_vp_chat_arrow->hide(); - m_chat_arrow_exists = !ao_app->find_theme_asset_path("chat_arrow", animated_or_static_extensions()).isEmpty(); ui_vp_effect->move(ui_viewport->x(), ui_viewport->y()); ui_vp_effect->resize(ui_viewport->width(), ui_viewport->height()); From e05ba4f9cb614d8412f4b884c1b0a0c05cdf0d11 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 6 Jul 2021 21:53:22 -0400 Subject: [PATCH 494/842] Update comments --- src/aomovie.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 5c61a1c66..a69f815d4 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -41,17 +41,15 @@ void AOMovie::play(QString p_file, QString p_char) char_p_file = p_file; // Asset lookup order - // 1. In the character folder, look for + // 1. If p_char is not empty, in the character folder, look for // `char_p_file` + extensions in `exts` in order - // 2. In the character folder, look for + // 2. If p_char is not empty, in the character folder, look for // `overlay/char_p_file` + extensions in `exts` in order // 3. In the theme folder (gamemode-timeofday/main/default), look for // `p_file` + extensions in `exts` in order // 4. In the theme folder (gamemode-timeofday/main/default), look for // "placeholder" + extensions in `exts` in order - // Small optimization. If p_char is empty, the function call would trivially return empty anyway - // Then, it only makes sense to call the IO function if p_char is not empty if (!p_char.isEmpty()) { file_path = ao_app->find_asset_path( From d7bd19aed74d94ee15839fce6275446808f0685c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 7 Jul 2021 19:00:40 +0200 Subject: [PATCH 495/842] Removed emote filter * This just caused more issues than it solved, people should just fix their INIs instead of being lazy with it. Thanks. --- src/text_file_functions.cpp | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 50dd3b0c1..a51e4f916 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -688,27 +688,6 @@ QVector AOApplication::get_emote_list(QString p_chr) } } - // remove duplicate emotes and bring the last one to the front - QVector l_filtered_list; - QStringList l_dialog_filter_list; - for (auto it = r_emote_list.cbegin(); it != r_emote_list.cend(); ++it) - { - const DREmote &it_emote = *it; - if (l_dialog_filter_list.contains(it_emote.dialog)) - continue; - l_dialog_filter_list.append(it_emote.dialog); - - for (auto rit = r_emote_list.crbegin(); rit != r_emote_list.crend(); ++rit) - { - const DREmote &rit_emote = *rit; - if (it_emote.dialog == rit_emote.dialog) - { - l_filtered_list.append(rit_emote); - break; - } - } - } - r_emote_list = std::move(l_filtered_list); return r_emote_list; } From 1a351f2216e399a00478a74718788f43703fcb81 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 12 Jul 2021 18:04:25 +0200 Subject: [PATCH 496/842] Reworked frame handling for AOMovie to be more accurate --- include/aomovie.h | 8 ++++-- src/aomovie.cpp | 68 ++++++++++++++++++++++++++++++----------------- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/include/aomovie.h b/include/aomovie.h index 170bdd661..f753aafd1 100644 --- a/include/aomovie.h +++ b/include/aomovie.h @@ -14,7 +14,10 @@ class AOMovie : public QLabel AOMovie(QWidget *p_parent, AOApplication *p_ao_app); ~AOMovie(); + bool is_play_once(); void set_play_once(bool p_play_once); + + void play_file_name(QString file); void play(QString p_file, QString p_char = ""); void play_interjection(QString p_char_name, QString p_interjection_name); void restart(); @@ -29,11 +32,12 @@ class AOMovie : public QLabel AOApplication *ao_app = nullptr; QMovie *m_movie = nullptr; - - bool is_play_once = true; + bool m_first_loop = false; + bool m_play_once = true; private slots: void frame_change(int n_frame); + void handle_state(QMovie::MovieState); }; #endif // AOMOVIE_H diff --git a/src/aomovie.cpp b/src/aomovie.cpp index a512e6dcc..3470037e8 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -1,6 +1,7 @@ #include "aomovie.h" #include "aoapplication.h" +#include "aopixmap.h" #include "file_functions.h" #include "misc_functions.h" @@ -12,9 +13,8 @@ AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) m_movie = new QMovie(); - this->setMovie(m_movie); - connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); + connect(m_movie, SIGNAL(stateChanged(QMovie::MovieState)), this, SLOT(handle_state(QMovie::MovieState))); } AOMovie::~AOMovie() @@ -22,14 +22,28 @@ AOMovie::~AOMovie() delete m_movie; } +bool AOMovie::is_play_once() +{ + return m_play_once; +} + void AOMovie::set_play_once(bool p_play_once) { - is_play_once = p_play_once; + m_play_once = p_play_once; } -void AOMovie::play(QString p_file, QString p_char) +void AOMovie::play_file_name(QString p_file_name) { m_movie->stop(); + m_movie->setFileName(p_file_name); + m_first_loop = true; + qDebug() << "playing" << p_file_name; + m_movie->start(); + this->show(); +} + +void AOMovie::play(QString p_file, QString p_char) +{ QString file_path = ""; QString char_p_file; @@ -67,12 +81,7 @@ void AOMovie::play(QString p_file, QString p_char) file_path = ao_app->find_theme_asset_path("placeholder", animated_or_static_extensions()); } - qDebug() << "playing" << file_path; - m_movie->setFileName(file_path); - - this->show(); - m_movie->setScaledSize(this->size()); - m_movie->start(); + play_file_name(file_path); } /// @@ -81,8 +90,6 @@ void AOMovie::play(QString p_file, QString p_char) /// void AOMovie::play_interjection(QString p_char_name, QString p_interjection_name) { - m_movie->stop(); - QString p_char_interjection_name; // FIXME: When looking in the character folder, append "_bubble" except for // custom We probably should drop this @@ -111,10 +118,7 @@ void AOMovie::play_interjection(QString p_char_name, QString p_interjection_name qDebug() << "playing interjection" << (p_interjection_name.isEmpty() ? "(none)" : p_interjection_name) << "for character" << (p_char_name.isEmpty() ? "(none)" : p_char_name) << "at" << (interjection_filepath.isEmpty() ? "(not found)" : interjection_filepath); - m_movie->setFileName(interjection_filepath); - this->show(); - m_movie->setScaledSize(this->size()); - m_movie->start(); + play_file_name(interjection_filepath); } void AOMovie::restart() @@ -126,20 +130,36 @@ void AOMovie::restart() void AOMovie::stop() { m_movie->stop(); - m_movie->setFileName(""); // Properly free up resources - // If the file is not cleared after the movie is stopped, the movie will - // continue using as much memory as it requested, which leads to invisible - // memory hogging. + // free up resources + m_movie->setFileName(nullptr); this->hide(); } void AOMovie::frame_change(int n_frame) { - const int l_real_frame = n_frame + 1; - if (is_play_once && l_real_frame == m_movie->frameCount()) + const QImage l_frame = m_movie->currentImage(); + bool l_paint_frame = true; + + if (m_play_once && n_frame == 0 && !m_first_loop) + { + l_paint_frame = false; + stop(); + } + + if (l_paint_frame || m_first_loop) + { + m_first_loop = false; + // paint + AOPixmap l_pixmap(QPixmap::fromImage(l_frame)); + this->setPixmap(l_pixmap.scale(this->size())); + } +} + +void AOMovie::handle_state(QMovie::MovieState p_state) +{ + if (m_play_once && p_state == QMovie::NotRunning) { - delay(m_movie->nextFrameDelay()); - m_movie->stop(); + stop(); Q_EMIT done(); } } From 8aa5e5b3d0fc4b9be83b01944d05097d77f0681f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 13 Jul 2021 15:24:30 -0400 Subject: [PATCH 497/842] Rename dropdown to stylesheet in functions, as other widget types use those now --- include/courtroom.h | 6 +++--- src/aonotearea.cpp | 2 +- src/courtroom_widgets.cpp | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index c631aa1a7..9674e1d29 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -66,11 +66,11 @@ class Courtroom : public QMainWindow // helper function that calls above function on the relevant widgets void set_fonts(); - // sets dropdown menu stylesheet - void set_dropdown(QWidget *widget, QString target_tag); + // sets stylesheet + void set_stylesheet(QWidget *widget, QString target_tag); // helper funciton that call above function on the relevant widgets - void set_dropdowns(); + void set_stylesheets(); void set_window_title(QString p_title); diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp index 1b2231764..b4a5b422d 100644 --- a/src/aonotearea.cpp +++ b/src/aonotearea.cpp @@ -59,7 +59,7 @@ void Courtroom::on_add_button_clicked() set_note_files(); } - set_dropdown(f_line, "[LINE EDIT]"); + set_stylesheet(f_line, "[LINE EDIT]"); connect(f_button, SIGNAL(clicked(bool)), this, SLOT(on_set_file_button_clicked())); connect(f_delete, SIGNAL(clicked(bool)), this, SLOT(on_delete_button_clicked())); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index bc311edc1..60be64747 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -987,11 +987,11 @@ void Courtroom::set_widgets() button->refresh_image(); } QLineEdit *f_line = notepicker->findChild(); - set_dropdown(f_line, "[LINE EDIT]"); + set_stylesheet(f_line, "[LINE EDIT]"); } adapt_numbered_items(ui_timers, "timer_number", "timer"); - set_dropdowns(); + set_stylesheets(); set_fonts(); Q_EMIT loaded_theme(); @@ -1393,7 +1393,7 @@ void Courtroom::set_free_blocks() } } -void Courtroom::set_dropdown(QWidget *widget, QString target_tag) +void Courtroom::set_stylesheet(QWidget *widget, QString target_tag) { QString f_file = "courtroom_stylesheets.css"; QString style_sheet_string = ao_app->get_stylesheet(target_tag, f_file); @@ -1401,14 +1401,14 @@ void Courtroom::set_dropdown(QWidget *widget, QString target_tag) widget->setStyleSheet(style_sheet_string); } -void Courtroom::set_dropdowns() +void Courtroom::set_stylesheets() { - set_dropdown(ui_text_color, "[TEXT COLOR]"); - set_dropdown(ui_emote_dropdown, "[EMOTE DROPDOWN]"); - set_dropdown(ui_iniswap_dropdown, "[INISWAP DROPDOWN]"); - set_dropdown(ui_pos_dropdown, "[POS DROPDOWN]"); - set_dropdown(ui_ic_chat_message, "[IC LINE]"); - set_dropdown(ui_ooc_chat_message, "[OOC LINE]"); + set_stylesheet(ui_text_color, "[TEXT COLOR]"); + set_stylesheet(ui_emote_dropdown, "[EMOTE DROPDOWN]"); + set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]"); + set_stylesheet(ui_pos_dropdown, "[POS DROPDOWN]"); + set_stylesheet(ui_ic_chat_message, "[IC LINE]"); + set_stylesheet(ui_ooc_chat_message, "[OOC LINE]"); } void Courtroom::set_fonts() From b7667a597e778e19de46925010d45447043eb95a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 13 Jul 2021 21:26:45 +0200 Subject: [PATCH 498/842] Adjusted AOMovie not to hide itself by default on being done --- include/aomovie.h | 4 ++++ src/aomovie.cpp | 13 ++++++++++++- src/courtroom_widgets.cpp | 3 +++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/aomovie.h b/include/aomovie.h index f753aafd1..6053d956b 100644 --- a/include/aomovie.h +++ b/include/aomovie.h @@ -17,6 +17,9 @@ class AOMovie : public QLabel bool is_play_once(); void set_play_once(bool p_play_once); + bool is_hide_on_done(); + void set_hide_on_done(bool p_hide_on_done); + void play_file_name(QString file); void play(QString p_file, QString p_char = ""); void play_interjection(QString p_char_name, QString p_interjection_name); @@ -34,6 +37,7 @@ class AOMovie : public QLabel QMovie *m_movie = nullptr; bool m_first_loop = false; bool m_play_once = true; + bool m_hide_on_done = false; private slots: void frame_change(int n_frame); diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 3470037e8..9092c76b9 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -32,6 +32,16 @@ void AOMovie::set_play_once(bool p_play_once) m_play_once = p_play_once; } +bool AOMovie::is_hide_on_done() +{ + return m_hide_on_done; +} + +void AOMovie::set_hide_on_done(bool p_hide_on_done) +{ + m_hide_on_done = p_hide_on_done; +} + void AOMovie::play_file_name(QString p_file_name) { m_movie->stop(); @@ -132,7 +142,8 @@ void AOMovie::stop() m_movie->stop(); // free up resources m_movie->setFileName(nullptr); - this->hide(); + if (m_hide_on_done) + this->hide(); } void AOMovie::frame_change(int n_frame) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index bc311edc1..52447b3ef 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -97,8 +97,11 @@ void Courtroom::create_widgets() ui_vp_showname_image = new AOImageDisplay(this, ao_app); ui_vp_effect = new AOMovie(this, ao_app); + ui_vp_effect->set_hide_on_done(true); ui_vp_wtce = new AOMovie(this, ao_app); + ui_vp_wtce->set_hide_on_done(true); ui_vp_objection = new AOMovie(this, ao_app); + ui_vp_objection->set_hide_on_done(true); ui_vp_chat_arrow = new AOMovie(this, ao_app); ui_vp_chat_arrow->set_play_once(false); From e4a7455490b0af1fb8b47e874dd35b7d112986cc Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 13 Jul 2021 15:28:12 -0400 Subject: [PATCH 499/842] Add IC showname [IC NAME LINE] and OOC username [OOC NAME LINE] as stylesheet options --- src/courtroom_widgets.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 60be64747..0b8e99063 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1408,7 +1408,9 @@ void Courtroom::set_stylesheets() set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]"); set_stylesheet(ui_pos_dropdown, "[POS DROPDOWN]"); set_stylesheet(ui_ic_chat_message, "[IC LINE]"); + set_stylesheet(ui_ic_chat_showname, "[IC NAME LINE]"); set_stylesheet(ui_ooc_chat_message, "[OOC LINE]"); + set_stylesheet(ui_ooc_chat_name, "[OOC NAME LINE]"); } void Courtroom::set_fonts() From 3132b91118ed8fd7886144117fedd5087556f548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 21 Jul 2021 02:48:45 +0200 Subject: [PATCH 500/842] Build QtApng instead of downloading it from a release --- .github/workflows/build-all.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index b27cc374f..4e39feb97 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -260,11 +260,17 @@ jobs: cp ./bass.h ./DRO-Client/3rd/include/bass.h cp ./x64/libbass.so ./DRO-Client/3rd/x86_64/libbass.so - - name: Fetch QtApng external library + - name: Build QtApng external library shell: bash working-directory: ${{env.parentworkspace}} run: | - curl -L https://cdn.discordapp.com/attachments/422857337133596692/845396654642954260/libqapng.so -o libqapng.so + git clone https://github.com/Skycoder42/QtApng + cd QtApng + qmake + make -j2 + cp plugins/imageformats/libqapng.so .. + cd .. + rm -r -f QtApng - name: Install Qt shell: bash From bc6007e3e3def09314276822c17b92491e221208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 21 Jul 2021 02:50:55 +0200 Subject: [PATCH 501/842] Fix out of order building process --- .github/workflows/build-all.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 4e39feb97..7c3d77929 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -260,6 +260,16 @@ jobs: cp ./bass.h ./DRO-Client/3rd/include/bass.h cp ./x64/libbass.so ./DRO-Client/3rd/x86_64/libbass.so + - name: Install Qt + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + sudo apt-get install build-essential + sudo apt-get install qt5-default qttools5-dev + sudo apt-get install libqt5designer5 + sudo apt-get install git + sudo snap install discord + - name: Build QtApng external library shell: bash working-directory: ${{env.parentworkspace}} @@ -272,16 +282,6 @@ jobs: cd .. rm -r -f QtApng - - name: Install Qt - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - sudo apt-get install build-essential - sudo apt-get install qt5-default qttools5-dev - sudo apt-get install libqt5designer5 - sudo apt-get install git - sudo snap install discord - - name: Run qmake shell: bash working-directory: ${{github.workspace}} From f3802336a797e70300b8776257594a0b32400f5c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 22 Jul 2021 07:15:56 +0200 Subject: [PATCH 502/842] Added character content url If `CONTENT.txt` is present within the base folder of the character, it will read the file and send a slash command (`files_set`) to notify the server of the current character content link. --- include/courtroom.h | 3 +++ src/courtroom.cpp | 1 + src/courtroom_character.cpp | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/include/courtroom.h b/include/courtroom.h index c631aa1a7..8bfaf76a2 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -117,9 +117,11 @@ class Courtroom : public QMainWindow public: QString get_character(); QString get_character_ini(); + QString get_character_content_url(); void update_iniswap_list(); void update_default_iniswap_item(); void select_base_character_iniswap(); + void refresh_character_content_url(); // Set the showname of the client void set_showname(QString p_showname); @@ -320,6 +322,7 @@ class Courtroom : public QMainWindow int char_rows = 9; int m_page_max_chr_count = 90; + QString m_character_content_url; QVector m_emote_list; int m_emote_id = 0; int m_current_emote_page = 0; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 0e9eb0752..4abe9e59c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -181,6 +181,7 @@ void Courtroom::enter_courtroom(int p_cid) if (l_changed_chr) set_character_position(ao_app->get_char_side(l_chr_name)); select_base_character_iniswap(); + refresh_character_content_url(); const int l_prev_emote_count = m_emote_list.count(); m_emote_list = ao_app->get_emote_list(l_chr_name); diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index e062d7775..a9ef48509 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -7,8 +7,10 @@ #include #include +#include #include #include +#include int Courtroom::get_character_id() { @@ -33,6 +35,19 @@ QString Courtroom::get_character_ini() return ao_config->character_ini(get_character()); } +QString Courtroom::get_character_content_url() +{ + QFile l_contentFile(ao_app->get_character_path(get_character_ini(), "CONTENT.txt")); + if (!l_contentFile.open(QIODevice::ReadOnly)) + return nullptr; + + const QUrl l_url(QString(l_contentFile.readAll()).simplified()); + if (l_url.isRelative() || l_url.isLocalFile()) + return nullptr; + + return l_url.toString(); +} + namespace { void drSetItemIcon(QComboBox *p_widget, const int p_index, const QString &p_chr_name, AOApplication *ao_app) @@ -93,6 +108,15 @@ void Courtroom::select_base_character_iniswap() ui_iniswap_dropdown->setCurrentText(l_current_chr); } +void Courtroom::refresh_character_content_url() +{ + const QString l_new_content_url = get_character_content_url(); + if (m_character_content_url == l_new_content_url) + return; + m_character_content_url = l_new_content_url; + send_ooc_packet("/files_set " + m_character_content_url); +} + void Courtroom::on_iniswap_dropdown_changed(int p_index) { ao_config->set_character_ini(get_character(), From b1e51a6e184059a182291f227978db826f1d5b65 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 23 Jul 2021 00:00:37 +0200 Subject: [PATCH 503/842] Moved stylesheet away from courtroom and moved to theme functions instead --- include/commondefs.h | 1 + include/courtroom.h | 6 ------ include/theme.h | 1 + src/aonotearea.cpp | 3 ++- src/commondefs.cpp | 1 + src/courtroom_widgets.cpp | 40 ++++++++++++++------------------------- src/theme.cpp | 23 ++++++++++++++++++---- 7 files changed, 38 insertions(+), 37 deletions(-) diff --git a/include/commondefs.h b/include/commondefs.h index 0d5a07267..1715dbcc9 100644 --- a/include/commondefs.h +++ b/include/commondefs.h @@ -18,6 +18,7 @@ extern const QString COURTROOM_FONTS_INI; extern const QString COURTROOM_LAYERS_INI; extern const QString COURTROOM_SOUNDS_INI; extern const QString COURTROOM_TEXT_COLOR_INI; +extern const QString COURTROOM_STYLESHEETS_CSS; extern const QString LOBBY_DESIGN_INI; extern const QString LOBBY_FONTS_INI; diff --git a/include/courtroom.h b/include/courtroom.h index 9674e1d29..002e37f9e 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -66,12 +66,6 @@ class Courtroom : public QMainWindow // helper function that calls above function on the relevant widgets void set_fonts(); - // sets stylesheet - void set_stylesheet(QWidget *widget, QString target_tag); - - // helper funciton that call above function on the relevant widgets - void set_stylesheets(); - void set_window_title(QString p_title); // sets status as taken on character with cid n_char and places proper shading diff --git a/include/theme.h b/include/theme.h index 0efd9b4ca..f29051842 100644 --- a/include/theme.h +++ b/include/theme.h @@ -13,3 +13,4 @@ void set_size_and_pos(QWidget *widget, QString identifier, QString ini_file, AOA void set_text_alignment(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); void set_font(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); void set_drtextedit_font(DRTextEdit *widget, QString identifier, QString ini_file, AOApplication *ao_app); +bool set_stylesheet(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp index b4a5b422d..725c0b0e1 100644 --- a/src/aonotearea.cpp +++ b/src/aonotearea.cpp @@ -5,6 +5,7 @@ #include "aonotepicker.h" #include "commondefs.h" #include "courtroom.h" +#include "theme.h" #include #include @@ -59,7 +60,7 @@ void Courtroom::on_add_button_clicked() set_note_files(); } - set_stylesheet(f_line, "[LINE EDIT]"); + set_stylesheet(f_line, "[LINE EDIT]", COURTROOM_STYLESHEETS_CSS, ao_app); connect(f_button, SIGNAL(clicked(bool)), this, SLOT(on_set_file_button_clicked())); connect(f_delete, SIGNAL(clicked(bool)), this, SLOT(on_delete_button_clicked())); diff --git a/src/commondefs.cpp b/src/commondefs.cpp index 7baa41134..3f1762787 100644 --- a/src/commondefs.cpp +++ b/src/commondefs.cpp @@ -18,6 +18,7 @@ const QString COURTROOM_FONTS_INI = "courtroom_fonts.ini"; const QString COURTROOM_LAYERS_INI = "courtroom_layers.ini"; const QString COURTROOM_SOUNDS_INI = "courtroom_sounds.ini"; const QString COURTROOM_TEXT_COLOR_INI = "courtroom_text_colors.ini"; +const QString COURTROOM_STYLESHEETS_CSS = "courtroom_stylesheets.css"; const QString LOBBY_DESIGN_INI = "lobby_design.ini"; const QString LOBBY_FONTS_INI = "lobby_fonts.ini"; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 0b8e99063..1dc9a3072 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -621,10 +621,13 @@ void Courtroom::set_widgets() set_size_and_pos(ui_ic_chat_showname, "ic_chat_name", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_ic_chat_showname, "ic_chat_name", COURTROOM_FONTS_INI, ao_app); - ui_ic_chat_showname->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + if (!set_stylesheet(ui_ic_chat_showname, "[IC NAME LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) + ui_ic_chat_showname->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_ic_chat_message, "ao2_ic_chat_message", COURTROOM_FONTS_INI, ao_app); - ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); + if (!set_stylesheet(ui_ic_chat_message, "[IC LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) + ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); set_size_and_pos(ui_vp_chatbox, "ao2_chatbox", COURTROOM_DESIGN_INI, ao_app); @@ -651,11 +654,13 @@ void Courtroom::set_widgets() set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_FONTS_INI, ao_app); - ui_ooc_chat_message->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + if (!set_stylesheet(ui_ooc_chat_message, "[OOC LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) + ui_ooc_chat_message->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_ooc_chat_name, "ooc_chat_name", COURTROOM_FONTS_INI, ao_app); - ui_ooc_chat_name->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + if (!set_stylesheet(ui_ooc_chat_name, "[OOC NAME LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) + ui_ooc_chat_name->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); set_size_and_pos(ui_music_search, "music_search", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_music_search, "music_search", COURTROOM_FONTS_INI, ao_app); @@ -689,10 +694,13 @@ void Courtroom::set_widgets() } set_size_and_pos(ui_emote_dropdown, "emote_dropdown", COURTROOM_DESIGN_INI, ao_app); + set_stylesheet(ui_emote_dropdown, "[EMOTE DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); set_size_and_pos(ui_iniswap_dropdown, "iniswap_dropdown", COURTROOM_DESIGN_INI, ao_app); + set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); set_size_and_pos(ui_pos_dropdown, "pos_dropdown", COURTROOM_DESIGN_INI, ao_app); + set_stylesheet(ui_pos_dropdown, "[POS DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); set_size_and_pos(ui_defense_bar, "defense_bar", COURTROOM_DESIGN_INI, ao_app); ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + ".png"); @@ -904,6 +912,7 @@ void Courtroom::set_widgets() ui_prosecution_minus->set_image("prominus.png"); set_size_and_pos(ui_text_color, "text_color", COURTROOM_DESIGN_INI, ao_app); + set_stylesheet(ui_text_color, "[TEXT COLOR]", COURTROOM_STYLESHEETS_CSS, ao_app); set_size_and_pos(ui_evidence_button, "evidence_button", COURTROOM_DESIGN_INI, ao_app); ui_evidence_button->set_image("evidencebutton.png"); @@ -987,11 +996,10 @@ void Courtroom::set_widgets() button->refresh_image(); } QLineEdit *f_line = notepicker->findChild(); - set_stylesheet(f_line, "[LINE EDIT]"); + set_stylesheet(f_line, "[LINE EDIT]", COURTROOM_STYLESHEETS_CSS, ao_app); } adapt_numbered_items(ui_timers, "timer_number", "timer"); - set_stylesheets(); set_fonts(); Q_EMIT loaded_theme(); @@ -1393,26 +1401,6 @@ void Courtroom::set_free_blocks() } } -void Courtroom::set_stylesheet(QWidget *widget, QString target_tag) -{ - QString f_file = "courtroom_stylesheets.css"; - QString style_sheet_string = ao_app->get_stylesheet(target_tag, f_file); - if (style_sheet_string != "") - widget->setStyleSheet(style_sheet_string); -} - -void Courtroom::set_stylesheets() -{ - set_stylesheet(ui_text_color, "[TEXT COLOR]"); - set_stylesheet(ui_emote_dropdown, "[EMOTE DROPDOWN]"); - set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]"); - set_stylesheet(ui_pos_dropdown, "[POS DROPDOWN]"); - set_stylesheet(ui_ic_chat_message, "[IC LINE]"); - set_stylesheet(ui_ic_chat_showname, "[IC NAME LINE]"); - set_stylesheet(ui_ooc_chat_message, "[OOC LINE]"); - set_stylesheet(ui_ooc_chat_name, "[OOC NAME LINE]"); -} - void Courtroom::set_fonts() { set_drtextedit_font(ui_vp_showname, "showname", COURTROOM_FONTS_INI, ao_app); diff --git a/src/theme.cpp b/src/theme.cpp index 578dcfa4e..00bba7278 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -79,15 +79,30 @@ void set_font(QWidget *p_widget, QString p_identifier, QString ini_file, AOAppli p_widget->setStyleSheet(style_sheet_string); } -void set_drtextedit_font(DRTextEdit *p_widget, QString p_identifier, QString ini_file, AOApplication *ao_app) +void set_drtextedit_font(DRTextEdit *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) { - set_font(p_widget, p_identifier, ini_file, ao_app); + set_font(p_widget, p_identifier, p_ini_file, ao_app); // Do outlines - bool outline = (ao_app->get_font_property(p_identifier + "_outline", ini_file) == 1); + bool outline = (ao_app->get_font_property(p_identifier + "_outline", p_ini_file) == 1); p_widget->set_outline(outline); // alignment - set_text_alignment_or_default(p_widget, p_identifier, ini_file, ao_app, "text_alignment", Qt::AlignLeft, + set_text_alignment_or_default(p_widget, p_identifier, p_ini_file, ao_app, "text_alignment", Qt::AlignLeft, Qt::AlignTop); } + +/** + * @brief set_stylesheet + * @param p_widget The widget to apply the stylesheet to + * @param p_identifier The identifier within the stylesheet + * @param p_ini_file The stylesheet file (glorified ini) + * @param ao_app AOApplication shenanigan + * @return Return true if any data (even if invalid) was applied to the widget + */ +bool set_stylesheet(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) +{ + const QString p_style = ao_app->get_stylesheet(p_identifier, p_ini_file); + p_widget->setStyleSheet(p_style); + return !p_style.isEmpty(); +} From d672f107c02ecfa217f505331f862a208c14cef5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 24 Jul 2021 08:11:14 +0200 Subject: [PATCH 504/842] Implemented #206 --- include/courtroom.h | 16 ++++++--- src/courtroom.cpp | 76 +++++++++++++++++++++++++-------------- src/courtroom_sfx.cpp | 12 +++---- src/courtroom_widgets.cpp | 51 ++++++++++++++++---------- 4 files changed, 100 insertions(+), 55 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 002e37f9e..c4b558c33 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -126,6 +126,7 @@ class Courtroom : public QMainWindow // helper function that populates ui_music_list with the contents of // music_list + void filter_list_widget(QListWidget *widget, QString filter); void list_music(); void list_areas(); @@ -377,7 +378,9 @@ class Courtroom : public QMainWindow DRChatLog *ui_ooc_chatlog = nullptr; QListWidget *ui_area_list = nullptr; + QLineEdit *ui_area_search = nullptr; QListWidget *ui_music_list = nullptr; + QLineEdit *ui_music_search = nullptr; QListWidget *ui_sfx_list = nullptr; QVector m_sfx_list; @@ -391,8 +394,6 @@ class Courtroom : public QMainWindow QLineEdit *ui_ooc_chat_name = nullptr; QLineEdit *ui_ooc_chat_message = nullptr; - QLineEdit *ui_music_search = nullptr; - QLineEdit *ui_sfx_search = nullptr; QWidget *ui_emotes = nullptr; @@ -569,11 +570,15 @@ private slots: void on_ooc_name_editing_finished(); void on_ooc_return_pressed(); - void on_music_search_edited(); - void on_music_list_clicked(); void on_area_list_clicked(); - void on_music_list_double_clicked(QModelIndex p_model); void on_area_list_double_clicked(QModelIndex p_model); + void on_area_search_edited(QString); + void on_area_search_edited(); + + void on_music_list_clicked(); + void on_music_list_double_clicked(QModelIndex p_model); + void on_music_search_edited(QString); + void on_music_search_edited(); void select_emote(int p_id); @@ -701,6 +706,7 @@ public slots: void update_all_sfx_item_color(); public slots: + void filter_sfx_list(QString); void filter_sfx_list(); private: diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 0e9eb0752..cc83c4854 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -392,19 +392,25 @@ void Courtroom::handle_clock(QString time) ui_vp_clock->show(); } -void Courtroom::list_music() +void Courtroom::filter_list_widget(QListWidget *p_list_widget, QString p_filter) { - ui_music_list->clear(); + const QString l_final_filter = p_filter.simplified(); + for (int i = 0; i < p_list_widget->count(); i++) + { + QListWidgetItem *i_item = p_list_widget->item(i); + i_item->setHidden(!l_final_filter.isEmpty() && !i_item->text().contains(l_final_filter, Qt::CaseInsensitive)); + } +} - const QString l_item_filter = ui_music_search->text(); +void Courtroom::list_music() +{ const QBrush l_song_brush(ao_app->get_color("found_song_color", COURTROOM_DESIGN_INI)); const QBrush l_missing_song_brush(ao_app->get_color("missing_song_color", COURTROOM_DESIGN_INI)); - for (const QString &i_item_name : qAsConst(m_music_list)) + ui_music_list->clear(); + for (const QString &i_song : qAsConst(m_music_list)) { - if (!i_item_name.contains(l_item_filter, Qt::CaseInsensitive)) - continue; - QListWidgetItem *l_item = new QListWidgetItem(i_item_name, ui_music_list); - const QString l_song_path = ao_app->find_asset_path({ao_app->get_music_path(i_item_name)}, audio_extensions()); + QListWidgetItem *l_item = new QListWidgetItem(i_song, ui_music_list); + const QString l_song_path = ao_app->find_asset_path({ao_app->get_music_path(i_song)}, audio_extensions()); l_item->setBackground(l_song_path.isEmpty() ? l_missing_song_brush : l_song_brush); } } @@ -412,13 +418,10 @@ void Courtroom::list_music() void Courtroom::list_areas() { ui_area_list->clear(); - - const QString l_item_filter = ui_music_search->text(); const QBrush l_area_brush(ao_app->get_color("area_free_color", COURTROOM_DESIGN_INI)); + ui_area_list->clear(); for (const QString &i_item_name : qAsConst(m_area_list)) { - if (!i_item_name.contains(l_item_filter, Qt::CaseInsensitive)) - continue; QListWidgetItem *l_item = new QListWidgetItem(i_item_name, ui_area_list); l_item->setBackground(l_area_brush); } @@ -1757,12 +1760,6 @@ void Courtroom::on_ooc_return_pressed() ui_ooc_chat_message->setFocus(); } -void Courtroom::on_music_search_edited() -{ - list_music(); - list_areas(); -} - void Courtroom::on_pos_dropdown_changed(int p_index) { ui_ic_chat_message->setFocus(); @@ -1807,12 +1804,29 @@ void Courtroom::on_pos_dropdown_changed(int p_index) // ao_app->send_server_packet(DRPacket("SP", {f_pos})); } -void Courtroom::on_music_list_clicked() +void Courtroom::on_area_list_clicked() { ui_ic_chat_message->setFocus(); } -void Courtroom::on_area_list_clicked() +void Courtroom::on_area_list_double_clicked(QModelIndex p_model) +{ + const QString l_song_name = ui_area_list->item(p_model.row())->text(); + ao_app->send_server_packet(DRPacket("MC", {l_song_name, QString::number(m_chr_id)})); + ui_ic_chat_message->setFocus(); +} + +void Courtroom::on_area_search_edited(QString p_filter) +{ + filter_list_widget(ui_area_list, p_filter); +} + +void Courtroom::on_area_search_edited() +{ + on_area_search_edited(ui_area_search->text()); +} + +void Courtroom::on_music_list_clicked() { ui_ic_chat_message->setFocus(); } @@ -1821,16 +1835,19 @@ void Courtroom::on_music_list_double_clicked(QModelIndex p_model) { if (is_client_muted) return; - QString p_song = ui_music_list->item(p_model.row())->text(); - ao_app->send_server_packet(DRPacket("MC", {p_song, QString::number(m_chr_id)})); + const QString p_area_name = ui_music_list->item(p_model.row())->text(); + ao_app->send_server_packet(DRPacket("MC", {p_area_name, QString::number(m_chr_id)})); ui_ic_chat_message->setFocus(); } -void Courtroom::on_area_list_double_clicked(QModelIndex p_model) +void Courtroom::on_music_search_edited(QString p_filter) { - QString p_area = ui_area_list->item(p_model.row())->text(); - ao_app->send_server_packet(DRPacket("MC", {p_area, QString::number(m_chr_id)})); - ui_ic_chat_message->setFocus(); + filter_list_widget(ui_music_list, p_filter); +} + +void Courtroom::on_music_search_edited() +{ + on_music_search_edited(ui_music_search->text()); } /** @@ -2151,15 +2168,22 @@ void Courtroom::on_call_mod_clicked() void Courtroom::on_switch_area_music_clicked() { + if (ao_app->read_theme_ini_bool("separate_music_and_area_list", COURTROOM_CONFIG_INI)) + return; + if (ui_area_list->isHidden()) { ui_area_list->show(); + ui_area_search->show(); ui_music_list->hide(); + ui_music_search->hide(); } else { ui_area_list->hide(); + ui_area_search->hide(); ui_music_list->show(); + ui_music_search->show(); } } diff --git a/src/courtroom_sfx.cpp b/src/courtroom_sfx.cpp index 7cf4a140d..0aa5a8017 100644 --- a/src/courtroom_sfx.cpp +++ b/src/courtroom_sfx.cpp @@ -72,14 +72,14 @@ void Courtroom::load_current_character_sfx_list() filter_sfx_list(); } +void Courtroom::filter_sfx_list(QString p_filter) +{ + filter_list_widget(ui_sfx_list, p_filter); +} + void Courtroom::filter_sfx_list() { - const QString l_filter = ui_sfx_search->text(); - for (int i = 0; i < ui_sfx_list->count(); ++i) - { - QListWidgetItem *i_item = ui_sfx_list->item(i); - i_item->setHidden(!i_item->text().contains(l_filter, Qt::CaseInsensitive)); - } + filter_sfx_list(ui_sfx_search->text()); } void Courtroom::select_default_sfx() diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index f41d0b498..d9749d9a5 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -117,8 +117,18 @@ void Courtroom::create_widgets() ui_ooc_chatlog->setOpenExternalLinks(true); ui_area_list = new QListWidget(this); + ui_area_search = new QLineEdit(this); + ui_area_search->setFrame(false); + ui_area_search->setPlaceholderText("Area filter"); + ui_music_list = new QListWidget(this); + ui_music_search = new QLineEdit(this); + ui_music_search->setFrame(false); + ui_music_search->setPlaceholderText("Music filter"); + ui_sfx_list = new QListWidget(this); + ui_sfx_search = new QLineEdit(this); + ui_sfx_search->setFrame(false); ui_ic_chat_showname = new QLineEdit(this); ui_ic_chat_showname->setFrame(false); @@ -139,12 +149,6 @@ void Courtroom::create_widgets() ui_ooc_chat_name->setPlaceholderText("Name"); ui_ooc_chat_name->setText(ao_config->username()); - ui_music_search = new QLineEdit(this); - ui_music_search->setFrame(false); - - ui_sfx_search = new QLineEdit(this); - ui_sfx_search->setFrame(false); - ui_note_area = new AONoteArea(this, ao_app); ui_note_area->add_button = new AOButton(ui_note_area, ao_app); ui_note_area->m_layout = new QVBoxLayout(ui_note_area); @@ -306,8 +310,9 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(log_display_music_switch_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); - connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited())); - connect(ui_sfx_search, SIGNAL(editingFinished()), this, SLOT(filter_sfx_list())); + connect(ui_area_search, SIGNAL(textChanged(QString)), this, SLOT(on_area_search_edited(QString))); + connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); + connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(filter_sfx_list(QString))); connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); @@ -359,15 +364,16 @@ void Courtroom::reset_widget_names() {"ic_chatlog", ui_ic_chatlog}, {"server_chatlog", ui_ooc_chatlog}, {"area_list", ui_area_list}, + {"area_search", ui_area_search}, {"music_list", ui_music_list}, + {"music_search", ui_music_search}, {"sfx_list", ui_sfx_list}, + {"sfx_search", ui_sfx_search}, {"ic_chat_name", ui_ic_chat_showname}, {"ao2_ic_chat_message", ui_ic_chat_message}, // ui_muted {"ooc_chat_message", ui_ooc_chat_message}, {"ooc_chat_name", ui_ooc_chat_name}, - {"music_search", ui_music_search}, - {"sfx_search", ui_sfx_search}, {"note_scroll_area", ui_note_scroll_area}, {"note_area", ui_note_area}, // add_button @@ -614,12 +620,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_ooc_chatlog, "server_chatlog", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_music_list, "music_list", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_area_list, "area_list", COURTROOM_DESIGN_INI, ao_app); - if (ui_music_list->isVisible()) - ui_area_list->hide(); - // ui_area_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - set_size_and_pos(ui_sfx_list, "sfx_list", COURTROOM_DESIGN_INI, ao_app); set_size_and_pos(ui_ic_chat_showname, "ic_chat_name", COURTROOM_DESIGN_INI, ao_app); @@ -665,11 +665,26 @@ void Courtroom::set_widgets() if (!set_stylesheet(ui_ooc_chat_name, "[OOC NAME LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) ui_ooc_chat_name->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + set_size_and_pos(ui_sfx_search, "sfx_search", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_sfx_search, "sfx_search", COURTROOM_FONTS_INI, ao_app); + + set_size_and_pos(ui_music_list, "music_list", COURTROOM_DESIGN_INI, ao_app); set_size_and_pos(ui_music_search, "music_search", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_music_search, "music_search", COURTROOM_FONTS_INI, ao_app); - set_size_and_pos(ui_sfx_search, "sfx_search", COURTROOM_DESIGN_INI, ao_app); - set_text_alignment(ui_sfx_search, "sfx_search", COURTROOM_FONTS_INI, ao_app); + { // area separation logic + const bool l_separate_music_area_list = + ao_app->read_theme_ini_bool("separate_music_and_area_list", COURTROOM_CONFIG_INI); + const QString p_area_identifier = l_separate_music_area_list ? "area" : "music"; + + set_size_and_pos(ui_area_list, p_area_identifier + "_list", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_area_search, p_area_identifier + "_search", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_area_search, p_area_identifier + "_search", COURTROOM_FONTS_INI, ao_app); + + ui_area_list->setVisible(l_separate_music_area_list); + ui_area_search->setVisible(l_separate_music_area_list); + ui_switch_area_music->setHidden(l_separate_music_area_list); + } // char select reconstruct_char_select(); From 2355d331ec93f1eeea53323444239f306c79e6a5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 25 Jul 2021 01:18:34 +0200 Subject: [PATCH 505/842] Variable name clear-up --- src/courtroom.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index cc83c4854..9a63b47d1 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1811,8 +1811,8 @@ void Courtroom::on_area_list_clicked() void Courtroom::on_area_list_double_clicked(QModelIndex p_model) { - const QString l_song_name = ui_area_list->item(p_model.row())->text(); - ao_app->send_server_packet(DRPacket("MC", {l_song_name, QString::number(m_chr_id)})); + const QString l_area_name = ui_area_list->item(p_model.row())->text(); + ao_app->send_server_packet(DRPacket("MC", {l_area_name, QString::number(m_chr_id)})); ui_ic_chat_message->setFocus(); } @@ -1835,8 +1835,8 @@ void Courtroom::on_music_list_double_clicked(QModelIndex p_model) { if (is_client_muted) return; - const QString p_area_name = ui_music_list->item(p_model.row())->text(); - ao_app->send_server_packet(DRPacket("MC", {p_area_name, QString::number(m_chr_id)})); + const QString p_song_name = ui_music_list->item(p_model.row())->text(); + ao_app->send_server_packet(DRPacket("MC", {p_song_name, QString::number(m_chr_id)})); ui_ic_chat_message->setFocus(); } From 20a3feb437a7106975d367207bcb7f28811cb925 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 25 Jul 2021 18:58:22 +0200 Subject: [PATCH 506/842] Fixed an issue where music list and search would disappear after changing theme * Music list and search would not reappear until clicking on the area/music switch button if the theme is switched from a theme that uses the `separate_music_and_area_list` feature --- include/courtroom.h | 1 + src/courtroom.cpp | 7 ++++++- src/courtroom_widgets.cpp | 13 +++++++------ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index c4b558c33..13c72e4d1 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -127,6 +127,7 @@ class Courtroom : public QMainWindow // helper function that populates ui_music_list with the contents of // music_list void filter_list_widget(QListWidget *widget, QString filter); + bool is_area_music_list_separated(); void list_music(); void list_areas(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 9a63b47d1..5ae52da40 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -402,6 +402,11 @@ void Courtroom::filter_list_widget(QListWidget *p_list_widget, QString p_filter) } } +bool Courtroom::is_area_music_list_separated() +{ + return ao_app->read_theme_ini_bool("separate_music_and_area_list", COURTROOM_CONFIG_INI); +} + void Courtroom::list_music() { const QBrush l_song_brush(ao_app->get_color("found_song_color", COURTROOM_DESIGN_INI)); @@ -2168,7 +2173,7 @@ void Courtroom::on_call_mod_clicked() void Courtroom::on_switch_area_music_clicked() { - if (ao_app->read_theme_ini_bool("separate_music_and_area_list", COURTROOM_CONFIG_INI)) + if (is_area_music_list_separated()) return; if (ui_area_list->isHidden()) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index d9749d9a5..8715c1cc9 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -671,19 +671,20 @@ void Courtroom::set_widgets() set_size_and_pos(ui_music_list, "music_list", COURTROOM_DESIGN_INI, ao_app); set_size_and_pos(ui_music_search, "music_search", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_music_search, "music_search", COURTROOM_FONTS_INI, ao_app); + ui_music_list->show(); + ui_music_search->show(); { // area separation logic - const bool l_separate_music_area_list = - ao_app->read_theme_ini_bool("separate_music_and_area_list", COURTROOM_CONFIG_INI); - const QString p_area_identifier = l_separate_music_area_list ? "area" : "music"; + const bool l_is_area_music_list_separated = is_area_music_list_separated(); + const QString p_area_identifier = l_is_area_music_list_separated ? "area" : "music"; set_size_and_pos(ui_area_list, p_area_identifier + "_list", COURTROOM_DESIGN_INI, ao_app); set_size_and_pos(ui_area_search, p_area_identifier + "_search", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_area_search, p_area_identifier + "_search", COURTROOM_FONTS_INI, ao_app); - ui_area_list->setVisible(l_separate_music_area_list); - ui_area_search->setVisible(l_separate_music_area_list); - ui_switch_area_music->setHidden(l_separate_music_area_list); + ui_area_list->setVisible(l_is_area_music_list_separated); + ui_area_search->setVisible(l_is_area_music_list_separated); + ui_switch_area_music->setHidden(l_is_area_music_list_separated); } // char select From af4f26eac4c4c128e8960c9c79b023727e1b71c7 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 25 Jul 2021 20:35:20 +0200 Subject: [PATCH 507/842] Changed variable name of `separate_music_and_area_list` inside `courtroom_config.ini` * Inside `courtroom_config.ini`, changed variable name of `separate_music_and_area_list` to `enable_music_and_area_list_separation` --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 5ae52da40..04859c5e4 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -404,7 +404,7 @@ void Courtroom::filter_list_widget(QListWidget *p_list_widget, QString p_filter) bool Courtroom::is_area_music_list_separated() { - return ao_app->read_theme_ini_bool("separate_music_and_area_list", COURTROOM_CONFIG_INI); + return ao_app->read_theme_ini_bool("enable_music_and_area_list_separation", COURTROOM_CONFIG_INI); } void Courtroom::list_music() From 45955168fa19b2df0d7a48b20bc7f9f9eb549f30 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 6 Aug 2021 14:02:30 +0200 Subject: [PATCH 508/842] Implemented #210, miscellaneous changes * Implemented #210 * Renamed a couple aoconfig methods and variables * Removed `get_background_path` from `Courtroom` --- include/aoapplication.h | 7 ++-- include/aoconfig.h | 16 +++++----- include/aoconfigpanel.h | 2 +- include/commondefs.h | 1 + include/courtroom.h | 10 ++++-- include/datatypes.h | 8 +++++ src/aoapplication.cpp | 3 +- src/aoconfig.cpp | 40 +++++++++++------------ src/aoconfigpanel.cpp | 24 +++++++------- src/aoscene.cpp | 21 +++--------- src/commondefs.cpp | 1 + src/courtroom.cpp | 42 +++++++++++++++++------- src/path_functions.cpp | 71 +++++++++++++++++++++++++++++++++-------- src/server_socket.cpp | 24 ++++++++++---- 14 files changed, 174 insertions(+), 96 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index a8af96202..c3d18a1de 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -71,14 +71,17 @@ class AOApplication : public QApplication // QString get_demothings_path(); QString get_sounds_path(QString p_file); QString get_music_path(QString p_song); - QString get_background_path(QString p_file); - QString get_default_background_path(QString p_file); + QString format_background_path(QString p_identifier); + QStringList get_available_background_identifier_list(); + QString get_current_background_path(); QString get_evidence_path(QString p_file); QString sanitize_path(QString p_file); QString find_asset_path(QStringList file_list, QStringList extension_list); QString find_asset_path(QStringList file_list); + QString find_asset_path(QString p_file, QStringList p_extension_list); + QString find_asset_path(QString p_file); QString find_theme_asset_path(QString file, QStringList extension_list); QString find_theme_asset_path(QString file); diff --git a/include/aoconfig.h b/include/aoconfig.h index 119a8d479..daff39dc9 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -33,9 +33,9 @@ class AOConfig : public QObject bool discord_hide_character() const; QString theme() const; QString gamemode() const; - bool manual_gamemode_enabled() const; - QString timeofday() const; - bool manual_timeofday_enabled() const; + bool is_manual_gamemode_enabled() const; + QString time_of_day() const; + bool is_manual_time_of_day_enabled() const; bool always_pre_enabled() const; int chat_tick_interval() const; int log_max_lines() const; @@ -80,9 +80,9 @@ public slots: void set_discord_hide_character(const bool p_enabled); void set_theme(QString p_string); void set_gamemode(QString p_string); - void set_manual_gamemode(bool p_enabled); - void set_timeofday(QString p_string); - void set_manual_timeofday(bool p_enabled); + void set_manual_gamemode_enabled(bool p_enabled); + void set_time_of_day(QString p_string); + void set_manual_time_of_day_enabled(bool p_enabled); void set_always_pre(bool p_enabled); void set_chat_tick_interval(int p_number); void set_log_max_lines(int p_number); @@ -125,8 +125,8 @@ public slots: void theme_changed(QString); void gamemode_changed(QString); void manual_gamemode_changed(bool); - void timeofday_changed(QString); - void manual_timeofday_changed(bool); + void time_of_day_changed(QString); + void manual_time_of_day_changed(bool); void showname_changed(QString); void showname_placeholder_changed(QString); void character_ini_changed(QString base_character); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index b9580a48a..503fe714d 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -44,7 +44,7 @@ private slots: void on_reload_theme_clicked(); void on_theme_changed(QString); void on_gamemode_changed(QString); - void on_timeofday_changed(QString); + void on_time_of_day_changed(QString); void on_gamemode_index_changed(QString p_text); void on_timeofday_index_changed(QString p_text); void on_showname_placeholder_changed(QString p_text); diff --git a/include/commondefs.h b/include/commondefs.h index 1715dbcc9..c3c2a8bab 100644 --- a/include/commondefs.h +++ b/include/commondefs.h @@ -3,6 +3,7 @@ class QString; extern const QString BACKGROUND_BACKGROUNDS_INI; +extern const QString BACKGROUND_DEFAULT_NAME; extern const QString BASE_CONFIG_INI; diff --git a/include/courtroom.h b/include/courtroom.h index 2d3fd0928..0b7d2e9a7 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -74,7 +74,10 @@ class Courtroom : public QMainWindow // sets the current background to argument. also does some checks to see if // it's a legacy bg - void set_background(QString p_background); + DRAreaBackground get_background(); + void set_background(DRAreaBackground p_area_bg); + QString get_time_of_day(); + void set_time_of_day(QString p_tod); void set_tick_rate(const int tick_rate); @@ -93,7 +96,7 @@ class Courtroom : public QMainWindow void done_received(); // sets desk and bg based on pos in chatmessage - void set_scene(); + void update_background_scene(); // sets text color based on text color in chatmessage void set_text_color(); @@ -339,7 +342,8 @@ class Courtroom : public QMainWindow int current_clock = -1; - QString current_background = "gs4"; + DRAreaBackground m_background; + QString m_time_of_day; AOImageDisplay *ui_background = nullptr; diff --git a/include/datatypes.h b/include/datatypes.h index ef2089f62..cb2109cf2 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -2,6 +2,7 @@ #define DATATYPES_H #include +#include class DREmote { @@ -17,6 +18,13 @@ class DREmote int sound_delay = 0; }; +class DRAreaBackground +{ +public: + QString background; + QMap background_tod_map; +}; + class DRChatRecord { public: diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index fc060d065..bac862901 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -34,7 +34,8 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(handle_theme_modification())); connect(ao_config, SIGNAL(gamemode_changed(QString)), this, SLOT(handle_theme_modification())); - connect(ao_config, SIGNAL(timeofday_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(time_of_day_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(manual_time_of_day_changed(bool)), this, SLOT(handle_theme_modification())); connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(handle_theme_modification())); ao_config_panel->hide(); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 52ab67a42..f78d9077c 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -55,8 +55,8 @@ private slots: QString theme; QString gamemode; bool manual_gamemode; - QString timeofday; - bool manual_timeofday; + QString time_of_day; + bool manual_time_of_day; QString showname; QString showname_placeholder; QMap ini_map; @@ -126,8 +126,8 @@ void AOConfigPrivate::read_file() gamemode = cfg.value("gamemode").toString(); manual_gamemode = cfg.value("manual_gamemode", false).toBool(); - timeofday = cfg.value("timeofday").toString(); - manual_timeofday = cfg.value("manual_timeofday", false).toBool(); + time_of_day = cfg.value("timeofday").toString(); + manual_time_of_day = cfg.value("manual_timeofday", false).toBool(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); log_max_lines = cfg.value("chatlog_limit", 100).toInt(); @@ -197,8 +197,8 @@ void AOConfigPrivate::save_file() cfg.setValue("theme", theme); cfg.setValue("gamemode", gamemode); cfg.setValue("manual_gamemode", manual_gamemode); - cfg.setValue("timeofday", timeofday); - cfg.setValue("manual_timeofday", manual_timeofday); + cfg.setValue("timeofday", time_of_day); + cfg.setValue("manual_timeofday", manual_time_of_day); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("chatlog_limit", log_max_lines); @@ -385,7 +385,7 @@ QString AOConfig::gamemode() const * * @return Current manual gamemode status. */ -bool AOConfig::manual_gamemode_enabled() const +bool AOConfig::is_manual_gamemode_enabled() const { return d->manual_gamemode; } @@ -396,9 +396,9 @@ bool AOConfig::manual_gamemode_enabled() const * * @return Current gamemode, or empty string if not set. */ -QString AOConfig::timeofday() const +QString AOConfig::time_of_day() const { - return d->timeofday; + return d->time_of_day; } /** @@ -411,9 +411,9 @@ QString AOConfig::timeofday() const * * @return Current manual time of day status. */ -bool AOConfig::manual_timeofday_enabled() const +bool AOConfig::is_manual_time_of_day_enabled() const { - return d->manual_timeofday; + return d->manual_time_of_day; } bool AOConfig::always_pre_enabled() const @@ -641,7 +641,7 @@ void AOConfig::set_gamemode(QString p_string) d->invoke_signal("gamemode_changed", Q_ARG(QString, p_string)); } -void AOConfig::set_manual_gamemode(bool p_enabled) +void AOConfig::set_manual_gamemode_enabled(bool p_enabled) { if (d->manual_gamemode == p_enabled) return; @@ -649,20 +649,20 @@ void AOConfig::set_manual_gamemode(bool p_enabled) d->invoke_signal("manual_gamemode_changed", Q_ARG(bool, p_enabled)); } -void AOConfig::set_timeofday(QString p_string) +void AOConfig::set_time_of_day(QString p_string) { - if (d->timeofday == p_string) + if (d->time_of_day == p_string) return; - d->timeofday = p_string; - d->invoke_signal("timeofday_changed", Q_ARG(QString, p_string)); + d->time_of_day = p_string; + d->invoke_signal("time_of_day_changed", Q_ARG(QString, p_string)); } -void AOConfig::set_manual_timeofday(bool p_enabled) +void AOConfig::set_manual_time_of_day_enabled(bool p_enabled) { - if (d->manual_timeofday == p_enabled) + if (d->manual_time_of_day == p_enabled) return; - d->manual_timeofday = p_enabled; - d->invoke_signal("manual_timeofday_changed", Q_ARG(bool, p_enabled)); + d->manual_time_of_day = p_enabled; + d->invoke_signal("manual_time_of_day_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_always_pre(bool p_enabled) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 8039f4ff2..8b9c80a6d 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -111,8 +111,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(theme_changed(QString)), this, SLOT(on_theme_changed(QString))); connect(m_config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_gamemode_changed(QString))); connect(m_config, SIGNAL(manual_gamemode_changed(bool)), ui_manual_gamemode, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(timeofday_changed(QString)), this, SLOT(on_theme_changed(QString))); - connect(m_config, SIGNAL(manual_timeofday_changed(bool)), ui_manual_timeofday, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(time_of_day_changed(QString)), this, SLOT(on_theme_changed(QString))); + connect(m_config, SIGNAL(manual_time_of_day_changed(bool)), ui_manual_timeofday, SLOT(setChecked(bool))); connect(m_config, SIGNAL(showname_changed(QString)), ui_showname, SLOT(setText(QString))); connect(m_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); @@ -172,9 +172,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(ui_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(ui_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); connect(ui_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); - connect(ui_manual_gamemode, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_gamemode(bool))); + connect(ui_manual_gamemode, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_gamemode_enabled(bool))); connect(ui_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); - connect(ui_manual_timeofday, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_timeofday(bool))); + connect(ui_manual_timeofday, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_time_of_day_enabled(bool))); connect(ui_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); connect(ui_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); connect(ui_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); @@ -218,9 +218,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // game ui_theme->setCurrentText(m_config->theme()); ui_gamemode->setCurrentText(m_config->gamemode()); - ui_manual_gamemode->setChecked(m_config->manual_gamemode_enabled()); - ui_timeofday->setCurrentText(m_config->timeofday()); - ui_manual_timeofday->setChecked(m_config->manual_timeofday_enabled()); + ui_manual_gamemode->setChecked(m_config->is_manual_gamemode_enabled()); + ui_timeofday->setCurrentText(m_config->time_of_day()); + ui_manual_timeofday->setChecked(m_config->is_manual_time_of_day_enabled()); ui_showname->setText(m_config->showname()); on_showname_placeholder_changed(m_config->showname_placeholder()); ui_always_pre->setChecked(m_config->always_pre_enabled()); @@ -264,12 +264,12 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_blank_blips->setChecked(m_config->blank_blips_enabled()); // Widget enabling connections - ui_gamemode->setEnabled(m_config->manual_gamemode_enabled()); - ui_timeofday->setEnabled(m_config->manual_timeofday_enabled()); + ui_gamemode->setEnabled(m_config->is_manual_gamemode_enabled()); + ui_timeofday->setEnabled(m_config->is_manual_time_of_day_enabled()); // The manual gamemode checkbox enables browsing the gamemode combox // similarly with time of day connect(m_config, SIGNAL(manual_gamemode_changed(bool)), ui_gamemode, SLOT(setEnabled(bool))); - connect(m_config, SIGNAL(manual_timeofday_changed(bool)), ui_timeofday, SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_time_of_day_changed(bool)), ui_timeofday, SLOT(setEnabled(bool))); } void AOConfigPanel::showEvent(QShowEvent *event) @@ -425,7 +425,7 @@ void AOConfigPanel::on_gamemode_changed(QString p_name) ui_gamemode->setCurrentText(p_name); } -void AOConfigPanel::on_timeofday_changed(QString p_name) +void AOConfigPanel::on_time_of_day_changed(QString p_name) { refresh_theme_list(); refresh_gamemode_list(); @@ -442,7 +442,7 @@ void AOConfigPanel::on_gamemode_index_changed(QString p_text) void AOConfigPanel::on_timeofday_index_changed(QString p_text) { Q_UNUSED(p_text); - m_config->set_timeofday(ui_timeofday->currentData().toString()); + m_config->set_time_of_day(ui_timeofday->currentData().toString()); } void AOConfigPanel::on_showname_placeholder_changed(QString p_text) diff --git a/src/aoscene.cpp b/src/aoscene.cpp index 3d4991b6e..c795af3aa 100644 --- a/src/aoscene.cpp +++ b/src/aoscene.cpp @@ -13,24 +13,11 @@ AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent), ao_ void AOScene::set_image(QString p_image) { - QString target_path = ao_app->get_default_background_path(p_image); - - // background specific path - QString background_path = ao_app->get_background_path(p_image); - - for (auto &ext : animated_or_static_extensions()) - { - QString full_background_path = ao_app->get_case_sensitive_path(background_path + ext); - - if (file_exists(full_background_path)) - { - target_path = full_background_path; - break; - } - } + const QString l_file_path = + ao_app->find_asset_path(ao_app->get_current_background_path() + "/" + p_image, animated_or_static_extensions()); // do not update the movie if we're using the same file - if (m_reader->fileName() == target_path) + if (m_reader->fileName() == l_file_path) return; m_reader->stop(); @@ -38,7 +25,7 @@ void AOScene::set_image(QString p_image) m_reader = new QMovie(this); m_reader->setScaledSize(size()); - m_reader->setFileName(target_path); + m_reader->setFileName(l_file_path); setMovie(m_reader); m_reader->start(); } diff --git a/src/commondefs.cpp b/src/commondefs.cpp index 3f1762787..cbc57e9b5 100644 --- a/src/commondefs.cpp +++ b/src/commondefs.cpp @@ -3,6 +3,7 @@ #include const QString BACKGROUND_BACKGROUNDS_INI = "backgrounds.ini"; +const QString BACKGROUND_DEFAULT_NAME = "gs4"; const QString BASE_CONFIG_INI = "/base/config.ini"; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 35ae89978..a9eb3ef03 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -240,14 +240,14 @@ void Courtroom::set_window_title(QString p_title) this->setWindowTitle(p_title); } -void Courtroom::set_scene() +void Courtroom::update_background_scene() { // witness is default if pos is invalid QString f_background = "witnessempty"; QString f_desk_image = "stand"; QString f_desk_mod = m_chatmessage[CMDeskModifier]; QString f_side = m_chatmessage[CMPosition]; - QString ini_path = ao_app->get_background_path(BACKGROUND_BACKGROUNDS_INI); + QString ini_path = ao_app->format_background_path(BACKGROUND_BACKGROUNDS_INI); if (file_exists(ini_path)) { @@ -291,21 +291,22 @@ void Courtroom::set_scene() f_desk_image = "stand"; } - bool has_all_desks = true; + bool l_all_desks_exists = true; QStringList alldesks{"defensedesk", "prosecutiondesk", "stand"}; - for (const QString &desk : alldesks) + for (const QString &i_desk : alldesks) { - QString full_path = ao_app->find_asset_path({get_background_path(desk)}, animated_or_static_extensions()); - if (full_path.isEmpty()) + const QString l_desk_path = ao_app->find_asset_path(ao_app->get_current_background_path() + "/" + i_desk, + animated_or_static_extensions()); + if (l_desk_path.isEmpty()) { - has_all_desks = false; + l_all_desks_exists = false; break; } } if (f_desk_mod == "0" || (f_desk_mod != "1" && (f_side == "jud" || f_side == "hld" || f_side == "hlp"))) ui_vp_desk->hide(); - else if (!has_all_desks) + else if (!l_all_desks_exists) ui_vp_desk->hide(); else ui_vp_desk->show(); @@ -330,9 +331,26 @@ void Courtroom::set_taken(int n_char, bool p_taken) m_chr_list.replace(n_char, f_char); } -void Courtroom::set_background(QString p_background) +DRAreaBackground Courtroom::get_background() { - current_background = p_background; + return m_background; +} + +void Courtroom::set_background(DRAreaBackground p_background) +{ + m_background = p_background; + update_background_scene(); +} + +QString Courtroom::get_time_of_day() +{ + return m_time_of_day; +} + +void Courtroom::set_time_of_day(QString p_tod) +{ + m_time_of_day = p_tod; + update_background_scene(); } void Courtroom::set_tick_rate(const int p_tick_rate) @@ -868,7 +886,7 @@ void Courtroom::handle_chatmessage_2() // handles IC if (m_msg_is_first_person == false) { - set_scene(); + update_background_scene(); } int emote_mod = m_chatmessage[CMEmoteModifier].toInt(); @@ -2134,7 +2152,7 @@ void Courtroom::on_app_reload_theme_requested() setup_courtroom(); // to update status on the background - set_background(current_background); + update_background_scene(); } void Courtroom::on_back_to_lobby_clicked() diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 207fa8697..6d139979f 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -1,6 +1,7 @@ #include "aoapplication.h" #include "aoconfig.h" +#include "commondefs.h" #include "courtroom.h" #include "drpather.h" #include "file_functions.h" @@ -48,21 +49,58 @@ QString AOApplication::get_music_path(QString p_song) return get_case_sensitive_path(path); } -QString AOApplication::get_background_path(QString p_file) +QString AOApplication::format_background_path(QString p_identifier) { + return get_base_path() + "background/" + p_identifier; +} + +QStringList AOApplication::get_available_background_identifier_list() +{ + QStringList l_bg_list; + if (is_courtroom_constructed) { - return get_case_sensitive_path(m_courtroom->get_background_path(p_file)); + const DRAreaBackground l_area_bg = m_courtroom->get_background(); + + const QMap &l_bg_map = l_area_bg.background_tod_map; + if (ao_config->is_manual_time_of_day_enabled()) + { + const QString l_manual_tod = ao_config->time_of_day(); + if (!l_manual_tod.isEmpty() && l_bg_map.contains(l_manual_tod)) + l_bg_list.append(l_bg_map.value(l_manual_tod)); + } + + const QString l_tod = m_courtroom->get_time_of_day(); + if (!l_tod.isEmpty() && l_bg_map.contains(l_tod)) + l_bg_list.append(l_bg_map.value(l_tod)); + + if (!l_area_bg.background.isEmpty()) + l_bg_list.append(l_area_bg.background); + + l_bg_list.append(BACKGROUND_DEFAULT_NAME); } - // this function being called when the courtroom isn't constructed makes no - // sense - return ""; + + return l_bg_list; } -QString AOApplication::get_default_background_path(QString p_file) +QString AOApplication::get_current_background_path() { - QString path = get_base_path() + "background/gs4/" + p_file; - return get_case_sensitive_path(path); + QString l_bg_path; + + if (is_courtroom_constructed) + { + QStringList l_bg_list = get_available_background_identifier_list(); + for (QString &i_bg : l_bg_list) + { + i_bg = get_case_sensitive_path(format_background_path(i_bg)); + if (i_bg.isEmpty()) + continue; + l_bg_path = i_bg; + break; + } + } + + return l_bg_path; } QString AOApplication::get_evidence_path(QString p_file) @@ -76,11 +114,6 @@ QString AOApplication::get_evidence_path(QString p_file) return alt_path; } -QString Courtroom::get_background_path(QString p_file) -{ - return ao_app->get_base_path() + "background/" + current_background + "/" + p_file; -} - /** * @brief Returns the 'correct' path for the file given as the parameter by * trying to match the case of the actual path. @@ -181,6 +214,16 @@ QString AOApplication::find_asset_path(QStringList p_file_list) return find_asset_path(p_file_list, QStringList{""}); } +QString AOApplication::find_asset_path(QString p_file, QStringList p_extension_list) +{ + return find_asset_path(QStringList{p_file}, p_extension_list); +} + +QString AOApplication::find_asset_path(QString p_file) +{ + return find_asset_path(QStringList{p_file}); +} + /** * @brief Returns the first case-sensitive file in the theme folder that is * of the form name+extension, or empty string if it fails. @@ -208,7 +251,7 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_exten // Only add gamemode and/or time of day if non empty. const QString l_gamemode = ao_config->gamemode(); - const QString l_timeofday = ao_config->timeofday(); + const QString l_timeofday = ao_config->time_of_day(); const QString l_theme_root = get_base_path() + "themes/" + ao_config->theme(); if (!l_gamemode.isEmpty()) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 3b01ead80..5d9a9adae 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -264,11 +264,23 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) if (l_content.size() < 1) return; - if (is_courtroom_constructed) + if (!is_courtroom_constructed) + return; + + DRAreaBackground l_area_bg; + l_area_bg.background = l_content.at(0); + + for (int i = 1; i < l_content.size(); ++i) { - m_courtroom->set_background(l_content.at(0)); - m_courtroom->set_scene(); + const QStringList l_tod_data = l_content.at(i).split("|", DR::SkipEmptyParts); + if (l_tod_data.size() < 2) + continue; + l_area_bg.background_tod_map.insert(l_tod_data.at(0), l_tod_data.at(1)); } + + qDebug() << l_area_bg.background << l_area_bg.background_tod_map; + + m_courtroom->set_background(l_area_bg); } else if (l_header == "chat_tick_rate") { @@ -376,7 +388,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) { if (l_content.length() < 1) return; - if (ao_config->manual_gamemode_enabled()) + if (ao_config->is_manual_gamemode_enabled()) return; ao_config->set_gamemode(l_content.at(0)); } @@ -384,9 +396,9 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) { if (l_content.length() < 1) return; - if (ao_config->manual_timeofday_enabled()) + if (!is_courtroom_constructed) return; - ao_config->set_timeofday(l_content.at(0)); + m_courtroom->set_time_of_day(l_content.at(0)); } else if (l_header == "TR") { From a35983264c223a8951b8f173825efb353d3543e3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 6 Aug 2021 14:47:20 +0200 Subject: [PATCH 509/842] Time of day now reload the theme when received This is horribly inefficient, there's no guarantee that the subtheme folder for this time of day even exist, leading to a basic reloading of everything. --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a9eb3ef03..704783230 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -350,7 +350,7 @@ QString Courtroom::get_time_of_day() void Courtroom::set_time_of_day(QString p_tod) { m_time_of_day = p_tod; - update_background_scene(); + setup_courtroom(); } void Courtroom::set_tick_rate(const int p_tick_rate) From 098df677aeb5db5ed38535fd9c34526816516741 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 6 Aug 2021 15:22:55 +0200 Subject: [PATCH 510/842] Update theme time of day based on whatever based on whatever manual time of day is set or the current time of day known --- src/path_functions.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 6d139979f..67fc47673 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -251,7 +251,8 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_exten // Only add gamemode and/or time of day if non empty. const QString l_gamemode = ao_config->gamemode(); - const QString l_timeofday = ao_config->time_of_day(); + const QString l_timeofday = + ao_config->is_manual_time_of_day_enabled() ? ao_config->time_of_day() : m_courtroom->get_time_of_day(); const QString l_theme_root = get_base_path() + "themes/" + ao_config->theme(); if (!l_gamemode.isEmpty()) From 12612f0a18fd052a1cbd403d9537e722bc4f8357 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 6 Aug 2021 16:16:34 +0200 Subject: [PATCH 511/842] Check if courtroom exist before pulling data from it --- src/path_functions.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 67fc47673..9d170123d 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -251,8 +251,9 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_exten // Only add gamemode and/or time of day if non empty. const QString l_gamemode = ao_config->gamemode(); - const QString l_timeofday = - ao_config->is_manual_time_of_day_enabled() ? ao_config->time_of_day() : m_courtroom->get_time_of_day(); + const QString l_timeofday = ao_config->is_manual_time_of_day_enabled() ? ao_config->time_of_day() + : is_courtroom_constructed ? m_courtroom->get_time_of_day() + : nullptr; const QString l_theme_root = get_base_path() + "themes/" + ao_config->theme(); if (!l_gamemode.isEmpty()) From c027b44f14d63b57e15b7603f79abee9d96a588f Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 6 Aug 2021 17:20:00 +0200 Subject: [PATCH 512/842] Added a failsafe for when time of day is changed --- src/courtroom.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 704783230..b23ea6a11 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -351,6 +351,7 @@ void Courtroom::set_time_of_day(QString p_tod) { m_time_of_day = p_tod; setup_courtroom(); + update_background_scene(); } void Courtroom::set_tick_rate(const int p_tick_rate) From c7a6c88e37724a505819163321afe5c77e8b09b6 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 6 Aug 2021 20:00:57 +0200 Subject: [PATCH 513/842] Fix background folder lookup not checking if directory exist or not --- src/path_functions.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 9d170123d..90ec425bf 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -83,6 +83,8 @@ QStringList AOApplication::get_available_background_identifier_list() return l_bg_list; } +#include + QString AOApplication::get_current_background_path() { QString l_bg_path; @@ -93,7 +95,7 @@ QString AOApplication::get_current_background_path() for (QString &i_bg : l_bg_list) { i_bg = get_case_sensitive_path(format_background_path(i_bg)); - if (i_bg.isEmpty()) + if (!dir_exists(i_bg)) continue; l_bg_path = i_bg; break; From a866f3c5538118766e3da25fcb3ce1b29c6d1675 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 10 Aug 2021 14:09:54 +0200 Subject: [PATCH 514/842] OPUS files should now play once more as intended --- res/ui/config_panel.ui | 78 ++++++++++++++++++++++-------------------- src/draudiostream.cpp | 9 ++++- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 1e343a925..6af4e0fa1 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 2 + 1 @@ -284,6 +284,43 @@ + + + Blip Rate: + + + + + + + + + 1 + + + 1000000000 + + + 1000000000 + + + + + + + + 0 + 0 + + + + Blanks + + + + + + Always-anim: @@ -293,7 +330,7 @@ - + @@ -309,7 +346,7 @@ - + Qt::Vertical @@ -844,41 +881,6 @@
    - - - - - - Blip Rate: - - - - - - - - - 1 - - - 1000000000 - - - 1000000000 - - - - - - - Blanks - - - - - - - diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index 1683f01fc..a89323e99 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -3,6 +3,8 @@ #include "draudioengine.h" #include "draudiostreamfamily.h" +#include + #include #include @@ -80,7 +82,12 @@ std::optional DRAudioStream::set_file(QString p_file) m_file = p_file; - HSTREAM stream_handle = BASS_StreamCreateFile(FALSE, m_file->utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); + HSTREAM stream_handle; + if (m_file->endsWith("opus", Qt::CaseInsensitive)) + stream_handle = BASS_OPUS_StreamCreateFile(FALSE, m_file->utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); + else + stream_handle = BASS_StreamCreateFile(FALSE, m_file->utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); + if (stream_handle == 0) return DRAudioError( QString("failed to create stream for file %1: %2").arg(p_file).arg(DRAudio::get_last_bass_error())); From 745b3a7de7345d46ed521d1cf3dc87f49ec79e84 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 10 Aug 2021 14:14:26 +0200 Subject: [PATCH 515/842] Fix missing library dependency declaration --- dronline-client.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dronline-client.pro b/dronline-client.pro index 8fd003532..65f851bbe 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -136,7 +136,7 @@ SOURCES += \ # 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers # in the same way as BASS. Discord RPC uses CMake, which does not play nicely with # QMake, so this step must be manual. -LIBS += -L$$PWD/3rd/$${QMAKE_HOST.arch} -lbass -ldiscord-rpc +LIBS += -L$$PWD/3rd/$${QMAKE_HOST.arch} -lbass -lbassopus -ldiscord-rpc RESOURCES += \ res.qrc From d9caf25baa56c013a23ec0a14a95b6e0827ed7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:18:12 +0200 Subject: [PATCH 516/842] Update build action to fetch BassOpus --- .github/workflows/build-all.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 7c3d77929..ff966ba0b 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -48,6 +48,16 @@ jobs: cp ./c/bass.h ./DRO-Client/3rd/include cp ./bass.dll ./DRO-Client/3rd/x86 cp ./c/bass.lib ./DRO-Client/3rd/x86 + + - name: Fetch BassOpus external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bassopus24.zip -o bassopus.zip + unzip bassopus.zip + cp ./c/bassopus.h ./DRO-Client/3rd/include + cp ./bassopus.dll ./DRO-Client/3rd/x86 + cp ./c/bassopus.lib ./DRO-Client/3rd/x86 - name: Fetch QtApng external library shell: bash @@ -165,6 +175,15 @@ jobs: unzip bass.zip cp ./bass.h ./DRO-Client/3rd/include cp ./libbass.dylib ./DRO-Client/3rd/x86_64 + + - name: Fetch BassOpus external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bassopus24-osx.zip -o bassopus.zip + unzip bassopus.zip + cp ./bassopus.h ./DRO-Client/3rd/include + cp ./libbassopus.dylib ./DRO-Client/3rd/x86_64 - name: Fetch QtApng external library shell: bash @@ -260,6 +279,15 @@ jobs: cp ./bass.h ./DRO-Client/3rd/include/bass.h cp ./x64/libbass.so ./DRO-Client/3rd/x86_64/libbass.so + - name: Fetch BassOpus external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip + unzip bassopus.zip + cp ./bassopus.h ./DRO-Client/3rd/include/bassopus.h + cp ./x64/libbassopus.so ./DRO-Client/3rd/x86_64/libbassopus.so + - name: Install Qt shell: bash working-directory: ${{env.parentworkspace}} From 5f44828866d7d2a3a8b999f77b619ef4f4806069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:38:53 +0200 Subject: [PATCH 517/842] Resolve bassopus missing dependencies * Automatically insert bassopus dependencies in their respective archives --- .github/workflows/build-all.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index ff966ba0b..74a1c16cc 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -121,6 +121,7 @@ jobs: windeployqt.exe --quick --compiler-runtime . cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . cp ../bass.dll . + cp ../bassopus.dll . cp ../mingw73_32/plugins/imageformats/qapng.dll ./imageformats/ cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libgcc_s_dw2-1.dll . cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libwinpthread-1.dll . @@ -227,6 +228,8 @@ jobs: cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/MacOS/" cp ../libbass.dylib "./Danganronpa Online.app/Contents/Frameworks/" cp ../libbass.dylib "./Danganronpa Online.app/Contents/MacOS/" + cp ../libbassopus.dylib "./Danganronpa Online.app/Contents/Frameworks/" + cp ../libbassopus.dylib "./Danganronpa Online.app/Contents/MacOS/" cp -R ../clang_64/plugins/imageformats "./Danganronpa Online.app/Contents/MacOS/" - name: Make DMG @@ -336,6 +339,7 @@ jobs: mkdir depends cp ../discord-rpc/linux-dynamic/lib/libdiscord-rpc.so "./depends" cp ../x64/libbass.so "./depends" + cp ../x64/libbassopus.so "./depends" mkdir imageformats cp -R ../libqapng.so "./imageformats" echo "LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client" > "dro-client.sh" From 60b22124401d49fe1d3c234792067e9789664de3 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 10 Aug 2021 12:26:46 -0400 Subject: [PATCH 518/842] Background improvements (#212) * Remove background AO2-isms and original backgrounds.ini implementation * Basic background TOD, pending disappearing clock on reload * Fix active clock being hidden on reload theme order (mostly notable on server-prompted time of day theme reloads based on clock) * Revert "Basic background TOD, pending disappearing clock on reload" This reverts commit 75e8e9583bb492d3a404af85b1aacd98459f84ee. * Remove lingering backgrounds.ini references * Only hide desk if desk_mod == 0, show it otherwise --- include/commondefs.h | 1 - include/courtroom.h | 2 +- src/commondefs.cpp | 1 - src/courtroom.cpp | 101 ++++++++++++++------------------------ src/courtroom_widgets.cpp | 3 +- 5 files changed, 39 insertions(+), 69 deletions(-) diff --git a/include/commondefs.h b/include/commondefs.h index c3c2a8bab..6c2f60780 100644 --- a/include/commondefs.h +++ b/include/commondefs.h @@ -2,7 +2,6 @@ class QString; -extern const QString BACKGROUND_BACKGROUNDS_INI; extern const QString BACKGROUND_DEFAULT_NAME; extern const QString BASE_CONFIG_INI; diff --git a/include/courtroom.h b/include/courtroom.h index 0b7d2e9a7..a44bbc8ba 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -340,7 +340,7 @@ class Courtroom : public QMainWindow int evidence_rows = 3; int max_evidence_on_page = 18; - int current_clock = -1; + int m_current_clock = -1; DRAreaBackground m_background; QString m_time_of_day; diff --git a/src/commondefs.cpp b/src/commondefs.cpp index cbc57e9b5..16b2ca463 100644 --- a/src/commondefs.cpp +++ b/src/commondefs.cpp @@ -2,7 +2,6 @@ #include -const QString BACKGROUND_BACKGROUNDS_INI = "backgrounds.ini"; const QString BACKGROUND_DEFAULT_NAME = "gs4"; const QString BASE_CONFIG_INI = "/base/config.ini"; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index b23ea6a11..7fb6a8867 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -247,73 +247,44 @@ void Courtroom::update_background_scene() QString f_desk_image = "stand"; QString f_desk_mod = m_chatmessage[CMDeskModifier]; QString f_side = m_chatmessage[CMPosition]; - QString ini_path = ao_app->format_background_path(BACKGROUND_BACKGROUNDS_INI); - - if (file_exists(ini_path)) + + if (f_side == "def") + { + f_background = "defenseempty"; + f_desk_image = "defensedesk"; + } + else if (f_side == "pro") + { + f_background = "prosecutorempty"; + f_desk_image = "prosecutiondesk"; + } + else if (f_side == "jud") + { + f_background = "judgestand"; + f_desk_image = "judgedesk"; + } + else if (f_side == "hld") + { + f_background = "helperstand"; + f_desk_image = "helperdesk"; + } + else if (f_side == "hlp") { - f_background = ao_app->read_ini(f_side, ini_path); - f_desk_image = ao_app->read_ini(f_side + "_desk", ini_path); + f_background = "prohelperstand"; + f_desk_image = "prohelperdesk"; + } - if (f_desk_mod == "0") // keeping a bit of the functionality for now - { - ui_vp_desk->hide(); - } + if (f_desk_mod == "0") + { + ui_vp_desk->hide(); } else { - if (f_side == "def") - { - f_background = "defenseempty"; - f_desk_image = "defensedesk"; - } - else if (f_side == "pro") - { - f_background = "prosecutorempty"; - f_desk_image = "prosecutiondesk"; - } - else if (f_side == "jud") - { - f_background = "judgestand"; - f_desk_image = "judgedesk"; - } - else if (f_side == "hld") - { - f_background = "helperstand"; - f_desk_image = "helperdesk"; - } - else if (f_side == "hlp") - { - f_background = "prohelperstand"; - f_desk_image = "prohelperdesk"; - } - else - { - f_desk_image = "stand"; - } - - bool l_all_desks_exists = true; - QStringList alldesks{"defensedesk", "prosecutiondesk", "stand"}; - for (const QString &i_desk : alldesks) - { - const QString l_desk_path = ao_app->find_asset_path(ao_app->get_current_background_path() + "/" + i_desk, - animated_or_static_extensions()); - if (l_desk_path.isEmpty()) - { - l_all_desks_exists = false; - break; - } - } - - if (f_desk_mod == "0" || (f_desk_mod != "1" && (f_side == "jud" || f_side == "hld" || f_side == "hlp"))) - ui_vp_desk->hide(); - else if (!l_all_desks_exists) - ui_vp_desk->hide(); - else - ui_vp_desk->show(); + ui_vp_desk->show(); + ui_vp_desk->set_image(f_desk_image); } ui_vp_background->set_image(f_background); - ui_vp_desk->set_image(f_desk_image); } void Courtroom::set_taken(int n_char, bool p_taken) @@ -387,21 +358,21 @@ void Courtroom::handle_music_anim() void Courtroom::handle_clock(QString time) { - current_clock = time.toInt(); - if (current_clock < 0 || current_clock > 23) - current_clock = -1; - qInfo() << QString("Clock time changed to %1").arg(current_clock); + m_current_clock = time.toInt(); + if (m_current_clock < 0 || m_current_clock > 23) + m_current_clock = -1; + qInfo() << QString("Clock time changed to %1").arg(m_current_clock); ui_vp_clock->hide(); - if (current_clock == -1) + if (m_current_clock == -1) { qInfo() << "Unknown time; no asset to be used."; return; } qDebug() << "Displaying clock asset..."; - QString clock_filename = "hours/" + QString::number(current_clock); + QString clock_filename = "hours/" + QString::number(m_current_clock); const QString asset_path = ao_app->find_theme_asset_path(clock_filename, animated_or_static_extensions()); if (asset_path.isEmpty()) { diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 8715c1cc9..cfd4d32d8 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -647,7 +647,8 @@ void Courtroom::set_widgets() ui_vp_music_display_b->show(); set_size_and_pos(ui_vp_clock, "clock", COURTROOM_DESIGN_INI, ao_app); - ui_vp_clock->hide(); + if (m_current_clock == -1) + ui_vp_clock->hide(); ui_vp_chatbox->set_image("chatmed.png"); ui_vp_chatbox->hide(); From 7df54e38d74cfc26e21c2a380e30f0926d634120 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 15 Aug 2021 00:05:58 +0200 Subject: [PATCH 519/842] Renamed gamemode and time of day in config, ... * Renamed `gamemode` and `time_of_day` in `AOConfig` to `manual_gamemode` and `manual_time_of_day` respectively * Courtroom now preserve the server's gamemode state (consistent with server time of day) --- include/aoconfig.h | 24 ++++++------- include/courtroom.h | 5 ++- src/aoapplication.cpp | 7 ++-- src/aoconfig.cpp | 80 ++++++++++++++++++++++-------------------- src/aoconfigpanel.cpp | 36 +++++++++---------- src/courtroom.cpp | 22 ++++++++++-- src/path_functions.cpp | 14 ++++---- src/server_socket.cpp | 4 +-- 8 files changed, 108 insertions(+), 84 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index daff39dc9..042248fb0 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -32,10 +32,10 @@ class AOConfig : public QObject bool discord_hide_server() const; bool discord_hide_character() const; QString theme() const; - QString gamemode() const; - bool is_manual_gamemode_enabled() const; - QString time_of_day() const; - bool is_manual_time_of_day_enabled() const; + QString manual_gamemode() const; + bool is_manual_gamemode_selection_enabled() const; + QString manual_time_of_day() const; + bool is_manual_time_of_day_selection_enabled() const; bool always_pre_enabled() const; int chat_tick_interval() const; int log_max_lines() const; @@ -79,10 +79,10 @@ public slots: void set_discord_hide_server(const bool p_enabled); void set_discord_hide_character(const bool p_enabled); void set_theme(QString p_string); - void set_gamemode(QString p_string); - void set_manual_gamemode_enabled(bool p_enabled); - void set_time_of_day(QString p_string); - void set_manual_time_of_day_enabled(bool p_enabled); + void set_manual_gamemode(QString p_string); + void set_manual_gamemode_selection_enabled(bool p_enabled); + void set_manual_time_of_day(QString p_string); + void set_manual_time_of_day_selection_enabled(bool p_enabled); void set_always_pre(bool p_enabled); void set_chat_tick_interval(int p_number); void set_log_max_lines(int p_number); @@ -123,10 +123,10 @@ public slots: // game void theme_changed(QString); - void gamemode_changed(QString); - void manual_gamemode_changed(bool); - void time_of_day_changed(QString); - void manual_time_of_day_changed(bool); + void manual_gamemode_changed(QString); + void manual_gamemode_selection_changed(bool); + void manual_time_of_day_changed(QString); + void manual_time_of_day_selection_changed(bool); void showname_changed(QString); void showname_placeholder_changed(QString); void character_ini_changed(QString base_character); diff --git a/include/courtroom.h b/include/courtroom.h index a44bbc8ba..5ba92c910 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -76,8 +76,10 @@ class Courtroom : public QMainWindow // it's a legacy bg DRAreaBackground get_background(); void set_background(DRAreaBackground p_area_bg); + QString get_gamemode(); + void set_gamemode(QString p_gamemode); QString get_time_of_day(); - void set_time_of_day(QString p_tod); + void set_time_of_day(QString p_time_of_day); void set_tick_rate(const int tick_rate); @@ -343,6 +345,7 @@ class Courtroom : public QMainWindow int m_current_clock = -1; DRAreaBackground m_background; + QString m_gamemode; QString m_time_of_day; AOImageDisplay *ui_background = nullptr; diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index bac862901..75fbc0db7 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -33,9 +33,10 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) m_server_socket = new DRServerSocket(this); connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(handle_theme_modification())); - connect(ao_config, SIGNAL(gamemode_changed(QString)), this, SLOT(handle_theme_modification())); - connect(ao_config, SIGNAL(time_of_day_changed(QString)), this, SLOT(handle_theme_modification())); - connect(ao_config, SIGNAL(manual_time_of_day_changed(bool)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(manual_gamemode_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(manual_gamemode_selection_changed(bool)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(manual_time_of_day_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(manual_time_of_day_selection_changed(bool)), this, SLOT(handle_theme_modification())); connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(handle_theme_modification())); ao_config_panel->hide(); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index f78d9077c..b552cfd28 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -54,9 +54,11 @@ private slots: bool discord_hide_character = false; QString theme; QString gamemode; - bool manual_gamemode; + QString manual_gamemode; + bool manual_gamemode_selection; QString time_of_day; - bool manual_time_of_day; + QString manual_time_of_day; + bool manual_time_of_day_selection; QString showname; QString showname_placeholder; QMap ini_map; @@ -124,10 +126,10 @@ void AOConfigPrivate::read_file() if (theme.trimmed().isEmpty()) theme = "default"; - gamemode = cfg.value("gamemode").toString(); - manual_gamemode = cfg.value("manual_gamemode", false).toBool(); - time_of_day = cfg.value("timeofday").toString(); - manual_time_of_day = cfg.value("manual_timeofday", false).toBool(); + manual_gamemode = cfg.value("gamemode").toString(); + manual_gamemode_selection = cfg.value("manual_gamemode", false).toBool(); + manual_time_of_day = cfg.value("timeofday").toString(); + manual_time_of_day_selection = cfg.value("manual_timeofday", false).toBool(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); log_max_lines = cfg.value("chatlog_limit", 100).toInt(); @@ -195,10 +197,10 @@ void AOConfigPrivate::save_file() cfg.setValue("discord_hide_character", discord_hide_character); cfg.setValue("theme", theme); - cfg.setValue("gamemode", gamemode); - cfg.setValue("manual_gamemode", manual_gamemode); - cfg.setValue("timeofday", time_of_day); - cfg.setValue("manual_timeofday", manual_time_of_day); + cfg.setValue("gamemode", manual_gamemode); + cfg.setValue("manual_gamemode", manual_gamemode_selection); + cfg.setValue("timeofday", manual_time_of_day); + cfg.setValue("manual_timeofday", manual_time_of_day_selection); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("chatlog_limit", log_max_lines); @@ -370,9 +372,9 @@ QString AOConfig::theme() const * * @return Current gamemode, or empty string if not set. */ -QString AOConfig::gamemode() const +QString AOConfig::manual_gamemode() const { - return d->gamemode; + return d->manual_gamemode; } /** @@ -385,20 +387,20 @@ QString AOConfig::gamemode() const * * @return Current manual gamemode status. */ -bool AOConfig::is_manual_gamemode_enabled() const +bool AOConfig::is_manual_gamemode_selection_enabled() const { - return d->manual_gamemode; + return d->manual_gamemode_selection; } /** - * @brief Returns the current time of day. If no time of day is set, return + * @brief Returns the current manual time of day. If no time of day is set, return * the empty string. * - * @return Current gamemode, or empty string if not set. + * @return Current manual time of day, or empty string if not set. */ -QString AOConfig::time_of_day() const +QString AOConfig::manual_time_of_day() const { - return d->time_of_day; + return d->manual_time_of_day; } /** @@ -411,9 +413,9 @@ QString AOConfig::time_of_day() const * * @return Current manual time of day status. */ -bool AOConfig::is_manual_time_of_day_enabled() const +bool AOConfig::is_manual_time_of_day_selection_enabled() const { - return d->manual_time_of_day; + return d->manual_time_of_day_selection; } bool AOConfig::always_pre_enabled() const @@ -606,7 +608,7 @@ void AOConfig::set_discord_presence(const bool p_enabled) if (d->discord_presence == p_enabled) return; d->discord_presence = p_enabled; - Q_EMIT d->invoke_signal("discord_presence_changed", Q_ARG(bool, d->discord_presence)); + d->invoke_signal("discord_presence_changed", Q_ARG(bool, d->discord_presence)); } void AOConfig::set_discord_hide_server(const bool p_enabled) @@ -614,7 +616,7 @@ void AOConfig::set_discord_hide_server(const bool p_enabled) if (d->discord_hide_server == p_enabled) return; d->discord_hide_server = p_enabled; - Q_EMIT d->invoke_signal("discord_hide_server_changed", Q_ARG(bool, d->discord_hide_server)); + d->invoke_signal("discord_hide_server_changed", Q_ARG(bool, d->discord_hide_server)); } void AOConfig::set_discord_hide_character(const bool p_enabled) @@ -622,7 +624,7 @@ void AOConfig::set_discord_hide_character(const bool p_enabled) if (d->discord_hide_character == p_enabled) return; d->discord_hide_character = p_enabled; - Q_EMIT d->invoke_signal("discord_hide_character_changed", Q_ARG(bool, d->discord_hide_character)); + d->invoke_signal("discord_hide_character_changed", Q_ARG(bool, d->discord_hide_character)); } void AOConfig::set_theme(QString p_string) @@ -633,36 +635,36 @@ void AOConfig::set_theme(QString p_string) d->invoke_signal("theme_changed", Q_ARG(QString, p_string)); } -void AOConfig::set_gamemode(QString p_string) +void AOConfig::set_manual_gamemode(QString p_string) { - if (d->gamemode == p_string) + if (d->manual_gamemode == p_string) return; - d->gamemode = p_string; - d->invoke_signal("gamemode_changed", Q_ARG(QString, p_string)); + d->manual_gamemode = p_string; + d->invoke_signal("manual_gamemode_changed", Q_ARG(QString, p_string)); } -void AOConfig::set_manual_gamemode_enabled(bool p_enabled) +void AOConfig::set_manual_gamemode_selection_enabled(bool p_enabled) { - if (d->manual_gamemode == p_enabled) + if (d->manual_gamemode_selection == p_enabled) return; - d->manual_gamemode = p_enabled; - d->invoke_signal("manual_gamemode_changed", Q_ARG(bool, p_enabled)); + d->manual_gamemode_selection = p_enabled; + d->invoke_signal("manual_gamemode_selection_changed", Q_ARG(bool, p_enabled)); } -void AOConfig::set_time_of_day(QString p_string) +void AOConfig::set_manual_time_of_day(QString p_string) { - if (d->time_of_day == p_string) + if (d->manual_time_of_day == p_string) return; - d->time_of_day = p_string; - d->invoke_signal("time_of_day_changed", Q_ARG(QString, p_string)); + d->manual_time_of_day = p_string; + d->invoke_signal("manual_time_of_day_changed", Q_ARG(QString, p_string)); } -void AOConfig::set_manual_time_of_day_enabled(bool p_enabled) +void AOConfig::set_manual_time_of_day_selection_enabled(bool p_enabled) { - if (d->manual_time_of_day == p_enabled) + if (d->manual_time_of_day_selection == p_enabled) return; - d->manual_time_of_day = p_enabled; - d->invoke_signal("manual_time_of_day_changed", Q_ARG(bool, p_enabled)); + d->manual_time_of_day_selection = p_enabled; + d->invoke_signal("manual_time_of_day_selection_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_always_pre(bool p_enabled) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 8b9c80a6d..2235b3d9d 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -109,10 +109,10 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // game connect(m_config, SIGNAL(theme_changed(QString)), this, SLOT(on_theme_changed(QString))); - connect(m_config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_gamemode_changed(QString))); - connect(m_config, SIGNAL(manual_gamemode_changed(bool)), ui_manual_gamemode, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(time_of_day_changed(QString)), this, SLOT(on_theme_changed(QString))); - connect(m_config, SIGNAL(manual_time_of_day_changed(bool)), ui_manual_timeofday, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(manual_gamemode_changed(QString)), this, SLOT(on_gamemode_changed(QString))); + connect(m_config, SIGNAL(manual_gamemode_selection_changed(bool)), ui_manual_gamemode, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(manual_time_of_day_changed(QString)), this, SLOT(on_theme_changed(QString))); + connect(m_config, SIGNAL(manual_time_of_day_selection_changed(bool)), ui_manual_timeofday, SLOT(setChecked(bool))); connect(m_config, SIGNAL(showname_changed(QString)), ui_showname, SLOT(setText(QString))); connect(m_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); @@ -172,9 +172,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(ui_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(ui_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); connect(ui_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); - connect(ui_manual_gamemode, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_gamemode_enabled(bool))); + connect(ui_manual_gamemode, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_gamemode_selection_enabled(bool))); connect(ui_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); - connect(ui_manual_timeofday, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_time_of_day_enabled(bool))); + connect(ui_manual_timeofday, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_time_of_day_selection_enabled(bool))); connect(ui_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); connect(ui_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); connect(ui_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); @@ -217,10 +217,10 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // game ui_theme->setCurrentText(m_config->theme()); - ui_gamemode->setCurrentText(m_config->gamemode()); - ui_manual_gamemode->setChecked(m_config->is_manual_gamemode_enabled()); - ui_timeofday->setCurrentText(m_config->time_of_day()); - ui_manual_timeofday->setChecked(m_config->is_manual_time_of_day_enabled()); + ui_gamemode->setCurrentText(m_config->manual_gamemode()); + ui_manual_gamemode->setChecked(m_config->is_manual_gamemode_selection_enabled()); + ui_timeofday->setCurrentText(m_config->manual_time_of_day()); + ui_manual_timeofday->setChecked(m_config->is_manual_time_of_day_selection_enabled()); ui_showname->setText(m_config->showname()); on_showname_placeholder_changed(m_config->showname_placeholder()); ui_always_pre->setChecked(m_config->always_pre_enabled()); @@ -264,12 +264,12 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_blank_blips->setChecked(m_config->blank_blips_enabled()); // Widget enabling connections - ui_gamemode->setEnabled(m_config->is_manual_gamemode_enabled()); - ui_timeofday->setEnabled(m_config->is_manual_time_of_day_enabled()); + ui_gamemode->setEnabled(m_config->is_manual_gamemode_selection_enabled()); + ui_timeofday->setEnabled(m_config->is_manual_time_of_day_selection_enabled()); // The manual gamemode checkbox enables browsing the gamemode combox // similarly with time of day - connect(m_config, SIGNAL(manual_gamemode_changed(bool)), ui_gamemode, SLOT(setEnabled(bool))); - connect(m_config, SIGNAL(manual_time_of_day_changed(bool)), ui_timeofday, SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_gamemode_selection_changed(bool)), ui_gamemode, SLOT(setEnabled(bool))); + connect(m_config, SIGNAL(manual_time_of_day_selection_changed(bool)), ui_timeofday, SLOT(setEnabled(bool))); } void AOConfigPanel::showEvent(QShowEvent *event) @@ -349,11 +349,11 @@ void AOConfigPanel::refresh_timeofday_list() // decide path to look for times of day. This differs whether there is a // gamemode chosen or not QString path; - if (m_config->gamemode().isEmpty()) + if (m_config->manual_gamemode().isEmpty()) path = DRPather::get_application_path() + "/base/themes/" + m_config->theme() + "/times/"; else path = DRPather::get_application_path() + "/base/themes/" + m_config->theme() + "/gamemodes/" + - m_config->gamemode() + "/times/"; + m_config->manual_gamemode() + "/times/"; // times of day for (const QString &i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) @@ -436,13 +436,13 @@ void AOConfigPanel::on_time_of_day_changed(QString p_name) void AOConfigPanel::on_gamemode_index_changed(QString p_text) { Q_UNUSED(p_text); - m_config->set_gamemode(ui_gamemode->currentData().toString()); + m_config->set_manual_gamemode(ui_gamemode->currentData().toString()); } void AOConfigPanel::on_timeofday_index_changed(QString p_text) { Q_UNUSED(p_text); - m_config->set_time_of_day(ui_timeofday->currentData().toString()); + m_config->set_manual_time_of_day(ui_timeofday->currentData().toString()); } void AOConfigPanel::on_showname_placeholder_changed(QString p_text) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 7fb6a8867..8e5300f9f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -247,7 +247,7 @@ void Courtroom::update_background_scene() QString f_desk_image = "stand"; QString f_desk_mod = m_chatmessage[CMDeskModifier]; QString f_side = m_chatmessage[CMPosition]; - + if (f_side == "def") { f_background = "defenseempty"; @@ -313,14 +313,30 @@ void Courtroom::set_background(DRAreaBackground p_background) update_background_scene(); } +QString Courtroom::get_gamemode() +{ + return m_gamemode; +} + +void Courtroom::set_gamemode(QString p_gamemode) +{ + if (m_gamemode == p_gamemode) + return; + m_gamemode = p_gamemode; + setup_courtroom(); + update_background_scene(); +} + QString Courtroom::get_time_of_day() { return m_time_of_day; } -void Courtroom::set_time_of_day(QString p_tod) +void Courtroom::set_time_of_day(QString p_time_of_day) { - m_time_of_day = p_tod; + if (m_time_of_day == p_time_of_day) + return; + m_time_of_day = p_time_of_day; setup_courtroom(); update_background_scene(); } diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 90ec425bf..4476c4490 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -63,9 +63,9 @@ QStringList AOApplication::get_available_background_identifier_list() const DRAreaBackground l_area_bg = m_courtroom->get_background(); const QMap &l_bg_map = l_area_bg.background_tod_map; - if (ao_config->is_manual_time_of_day_enabled()) + if (ao_config->is_manual_time_of_day_selection_enabled()) { - const QString l_manual_tod = ao_config->time_of_day(); + const QString l_manual_tod = ao_config->manual_time_of_day(); if (!l_manual_tod.isEmpty() && l_bg_map.contains(l_manual_tod)) l_bg_list.append(l_bg_map.value(l_manual_tod)); } @@ -252,10 +252,12 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_exten QStringList l_path_list; // Only add gamemode and/or time of day if non empty. - const QString l_gamemode = ao_config->gamemode(); - const QString l_timeofday = ao_config->is_manual_time_of_day_enabled() ? ao_config->time_of_day() - : is_courtroom_constructed ? m_courtroom->get_time_of_day() - : nullptr; + const QString l_gamemode = ao_config->is_manual_gamemode_selection_enabled() ? ao_config->manual_gamemode() + : is_courtroom_constructed ? m_courtroom->get_gamemode() + : nullptr; + const QString l_timeofday = ao_config->is_manual_time_of_day_selection_enabled() ? ao_config->manual_time_of_day() + : is_courtroom_constructed ? m_courtroom->get_time_of_day() + : nullptr; const QString l_theme_root = get_base_path() + "themes/" + ao_config->theme(); if (!l_gamemode.isEmpty()) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 5d9a9adae..84022a341 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -388,9 +388,9 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) { if (l_content.length() < 1) return; - if (ao_config->is_manual_gamemode_enabled()) + if (!is_courtroom_constructed) return; - ao_config->set_gamemode(l_content.at(0)); + m_courtroom->set_gamemode(l_content.at(0)); } else if (l_header == "TOD") { From 5fe6782ddbc437a23f5f75c7497cd4d854913544 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 15 Aug 2021 03:46:51 +0200 Subject: [PATCH 520/842] Config panel tweak (show current gamemode and time of day) * Config panel now reflects the currently active gamemode and time of day. --- include/aoconfig.h | 18 +++-- include/aoconfigpanel.h | 20 ++++-- include/courtroom.h | 6 +- res/ui/config_panel.ui | 83 ++++++++++++++++++++--- src/aoapplication.cpp | 4 +- src/aoconfig.cpp | 64 ++++++++++++------ src/aoconfigpanel.cpp | 144 +++++++++++++++++++++++++++------------- src/courtroom.cpp | 14 ++-- src/path_functions.cpp | 12 ++-- src/server_socket.cpp | 2 +- 10 files changed, 262 insertions(+), 105 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 042248fb0..0fbb287f3 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -32,10 +32,12 @@ class AOConfig : public QObject bool discord_hide_server() const; bool discord_hide_character() const; QString theme() const; + QString gamemode() const; QString manual_gamemode() const; bool is_manual_gamemode_selection_enabled() const; - QString manual_time_of_day() const; - bool is_manual_time_of_day_selection_enabled() const; + QString timeofday() const; + QString manual_timeofday() const; + bool is_manual_timeofday_selection_enabled() const; bool always_pre_enabled() const; int chat_tick_interval() const; int log_max_lines() const; @@ -79,10 +81,12 @@ public slots: void set_discord_hide_server(const bool p_enabled); void set_discord_hide_character(const bool p_enabled); void set_theme(QString p_string); + void set_gamemode(QString p_string); void set_manual_gamemode(QString p_string); void set_manual_gamemode_selection_enabled(bool p_enabled); - void set_manual_time_of_day(QString p_string); - void set_manual_time_of_day_selection_enabled(bool p_enabled); + void set_timeofday(QString p_string); + void set_manual_timeofday(QString p_string); + void set_manual_timeofday_selection_enabled(bool p_enabled); void set_always_pre(bool p_enabled); void set_chat_tick_interval(int p_number); void set_log_max_lines(int p_number); @@ -123,10 +127,12 @@ public slots: // game void theme_changed(QString); + void gamemode_changed(QString); void manual_gamemode_changed(QString); void manual_gamemode_selection_changed(bool); - void manual_time_of_day_changed(QString); - void manual_time_of_day_selection_changed(bool); + void timeofday_changed(QString); + void manual_timeofday_changed(QString); + void manual_timeofday_selection_changed(bool); void showname_changed(QString); void showname_placeholder_changed(QString); void character_ini_changed(QString base_character); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 503fe714d..5ffe1ba85 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -44,9 +44,13 @@ private slots: void on_reload_theme_clicked(); void on_theme_changed(QString); void on_gamemode_changed(QString); - void on_time_of_day_changed(QString); - void on_gamemode_index_changed(QString p_text); - void on_timeofday_index_changed(QString p_text); + void on_manual_gamemode_changed(QString); + void on_manual_gamemode_index_changed(QString p_text); + void on_manual_gamemode_selection_changed(bool); + void on_timeofday_changed(QString); + void on_manual_timeofday_changed(QString); + void on_manual_timeofday_index_changed(QString p_text); + void on_manual_timeofday_selection_changed(bool); void on_showname_placeholder_changed(QString p_text); void on_log_is_topdown_changed(bool p_enabled); void on_device_current_index_changed(int p_index); @@ -79,10 +83,12 @@ private slots: // game QComboBox *ui_theme = nullptr; QPushButton *ui_reload_theme = nullptr; - QComboBox *ui_gamemode = nullptr; - QCheckBox *ui_manual_gamemode = nullptr; - QComboBox *ui_timeofday = nullptr; - QCheckBox *ui_manual_timeofday = nullptr; + QLineEdit *ui_gamemode = nullptr; + QComboBox *ui_manual_gamemode = nullptr; + QCheckBox *ui_manual_gamemode_selection = nullptr; + QLineEdit *ui_timeofday = nullptr; + QComboBox *ui_manual_timeofday = nullptr; + QCheckBox *ui_manual_timeofday_selection = nullptr; QLineEdit *ui_showname = nullptr; QCheckBox *ui_always_pre = nullptr; QSpinBox *ui_chat_tick_interval = nullptr; diff --git a/include/courtroom.h b/include/courtroom.h index 5ba92c910..ba66a9f9d 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -78,8 +78,8 @@ class Courtroom : public QMainWindow void set_background(DRAreaBackground p_area_bg); QString get_gamemode(); void set_gamemode(QString p_gamemode); - QString get_time_of_day(); - void set_time_of_day(QString p_time_of_day); + QString get_timeofday(); + void set_timeofday(QString p_time_of_day); void set_tick_rate(const int tick_rate); @@ -346,7 +346,7 @@ class Courtroom : public QMainWindow DRAreaBackground m_background; QString m_gamemode; - QString m_time_of_day; + QString m_timeofday; AOImageDisplay *ui_background = nullptr; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 6af4e0fa1..65d3faede 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -193,16 +193,47 @@ - + - - - false - - + + + + + false + + + + true + + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> gamemode.</p></body></html> + + + <default> + + + false + + + true + + + + + + + false + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> gamemode.</p></body></html> + + + + - + 0 @@ -227,12 +258,44 @@ - + - + + + + + false + + + + true + + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> time of day.</p></body></html> + + + <default> + + + false + + + true + + + + + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> time of day.</p></body></html> + + + + - + 0 diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 75fbc0db7..bd1012409 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -35,8 +35,8 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(handle_theme_modification())); connect(ao_config, SIGNAL(manual_gamemode_changed(QString)), this, SLOT(handle_theme_modification())); connect(ao_config, SIGNAL(manual_gamemode_selection_changed(bool)), this, SLOT(handle_theme_modification())); - connect(ao_config, SIGNAL(manual_time_of_day_changed(QString)), this, SLOT(handle_theme_modification())); - connect(ao_config, SIGNAL(manual_time_of_day_selection_changed(bool)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(manual_timeofday_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(manual_timeofday_selection_changed(bool)), this, SLOT(handle_theme_modification())); connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(handle_theme_modification())); ao_config_panel->hide(); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index b552cfd28..b45d66bc7 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -56,9 +56,9 @@ private slots: QString gamemode; QString manual_gamemode; bool manual_gamemode_selection; - QString time_of_day; - QString manual_time_of_day; - bool manual_time_of_day_selection; + QString timeofday; + QString manual_timeofday; + bool manual_timeofday_selection; QString showname; QString showname_placeholder; QMap ini_map; @@ -128,8 +128,8 @@ void AOConfigPrivate::read_file() manual_gamemode = cfg.value("gamemode").toString(); manual_gamemode_selection = cfg.value("manual_gamemode", false).toBool(); - manual_time_of_day = cfg.value("timeofday").toString(); - manual_time_of_day_selection = cfg.value("manual_timeofday", false).toBool(); + manual_timeofday = cfg.value("timeofday").toString(); + manual_timeofday_selection = cfg.value("manual_timeofday", false).toBool(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); log_max_lines = cfg.value("chatlog_limit", 100).toInt(); @@ -199,8 +199,8 @@ void AOConfigPrivate::save_file() cfg.setValue("theme", theme); cfg.setValue("gamemode", manual_gamemode); cfg.setValue("manual_gamemode", manual_gamemode_selection); - cfg.setValue("timeofday", manual_time_of_day); - cfg.setValue("manual_timeofday", manual_time_of_day_selection); + cfg.setValue("timeofday", manual_timeofday); + cfg.setValue("manual_timeofday", manual_timeofday_selection); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("chatlog_limit", log_max_lines); @@ -366,6 +366,11 @@ QString AOConfig::theme() const return d->theme; } +QString AOConfig::gamemode() const +{ + return d->gamemode; +} + /** * @brief Return the current gamemode. If no gamemode is set, return the * empty string. @@ -392,15 +397,20 @@ bool AOConfig::is_manual_gamemode_selection_enabled() const return d->manual_gamemode_selection; } +QString AOConfig::timeofday() const +{ + return d->timeofday; +} + /** * @brief Returns the current manual time of day. If no time of day is set, return * the empty string. * * @return Current manual time of day, or empty string if not set. */ -QString AOConfig::manual_time_of_day() const +QString AOConfig::manual_timeofday() const { - return d->manual_time_of_day; + return d->manual_timeofday; } /** @@ -413,9 +423,9 @@ QString AOConfig::manual_time_of_day() const * * @return Current manual time of day status. */ -bool AOConfig::is_manual_time_of_day_selection_enabled() const +bool AOConfig::is_manual_timeofday_selection_enabled() const { - return d->manual_time_of_day_selection; + return d->manual_timeofday_selection; } bool AOConfig::always_pre_enabled() const @@ -635,6 +645,14 @@ void AOConfig::set_theme(QString p_string) d->invoke_signal("theme_changed", Q_ARG(QString, p_string)); } +void AOConfig::set_gamemode(QString p_string) +{ + if (d->gamemode == p_string) + return; + d->gamemode = p_string; + d->invoke_signal("gamemode_changed", Q_ARG(QString, p_string)); +} + void AOConfig::set_manual_gamemode(QString p_string) { if (d->manual_gamemode == p_string) @@ -651,20 +669,28 @@ void AOConfig::set_manual_gamemode_selection_enabled(bool p_enabled) d->invoke_signal("manual_gamemode_selection_changed", Q_ARG(bool, p_enabled)); } -void AOConfig::set_manual_time_of_day(QString p_string) +void AOConfig::set_timeofday(QString p_string) +{ + if (d->timeofday == p_string) + return; + d->timeofday = p_string; + d->invoke_signal("timeofday_changed", Q_ARG(QString, p_string)); +} + +void AOConfig::set_manual_timeofday(QString p_string) { - if (d->manual_time_of_day == p_string) + if (d->manual_timeofday == p_string) return; - d->manual_time_of_day = p_string; - d->invoke_signal("manual_time_of_day_changed", Q_ARG(QString, p_string)); + d->manual_timeofday = p_string; + d->invoke_signal("manual_timeofday_changed", Q_ARG(QString, p_string)); } -void AOConfig::set_manual_time_of_day_selection_enabled(bool p_enabled) +void AOConfig::set_manual_timeofday_selection_enabled(bool p_enabled) { - if (d->manual_time_of_day_selection == p_enabled) + if (d->manual_timeofday_selection == p_enabled) return; - d->manual_time_of_day_selection = p_enabled; - d->invoke_signal("manual_time_of_day_selection_changed", Q_ARG(bool, p_enabled)); + d->manual_timeofday_selection = p_enabled; + d->invoke_signal("manual_timeofday_selection_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_always_pre(bool p_enabled) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 2235b3d9d..a26ea25ba 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -51,10 +51,12 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // game ui_theme = AO_GUI_WIDGET(QComboBox, "theme"); ui_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); - ui_gamemode = AO_GUI_WIDGET(QComboBox, "gamemode"); - ui_manual_gamemode = AO_GUI_WIDGET(QCheckBox, "manual_gamemode"); - ui_timeofday = AO_GUI_WIDGET(QComboBox, "timeofday"); - ui_manual_timeofday = AO_GUI_WIDGET(QCheckBox, "manual_timeofday"); + ui_gamemode = AO_GUI_WIDGET(QLineEdit, "gamemode"); + ui_manual_gamemode = AO_GUI_WIDGET(QComboBox, "manual_gamemode"); + ui_manual_gamemode_selection = AO_GUI_WIDGET(QCheckBox, "manual_gamemode_selection"); + ui_timeofday = AO_GUI_WIDGET(QLineEdit, "timeofday"); + ui_manual_timeofday = AO_GUI_WIDGET(QComboBox, "manual_timeofday"); + ui_manual_timeofday_selection = AO_GUI_WIDGET(QCheckBox, "manual_timeofday_selection"); ui_showname = AO_GUI_WIDGET(QLineEdit, "showname"); ui_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); ui_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); @@ -109,10 +111,14 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // game connect(m_config, SIGNAL(theme_changed(QString)), this, SLOT(on_theme_changed(QString))); - connect(m_config, SIGNAL(manual_gamemode_changed(QString)), this, SLOT(on_gamemode_changed(QString))); - connect(m_config, SIGNAL(manual_gamemode_selection_changed(bool)), ui_manual_gamemode, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(manual_time_of_day_changed(QString)), this, SLOT(on_theme_changed(QString))); - connect(m_config, SIGNAL(manual_time_of_day_selection_changed(bool)), ui_manual_timeofday, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_gamemode_changed(QString))); + connect(m_config, SIGNAL(manual_gamemode_changed(QString)), this, SLOT(on_manual_gamemode_changed(QString))); + connect(m_config, SIGNAL(manual_gamemode_selection_changed(bool)), this, + SLOT(on_manual_gamemode_selection_changed(bool))); + connect(m_config, SIGNAL(timeofday_changed(QString)), this, SLOT(on_timeofday_changed(QString))); + connect(m_config, SIGNAL(manual_timeofday_changed(QString)), this, SLOT(on_manual_timeofday_changed(QString))); + connect(m_config, SIGNAL(manual_timeofday_selection_changed(bool)), this, + SLOT(on_manual_timeofday_selection_changed(bool))); connect(m_config, SIGNAL(showname_changed(QString)), ui_showname, SLOT(setText(QString))); connect(m_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); @@ -154,7 +160,6 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_engine, SIGNAL(favorite_device_changed(DRAudioDevice)), this, SLOT(on_favorite_audio_device_changed(DRAudioDevice))); - // output // meta connect(ui_close, SIGNAL(clicked()), this, SLOT(close())); connect(ui_save, SIGNAL(clicked()), m_config, SLOT(save_file())); @@ -171,10 +176,14 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // game connect(ui_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(ui_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); - connect(ui_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_gamemode_index_changed(QString))); - connect(ui_manual_gamemode, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_gamemode_selection_enabled(bool))); - connect(ui_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_timeofday_index_changed(QString))); - connect(ui_manual_timeofday, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_time_of_day_selection_enabled(bool))); + connect(ui_manual_gamemode, SIGNAL(currentIndexChanged(QString)), this, + SLOT(on_manual_gamemode_index_changed(QString))); + connect(ui_manual_gamemode_selection, SIGNAL(toggled(bool)), m_config, + SLOT(set_manual_gamemode_selection_enabled(bool))); + connect(ui_manual_timeofday, SIGNAL(currentIndexChanged(QString)), this, + SLOT(on_manual_timeofday_index_changed(QString))); + connect(ui_manual_timeofday_selection, SIGNAL(toggled(bool)), m_config, + SLOT(set_manual_timeofday_selection_enabled(bool))); connect(ui_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); connect(ui_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); connect(ui_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); @@ -217,10 +226,10 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // game ui_theme->setCurrentText(m_config->theme()); - ui_gamemode->setCurrentText(m_config->manual_gamemode()); - ui_manual_gamemode->setChecked(m_config->is_manual_gamemode_selection_enabled()); - ui_timeofday->setCurrentText(m_config->manual_time_of_day()); - ui_manual_timeofday->setChecked(m_config->is_manual_time_of_day_selection_enabled()); + ui_manual_gamemode->setCurrentText(m_config->manual_gamemode()); + ui_manual_gamemode_selection->setChecked(m_config->is_manual_gamemode_selection_enabled()); + ui_manual_timeofday->setCurrentText(m_config->manual_timeofday()); + ui_manual_timeofday_selection->setChecked(m_config->is_manual_timeofday_selection_enabled()); ui_showname->setText(m_config->showname()); on_showname_placeholder_changed(m_config->showname_placeholder()); ui_always_pre->setChecked(m_config->always_pre_enabled()); @@ -263,13 +272,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_blip_rate->setValue(m_config->blip_rate()); ui_blank_blips->setChecked(m_config->blank_blips_enabled()); - // Widget enabling connections - ui_gamemode->setEnabled(m_config->is_manual_gamemode_selection_enabled()); - ui_timeofday->setEnabled(m_config->is_manual_time_of_day_selection_enabled()); - // The manual gamemode checkbox enables browsing the gamemode combox - // similarly with time of day - connect(m_config, SIGNAL(manual_gamemode_selection_changed(bool)), ui_gamemode, SLOT(setEnabled(bool))); - connect(m_config, SIGNAL(manual_time_of_day_selection_changed(bool)), ui_timeofday, SLOT(setEnabled(bool))); + on_manual_gamemode_selection_changed(m_config->is_manual_gamemode_selection_enabled()); + on_manual_timeofday_selection_changed(m_config->is_manual_timeofday_selection_enabled()); } void AOConfigPanel::showEvent(QShowEvent *event) @@ -311,40 +315,40 @@ void AOConfigPanel::refresh_theme_list() void AOConfigPanel::refresh_gamemode_list() { - const QString p_prev_text = ui_gamemode->currentText(); + const QString p_prev_text = ui_manual_gamemode->currentText(); // block signals - ui_gamemode->blockSignals(true); - ui_gamemode->clear(); + ui_manual_gamemode->blockSignals(true); + ui_manual_gamemode->clear(); // add empty entry indicating no gamemode chosen - ui_gamemode->addItem(""); + ui_manual_gamemode->addItem(""); // gamemodes QString path = DRPather::get_application_path() + "/base/themes/" + m_config->theme() + "/gamemodes/"; for (const QString &i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; - ui_gamemode->addItem(i_folder, i_folder); + ui_manual_gamemode->addItem(i_folder, i_folder); } // restore previous selection - ui_gamemode->setCurrentText(p_prev_text); + ui_manual_gamemode->setCurrentText(p_prev_text); // unblock - ui_gamemode->blockSignals(false); + ui_manual_gamemode->blockSignals(false); } void AOConfigPanel::refresh_timeofday_list() { - const QString p_prev_text = ui_timeofday->currentText(); + const QString p_prev_text = ui_manual_timeofday->currentText(); // block signals - ui_timeofday->blockSignals(true); - ui_timeofday->clear(); + ui_manual_timeofday->blockSignals(true); + ui_manual_timeofday->clear(); // add empty entry indicating no time of day chosen - ui_timeofday->addItem(""); + ui_manual_timeofday->addItem(""); // decide path to look for times of day. This differs whether there is a // gamemode chosen or not @@ -360,14 +364,14 @@ void AOConfigPanel::refresh_timeofday_list() { if (i_folder == "." || i_folder == "..") continue; - ui_timeofday->addItem(i_folder, i_folder); + ui_manual_timeofday->addItem(i_folder, i_folder); } // restore previous selection - ui_timeofday->setCurrentText(p_prev_text); + ui_manual_timeofday->setCurrentText(p_prev_text); // unblock - ui_timeofday->blockSignals(false); + ui_manual_timeofday->blockSignals(false); } void AOConfigPanel::update_audio_device_list() @@ -417,32 +421,80 @@ void AOConfigPanel::on_theme_changed(QString p_name) ui_theme->setCurrentText(p_name); } -void AOConfigPanel::on_gamemode_changed(QString p_name) +void AOConfigPanel::on_gamemode_changed(QString p_text) +{ + ui_gamemode->setText(p_text.isEmpty() ? "" : p_text); +} + +void AOConfigPanel::on_manual_gamemode_changed(QString p_name) { refresh_theme_list(); refresh_gamemode_list(); refresh_timeofday_list(); - ui_gamemode->setCurrentText(p_name); + ui_manual_gamemode->setCurrentText(p_name); } -void AOConfigPanel::on_time_of_day_changed(QString p_name) +void AOConfigPanel::on_manual_timeofday_changed(QString p_name) { refresh_theme_list(); refresh_gamemode_list(); refresh_timeofday_list(); - ui_timeofday->setCurrentText(p_name); + ui_manual_timeofday->setCurrentText(p_name); } -void AOConfigPanel::on_gamemode_index_changed(QString p_text) +void AOConfigPanel::on_manual_gamemode_index_changed(QString p_text) { Q_UNUSED(p_text); - m_config->set_manual_gamemode(ui_gamemode->currentData().toString()); + m_config->set_manual_gamemode(ui_manual_gamemode->currentData().toString()); +} + +void AOConfigPanel::on_manual_gamemode_selection_changed(bool p_enabled) +{ + /*** + * As of time of writing, it is better to do this kind of ordered + * visibility calls to prevent layout reorganization 'lag'. + * + * The lag occurs due to themes immediately attempting to reload. + */ + if (p_enabled) + { + ui_gamemode->hide(); + ui_manual_gamemode->show(); + } + else + { + ui_manual_gamemode->hide(); + ui_gamemode->show(); + } + + ui_manual_gamemode_selection->setChecked(p_enabled); } -void AOConfigPanel::on_timeofday_index_changed(QString p_text) +void AOConfigPanel::on_timeofday_changed(QString p_text) +{ + ui_timeofday->setText(p_text.isEmpty() ? "" : p_text); +} + +void AOConfigPanel::on_manual_timeofday_index_changed(QString p_text) { Q_UNUSED(p_text); - m_config->set_manual_time_of_day(ui_timeofday->currentData().toString()); + m_config->set_manual_timeofday(ui_manual_timeofday->currentData().toString()); +} + +void AOConfigPanel::on_manual_timeofday_selection_changed(bool p_enabled) +{ + if (p_enabled) + { + ui_timeofday->hide(); + ui_manual_timeofday->show(); + } + else + { + ui_manual_timeofday->hide(); + ui_timeofday->show(); + } + + ui_manual_timeofday_selection->setChecked(p_enabled); } void AOConfigPanel::on_showname_placeholder_changed(QString p_text) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 8e5300f9f..d4bfab2a8 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -62,6 +62,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() Courtroom::~Courtroom() { + ao_config->set_gamemode(nullptr); + ao_config->set_timeofday(nullptr); stop_all_audio(); } @@ -323,20 +325,22 @@ void Courtroom::set_gamemode(QString p_gamemode) if (m_gamemode == p_gamemode) return; m_gamemode = p_gamemode; + ao_config->set_gamemode(p_gamemode); setup_courtroom(); update_background_scene(); } -QString Courtroom::get_time_of_day() +QString Courtroom::get_timeofday() { - return m_time_of_day; + return m_timeofday; } -void Courtroom::set_time_of_day(QString p_time_of_day) +void Courtroom::set_timeofday(QString p_timeofday) { - if (m_time_of_day == p_time_of_day) + if (m_timeofday == p_timeofday) return; - m_time_of_day = p_time_of_day; + m_timeofday = p_timeofday; + ao_config->set_timeofday(p_timeofday); setup_courtroom(); update_background_scene(); } diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 4476c4490..a75bbd78a 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -63,14 +63,14 @@ QStringList AOApplication::get_available_background_identifier_list() const DRAreaBackground l_area_bg = m_courtroom->get_background(); const QMap &l_bg_map = l_area_bg.background_tod_map; - if (ao_config->is_manual_time_of_day_selection_enabled()) + if (ao_config->is_manual_timeofday_selection_enabled()) { - const QString l_manual_tod = ao_config->manual_time_of_day(); + const QString l_manual_tod = ao_config->manual_timeofday(); if (!l_manual_tod.isEmpty() && l_bg_map.contains(l_manual_tod)) l_bg_list.append(l_bg_map.value(l_manual_tod)); } - const QString l_tod = m_courtroom->get_time_of_day(); + const QString l_tod = m_courtroom->get_timeofday(); if (!l_tod.isEmpty() && l_bg_map.contains(l_tod)) l_bg_list.append(l_bg_map.value(l_tod)); @@ -255,9 +255,9 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_exten const QString l_gamemode = ao_config->is_manual_gamemode_selection_enabled() ? ao_config->manual_gamemode() : is_courtroom_constructed ? m_courtroom->get_gamemode() : nullptr; - const QString l_timeofday = ao_config->is_manual_time_of_day_selection_enabled() ? ao_config->manual_time_of_day() - : is_courtroom_constructed ? m_courtroom->get_time_of_day() - : nullptr; + const QString l_timeofday = ao_config->is_manual_timeofday_selection_enabled() ? ao_config->manual_timeofday() + : is_courtroom_constructed ? m_courtroom->get_timeofday() + : nullptr; const QString l_theme_root = get_base_path() + "themes/" + ao_config->theme(); if (!l_gamemode.isEmpty()) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 84022a341..b9b60b2e3 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -398,7 +398,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) return; if (!is_courtroom_constructed) return; - m_courtroom->set_time_of_day(l_content.at(0)); + m_courtroom->set_timeofday(l_content.at(0)); } else if (l_header == "TR") { From ff8b94287efcadfde995aaf50ae9bf2b36379c68 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 15 Aug 2021 11:37:22 +0200 Subject: [PATCH 521/842] Small tweak to prevent the UI from reloading automatically if set to manual --- src/courtroom.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d4bfab2a8..60b93fa9c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -326,6 +326,8 @@ void Courtroom::set_gamemode(QString p_gamemode) return; m_gamemode = p_gamemode; ao_config->set_gamemode(p_gamemode); + if (ao_config->is_manual_gamemode_selection_enabled()) + return; setup_courtroom(); update_background_scene(); } @@ -341,6 +343,8 @@ void Courtroom::set_timeofday(QString p_timeofday) return; m_timeofday = p_timeofday; ao_config->set_timeofday(p_timeofday); + if (ao_config->is_manual_timeofday_selection_enabled()) + return; setup_courtroom(); update_background_scene(); } From 5a961941a97f480d2b76e549e25849f6db6bb45f Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 15 Aug 2021 13:57:18 +0200 Subject: [PATCH 522/842] Fix manual gamemode dropdown not being enabled * Fix manual gamemode dropdown not being enabled when manual gamemode selection is checked --- res/ui/config_panel.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 65d3faede..ad5b580c9 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -223,7 +223,7 @@ - false + true <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> gamemode.</p></body></html> From 2ebd6663ff619907c02abbe49d02938387549d43 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 15 Aug 2021 15:27:27 +0200 Subject: [PATCH 523/842] Switching manual gamemode now clears the manual time of day * Switching manual gamemode now clears the manual time of day (This does not affect the server-provided gamemode and time of day) --- src/aoconfig.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index b45d66bc7..0ba7e28b9 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -659,6 +659,7 @@ void AOConfig::set_manual_gamemode(QString p_string) return; d->manual_gamemode = p_string; d->invoke_signal("manual_gamemode_changed", Q_ARG(QString, p_string)); + set_manual_timeofday(nullptr); } void AOConfig::set_manual_gamemode_selection_enabled(bool p_enabled) From 53b0235112d0c8902b35b255de439bb7fdca03b2 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 23 Aug 2021 20:02:37 -0400 Subject: [PATCH 524/842] Taken status for character is visually refreshed when server indicates so --- src/courtroom.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 60b93fa9c..2ecf1e6df 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -3,6 +3,7 @@ #include "aoapplication.h" #include "aoblipplayer.h" #include "aobutton.h" +#include "aocharbutton.h" #include "aocharmovie.h" #include "aoconfig.h" #include "aoevidencedisplay.h" @@ -229,7 +230,6 @@ void Courtroom::done_received() suppress_audio(true); set_char_select_page(); - set_char_select(); show(); @@ -302,6 +302,10 @@ void Courtroom::set_taken(int n_char, bool p_taken) f_char.taken = p_taken; m_chr_list.replace(n_char, f_char); + AOCharButton *l_button = ui_char_button_list.at(n_char); + if (l_button->isVisible()) { + l_button->set_taken(p_taken); + } } DRAreaBackground Courtroom::get_background() From f3e7d2f69da0591a75e21429c107d83f5edf69a0 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Tue, 24 Aug 2021 08:52:51 -0400 Subject: [PATCH 525/842] Force Ubuntu worker update before running action Original PR at https://github.com/Chrezm/DRO-Client/pull/214 --- .github/workflows/build-all.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 74a1c16cc..7aefaff63 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -148,7 +148,7 @@ jobs: steps: - uses: actions/checkout@v2 - + - name: Setup workspace shell: bash working-directory: ${{github.workspace}} @@ -204,7 +204,7 @@ jobs: run: | ./Qt/5.14.2/clang_64/bin/qmake -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec macx-clang "CONFIG+=x86_64" "CONFIG+=qtquickcompiler" - - name: Run Make + - name: Run make shell: bash working-directory: ${{env.parentworkspace}} run: | @@ -254,6 +254,13 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Update worker + shell: bash + working-directory: ${{github.workspace}} + run: | + sudo apt-get update + sudo apt-get upgrade + - name: Setup workspace shell: bash working-directory: ${{github.workspace}} From cdeecd5e17128d823f153624e1e33a97cf138a0d Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 24 Aug 2021 17:30:11 -0400 Subject: [PATCH 526/842] Add character availability request packet --- include/aoapplication.h | 2 ++ src/aoapplication.cpp | 5 +++++ src/courtroom.cpp | 7 +++++-- src/server_socket.cpp | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index c3d18a1de..c0f77ef4e 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -52,6 +52,7 @@ class AOApplication : public QApplication bool has_character_declaration_feature() const; bool has_showname_declaration_feature() const; bool has_chat_speed_feature() const; + bool has_character_availability_request_feature() const; /////////////////////////////////////////// @@ -230,6 +231,7 @@ public slots: bool feature_showname = false; bool feature_chrini = false; bool feature_chat_speed = false; + bool feature_charscheck = false; ///////////////loading info/////////////////// // player number, it's hardly used but might be needed for some old servers diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index bd1012409..80041fea6 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -194,6 +194,11 @@ bool AOApplication::has_chat_speed_feature() const return feature_chat_speed; } +bool AOApplication::has_character_availability_request_feature() const +{ + return feature_charscheck; +} + void AOApplication::handle_theme_modification() { load_fonts(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2ecf1e6df..a81992a7b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -229,8 +229,8 @@ void Courtroom::done_received() suppress_audio(true); - set_char_select_page(); set_char_select(); + set_char_select_page(); show(); @@ -2134,9 +2134,12 @@ void Courtroom::on_change_character_clicked() suppress_audio(true); set_char_select(); + set_char_select_page(); - ui_char_select_background->show(); ui_spectator->show(); + + if (ao_app->has_character_availability_request_feature()) + ao_app->send_server_packet(DRPacket("CharsCheck")); } void Courtroom::on_app_reload_theme_requested() diff --git a/src/server_socket.cpp b/src/server_socket.cpp index b9b60b2e3..7394effcc 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -61,6 +61,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) feature_showname = false; feature_chrini = false; feature_chat_speed = false; + feature_charscheck = false; send_server_packet(DRPacket("HI", {get_hdid()})); } @@ -90,6 +91,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) feature_showname = l_content.contains("showname", Qt::CaseInsensitive); feature_chrini = l_content.contains("chrini", Qt::CaseInsensitive); feature_chat_speed = l_content.contains("chat_speed", Qt::CaseInsensitive); + feature_charscheck = l_content.contains("charscheck", Qt::CaseInsensitive); } else if (l_header == "PN") { From b0fbb2d945605a0d84e5fad46f4aba1c4d6ec4ef Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 24 Aug 2021 17:55:57 -0400 Subject: [PATCH 527/842] Stop rendering message process if chatbox becomes invisible, and prevent chat arrow from appearing if so This mostly affects the case where a character is talking and during that the client reloads theme --- src/courtroom.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 60b93fa9c..c69ba3be0 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1330,7 +1330,7 @@ void Courtroom::calculate_chat_tick_interval() void Courtroom::next_chat_letter() { const QString &f_message = m_chatmessage[CMMessage]; - if (m_tick_step >= f_message.length()) + if (m_tick_step >= f_message.length() || !ui_vp_chatbox->isVisible()) { post_chat(); return; @@ -1489,9 +1489,9 @@ void Courtroom::post_chat() m_message_color_name = ""; m_message_color_stack.clear(); - if (!chatmessage_is_empty) + if (ui_vp_chatbox->isVisible()) ui_vp_chat_arrow->restart(); - ui_vp_chat_arrow->setHidden(chatmessage_is_empty); + ui_vp_chat_arrow->setVisible(ui_vp_chatbox->isVisible()); } void Courtroom::play_sfx() From 0c5afc47f2a14a5a340833300218af5b75166ee2 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 27 Aug 2021 03:26:48 +0200 Subject: [PATCH 528/842] Remove negative bool check, ... * Simplify the chatbox visibility check for the chat arrow --- src/courtroom.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index c69ba3be0..870340e83 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1330,7 +1330,7 @@ void Courtroom::calculate_chat_tick_interval() void Courtroom::next_chat_letter() { const QString &f_message = m_chatmessage[CMMessage]; - if (m_tick_step >= f_message.length() || !ui_vp_chatbox->isVisible()) + if (m_tick_step >= f_message.length() || ui_vp_chatbox->isHidden()) { post_chat(); return; @@ -1489,9 +1489,12 @@ void Courtroom::post_chat() m_message_color_name = ""; m_message_color_stack.clear(); + if (ui_vp_chatbox->isVisible()) + { ui_vp_chat_arrow->restart(); - ui_vp_chat_arrow->setVisible(ui_vp_chatbox->isVisible()); + ui_vp_chat_arrow->show(); + } } void Courtroom::play_sfx() From 63c55dcd6128168f1f83c5da9bdfae988b3fe49d Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 27 Aug 2021 17:44:01 -0400 Subject: [PATCH 529/842] Fix crash attempting to process a character taken status if theme has multiple character select pages --- include/courtroom.h | 2 +- src/charselect.cpp | 29 +++++++++++++++++++++++++++++ src/courtroom.cpp | 19 ------------------- src/server_socket.cpp | 8 ++++---- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index ba66a9f9d..0052d624a 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -70,7 +70,7 @@ class Courtroom : public QMainWindow // sets status as taken on character with cid n_char and places proper shading // on charselect - void set_taken(int n_char, bool p_taken); + void char_set_taken(int n_real_char, bool p_taken); // sets the current background to argument. also does some checks to see if // it's a legacy bg diff --git a/src/charselect.cpp b/src/charselect.cpp index 850e97832..bb3cce853 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -214,3 +214,32 @@ void Courtroom::char_mouse_left() { ui_char_button_selector->hide(); } + +void Courtroom::char_set_taken(int n_real_char, bool p_taken) +{ + // First update the taken status in the internal list + if (n_real_char >= m_chr_list.size()) + { + qDebug() << "W: set_taken attempted to set an index bigger than char_list size"; + return; + } + + char_type f_char; + f_char.name = m_chr_list.at(n_real_char).name; + f_char.taken = p_taken; + + m_chr_list.replace(n_real_char, f_char); + + // Then visually update the visible character button if necessary + // It is not necessary to update a button if visually it is not present + if (n_real_char < m_current_chr_page * m_page_max_chr_count) + return; + if (n_real_char >= (m_current_chr_page+1) * m_page_max_chr_count) + return; + + int l_button_id = n_real_char % m_page_max_chr_count; + AOCharButton *l_button = ui_char_button_list.at(l_button_id); + if (l_button->isVisible()) { + l_button->set_taken(p_taken); + } +} diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a8171f94e..ac2d0da23 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -289,25 +289,6 @@ void Courtroom::update_background_scene() ui_vp_background->set_image(f_background); } -void Courtroom::set_taken(int n_char, bool p_taken) -{ - if (n_char >= m_chr_list.size()) - { - qDebug() << "W: set_taken attempted to set an index bigger than char_list size"; - return; - } - - char_type f_char; - f_char.name = m_chr_list.at(n_char).name; - f_char.taken = p_taken; - - m_chr_list.replace(n_char, f_char); - AOCharButton *l_button = ui_char_button_list.at(n_char); - if (l_button->isVisible()) { - l_button->set_taken(p_taken); - } -} - DRAreaBackground Courtroom::get_background() { return m_background; diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 7394effcc..0870620fb 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -176,12 +176,12 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) if (!is_courtroom_constructed) return; - for (int n_char = 0; n_char < l_content.size(); ++n_char) + for (int n_real_char = 0; n_real_char < l_content.size(); ++n_real_char) { - if (l_content.at(n_char) == "-1") - m_courtroom->set_taken(n_char, true); + if (l_content.at(n_real_char) == "-1") + m_courtroom->char_set_taken(n_real_char, true); else - m_courtroom->set_taken(n_char, false); + m_courtroom->char_set_taken(n_real_char, false); } } else if (l_header == "SC") From 1d79042465aad93a2a1ca878218dbd0537088207 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 28 Aug 2021 18:27:46 +0200 Subject: [PATCH 530/842] The config panel now reset back to the previous TOD (if available) --- src/aoconfigpanel.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index a26ea25ba..94af06af3 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -332,11 +332,11 @@ void AOConfigPanel::refresh_gamemode_list() ui_manual_gamemode->addItem(i_folder, i_folder); } - // restore previous selection - ui_manual_gamemode->setCurrentText(p_prev_text); - // unblock ui_manual_gamemode->blockSignals(false); + + // restore previous selection + ui_manual_gamemode->setCurrentText(p_prev_text); } void AOConfigPanel::refresh_timeofday_list() @@ -367,11 +367,11 @@ void AOConfigPanel::refresh_timeofday_list() ui_manual_timeofday->addItem(i_folder, i_folder); } - // restore previous selection - ui_manual_timeofday->setCurrentText(p_prev_text); - // unblock ui_manual_timeofday->blockSignals(false); + + // restore previous selection + ui_manual_timeofday->setCurrentText(p_prev_text); } void AOConfigPanel::update_audio_device_list() From d3f59465588f343587e2d9d99d6398cb51db2b51 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 13 Sep 2021 21:16:13 +0200 Subject: [PATCH 531/842] Changing theme always reset manual gamemode and manual time of day, changing manual gamemode always reset manual time of day --- include/aoapplication.h | 4 +- include/aoconfigpanel.h | 4 +- include/courtroom.h | 11 ++-- src/aoapplication.cpp | 16 ++---- src/aoconfig.cpp | 2 + src/aoconfigpanel.cpp | 107 ++++++++++++-------------------------- src/courtroom.cpp | 60 +++++---------------- src/courtroom_widgets.cpp | 2 - src/path_functions.cpp | 14 +++-- src/server_socket.cpp | 8 +-- 10 files changed, 68 insertions(+), 160 deletions(-) diff --git a/include/aoapplication.h b/include/aoapplication.h index c0f77ef4e..97c702762 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -77,7 +77,7 @@ class AOApplication : public QApplication QString get_current_background_path(); QString get_evidence_path(QString p_file); - QString sanitize_path(QString p_file); + bool is_safe_path(QString p_file); QString find_asset_path(QStringList file_list, QStringList extension_list); QString find_asset_path(QStringList file_list); @@ -207,7 +207,7 @@ public slots: void loading_cancelled(); signals: - void reload_theme(); + void theme_reloaded(); private: AOConfig *ao_config = nullptr; diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 5ffe1ba85..cc0915f93 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -44,13 +44,13 @@ private slots: void on_reload_theme_clicked(); void on_theme_changed(QString); void on_gamemode_changed(QString); + void on_manual_gamemode_selection_changed(bool); void on_manual_gamemode_changed(QString); void on_manual_gamemode_index_changed(QString p_text); - void on_manual_gamemode_selection_changed(bool); void on_timeofday_changed(QString); + void on_manual_timeofday_selection_changed(bool); void on_manual_timeofday_changed(QString); void on_manual_timeofday_index_changed(QString p_text); - void on_manual_timeofday_selection_changed(bool); void on_showname_placeholder_changed(QString p_text); void on_log_is_topdown_changed(bool p_enabled); void on_device_current_index_changed(int p_index); diff --git a/include/courtroom.h b/include/courtroom.h index ba66a9f9d..30cd41010 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -76,10 +76,6 @@ class Courtroom : public QMainWindow // it's a legacy bg DRAreaBackground get_background(); void set_background(DRAreaBackground p_area_bg); - QString get_gamemode(); - void set_gamemode(QString p_gamemode); - QString get_timeofday(); - void set_timeofday(QString p_time_of_day); void set_tick_rate(const int tick_rate); @@ -303,7 +299,7 @@ class Courtroom : public QMainWindow // if true, a reload theme order was delayed to be executed *after* a shout // this allows reload theme orders that were received while a shout was // playing to be executed only after the shout is done playing - bool shout_delayed_reload_theme = false; + bool m_shout_reload_theme = false; int m_shout_state = 0; int m_effect_state = 0; @@ -345,8 +341,6 @@ class Courtroom : public QMainWindow int m_current_clock = -1; DRAreaBackground m_background; - QString m_gamemode; - QString m_timeofday; AOImageDisplay *ui_background = nullptr; @@ -653,7 +647,8 @@ private slots: void on_wtce_clicked(); void on_change_character_clicked(); - void on_app_reload_theme_requested(); + void reload_theme(); + void schedule_theme_reload(); void on_call_mod_clicked(); void on_switch_area_music_clicked(); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 80041fea6..eb83cb182 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -159,6 +159,8 @@ void AOApplication::destruct_courtroom() { delete m_courtroom; is_courtroom_constructed = false; + ao_config->set_gamemode(nullptr); + ao_config->set_timeofday(nullptr); } else { @@ -203,7 +205,7 @@ void AOApplication::handle_theme_modification() { load_fonts(); - Q_EMIT reload_theme(); + Q_EMIT theme_reloaded(); } void AOApplication::set_favorite_list() @@ -232,17 +234,9 @@ QString AOApplication::get_current_char() * @return A sanitized path. If any check fails, the path returned is an empty string. The sanitized path does not * necessarily exist. */ -QString AOApplication::sanitize_path(QString p_file) +bool AOApplication::is_safe_path(QString p_file) { - if (!p_file.contains("..")) - return p_file; - - QStringList list = p_file.split(QRegularExpression("[\\/]")); - while (!list.isEmpty()) - if (list.takeFirst().contains(QRegularExpression("\\.{2,}"))) - return nullptr; - - return p_file; + return !p_file.contains(".."); } void AOApplication::toggle_config_panel() diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 0ba7e28b9..bfcf6630a 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -643,6 +643,8 @@ void AOConfig::set_theme(QString p_string) return; d->theme = p_string; d->invoke_signal("theme_changed", Q_ARG(QString, p_string)); + set_manual_gamemode(nullptr); + set_manual_timeofday(nullptr); } void AOConfig::set_gamemode(QString p_string) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 94af06af3..f309ac4f2 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -282,18 +282,12 @@ void AOConfigPanel::showEvent(QShowEvent *event) if (isVisible()) { - // refresh theme, gamemode and time of day comboboxes refresh_theme_list(); - refresh_gamemode_list(); - refresh_timeofday_list(); } } void AOConfigPanel::refresh_theme_list() { - const QString p_prev_text = ui_theme->currentText(); - - // block signals ui_theme->blockSignals(true); ui_theme->clear(); @@ -307,17 +301,12 @@ void AOConfigPanel::refresh_theme_list() } // restore previous selection - ui_theme->setCurrentText(p_prev_text); - - // unblock + ui_theme->setCurrentText(m_config->theme()); ui_theme->blockSignals(false); } void AOConfigPanel::refresh_gamemode_list() { - const QString p_prev_text = ui_manual_gamemode->currentText(); - - // block signals ui_manual_gamemode->blockSignals(true); ui_manual_gamemode->clear(); @@ -332,46 +321,42 @@ void AOConfigPanel::refresh_gamemode_list() ui_manual_gamemode->addItem(i_folder, i_folder); } - // unblock + ui_manual_gamemode->setCurrentText(m_config->manual_gamemode()); ui_manual_gamemode->blockSignals(false); - - // restore previous selection - ui_manual_gamemode->setCurrentText(p_prev_text); } void AOConfigPanel::refresh_timeofday_list() { - const QString p_prev_text = ui_manual_timeofday->currentText(); - - // block signals ui_manual_timeofday->blockSignals(true); ui_manual_timeofday->clear(); // add empty entry indicating no time of day chosen ui_manual_timeofday->addItem(""); + const QString l_theme = m_config->theme(); + const QString l_gamemode = + m_config->is_manual_gamemode_selection_enabled() ? m_config->manual_gamemode() : m_config->gamemode(); + // decide path to look for times of day. This differs whether there is a // gamemode chosen or not - QString path; - if (m_config->manual_gamemode().isEmpty()) - path = DRPather::get_application_path() + "/base/themes/" + m_config->theme() + "/times/"; + QString l_timeofday_path; + + if (l_gamemode.isEmpty()) + l_timeofday_path = DRPather::get_application_path() + "/base/themes/" + l_theme + "/times/"; else - path = DRPather::get_application_path() + "/base/themes/" + m_config->theme() + "/gamemodes/" + - m_config->manual_gamemode() + "/times/"; + l_timeofday_path = + DRPather::get_application_path() + "/base/themes/" + l_theme + "/gamemodes/" + l_gamemode + "/times/"; // times of day - for (const QString &i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + for (const QString &i_folder : QDir(ao_app->get_case_sensitive_path(l_timeofday_path)).entryList(QDir::Dirs)) { if (i_folder == "." || i_folder == "..") continue; ui_manual_timeofday->addItem(i_folder, i_folder); } - // unblock + ui_manual_timeofday->setCurrentText(m_config->manual_timeofday()); ui_manual_timeofday->blockSignals(false); - - // restore previous selection - ui_manual_timeofday->setCurrentText(p_prev_text); } void AOConfigPanel::update_audio_device_list() @@ -415,10 +400,8 @@ void AOConfigPanel::on_reload_theme_clicked() void AOConfigPanel::on_theme_changed(QString p_name) { + Q_UNUSED(p_name); refresh_theme_list(); - refresh_gamemode_list(); - refresh_timeofday_list(); - ui_theme->setCurrentText(p_name); } void AOConfigPanel::on_gamemode_changed(QString p_text) @@ -426,20 +409,20 @@ void AOConfigPanel::on_gamemode_changed(QString p_text) ui_gamemode->setText(p_text.isEmpty() ? "" : p_text); } -void AOConfigPanel::on_manual_gamemode_changed(QString p_name) +void AOConfigPanel::on_manual_gamemode_selection_changed(bool p_enabled) { - refresh_theme_list(); + ui_gamemode->setHidden(p_enabled); + ui_manual_gamemode->setVisible(p_enabled); + ui_manual_gamemode_selection->setChecked(p_enabled); refresh_gamemode_list(); refresh_timeofday_list(); - ui_manual_gamemode->setCurrentText(p_name); } -void AOConfigPanel::on_manual_timeofday_changed(QString p_name) +void AOConfigPanel::on_manual_gamemode_changed(QString p_name) { - refresh_theme_list(); + Q_UNUSED(p_name); refresh_gamemode_list(); refresh_timeofday_list(); - ui_manual_timeofday->setCurrentText(p_name); } void AOConfigPanel::on_manual_gamemode_index_changed(QString p_text) @@ -448,31 +431,23 @@ void AOConfigPanel::on_manual_gamemode_index_changed(QString p_text) m_config->set_manual_gamemode(ui_manual_gamemode->currentData().toString()); } -void AOConfigPanel::on_manual_gamemode_selection_changed(bool p_enabled) +void AOConfigPanel::on_timeofday_changed(QString p_text) { - /*** - * As of time of writing, it is better to do this kind of ordered - * visibility calls to prevent layout reorganization 'lag'. - * - * The lag occurs due to themes immediately attempting to reload. - */ - if (p_enabled) - { - ui_gamemode->hide(); - ui_manual_gamemode->show(); - } - else - { - ui_manual_gamemode->hide(); - ui_gamemode->show(); - } + ui_timeofday->setText(p_text.isEmpty() ? "" : p_text); +} - ui_manual_gamemode_selection->setChecked(p_enabled); +void AOConfigPanel::on_manual_timeofday_selection_changed(bool p_enabled) +{ + ui_timeofday->setHidden(p_enabled); + ui_manual_timeofday->setVisible(p_enabled); + ui_manual_timeofday_selection->setChecked(p_enabled); + refresh_timeofday_list(); } -void AOConfigPanel::on_timeofday_changed(QString p_text) +void AOConfigPanel::on_manual_timeofday_changed(QString p_name) { - ui_timeofday->setText(p_text.isEmpty() ? "" : p_text); + Q_UNUSED(p_name); + refresh_timeofday_list(); } void AOConfigPanel::on_manual_timeofday_index_changed(QString p_text) @@ -481,22 +456,6 @@ void AOConfigPanel::on_manual_timeofday_index_changed(QString p_text) m_config->set_manual_timeofday(ui_manual_timeofday->currentData().toString()); } -void AOConfigPanel::on_manual_timeofday_selection_changed(bool p_enabled) -{ - if (p_enabled) - { - ui_timeofday->hide(); - ui_manual_timeofday->show(); - } - else - { - ui_manual_timeofday->hide(); - ui_timeofday->show(); - } - - ui_manual_timeofday_selection->setChecked(p_enabled); -} - void AOConfigPanel::on_showname_placeholder_changed(QString p_text) { const QString l_showname(p_text.trimmed().isEmpty() ? "Showname" : p_text); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a8171f94e..bc2452f7d 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -51,8 +51,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() m_reload_timer = new QTimer(this); m_reload_timer->setInterval(200); m_reload_timer->setSingleShot(true); - connect(m_reload_timer, SIGNAL(timeout()), this, SLOT(on_app_reload_theme_requested())); - connect(ao_app, SIGNAL(reload_theme()), m_reload_timer, SLOT(start())); + connect(m_reload_timer, SIGNAL(timeout()), this, SLOT(reload_theme())); + connect(ao_app, SIGNAL(theme_reloaded()), m_reload_timer, SLOT(start())); create_widgets(); connect_widgets(); @@ -303,7 +303,8 @@ void Courtroom::set_taken(int n_char, bool p_taken) m_chr_list.replace(n_char, f_char); AOCharButton *l_button = ui_char_button_list.at(n_char); - if (l_button->isVisible()) { + if (l_button->isVisible()) + { l_button->set_taken(p_taken); } } @@ -319,40 +320,6 @@ void Courtroom::set_background(DRAreaBackground p_background) update_background_scene(); } -QString Courtroom::get_gamemode() -{ - return m_gamemode; -} - -void Courtroom::set_gamemode(QString p_gamemode) -{ - if (m_gamemode == p_gamemode) - return; - m_gamemode = p_gamemode; - ao_config->set_gamemode(p_gamemode); - if (ao_config->is_manual_gamemode_selection_enabled()) - return; - setup_courtroom(); - update_background_scene(); -} - -QString Courtroom::get_timeofday() -{ - return m_timeofday; -} - -void Courtroom::set_timeofday(QString p_timeofday) -{ - if (m_timeofday == p_timeofday) - return; - m_timeofday = p_timeofday; - ao_config->set_timeofday(p_timeofday); - if (ao_config->is_manual_timeofday_selection_enabled()) - return; - setup_courtroom(); - update_background_scene(); -} - void Courtroom::set_tick_rate(const int p_tick_rate) { if (p_tick_rate < 0) @@ -838,10 +805,10 @@ void Courtroom::handle_chatmessage_2() // handles IC qDebug() << "handle_chatmessage_2"; ui_vp_player_char->stop(); - if (shout_delayed_reload_theme) + if (m_shout_reload_theme) { - shout_delayed_reload_theme = false; - on_app_reload_theme_requested(); + m_shout_reload_theme = false; + reload_theme(); } QString real_name = m_chr_list.at(m_chatmessage[CMChrId].toInt()).name; @@ -2145,22 +2112,21 @@ void Courtroom::on_change_character_clicked() ao_app->send_server_packet(DRPacket("CharsCheck")); } -void Courtroom::on_app_reload_theme_requested() +void Courtroom::reload_theme() { - // If an objection is playing, delay reload theme order to be executed - // after objection is done - if (ui_vp_objection->state() == QMovie::MovieState::Running) + if (ui_vp_objection->state() == QMovie::Running) { - shout_delayed_reload_theme = true; + m_shout_reload_theme = true; return; } setup_courtroom(); - - // to update status on the background update_background_scene(); } +void Courtroom::schedule_theme_reload() +{} + void Courtroom::on_back_to_lobby_clicked() { // hide so we don't get the 'disconnected from server' prompt diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index cfd4d32d8..0ec297941 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -249,8 +249,6 @@ void Courtroom::connect_widgets() { connect(m_keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); - connect(ao_app, SIGNAL(reload_theme()), this, SLOT(on_app_reload_theme_requested())); - connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); diff --git a/src/path_functions.cpp b/src/path_functions.cpp index a75bbd78a..0da76ceb4 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -70,7 +70,7 @@ QStringList AOApplication::get_available_background_identifier_list() l_bg_list.append(l_bg_map.value(l_manual_tod)); } - const QString l_tod = m_courtroom->get_timeofday(); + const QString l_tod = ao_config->timeofday(); if (!l_tod.isEmpty() && l_bg_map.contains(l_tod)) l_bg_list.append(l_bg_map.value(l_tod)); @@ -193,7 +193,7 @@ QString AOApplication::find_asset_path(QStringList p_file_list, QStringList p_ex // We can assume that possible_exts will only be populated with hardcoded strings. // Therefore, the only place where sanitize_path could catch something bad is in the root. // So, check that now, so we don't need to check later. - if (sanitize_path(i_root).isEmpty()) + if (!is_safe_path(i_root)) continue; // Check if parent folder actually exists. If it does not, none of the following files would exist @@ -252,12 +252,10 @@ QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_exten QStringList l_path_list; // Only add gamemode and/or time of day if non empty. - const QString l_gamemode = ao_config->is_manual_gamemode_selection_enabled() ? ao_config->manual_gamemode() - : is_courtroom_constructed ? m_courtroom->get_gamemode() - : nullptr; - const QString l_timeofday = ao_config->is_manual_timeofday_selection_enabled() ? ao_config->manual_timeofday() - : is_courtroom_constructed ? m_courtroom->get_timeofday() - : nullptr; + const QString l_gamemode = + ao_config->is_manual_gamemode_selection_enabled() ? ao_config->manual_gamemode() : ao_config->gamemode(); + const QString l_timeofday = + ao_config->is_manual_timeofday_selection_enabled() ? ao_config->manual_timeofday() : ao_config->timeofday(); const QString l_theme_root = get_base_path() + "themes/" + ao_config->theme(); if (!l_gamemode.isEmpty()) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 7394effcc..592d3c698 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -390,17 +390,13 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) { if (l_content.length() < 1) return; - if (!is_courtroom_constructed) - return; - m_courtroom->set_gamemode(l_content.at(0)); + ao_config->set_gamemode(l_content.at(0)); } else if (l_header == "TOD") { if (l_content.length() < 1) return; - if (!is_courtroom_constructed) - return; - m_courtroom->set_timeofday(l_content.at(0)); + ao_config->set_timeofday(l_content.at(0)); } else if (l_header == "TR") { From d2b1863a6e4c5b2ba0ac837d39abc62681f99faa Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 13 Sep 2021 21:39:05 +0200 Subject: [PATCH 532/842] Restored safe pathing check for files with multiple dots --- src/aoapplication.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index eb83cb182..cb2668ce7 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -236,7 +236,13 @@ QString AOApplication::get_current_char() */ bool AOApplication::is_safe_path(QString p_file) { - return !p_file.contains(".."); + if (!p_file.contains("..")) + return true; + const QStringList i_item_list = p_file.split(QRegularExpression("[\\/]")); + for (auto it = i_item_list.crbegin(); it != i_item_list.crend(); ++it) + if (*it == "..") + return false; + return true; } void AOApplication::toggle_config_panel() From 837405c383e4c5bd446990d64ebee13734bc59a6 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 13 Sep 2021 21:57:15 +0200 Subject: [PATCH 533/842] Renamed variable to confirm to defined standard --- src/aoapplication.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index cb2668ce7..7cf5c8946 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -238,8 +238,8 @@ bool AOApplication::is_safe_path(QString p_file) { if (!p_file.contains("..")) return true; - const QStringList i_item_list = p_file.split(QRegularExpression("[\\/]")); - for (auto it = i_item_list.crbegin(); it != i_item_list.crend(); ++it) + const QStringList l_item_list = p_file.split(QRegularExpression("[\\/]")); + for (auto it = l_item_list.crbegin(); it != l_item_list.crend(); ++it) if (*it == "..") return false; return true; From db486795a9fb6e6e8db1c23a35b03b43db0fe9cd Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 14 Sep 2021 00:41:23 +0200 Subject: [PATCH 534/842] Changed character slot refresh implementation --- include/courtroom.h | 5 +---- src/charselect.cpp | 29 ----------------------------- src/courtroom.cpp | 6 ++++++ src/server_socket.cpp | 11 ++++------- 4 files changed, 11 insertions(+), 40 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 0052d624a..c4a4837a8 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -57,6 +57,7 @@ class Courtroom : public QMainWindow explicit Courtroom(AOApplication *p_ao_app); ~Courtroom(); + QVector get_character_list(); void set_character_list(QVector character_list); void set_area_list(QStringList area_list); void set_music_list(QStringList music_list); @@ -68,10 +69,6 @@ class Courtroom : public QMainWindow void set_window_title(QString p_title); - // sets status as taken on character with cid n_char and places proper shading - // on charselect - void char_set_taken(int n_real_char, bool p_taken); - // sets the current background to argument. also does some checks to see if // it's a legacy bg DRAreaBackground get_background(); diff --git a/src/charselect.cpp b/src/charselect.cpp index bb3cce853..850e97832 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -214,32 +214,3 @@ void Courtroom::char_mouse_left() { ui_char_button_selector->hide(); } - -void Courtroom::char_set_taken(int n_real_char, bool p_taken) -{ - // First update the taken status in the internal list - if (n_real_char >= m_chr_list.size()) - { - qDebug() << "W: set_taken attempted to set an index bigger than char_list size"; - return; - } - - char_type f_char; - f_char.name = m_chr_list.at(n_real_char).name; - f_char.taken = p_taken; - - m_chr_list.replace(n_real_char, f_char); - - // Then visually update the visible character button if necessary - // It is not necessary to update a button if visually it is not present - if (n_real_char < m_current_chr_page * m_page_max_chr_count) - return; - if (n_real_char >= (m_current_chr_page+1) * m_page_max_chr_count) - return; - - int l_button_id = n_real_char % m_page_max_chr_count; - AOCharButton *l_button = ui_char_button_list.at(l_button_id); - if (l_button->isVisible()) { - l_button->set_taken(p_taken); - } -} diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ac2d0da23..6b9b80c13 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -68,9 +68,15 @@ Courtroom::~Courtroom() stop_all_audio(); } +QVector Courtroom::get_character_list() +{ + return m_chr_list; +} + void Courtroom::set_character_list(QVector p_chr_list) { m_chr_list = p_chr_list; + set_char_select_page(); } void Courtroom::set_area_list(QStringList p_area_list) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 0870620fb..e7e905770 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -176,13 +176,10 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) if (!is_courtroom_constructed) return; - for (int n_real_char = 0; n_real_char < l_content.size(); ++n_real_char) - { - if (l_content.at(n_real_char) == "-1") - m_courtroom->char_set_taken(n_real_char, true); - else - m_courtroom->char_set_taken(n_real_char, false); - } + QVector l_chr_list = m_courtroom->get_character_list(); + for (int i = 0; i < l_chr_list.length(); ++i) + l_chr_list[i].taken = l_content.at(i) == "-1"; + m_courtroom->set_character_list(l_chr_list); } else if (l_header == "SC") { From 8e5837943fdd7efe3b8d18ddf9fea1ef346e0ee5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 14 Sep 2021 04:06:41 +0200 Subject: [PATCH 535/842] Gamemode and timeofday properly force a reload --- src/aoapplication.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 7cf5c8946..5307e5453 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -33,6 +33,8 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) m_server_socket = new DRServerSocket(this); connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(gamemode_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(timeofday_changed(QString)), this, SLOT(handle_theme_modification())); connect(ao_config, SIGNAL(manual_gamemode_changed(QString)), this, SLOT(handle_theme_modification())); connect(ao_config, SIGNAL(manual_gamemode_selection_changed(bool)), this, SLOT(handle_theme_modification())); connect(ao_config, SIGNAL(manual_timeofday_changed(QString)), this, SLOT(handle_theme_modification())); From 839dc5a3544adc040bf08a98b65e2a594e5a5529 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 14 Sep 2021 23:06:03 +0200 Subject: [PATCH 536/842] [FIX] Theme reloading is now immediately processed --- include/courtroom.h | 3 --- src/aoconfig.cpp | 6 +++--- src/aoconfigpanel.cpp | 2 ++ src/courtroom.cpp | 9 +-------- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index ccaf7895f..40477b343 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -223,8 +223,6 @@ class Courtroom : public QMainWindow AOApplication *ao_app = nullptr; AOConfig *ao_config = nullptr; - QTimer *m_reload_timer = nullptr; - QVector m_chr_list; QVector m_evidence_list; QStringList m_area_list; @@ -645,7 +643,6 @@ private slots: void on_change_character_clicked(); void reload_theme(); - void schedule_theme_reload(); void on_call_mod_clicked(); void on_switch_area_music_clicked(); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index bfcf6630a..f2ba4e692 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -642,9 +642,9 @@ void AOConfig::set_theme(QString p_string) if (d->theme == p_string) return; d->theme = p_string; + d->manual_gamemode.clear(); + d->manual_timeofday.clear(); d->invoke_signal("theme_changed", Q_ARG(QString, p_string)); - set_manual_gamemode(nullptr); - set_manual_timeofday(nullptr); } void AOConfig::set_gamemode(QString p_string) @@ -660,8 +660,8 @@ void AOConfig::set_manual_gamemode(QString p_string) if (d->manual_gamemode == p_string) return; d->manual_gamemode = p_string; + d->manual_timeofday.clear(); d->invoke_signal("manual_gamemode_changed", Q_ARG(QString, p_string)); - set_manual_timeofday(nullptr); } void AOConfig::set_manual_gamemode_selection_enabled(bool p_enabled) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index f309ac4f2..e3fc78a5b 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -402,6 +402,8 @@ void AOConfigPanel::on_theme_changed(QString p_name) { Q_UNUSED(p_name); refresh_theme_list(); + refresh_gamemode_list(); + refresh_timeofday_list(); } void AOConfigPanel::on_gamemode_changed(QString p_text) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 4d44ba3b4..efbfe5cd8 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -48,11 +48,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ao_app = p_ao_app; ao_config = new AOConfig(this); - m_reload_timer = new QTimer(this); - m_reload_timer->setInterval(200); - m_reload_timer->setSingleShot(true); - connect(m_reload_timer, SIGNAL(timeout()), this, SLOT(reload_theme())); - connect(ao_app, SIGNAL(theme_reloaded()), m_reload_timer, SLOT(start())); + connect(ao_app, SIGNAL(theme_reloaded()), this, SLOT(reload_theme())); create_widgets(); connect_widgets(); @@ -2110,9 +2106,6 @@ void Courtroom::reload_theme() update_background_scene(); } -void Courtroom::schedule_theme_reload() -{} - void Courtroom::on_back_to_lobby_clicked() { // hide so we don't get the 'disconnected from server' prompt From 13a800791a1fdbf7cc28f26ee6544dffd750130e Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 18 Sep 2021 19:35:29 -0400 Subject: [PATCH 537/842] Add emote preview toggle --- include/aoconfig.h | 3 +++ include/aoconfigpanel.h | 2 ++ res/ui/config_panel.ui | 16 +++++++++++++++- src/aoconfig.cpp | 16 ++++++++++++++++ src/aoconfigpanel.cpp | 4 ++++ src/emotes.cpp | 3 +++ 6 files changed, 43 insertions(+), 1 deletion(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 0fbb287f3..46fce7240 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -40,6 +40,7 @@ class AOConfig : public QObject bool is_manual_timeofday_selection_enabled() const; bool always_pre_enabled() const; int chat_tick_interval() const; + bool emote_preview_enabled() const; int log_max_lines() const; bool log_display_timestamp_enabled() const; bool log_display_self_highlight_enabled() const; @@ -89,6 +90,7 @@ public slots: void set_manual_timeofday_selection_enabled(bool p_enabled); void set_always_pre(bool p_enabled); void set_chat_tick_interval(int p_number); + void set_emote_preview(bool p_enabled); void set_log_max_lines(int p_number); void set_log_display_timestamp(bool p_enabled); void set_log_display_self_highlight(bool p_enabled); @@ -138,6 +140,7 @@ public slots: void character_ini_changed(QString base_character); void always_pre_changed(bool); void chat_tick_interval_changed(int); + void emote_preview_changed(bool); // log void log_max_lines_changed(int); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index cc0915f93..6615ad1ab 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -92,6 +92,8 @@ private slots: QLineEdit *ui_showname = nullptr; QCheckBox *ui_always_pre = nullptr; QSpinBox *ui_chat_tick_interval = nullptr; + QCheckBox *ui_emote_preview = nullptr; + // IC Chatlog QSpinBox *ui_log_max_lines = nullptr; QCheckBox *ui_log_display_timestamp = nullptr; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index ad5b580c9..cd03f5eaf 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -409,7 +409,7 @@ - + Qt::Vertical @@ -422,6 +422,20 @@ + + + + + + + + + + + Emote preview: + + + diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index f2ba4e692..2fb58a8af 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -64,6 +64,7 @@ private slots: QMap ini_map; bool always_pre; int chat_tick_interval; + bool emote_preview; int log_max_lines; bool log_display_timestamp; bool log_display_self_highlight; @@ -132,6 +133,7 @@ void AOConfigPrivate::read_file() manual_timeofday_selection = cfg.value("manual_timeofday", false).toBool(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); + emote_preview = cfg.value("emote_preview", true).toBool(); log_max_lines = cfg.value("chatlog_limit", 100).toInt(); log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); log_display_timestamp = cfg.value("chatlog_display_timestamp", true).toBool(); @@ -203,6 +205,7 @@ void AOConfigPrivate::save_file() cfg.setValue("manual_timeofday", manual_timeofday_selection); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); + cfg.setValue("emote_preview", emote_preview); cfg.setValue("chatlog_limit", log_max_lines); cfg.setValue("chatlog_display_timestamp", log_display_timestamp); cfg.setValue("chatlog_display_self_highlight", log_display_self_highlight); @@ -438,6 +441,11 @@ int AOConfig::chat_tick_interval() const return d->chat_tick_interval; } +bool AOConfig::emote_preview_enabled() const +{ + return d->emote_preview; +} + int AOConfig::log_max_lines() const { return d->log_max_lines; @@ -712,6 +720,14 @@ void AOConfig::set_chat_tick_interval(int p_number) d->invoke_signal("chat_tick_interval_changed", Q_ARG(int, p_number)); } +void AOConfig::set_emote_preview(bool p_enabled) +{ + if (d->emote_preview == p_enabled) + return; + d->emote_preview = p_enabled; + d->invoke_signal("emote_preview_changed", Q_ARG(bool, p_enabled)); +} + void AOConfig::set_log_max_lines(int p_number) { if (d->log_max_lines == p_number) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index e3fc78a5b..6d65e067f 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -60,6 +60,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_showname = AO_GUI_WIDGET(QLineEdit, "showname"); ui_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); ui_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); + ui_emote_preview = AO_GUI_WIDGET(QCheckBox, "emote_preview"); // IC Chatlog ui_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); @@ -124,6 +125,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) SLOT(on_showname_placeholder_changed(QString))); connect(m_config, SIGNAL(always_pre_changed(bool)), ui_always_pre, SLOT(setChecked(bool))); connect(m_config, SIGNAL(chat_tick_interval_changed(int)), ui_chat_tick_interval, SLOT(setValue(int))); + connect(m_config, SIGNAL(emote_preview_changed(bool)), ui_emote_preview, SLOT(setChecked(bool))); // log connect(m_config, SIGNAL(log_max_lines_changed(int)), ui_log_max_lines, SLOT(setValue(int))); @@ -187,6 +189,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(ui_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); connect(ui_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); connect(ui_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); + connect(ui_emote_preview, SIGNAL(toggled(bool)), m_config, SLOT(set_emote_preview(bool))); // out, log connect(ui_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); @@ -234,6 +237,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) on_showname_placeholder_changed(m_config->showname_placeholder()); ui_always_pre->setChecked(m_config->always_pre_enabled()); ui_chat_tick_interval->setValue(m_config->chat_tick_interval()); + ui_emote_preview->setChecked(m_config->emote_preview_enabled()); // log ui_log_max_lines->setValue(m_config->log_max_lines()); diff --git a/src/emotes.cpp b/src/emotes.cpp index 20dd3113c..a1166ff2a 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -202,6 +202,9 @@ void Courtroom::on_emote_clicked(int p_id) void Courtroom::show_emote_tooltip(int p_id, QPoint p_global_pos) { + if (!ao_config->emote_preview_enabled()) + return; + if (m_emote_preview_id != -1 || m_emote_preview_id == p_id) return; m_emote_preview_id = p_id; From bbc01f1af352547133894eb7157e5a35736e8449 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 25 Sep 2021 06:45:39 +0200 Subject: [PATCH 538/842] Current emote preview now clears itself if disabled --- include/courtroom.h | 1 + src/aoemotebutton.cpp | 2 +- src/courtroom_widgets.cpp | 1 + src/emotes.cpp | 6 ++++++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/courtroom.h b/include/courtroom.h index 40477b343..4f59788df 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -585,6 +585,7 @@ private slots: void on_emote_clicked(int id); void show_emote_tooltip(int id, QPoint global_pos); void hide_emote_tooltip(int id); + void on_emote_preview_toggled(bool); void on_emote_left_clicked(); void on_emote_right_clicked(); diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index 72773383a..2d21c99c5 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -87,7 +87,7 @@ bool AOEmoteButton::event(QEvent *event) Q_EMIT tooltip_requested(m_index, dynamic_cast(event)->globalPos()); break; - case QEvent::HoverLeave: + case QEvent::Leave: Q_EMIT mouse_left(m_index); break; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 0ec297941..aaeb7aba1 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -258,6 +258,7 @@ void Courtroom::connect_widgets() connect(m_flash_timer, SIGNAL(timeout()), this, SLOT(realization_done())); + connect(ao_config, SIGNAL(emote_preview_changed(bool)), this, SLOT(on_emote_preview_toggled(bool))); connect(ui_emote_left, SIGNAL(clicked()), this, SLOT(on_emote_left_clicked())); connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); diff --git a/src/emotes.cpp b/src/emotes.cpp index a1166ff2a..cda532841 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -245,6 +245,12 @@ void Courtroom::hide_emote_tooltip(int p_id) ui_emote_preview_character->stop(); } +void Courtroom::on_emote_preview_toggled(bool p_enabled) +{ + if (!p_enabled) + hide_emote_tooltip(m_emote_preview_id); +} + void Courtroom::on_emote_left_clicked() { --m_current_emote_page; From 97527dfe92ae4ecc13e457b624ab122386e70f58 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 9 Oct 2021 08:03:06 -0400 Subject: [PATCH 539/842] Add basic music looping --- include/draudiostream.h | 10 ++++- src/draudiostream.cpp | 83 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/include/draudiostream.h b/include/draudiostream.h index 942236a42..6d0ccfeb4 100644 --- a/include/draudiostream.h +++ b/include/draudiostream.h @@ -39,13 +39,15 @@ class DRAudioStream : public QObject // state bool is_playing() const; + QWORD loop_start(); + QWORD loop_end(); + public slots: std::optional set_file(QString m_file); void set_volume(float p_volume); void play(); void stop(); - signals: void file_changed(QString p_file); void finished(); @@ -61,8 +63,12 @@ public slots: // static method DRAudio::Family m_family; std::optional m_file; + QWORD m_loop_start; + QWORD m_loop_end; float m_volume; + int m_loop_sync; + // bass std::optional m_hstream; QStack m_hsync_stack; @@ -71,6 +77,8 @@ public slots: void cache_position(); void update_device(); + void setup_looping(); + private slots: void on_device_error(); diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index a89323e99..3f08d36d2 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -58,6 +58,7 @@ void DRAudioStream::play() .what(); Q_EMIT finished(); } + setup_looping(); } void DRAudioStream::stop() @@ -68,6 +69,88 @@ void DRAudioStream::stop() Q_EMIT finished(); } +QWORD DRAudioStream::loop_start() +{ + return m_loop_start; +} + +QWORD DRAudioStream::loop_end() +{ + return m_loop_end; +} + +// the sync callback +static void CALLBACK loop_sync(unsigned long syncHandle, unsigned long channel, unsigned long data, void* user) +{ + Q_UNUSED(syncHandle); + Q_UNUSED(data); + + // move the position to the loopStart + DRAudioStream *stream = static_cast(user); + QWORD loop_start = stream->loop_start(); + BASS_ChannelSetPosition(channel, loop_start, BASS_POS_BYTE); +} + +void DRAudioStream::setup_looping() +{ + // Remove all previously set loop information + m_loop_start = 0; + m_loop_end = 0; + + if (m_loop_sync > 0) + { + BASS_ChannelRemoveSync(m_hstream.value(), m_loop_sync); + m_loop_sync = 0; + } + + // Right now we only support files that satisfy the following: + // 1. They are OGG files + // 2. They indicate a positive sample rate + // 3. They have, as OGG comments, positive values of LoopStart and LoopEnd (denoted as an int in samples) + // 4. LoopStart <= LoopEnd + + if (!m_file->endsWith("ogg", Qt::CaseInsensitive)) + return; + + float sample_rate = 0; + if (!BASS_ChannelGetAttribute(m_hstream.value(), BASS_ATTRIB_FREQ, &sample_rate)) + return; + if (sample_rate == 0) + return; + // Now sample_rate holds the sample rate in Hertz + + const char* ogg_value = BASS_ChannelGetTags(m_hstream.value(), BASS_TAG_OGG); + QStringList ogg_comments; + while (*ogg_value) + { + ogg_comments.push_back(QString(ogg_value)); + ogg_value += ogg_comments.back().size() + 1; + } + + float loop_start = 0; + float loop_end = 0; + for (const QString &ogg_comment : ogg_comments) + { + QStringList split = ogg_comment.split('='); + if (split.size() == 0) + continue; + if (split.at(0) == "LoopStart") + loop_start = split.at(1).toFloat(); + else if (split.at(0) == "LoopEnd") + loop_end = split.at(1).toFloat(); + } + if (loop_start > loop_end || (loop_start == 0 && loop_end == 0)) + return; + + // If we are at this point, we are successful in fetching all required values + + m_loop_start = BASS_ChannelSeconds2Bytes(m_hstream.value(), loop_start / sample_rate); + m_loop_end = BASS_ChannelSeconds2Bytes(m_hstream.value(), loop_end / sample_rate); + + m_loop_sync = BASS_ChannelSetSync(m_hstream.value(), BASS_SYNC_POS | BASS_SYNC_MIXTIME, + m_loop_end, loop_sync, this); +} + std::optional DRAudioStream::set_file(QString p_file) { if (m_file.has_value()) From 7d4ae6296c44c39da5af77312e8f573e980c7838 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 9 Oct 2021 17:58:25 +0200 Subject: [PATCH 540/842] Fix Linux and MacOS compilation failure --- src/draudiostream.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index 3f08d36d2..725c5c2c8 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -80,13 +80,13 @@ QWORD DRAudioStream::loop_end() } // the sync callback -static void CALLBACK loop_sync(unsigned long syncHandle, unsigned long channel, unsigned long data, void* user) +static void CALLBACK loop_sync(HSYNC syncHandle, DWORD channel, DWORD data, void *user) { Q_UNUSED(syncHandle); Q_UNUSED(data); // move the position to the loopStart - DRAudioStream *stream = static_cast(user); + DRAudioStream *stream = static_cast(user); QWORD loop_start = stream->loop_start(); BASS_ChannelSetPosition(channel, loop_start, BASS_POS_BYTE); } @@ -119,7 +119,7 @@ void DRAudioStream::setup_looping() return; // Now sample_rate holds the sample rate in Hertz - const char* ogg_value = BASS_ChannelGetTags(m_hstream.value(), BASS_TAG_OGG); + const char *ogg_value = BASS_ChannelGetTags(m_hstream.value(), BASS_TAG_OGG); QStringList ogg_comments; while (*ogg_value) { @@ -147,8 +147,7 @@ void DRAudioStream::setup_looping() m_loop_start = BASS_ChannelSeconds2Bytes(m_hstream.value(), loop_start / sample_rate); m_loop_end = BASS_ChannelSeconds2Bytes(m_hstream.value(), loop_end / sample_rate); - m_loop_sync = BASS_ChannelSetSync(m_hstream.value(), BASS_SYNC_POS | BASS_SYNC_MIXTIME, - m_loop_end, loop_sync, this); + m_loop_sync = BASS_ChannelSetSync(m_hstream.value(), BASS_SYNC_POS | BASS_SYNC_MIXTIME, m_loop_end, &loop_sync, this); } std::optional DRAudioStream::set_file(QString p_file) From 94513dc086a0f1731263fbebc8677ebb9b728aa6 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 11 Oct 2021 11:27:24 -0400 Subject: [PATCH 541/842] Move documentation to header --- include/draudiostream.h | 9 +++++++++ src/draudiostream.cpp | 9 ++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/include/draudiostream.h b/include/draudiostream.h index 6d0ccfeb4..a7569b1a2 100644 --- a/include/draudiostream.h +++ b/include/draudiostream.h @@ -77,6 +77,15 @@ public slots: void cache_position(); void update_device(); + /** + * @brief Sets up looping. + * + * Right now we only support files that satisfy the following: + * 1. They are OGG files + * 2. They indicate a positive sample rate + * 3. They have, as OGG comments, positive values of LoopStart and LoopEnd (denoted as an int in samples) + * 4. LoopStart <= LoopEnd + */ void setup_looping(); private slots: diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index 725c5c2c8..118e43438 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -103,12 +103,7 @@ void DRAudioStream::setup_looping() m_loop_sync = 0; } - // Right now we only support files that satisfy the following: - // 1. They are OGG files - // 2. They indicate a positive sample rate - // 3. They have, as OGG comments, positive values of LoopStart and LoopEnd (denoted as an int in samples) - // 4. LoopStart <= LoopEnd - + // Now decide whether the track we are playing now is loopable. if (!m_file->endsWith("ogg", Qt::CaseInsensitive)) return; @@ -132,7 +127,7 @@ void DRAudioStream::setup_looping() for (const QString &ogg_comment : ogg_comments) { QStringList split = ogg_comment.split('='); - if (split.size() == 0) + if (split.size() != 2) continue; if (split.at(0) == "LoopStart") loop_start = split.at(1).toFloat(); From 44efbedaec4d32dd980eb5e013b79365cf742077 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 11 Oct 2021 11:52:26 -0400 Subject: [PATCH 542/842] Bump up minor version --- src/version.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.cpp b/src/version.cpp index 58f2d3b2b..3bed3ae63 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -14,7 +14,7 @@ int get_major_version() int get_minor_version() { - return 0; + return 1; } QString get_version_string() From 0a16082f64beb352acd247dfc982627c30c62fc9 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 11 Oct 2021 13:14:07 -0400 Subject: [PATCH 543/842] Notes are now only loaded if their associated selection is on --- include/aonotepicker.h | 4 ++++ src/aonotepicker.cpp | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/include/aonotepicker.h b/include/aonotepicker.h index 64f4ac169..5e8324bcd 100644 --- a/include/aonotepicker.h +++ b/include/aonotepicker.h @@ -24,8 +24,12 @@ class AONotePicker : public QLabel AOButton *ui_hover = nullptr; QHBoxLayout *ui_layout = nullptr; + void set_active(bool p_active); + bool is_active(); private: AOApplication *ao_app = nullptr; + + bool m_active = false; }; #endif // AONOTEPICKER_HPP diff --git a/src/aonotepicker.cpp b/src/aonotepicker.cpp index 9a95ea71d..e7f209a25 100644 --- a/src/aonotepicker.cpp +++ b/src/aonotepicker.cpp @@ -37,10 +37,12 @@ void Courtroom::on_file_selected() current_file = f_notepicker->m_file; load_note(); f_notepicker->ui_hover->set_image("note_select_selected.png"); + f_notepicker->set_active(true); } else { current_file = ""; + f_notepicker->set_active(false); } } @@ -55,9 +57,8 @@ void Courtroom::on_set_file_button_clicked() { f_notepicker->ui_line->setText(f_filename); - // If this notepicker is the currently selected slot, update the notepad as - // the file given changes. - if (f_notepicker->m_file == current_file) + // If this notepicker is the currently selected slot, update the notepad as the file given changes. + if (f_notepicker->is_active()) { current_file = f_filename; load_note(); @@ -83,3 +84,13 @@ void Courtroom::on_delete_button_clicked() delete f_notepicker; set_note_files(); } + +void AONotePicker::set_active(bool p_active) +{ + m_active = p_active; +} + +bool AONotePicker::is_active() +{ + return m_active; +} From 53e0c3829ed19e1132e1bb58f1d7968afa55fd72 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 11 Oct 2021 17:10:22 -0400 Subject: [PATCH 544/842] When showing a preanimation, always set to visible if playing it out --- src/courtroom.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index efbfe5cd8..16d43f01a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1177,13 +1177,16 @@ void Courtroom::play_preanim() QString f_preanim = m_chatmessage[CMPreAnim]; - if (f_preanim.trimmed() == "-") + if (f_preanim.trimmed() == "-" || f_preanim.trimmed() == "../../misc/blank") { // no animation, continue preanim_done(); return; } + // If the player char was previously hidden due to playing blank, manually show it again. + ui_vp_player_char->show(); + QString f_char = m_chatmessage[CMChrName]; // set state anim_state = 1; From 13c850f918e42b0cf0e9f878a51857bdbcb15e98 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 12 Oct 2021 12:40:29 +0200 Subject: [PATCH 545/842] Reworked play-animation logic to ignore missing files Resolve #221 and a non-issued bug that would prevent the chat from ever progressing --- include/aocharmovie.h | 3 ++- src/aocharmovie.cpp | 24 ++++++++++++++---------- src/courtroom.cpp | 36 ++++++++++++------------------------ 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/include/aocharmovie.h b/include/aocharmovie.h index d5552bac8..7040c70e4 100644 --- a/include/aocharmovie.h +++ b/include/aocharmovie.h @@ -15,8 +15,9 @@ class AOCharMovie : public QLabel public: AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app); + QString file_name(); + bool play(QString character, QString emote, QString prefix, bool play_once); - bool play(QString character, QString emote, bool play_once); bool play_pre(QString character, QString emote); bool play_talk(QString character, QString emote); bool play_idle(QString character, QString emote); diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 55dce1773..855336444 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -23,6 +23,11 @@ AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_ connect(m_frame_timer, SIGNAL(timeout()), this, SLOT(on_timer_timeout())); } +QString AOCharMovie::file_name() +{ + return m_movie->fileName(); +} + bool AOCharMovie::play(QString p_chr, QString p_emote, QString p_prefix, bool p_play_once) { // Asset lookup order @@ -35,10 +40,12 @@ bool AOCharMovie::play(QString p_chr, QString p_emote, QString p_prefix, bool p_ bool r_exist = true; + const bool l_is_anim = p_prefix.isEmpty(); + QStringList l_file_list; for (const QString &i_chr : ao_app->get_char_include_tree(p_chr)) { - if (!p_prefix.isEmpty()) + if (!l_is_anim) l_file_list.append(ao_app->get_character_path(i_chr, QString("%1%2").arg(p_prefix, p_emote))); l_file_list.append(ao_app->get_character_path(i_chr, p_emote)); } @@ -46,26 +53,23 @@ bool AOCharMovie::play(QString p_chr, QString p_emote, QString p_prefix, bool p_ QString l_file = ao_app->find_asset_path(l_file_list, animated_or_static_extensions()); if (l_file.isEmpty()) { - l_file = ao_app->find_theme_asset_path("placeholder", animated_extensions()); + if (!l_is_anim) + l_file = ao_app->find_theme_asset_path("placeholder", animated_extensions()); r_exist = false; } stop(); - m_movie->setFileName(l_file); is_play_once = p_play_once; - m_movie->start(); + m_movie->setFileName(l_file); + if (m_movie->isValid()) + m_movie->start(); return r_exist; } -bool AOCharMovie::play(QString p_chr, QString p_emote, bool p_play_once) -{ - return play(p_chr, p_emote, nullptr, p_play_once); -} - bool AOCharMovie::play_pre(QString p_chr, QString p_emote) { - return play(p_chr, p_emote, true); + return play(p_chr, p_emote, nullptr, true); } bool AOCharMovie::play_talk(QString p_chr, QString p_emote) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 16d43f01a..a9bce542b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1175,40 +1175,28 @@ void Courtroom::play_preanim() int sfx_delay = m_chatmessage[CMSoundDelay].toInt() * 60; m_sound_timer->start(sfx_delay); - QString f_preanim = m_chatmessage[CMPreAnim]; + // set state + anim_state = 1; + ui_vp_player_char->show(); - if (f_preanim.trimmed() == "-" || f_preanim.trimmed() == "../../misc/blank") + if (m_msg_is_first_person) { - // no animation, continue preanim_done(); return; } - // If the player char was previously hidden due to playing blank, manually show it again. - ui_vp_player_char->show(); + const QString l_chr_name = m_chatmessage[CMChrName]; + const QString l_anim_name = m_chatmessage[CMPreAnim]; - QString f_char = m_chatmessage[CMChrName]; - // set state - anim_state = 1; - - if (m_msg_is_first_person == false) + if (!ui_vp_player_char->play_pre(l_chr_name, l_anim_name)) { - QString f_anim_path = ao_app->get_character_path(f_char, f_preanim); - if (ui_vp_player_char->play_pre(f_char, f_preanim)) - { - qDebug() << "Playing" << f_anim_path; - - // finished - return; - } - else - { - qDebug() << "could not find " + f_anim_path; - } + qDebug() << "Unable to play animation: missing file; character:" << l_chr_name << ", animation:" << l_anim_name; + preanim_done(); + return; } - // no animation, continue - preanim_done(); + qDebug() << "Playing character animation; character:" << l_chr_name << ", animation: " << l_anim_name + << ", file:" << ui_vp_player_char->file_name(); } void Courtroom::preanim_done() From fc7d945ffb3d329b655951d9b3c9891607dd09c3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 12 Oct 2021 15:13:11 +0200 Subject: [PATCH 546/842] Fix general client stutter when OOC messages are rendered Resolve #222 --- src/drchatlog.cpp | 76 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/src/drchatlog.cpp b/src/drchatlog.cpp index 9947b6663..381552ce9 100644 --- a/src/drchatlog.cpp +++ b/src/drchatlog.cpp @@ -2,7 +2,9 @@ #include "aoconfig.h" -#include +#include +#include +#include #include DRChatLog::DRChatLog(QWidget *parent) : QTextBrowser(parent), dr_config(new AOConfig(this)) @@ -36,26 +38,84 @@ void DRChatLog::_p_write_message_queue() QScrollBar *l_scrollbar = verticalScrollBar(); const bool l_is_end_scroll_pos = l_scrollbar->value() == l_scrollbar->maximum(); + QTextCharFormat l_normal_format; + l_normal_format.setFont(font()); + l_normal_format.setFontWeight(QFont::Normal); + QTextCharFormat l_name_format = l_normal_format; + l_name_format.setFontWeight(QFont::Bold); + QTextCharFormat l_href_format = l_normal_format; + l_href_format.setAnchor(true); + l_href_format.setFontWeight(QFont::Bold); + l_href_format.setForeground(Qt::blue); + l_href_format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + QTextCursor l_cursor = textCursor(); l_cursor.movePosition(QTextCursor::End); + l_cursor.setCharFormat(l_normal_format); while (!m_message_queue.isEmpty()) { if (!m_message_list.isEmpty()) - l_cursor.insertHtml(QString("
    ")); + l_cursor.insertText(QString(QChar::LineFeed)); const Message l_message = m_message_queue.dequeue(); m_message_list.append(l_message); l_cursor.insertText(QString(QString("[%1] ").arg(l_message.timestamp.toString("hh:mm")))); if (!l_message.name.isEmpty()) - l_cursor.insertHtml(QString("%1: ").arg(l_message.name.toHtmlEscaped())); + { + l_cursor.insertText(l_message.name, l_name_format); + l_cursor.insertText(": "); + } + + const QString l_text = l_message.text.toHtmlEscaped(); + + class TextPiece + { + public: + QString text; + bool is_href = false; + + TextPiece() + {} + TextPiece(QString p_text, bool p_is_href = false) : text(p_text), is_href(p_is_href) + {} + }; + QVector l_piece_list; + + const QRegularExpression l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); + QRegularExpressionMatchIterator l_iterator = l_regex.globalMatch(l_text); + { // capture all text pieces + int l_index = 0; + + while (l_iterator.hasNext()) + { + QRegularExpressionMatch l_match = l_iterator.next(); + + const int l_captureIndex = l_match.capturedStart(); + if (l_index < l_captureIndex) + l_piece_list.append(l_text.mid(l_index, l_captureIndex - l_index)); + l_piece_list.append(TextPiece(l_match.captured(), true)); + l_index = l_match.capturedEnd(); + } + + l_piece_list.append(l_text.mid(l_index)); + } + + for (const TextPiece &i_piece : qAsConst(l_piece_list)) + { + if (i_piece.text.isEmpty()) + continue; + + QTextCharFormat l_piece_format = l_normal_format; + if (i_piece.is_href) + { + l_piece_format = l_href_format; + l_piece_format.setAnchorHref(i_piece.text); + } - QString l_text = l_message.text.toHtmlEscaped(); - const QRegExp l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); - if (l_text.contains(l_regex)) - l_text.replace(l_regex, "\\1"); - l_cursor.insertHtml(l_text.replace("\n", "
    ")); + l_cursor.insertText(i_piece.text, l_piece_format); + } } if (l_is_end_scroll_pos) From 3d5a5e1a409c051ee450592e14c71d103dab9977 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 12 Oct 2021 15:17:05 +0200 Subject: [PATCH 547/842] Added extra debug information --- src/courtroom.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a9bce542b..4cd33fd19 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1190,7 +1190,8 @@ void Courtroom::play_preanim() if (!ui_vp_player_char->play_pre(l_chr_name, l_anim_name)) { - qDebug() << "Unable to play animation: missing file; character:" << l_chr_name << ", animation:" << l_anim_name; + qDebug() << "Unable to play animation: missing or invalid file; character:" << l_chr_name + << ", animation:" << l_anim_name; preanim_done(); return; } From c83a8fd641d207d667d5349fc827e07bab695232 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 12 Oct 2021 15:33:40 +0200 Subject: [PATCH 548/842] Guarantee text format remains uniform --- src/drchatlog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/drchatlog.cpp b/src/drchatlog.cpp index 381552ce9..eb0dd6c66 100644 --- a/src/drchatlog.cpp +++ b/src/drchatlog.cpp @@ -60,12 +60,12 @@ void DRChatLog::_p_write_message_queue() const Message l_message = m_message_queue.dequeue(); m_message_list.append(l_message); - l_cursor.insertText(QString(QString("[%1] ").arg(l_message.timestamp.toString("hh:mm")))); + l_cursor.insertText(QString(QString("[%1] ").arg(l_message.timestamp.toString("hh:mm"))), l_normal_format); if (!l_message.name.isEmpty()) { l_cursor.insertText(l_message.name, l_name_format); - l_cursor.insertText(": "); + l_cursor.insertText(": ", l_normal_format); } const QString l_text = l_message.text.toHtmlEscaped(); From 6dc97fe8f7cafc40955dae2c1135ca089dbdd2d1 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 12 Oct 2021 15:38:32 +0200 Subject: [PATCH 549/842] Small formatting change --- src/courtroom.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 4cd33fd19..97167a821 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1191,13 +1191,13 @@ void Courtroom::play_preanim() if (!ui_vp_player_char->play_pre(l_chr_name, l_anim_name)) { qDebug() << "Unable to play animation: missing or invalid file; character:" << l_chr_name - << ", animation:" << l_anim_name; + << "animation:" << l_anim_name; preanim_done(); return; } - qDebug() << "Playing character animation; character:" << l_chr_name << ", animation: " << l_anim_name - << ", file:" << ui_vp_player_char->file_name(); + qDebug() << "Playing character animation; character:" << l_chr_name << "animation: " << l_anim_name + << "file:" << ui_vp_player_char->file_name(); } void Courtroom::preanim_done() From a70485de8c45b3a20e842eea8a123291ae489bc2 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 12 Oct 2021 17:43:09 +0200 Subject: [PATCH 550/842] Float/double precision equality check changes --- include/draudiostream.h | 4 ++-- src/draudiostream.cpp | 28 +++++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/include/draudiostream.h b/include/draudiostream.h index a7569b1a2..f3a5355e3 100644 --- a/include/draudiostream.h +++ b/include/draudiostream.h @@ -63,8 +63,8 @@ public slots: // static method DRAudio::Family m_family; std::optional m_file; - QWORD m_loop_start; - QWORD m_loop_end; + QWORD m_loop_start = {}; + QWORD m_loop_end = {}; float m_volume; int m_loop_sync; diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index 118e43438..82c74576a 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -7,6 +7,7 @@ #include #include +#include DRAudioStream::DRAudioStream(DRAudio::Family p_family) : m_family(p_family) { @@ -94,8 +95,8 @@ static void CALLBACK loop_sync(HSYNC syncHandle, DWORD channel, DWORD data, void void DRAudioStream::setup_looping() { // Remove all previously set loop information - m_loop_start = 0; - m_loop_end = 0; + m_loop_start = {}; + m_loop_end = {}; if (m_loop_sync > 0) { @@ -107,12 +108,13 @@ void DRAudioStream::setup_looping() if (!m_file->endsWith("ogg", Qt::CaseInsensitive)) return; - float sample_rate = 0; - if (!BASS_ChannelGetAttribute(m_hstream.value(), BASS_ATTRIB_FREQ, &sample_rate)) + double l_sample_rate = 0.0; + if (float l_float_sample_rate = 0.0f; + BASS_ChannelGetAttribute(m_hstream.value(), BASS_ATTRIB_FREQ, &l_float_sample_rate)) + l_sample_rate = double(l_float_sample_rate); + if (qFabs(l_sample_rate) == 0.0) return; - if (sample_rate == 0) - return; - // Now sample_rate holds the sample rate in Hertz + // Now sample_rate holds the sample rate in hertz const char *ogg_value = BASS_ChannelGetTags(m_hstream.value(), BASS_TAG_OGG); QStringList ogg_comments; @@ -122,25 +124,25 @@ void DRAudioStream::setup_looping() ogg_value += ogg_comments.back().size() + 1; } - float loop_start = 0; - float loop_end = 0; + double loop_start = 0; + double loop_end = 0; for (const QString &ogg_comment : ogg_comments) { QStringList split = ogg_comment.split('='); if (split.size() != 2) continue; if (split.at(0) == "LoopStart") - loop_start = split.at(1).toFloat(); + loop_start = split.at(1).toDouble(); else if (split.at(0) == "LoopEnd") - loop_end = split.at(1).toFloat(); + loop_end = split.at(1).toDouble(); } if (loop_start > loop_end || (loop_start == 0 && loop_end == 0)) return; // If we are at this point, we are successful in fetching all required values - m_loop_start = BASS_ChannelSeconds2Bytes(m_hstream.value(), loop_start / sample_rate); - m_loop_end = BASS_ChannelSeconds2Bytes(m_hstream.value(), loop_end / sample_rate); + m_loop_start = BASS_ChannelSeconds2Bytes(m_hstream.value(), loop_start / l_sample_rate); + m_loop_end = BASS_ChannelSeconds2Bytes(m_hstream.value(), loop_end / l_sample_rate); m_loop_sync = BASS_ChannelSetSync(m_hstream.value(), BASS_SYNC_POS | BASS_SYNC_MIXTIME, m_loop_end, &loop_sync, this); } From de89ed0a03e68355e974f65fd93bddbeffdecdf7 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 12 Oct 2021 23:43:32 +0200 Subject: [PATCH 551/842] Removed html escape from chatlog --- src/drchatlog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drchatlog.cpp b/src/drchatlog.cpp index eb0dd6c66..d6b00b6b0 100644 --- a/src/drchatlog.cpp +++ b/src/drchatlog.cpp @@ -68,7 +68,7 @@ void DRChatLog::_p_write_message_queue() l_cursor.insertText(": ", l_normal_format); } - const QString l_text = l_message.text.toHtmlEscaped(); + const QString l_text = l_message.text; class TextPiece { From 09d95a3099581903e017a86634b7b12f098a945d Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 13 Oct 2021 17:41:48 -0400 Subject: [PATCH 552/842] Add About tab to config panel --- include/aoconfigpanel.h | 3 +++ include/version.h | 1 + res/ui/config_panel.ui | 26 +++++++++++++++++++++++++- src/aoapplication.cpp | 1 + src/aoconfigpanel.cpp | 6 ++++++ src/courtroom_widgets.cpp | 3 +-- src/lobby.cpp | 23 +---------------------- src/version.cpp | 28 ++++++++++++++++++++++++++++ 8 files changed, 66 insertions(+), 25 deletions(-) diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 6615ad1ab..032b418a7 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -124,6 +124,9 @@ private slots: QSpinBox *ui_blip_rate = nullptr; QCheckBox *ui_blank_blips = nullptr; + // about + QLabel *ui_about = nullptr; + private slots: void username_editing_finished(); void showname_editing_finished(); diff --git a/include/version.h b/include/version.h index 7da7f84b9..5607d241a 100644 --- a/include/version.h +++ b/include/version.h @@ -6,3 +6,4 @@ int get_release_version(); int get_major_version(); int get_minor_version(); QString get_version_string(); +QString get_about_message(); diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index cd03f5eaf..ca669f52f 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 1 + 4 @@ -973,6 +973,30 @@
    + + + About + + + + + 80 + 20 + 351 + 271 + + + + This is where the about text appears. If you see this message, something's gone wrong. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + +
    diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 5307e5453..1110fe873 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -8,6 +8,7 @@ #include "drpacket.h" #include "drserversocket.h" #include "lobby.h" +#include "version.h" #include #include diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 6d65e067f..cca089ef1 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -5,6 +5,7 @@ #include "aoguiloader.h" #include "datatypes.h" #include "drpather.h" +#include "version.h" #include #include @@ -93,6 +94,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_blip_rate = AO_GUI_WIDGET(QSpinBox, "blip_rate"); ui_blank_blips = AO_GUI_WIDGET(QCheckBox, "blank_blips"); + // about + ui_about = AO_GUI_WIDGET(QLabel, "about_label"); + // themes refresh_theme_list(); refresh_gamemode_list(); @@ -278,6 +282,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) on_manual_gamemode_selection_changed(m_config->is_manual_gamemode_selection_enabled()); on_manual_timeofday_selection_changed(m_config->is_manual_timeofday_selection_enabled()); + + ui_about->setText(get_about_message()); } void AOConfigPanel::showEvent(QShowEvent *event) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index aaeb7aba1..6eae443b2 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1175,8 +1175,7 @@ void Courtroom::delete_widget(QWidget *p_widget) // remove the widget from recorded names widget_names.remove(p_widget->objectName()); - // transfer the children to our grandparent since our parent is - // about to commit suicide + // transfer the children to our grandparent since our parent is about to be deleted QWidget *grand_parent = p_widget->parentWidget(); // if we don't have a grand parent, attach ourselves to courtroom if (!grand_parent) diff --git a/src/lobby.cpp b/src/lobby.cpp index a5df9c597..d8538e1b0 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -15,7 +15,6 @@ #include #include -#include #include #include #include @@ -309,27 +308,7 @@ void Lobby::on_connect_released() void Lobby::on_about_clicked() { - const bool hasApng = QImageReader::supportedImageFormats().contains("apng"); - - QString msg = tr("

    Danganronpa Online

    " - "version: %1" - "

    Source code: " - "" - "https://github.com/Chrezm/DRO-Client" - "

    Development:
    " - "Cerapter, Elf, Iuvee, Tricky Leifa, Ziella" - "

    Based on Attorney Online 2:
    " - "" - "https://github.com/AttorneyOnline/AO2-Client" - "

    Running on Qt version %2 with the BASS audio engine.
    " - "APNG plugin loaded: %3" - "

    Built on %4") - .arg(get_version_string()) - .arg(QLatin1String(QT_VERSION_STR)) - .arg(hasApng ? tr("Yes") : tr("No")) - .arg(QLatin1String(__DATE__)); - - QMessageBox::about(this, tr("About"), msg); + QMessageBox::about(this, tr("About"), get_about_message()); } void Lobby::on_server_list_clicked(QModelIndex p_model) diff --git a/src/version.cpp b/src/version.cpp index 3bed3ae63..57e72aef2 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -1,5 +1,6 @@ #include "version.h" +#include #include int get_release_version() @@ -22,3 +23,30 @@ QString get_version_string() return QString::number(get_release_version()) + "." + QString::number(get_major_version()) + "." + QString::number(get_minor_version()); } + +QString get_about_message() +{ + const bool hasApng = QImageReader::supportedImageFormats().contains("apng"); + + QString msg = QString( + "

    Danganronpa Online

    " + "version: %1" + "

    Source code: " + "" + "https://github.com/Chrezm/DRO-Client" + "

    Development:
    " + "Cerapter, Elf, Iuvee, Tricky Leifa, Ziella" + "

    Based on Attorney Online 2:
    " + "" + "https://github.com/AttorneyOnline/AO2-Client" + "

    Running on Qt version %2 with the BASS audio engine.
    " + "APNG plugin loaded: %3" + "

    Built on %4" + ) + .arg(get_version_string()) + .arg(QLatin1String(QT_VERSION_STR)) + .arg(hasApng ? "Yes" : "No") + .arg(QLatin1String(__DATE__)); + + return msg; +} From ba75bc26f3403a79651df6f86b94203ee32993bb Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 13 Oct 2021 20:10:18 -0400 Subject: [PATCH 553/842] Add resource files for current branch and current hash (default empty) --- res.qrc | 2 ++ res/git/git_branch.txt | 0 res/git/git_hash.txt | 0 src/version.cpp | 28 +++++++++++++++++++++++++++- 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 res/git/git_branch.txt create mode 100644 res/git/git_hash.txt diff --git a/res.qrc b/res.qrc index 7a3f29c47..f0ebec7b1 100644 --- a/res.qrc +++ b/res.qrc @@ -2,5 +2,7 @@ res/fonts/Ace-Attorney.ttf res/ui/config_panel.ui + res/git/git_branch.txt + res/git/git_hash.txt diff --git a/res/git/git_branch.txt b/res/git/git_branch.txt new file mode 100644 index 000000000..e69de29bb diff --git a/res/git/git_hash.txt b/res/git/git_hash.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/version.cpp b/src/version.cpp index 57e72aef2..d198ffc18 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -1,5 +1,6 @@ #include "version.h" +#include #include #include @@ -24,9 +25,24 @@ QString get_version_string() QString::number(get_minor_version()); } +QString get_resource_file_text(QString filename) +{ + QString data; + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) { + return ""; + } + data = file.readAll(); + file.close(); + return data; +} + QString get_about_message() { const bool hasApng = QImageReader::supportedImageFormats().contains("apng"); + const QString git_branch = get_resource_file_text(":/res/git/git_branch.txt"); + const QString git_hash = get_resource_file_text(":/res/git/git_hash.txt"); QString msg = QString( "

    Danganronpa Online

    " @@ -41,12 +57,22 @@ QString get_about_message() "https://github.com/AttorneyOnline/AO2-Client" "

    Running on Qt version %2 with the BASS audio engine.
    " "APNG plugin loaded: %3" - "

    Built on %4" + "

    Built on %4
    " ) .arg(get_version_string()) .arg(QLatin1String(QT_VERSION_STR)) .arg(hasApng ? "Yes" : "No") .arg(QLatin1String(__DATE__)); + if (git_branch.isEmpty()) + msg += QString("No git branch information available.
    "); + else + msg += QString("Built from git branch %1
    ").arg(git_branch); + + if (git_hash.isEmpty()) + msg += QString("No git hash information available."); + else + msg += QString("Hash of the latest commit: %1").arg(git_hash); + return msg; } From 4ef414e710cf319ceadbea4a2204ec80eb423cd9 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Wed, 13 Oct 2021 20:21:19 -0400 Subject: [PATCH 554/842] Automatically tag generated artifacts --- .github/workflows/build-all.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 7aefaff63..77e285cd8 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -93,6 +93,15 @@ jobs: export PATH=/d/a/DRO-Client/Qt/Tools/mingw730_32/bin:$PATH rm -r /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ cp -r /d/a/DRO-Client/Qt/Tools/mingw730_32/ /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ + + - name: Setup branch and commit hash tags + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + git_hash=$(git rev-parse --short "$GITHUB_SHA") + git_branch=${branch#refs/heads/} + echo "$git_hash" >> ./DRO-Client/res/git/git_hash.txt + echo "$git_branch" >> ./DRO-Client/res/git/git_branch.txt - name: Run qmake shell: bash @@ -197,6 +206,15 @@ jobs: uses: jurplel/install-qt-action@v2 with: version: '5.14.2' + + - name: Setup branch and commit hash tags + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + git_hash=$(git rev-parse --short "$GITHUB_SHA") + git_branch=${branch#refs/heads/} + echo "$git_hash" >> ./DRO-Client/res/git/git_hash.txt + echo "$git_branch" >> ./DRO-Client/res/git/git_branch.txt - name: Run qmake shell: bash @@ -319,6 +337,15 @@ jobs: cp plugins/imageformats/libqapng.so .. cd .. rm -r -f QtApng + + - name: Setup branch and commit hash tags + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + git_hash=$(git rev-parse --short "$GITHUB_SHA") + git_branch=${branch#refs/heads/} + echo "$git_hash" >> ./DRO-Client/res/git/git_hash.txt + echo "$git_branch" >> ./DRO-Client/res/git/git_branch.txt - name: Run qmake shell: bash From ec866780c664f7cd248dbbf492a88cd45319edf2 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Wed, 13 Oct 2021 20:26:20 -0400 Subject: [PATCH 555/842] Make sure the tagging process is run from the correct directory --- .github/workflows/build-all.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 77e285cd8..7094d0da6 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -96,12 +96,12 @@ jobs: - name: Setup branch and commit hash tags shell: bash - working-directory: ${{env.parentworkspace}} + working-directory: ${{github.workspace}} run: | git_hash=$(git rev-parse --short "$GITHUB_SHA") git_branch=${branch#refs/heads/} - echo "$git_hash" >> ./DRO-Client/res/git/git_hash.txt - echo "$git_branch" >> ./DRO-Client/res/git/git_branch.txt + echo "$git_hash" >> ./res/git/git_hash.txt + echo "$git_branch" >> ./res/git/git_branch.txt - name: Run qmake shell: bash @@ -209,12 +209,12 @@ jobs: - name: Setup branch and commit hash tags shell: bash - working-directory: ${{env.parentworkspace}} + working-directory: ${{github.workspace}} run: | git_hash=$(git rev-parse --short "$GITHUB_SHA") git_branch=${branch#refs/heads/} - echo "$git_hash" >> ./DRO-Client/res/git/git_hash.txt - echo "$git_branch" >> ./DRO-Client/res/git/git_branch.txt + echo "$git_hash" >> ./res/git/git_hash.txt + echo "$git_branch" >> ./res/git/git_branch.txt - name: Run qmake shell: bash @@ -340,12 +340,12 @@ jobs: - name: Setup branch and commit hash tags shell: bash - working-directory: ${{env.parentworkspace}} + working-directory: ${{github.workspace}} run: | git_hash=$(git rev-parse --short "$GITHUB_SHA") git_branch=${branch#refs/heads/} - echo "$git_hash" >> ./DRO-Client/res/git/git_hash.txt - echo "$git_branch" >> ./DRO-Client/res/git/git_branch.txt + echo "$git_hash" >> ./res/git/git_hash.txt + echo "$git_branch" >> ./res/git/git_branch.txt - name: Run qmake shell: bash From a4b5ab9e5ead95704fd1a10780318a9242d536c4 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Wed, 13 Oct 2021 21:39:09 -0400 Subject: [PATCH 556/842] Fix branch? --- .github/workflows/build-all.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 7094d0da6..0d850f641 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -99,7 +99,7 @@ jobs: working-directory: ${{github.workspace}} run: | git_hash=$(git rev-parse --short "$GITHUB_SHA") - git_branch=${branch#refs/heads/} + git_branch=${{ github.head_ref }} echo "$git_hash" >> ./res/git/git_hash.txt echo "$git_branch" >> ./res/git/git_branch.txt @@ -212,7 +212,7 @@ jobs: working-directory: ${{github.workspace}} run: | git_hash=$(git rev-parse --short "$GITHUB_SHA") - git_branch=${branch#refs/heads/} + git_branch=${{ github.head_ref }} echo "$git_hash" >> ./res/git/git_hash.txt echo "$git_branch" >> ./res/git/git_branch.txt @@ -343,7 +343,7 @@ jobs: working-directory: ${{github.workspace}} run: | git_hash=$(git rev-parse --short "$GITHUB_SHA") - git_branch=${branch#refs/heads/} + git_branch=${{ github.head_ref }} echo "$git_hash" >> ./res/git/git_hash.txt echo "$git_branch" >> ./res/git/git_branch.txt From 3cc49a90b781002750921386d8097b4f0c541b9a Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Wed, 13 Oct 2021 22:02:15 -0400 Subject: [PATCH 557/842] Fix branch? (2) --- .github/workflows/build-all.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 0d850f641..17e71f638 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -99,7 +99,7 @@ jobs: working-directory: ${{github.workspace}} run: | git_hash=$(git rev-parse --short "$GITHUB_SHA") - git_branch=${{ github.head_ref }} + git_branch=${GITHUB_REF##*/} echo "$git_hash" >> ./res/git/git_hash.txt echo "$git_branch" >> ./res/git/git_branch.txt @@ -212,7 +212,7 @@ jobs: working-directory: ${{github.workspace}} run: | git_hash=$(git rev-parse --short "$GITHUB_SHA") - git_branch=${{ github.head_ref }} + git_branch=${GITHUB_REF##*/} echo "$git_hash" >> ./res/git/git_hash.txt echo "$git_branch" >> ./res/git/git_branch.txt @@ -343,7 +343,7 @@ jobs: working-directory: ${{github.workspace}} run: | git_hash=$(git rev-parse --short "$GITHUB_SHA") - git_branch=${{ github.head_ref }} + git_branch=${GITHUB_REF##*/} echo "$git_hash" >> ./res/git/git_hash.txt echo "$git_branch" >> ./res/git/git_branch.txt From 8b2a2fe969212f947b22d5f00a287888ba5a56a4 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Thu, 14 Oct 2021 09:29:18 -0400 Subject: [PATCH 558/842] Add time of compilation to about message --- src/version.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/version.cpp b/src/version.cpp index d198ffc18..1884ef4d5 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -57,12 +57,13 @@ QString get_about_message() "https://github.com/AttorneyOnline/AO2-Client" "

    Running on Qt version %2 with the BASS audio engine.
    " "APNG plugin loaded: %3" - "

    Built on %4
    " + "

    Built on %4 %5
    " ) .arg(get_version_string()) .arg(QLatin1String(QT_VERSION_STR)) .arg(hasApng ? "Yes" : "No") - .arg(QLatin1String(__DATE__)); + .arg(QLatin1String(__DATE__)) + .arg(QLatin1String(__TIME__)); if (git_branch.isEmpty()) msg += QString("No git branch information available.
    "); From c8076fe4164dbfe321876882bb1e9ce3b0f6e66c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 21 Oct 2021 15:50:02 +0200 Subject: [PATCH 559/842] Resolve #234 | Fix URL special character encoding --- src/courtroom_character.cpp | 2 +- src/drchatlog.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index a9ef48509..112873c7f 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -45,7 +45,7 @@ QString Courtroom::get_character_content_url() if (l_url.isRelative() || l_url.isLocalFile()) return nullptr; - return l_url.toString(); + return l_url.toString(QUrl::FullyEncoded); } namespace diff --git a/src/drchatlog.cpp b/src/drchatlog.cpp index d6b00b6b0..e66023950 100644 --- a/src/drchatlog.cpp +++ b/src/drchatlog.cpp @@ -6,6 +6,7 @@ #include #include #include +#include DRChatLog::DRChatLog(QWidget *parent) : QTextBrowser(parent), dr_config(new AOConfig(this)) { @@ -114,7 +115,7 @@ void DRChatLog::_p_write_message_queue() l_piece_format.setAnchorHref(i_piece.text); } - l_cursor.insertText(i_piece.text, l_piece_format); + l_cursor.insertText(i_piece.is_href ? QUrl(i_piece.text).toString() : i_piece.text, l_piece_format); } } From d23e120c13b1c5888468d5299ff45fc4f4b1a5f0 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 21 Oct 2021 16:01:11 +0200 Subject: [PATCH 560/842] Fix highlight color leak on new message Resolve #235 --- src/courtroom.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 97167a821..a1500106a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -741,6 +741,9 @@ void Courtroom::handle_chatmessage(QStringList p_contents) ui_vp_objection->stop(); ui_vp_evidence_display->reset(); + m_message_color_name = ""; + m_message_color_stack.clear(); + // reset effect ui_vp_effect->stop(); From 6fc97b70bc42356d2277eca33e84199ebd65435b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 21 Oct 2021 16:18:49 +0200 Subject: [PATCH 561/842] Fix OOC log to reset formatting after reloading UI --- include/drchatlog.h | 1 + src/courtroom_widgets.cpp | 2 ++ src/drchatlog.cpp | 6 +++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/drchatlog.h b/include/drchatlog.h index b699c2058..41758cdb7 100644 --- a/include/drchatlog.h +++ b/include/drchatlog.h @@ -16,6 +16,7 @@ class DRChatLog : public QTextBrowser void append_chatmessage(QString name, QString text); void append_error(QString text); + void reset_message_format(); signals: void message_queued(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index aaeb7aba1..906a4f294 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1428,6 +1428,8 @@ void Courtroom::set_fonts() // Chatlog does not support drtextedit because html set_font(ui_ooc_chatlog, "server_chatlog", COURTROOM_FONTS_INI, ao_app); + ui_ooc_chatlog->reset_message_format(); + set_font(ui_music_list, "music_list", COURTROOM_FONTS_INI, ao_app); set_font(ui_area_list, "area_list", COURTROOM_FONTS_INI, ao_app); set_font(ui_sfx_list, "sfx_list", COURTROOM_FONTS_INI, ao_app); diff --git a/src/drchatlog.cpp b/src/drchatlog.cpp index e66023950..61cb71b85 100644 --- a/src/drchatlog.cpp +++ b/src/drchatlog.cpp @@ -11,7 +11,6 @@ DRChatLog::DRChatLog(QWidget *parent) : QTextBrowser(parent), dr_config(new AOConfig(this)) { connect(this, SIGNAL(message_queued()), this, SLOT(_p_write_message_queue())); - connect(dr_config, SIGNAL(loaded_theme()), this, SLOT(_p_reset_log())); } void DRChatLog::append_chatmessage(QString p_name, QString p_text) @@ -24,6 +23,11 @@ void DRChatLog::append_error(QString p_text) queue_message(nullptr, p_text); } +void DRChatLog::reset_message_format() +{ + _p_reset_log(); +} + void DRChatLog::queue_message(QString p_name, QString p_text) { Message l_message; From 0710f27a0b451e4ae5499990a2283da82feeb75f Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 26 Oct 2021 15:32:29 +0200 Subject: [PATCH 562/842] Resolve #242 - Fix disappearing server name if switching between public and favorite servers --- include/datatypes.h | 7 +++++- include/lobby.h | 2 +- src/lobby.cpp | 5 +++-- src/server_socket.cpp | 50 +++++++++++-------------------------------- 4 files changed, 23 insertions(+), 41 deletions(-) diff --git a/include/datatypes.h b/include/datatypes.h index cb2109cf2..0fb3037ea 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -106,6 +106,7 @@ struct server_type QString desc; QString ip; int port; + bool is_favorite = false; QString to_info() const { @@ -117,11 +118,15 @@ struct server_type } else if (!ip.isEmpty()) { - r_info = ip + ":" + QString::number(port); + r_info = to_address(); } return r_info; } + QString to_address() const + { + return ip + ":" + QString::number(port); + } }; struct char_type diff --git a/include/lobby.h b/include/lobby.h index 02376768f..977fe72a7 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -40,7 +40,7 @@ class Lobby : public QMainWindow void set_fonts(); void show_loading_overlay(); void hide_loading_overlay(); - int get_selected_server(); + server_type get_selected_server(); void set_loading_value(int p_value); private: diff --git a/src/lobby.cpp b/src/lobby.cpp index a5df9c597..7cb3c2678 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -236,9 +236,9 @@ void Lobby::set_loading_text(QString p_text) ui_loading_text->append(p_text); } -int Lobby::get_selected_server() +server_type Lobby::get_selected_server() { - return ui_server_list->currentRow(); + return m_last_server; } void Lobby::set_loading_value(int p_value) @@ -354,6 +354,7 @@ void Lobby::on_server_list_clicked(QModelIndex p_model) return; m_last_server = ao_app->get_favorite_list().at(p_model.row()); + m_last_server.is_favorite = true; } ui_player_count->setText("Connecting..."); diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 8d4a34511..d2fdf08cc 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -120,34 +120,23 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) is_courtroom_loaded = false; - QString window_title = "Danganronpa Online"; - int selected_server = m_lobby->get_selected_server(); - - QString server_name, server_address; - bool is_favorite = false; - if (m_lobby->is_public_server()) + server_type l_current_server = m_lobby->get_selected_server(); + if (l_current_server.is_favorite) { - if (selected_server >= 0 && selected_server < m_server_list.size()) + const QString l_current_server_address = l_current_server.to_address(); + for (const server_type &i_server : qAsConst(m_server_list)) { - auto info = m_server_list.at(selected_server); - server_name = info.name; - server_address = info.ip + info.port; - window_title += ": " + server_name; - } - } - else - { - if (selected_server >= 0 && selected_server < m_favorite_server_list.size()) - { - auto info = m_favorite_server_list.at(selected_server); - server_name = info.name; - server_address = info.ip + info.port; - is_favorite = true; - window_title += ": " + server_name; + if (l_current_server_address != i_server.to_address()) + continue; + l_current_server.name = i_server.name; + break; } } - m_courtroom->set_window_title(window_title); + QString l_window_title = "Danganronpa Online"; + if (!l_current_server.name.isEmpty()) + l_window_title = l_window_title + ": " + l_current_server.to_info(); + m_courtroom->set_window_title(l_window_title); m_lobby->show_loading_overlay(); m_lobby->set_loading_text("Loading"); @@ -155,21 +144,8 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) send_server_packet(DRPacket("RC")); - // look for the server inside the known public list and report it - if (is_favorite) - { - for (server_type &server : m_server_list) - { - const QString l_address = server.ip + server.port; - if (server_address == l_address) - { - server_name = server.name; - break; - } - } - } dr_discord->set_state(DRDiscord::State::Connected); - dr_discord->set_server_name(server_name); + dr_discord->set_server_name(l_current_server.to_info()); } else if (l_header == "CharsCheck") { From f3c63ab7999868a85ac31636df8de4fae8cf1c3b Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 26 Oct 2021 12:12:28 -0400 Subject: [PATCH 563/842] Bump up version to 1.0.2 --- dronline-client.pro | 2 +- src/version.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index 65f851bbe..af54f6f2d 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -3,7 +3,7 @@ QT += core gui widgets uitools network CONFIG += c++17 TEMPLATE = app -VERSION = 1.0.0.0 +VERSION = 1.0.2.0 TARGET = dro-client RC_ICONS = icon.ico diff --git a/src/version.cpp b/src/version.cpp index 3bed3ae63..476641d20 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -14,7 +14,7 @@ int get_major_version() int get_minor_version() { - return 1; + return 2; } QString get_version_string() From 12108ce3322fca2ba6ac02853966ad623c3f7cbe Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 26 Oct 2021 22:18:57 +0200 Subject: [PATCH 564/842] Added -noscaling argument option Resolve #244 --- src/main.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f50e21a17..536fff7e9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,9 +6,18 @@ int main(int argc, char *argv[]) { #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) // High-DPI support is for Qt version >=5.6. - // However, many Linux distros still haven't brought their stable/LTS - // packages up to Qt 5.6, so this is conditional. AOApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + for (int i = 0; i < argc; ++i) + { + const QString l_arg(argv[i]); + + if (l_arg == "-noscaling") + { + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, false); + QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); + } + } #endif #ifdef Q_OS_MACOS From 722c23bc7874ce543c17e8c0b3b989795b152677 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 27 Oct 2021 21:37:59 +0200 Subject: [PATCH 565/842] Alternative solution --- src/main.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 536fff7e9..82cf5bc2f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,11 +2,13 @@ #include "lobby.h" +#include + int main(int argc, char *argv[]) { #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) // High-DPI support is for Qt version >=5.6. - AOApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + bool l_dpi_scaling = false; for (int i = 0; i < argc; ++i) { @@ -14,10 +16,19 @@ int main(int argc, char *argv[]) if (l_arg == "-noscaling") { - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, false); - QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); + l_dpi_scaling = false; } } + + if (l_dpi_scaling) + { + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + } + else + { + QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); + QCoreApplication::setAttribute(Qt::AA_Use96Dpi); + } #endif #ifdef Q_OS_MACOS From 54bad29d61cf5e3f69ddd3dac000307b45de9749 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 27 Oct 2021 23:56:08 +0200 Subject: [PATCH 566/842] Extra changes to ensure DPI scaling is fixed when disabled --- src/main.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 82cf5bc2f..054aee296 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,9 +6,9 @@ int main(int argc, char *argv[]) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) // High-DPI support is for Qt version >=5.6. - bool l_dpi_scaling = false; +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) + bool l_dpi_scaling = true; for (int i = 0; i < argc; ++i) { @@ -26,8 +26,12 @@ int main(int argc, char *argv[]) } else { + qputenv("QT_FONT_DPI", "96"); + qputenv("QT_SCALE_FACTOR", "1"); + qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0"); + QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); - QCoreApplication::setAttribute(Qt::AA_Use96Dpi); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, false); } #endif From 9bd7530eac338bb9d05406ba10652cead2d62c7a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 31 Oct 2021 17:36:12 +0100 Subject: [PATCH 567/842] Added BASS version details --- src/version.cpp | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/version.cpp b/src/version.cpp index 1884ef4d5..1ca7521ec 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -1,5 +1,7 @@ #include "version.h" +#include + #include #include #include @@ -30,8 +32,9 @@ QString get_resource_file_text(QString filename) QString data; QFile file(filename); - if (!file.open(QIODevice::ReadOnly)) { - return ""; + if (!file.open(QIODevice::ReadOnly)) + { + return ""; } data = file.readAll(); file.close(); @@ -44,26 +47,25 @@ QString get_about_message() const QString git_branch = get_resource_file_text(":/res/git/git_branch.txt"); const QString git_hash = get_resource_file_text(":/res/git/git_hash.txt"); - QString msg = QString( - "

    Danganronpa Online

    " - "version: %1" - "

    Source code: " - "" - "https://github.com/Chrezm/DRO-Client" - "

    Development:
    " - "Cerapter, Elf, Iuvee, Tricky Leifa, Ziella" - "

    Based on Attorney Online 2:
    " - "" - "https://github.com/AttorneyOnline/AO2-Client" - "

    Running on Qt version %2 with the BASS audio engine.
    " - "APNG plugin loaded: %3" - "

    Built on %4 %5
    " - ) - .arg(get_version_string()) - .arg(QLatin1String(QT_VERSION_STR)) - .arg(hasApng ? "Yes" : "No") - .arg(QLatin1String(__DATE__)) - .arg(QLatin1String(__TIME__)); + QString msg = QString("

    Danganronpa Online

    " + "version: %1" + "

    Source code: " + "" + "https://github.com/Chrezm/DRO-Client" + "

    Development:
    " + "Cerapter, Elf, Iuvee, Tricky Leifa, Ziella" + "

    Based on Attorney Online 2:
    " + "" + "https://github.com/AttorneyOnline/AO2-Client" + "

    Running on Qt version %2 with the BASS %3 audio engine.
    " + "APNG plugin loaded: %4" + "

    Built on %4 %5
    ") + .arg(get_version_string()) + .arg(QLatin1String(QT_VERSION_STR)) + .arg(QLatin1String(BASSVERSIONTEXT)) + .arg(hasApng ? "Yes" : "No") + .arg(QLatin1String(__DATE__)) + .arg(QLatin1String(__TIME__)); if (git_branch.isEmpty()) msg += QString("No git branch information available.
    "); From 39da03eb09bdc10ffdccdda8d9b8b1f873f7a100 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 31 Oct 2021 19:10:07 +0100 Subject: [PATCH 568/842] Emote buttons are now resized smoothly --- include/aoemotebutton.h | 6 ++++++ src/aoemotebutton.cpp | 25 ++++++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/include/aoemotebutton.h b/include/aoemotebutton.h index 3781aea07..440e4ec3c 100644 --- a/include/aoemotebutton.h +++ b/include/aoemotebutton.h @@ -35,11 +35,17 @@ class AOEmoteButton : public QPushButton AOApplication *ao_app = nullptr; int m_index = 0; + QImage m_texture; + QString m_comment; QLabel *ui_selected = nullptr; private slots: void on_clicked(); + + // QWidget interface +protected: + void paintEvent(QPaintEvent *event) final; }; #endif // AOEMOTEBUTTON_H diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index 2d21c99c5..97310c47f 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -4,7 +4,10 @@ #include "file_functions.h" #include +#include #include +#include +#include AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) { @@ -67,11 +70,9 @@ void AOEmoteButton::set_image(DREmote p_emote, bool p_enabled) } } - const bool l_texture_exist = file_exists(l_texture); - setText(l_texture_exist ? nullptr : p_emote.comment); - setStyleSheet(l_texture_exist - ? QString("%1 { border-image: url(\"%2\"); }").arg(metaObject()->className()).arg(l_texture) - : nullptr); + m_texture.load(l_texture); + m_comment = p_emote.comment; + setText(m_texture.isNull() ? p_emote.comment : nullptr); } void AOEmoteButton::on_clicked() @@ -79,6 +80,20 @@ void AOEmoteButton::on_clicked() Q_EMIT emote_clicked(m_index); } +void AOEmoteButton::paintEvent(QPaintEvent *event) +{ + if (m_texture.isNull()) + { + QPushButton::paintEvent(event); + return; + } + + QPainter l_painter(this); + l_painter.drawImage(event->rect(), + m_texture.scaled(event->rect().size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + l_painter.end(); +} + bool AOEmoteButton::event(QEvent *event) { switch (event->type()) From 29a1a8f0a1773b5a3ea057fe8f4fed6908167a63 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 31 Oct 2021 20:18:49 +0100 Subject: [PATCH 569/842] Added emote blacklist Resolve #247 --- src/aocharmovie.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 855336444..6736982cc 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -43,8 +43,15 @@ bool AOCharMovie::play(QString p_chr, QString p_emote, QString p_prefix, bool p_ const bool l_is_anim = p_prefix.isEmpty(); QStringList l_file_list; + QStringList l_blacklist; for (const QString &i_chr : ao_app->get_char_include_tree(p_chr)) { + l_blacklist.append(QStringList{ + ao_app->get_character_path(i_chr, "char_icon.png"), + ao_app->get_character_path(i_chr, "showname.png"), + ao_app->get_character_path(i_chr, "emotions"), + }); + if (!l_is_anim) l_file_list.append(ao_app->get_character_path(i_chr, QString("%1%2").arg(p_prefix, p_emote))); l_file_list.append(ao_app->get_character_path(i_chr, p_emote)); @@ -58,6 +65,15 @@ bool AOCharMovie::play(QString p_chr, QString p_emote, QString p_prefix, bool p_ r_exist = false; } + for (const QString &i_blacklisted_file : qAsConst(l_blacklist)) + { + if (!l_file.startsWith(i_blacklisted_file, Qt::CaseInsensitive)) + continue; + l_file.clear(); + r_exist = false; + break; + } + stop(); is_play_once = p_play_once; m_movie->setFileName(l_file); From 3eb772ddec042ec4f70d4b1ef86b8523a0ae18bc Mon Sep 17 00:00:00 2001 From: Dakkimakura Date: Thu, 2 Dec 2021 23:32:57 +0100 Subject: [PATCH 570/842] better fstab UUID grabbing --- src/hardware_functions.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) mode change 100644 => 100755 src/hardware_functions.cpp diff --git a/src/hardware_functions.cpp b/src/hardware_functions.cpp old mode 100644 new mode 100755 index ff591504e..194fe5ea1 --- a/src/hardware_functions.cpp +++ b/src/hardware_functions.cpp @@ -34,9 +34,10 @@ QString get_hdid() while (!in.atEnd()) { QString line = in.readLine(); - - if (line.startsWith("UUID")) + int i = line.indexOf(QRegExp("UUID|uuid"), 0); + if (i >= 0) { + line = line.chopped(i); QStringList line_elements = line.split("="); if (line_elements.size() > 1) From a41f1d0e19d2979a8511590f16086ca86148f4bf Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Sat, 11 Dec 2021 18:47:50 -0500 Subject: [PATCH 571/842] Update workers to Qt 5.15 (#232) Update workers to Qt 5.15 (Windows only) --- .github/workflows/build-all.yml | 52 +++++++----- .github/workflows/ubuntu-515.yml | 136 +++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/ubuntu-515.yml diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 17e71f638..9085402b7 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -1,4 +1,4 @@ -# Original code from skyedeving and oldmud0 +# Based from code from skyedeving and oldmud0 # at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml name: build-all @@ -66,21 +66,28 @@ jobs: curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-mingw73_32-5.14.1.zip -o apng.zip unzip apng.zip + - name: Update Python Pip + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + python -m pip install --upgrade pip + - name: Install Qt uses: jurplel/install-qt-action@v2 with: - version: '5.14.2' - arch: 'win32_mingw73' + version: '5.15.2' + arch: 'win32_mingw81' - name: Install AQt shell: bash working-directory: ${{env.parentworkspace}} - run: pip install aqtinstall + run: | + pip install aqtinstall - name: Install MinGW shell: bash working-directory: ${{env.parentworkspace}} - run: aqt tool -O ./Qt windows tools_mingw 7.3.0-1-202004170606 qt.tools.win32_mingw730 + run: aqt tool -O ./Qt windows tools_mingw 8.1.0-1-202004170606 qt.tools.win32_mingw810 - name: Setup MinGW shell: bash @@ -90,9 +97,9 @@ jobs: # Therefore, I just brought the MinGW32 folder to the MinGW64 folder. # Tom Scott would be proud of this bodge run: | - export PATH=/d/a/DRO-Client/Qt/Tools/mingw730_32/bin:$PATH + export PATH=/d/a/DRO-Client/Qt/Tools/mingw810_32/bin:$PATH rm -r /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ - cp -r /d/a/DRO-Client/Qt/Tools/mingw730_32/ /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ + cp -r /d/a/DRO-Client/Qt/Tools/mingw810_32/ /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ - name: Setup branch and commit hash tags shell: bash @@ -107,14 +114,14 @@ jobs: shell: bash working-directory: ${{env.parentworkspace}} run: | - ./Qt/5.14.2/mingw73_32/bin/qmake.exe -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec win32-g++ "CONFIG+=qtquickcompiler" + ./Qt/5.15.2/mingw81_32/bin/qmake.exe -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec win32-g++ "CONFIG+=qtquickcompiler" - name: Run Make shell: bash working-directory: ${{env.parentworkspace}} run: | - ./Qt/Tools/mingw730_32/bin/mingw32-make.exe -f ./Makefile qmake_all - ./Qt/Tools/mingw730_32/bin/mingw32-make.exe -j8 + ./Qt/Tools/mingw810_32/bin/mingw32-make.exe -f ./Makefile qmake_all + ./Qt/Tools/mingw810_32/bin/mingw32-make.exe -j8 - name: Set up deploy folder shell: bash @@ -202,10 +209,16 @@ jobs: curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-clang_64-5.14.1.tar.xz -o qtapng.tar.xz tar -xvf qtapng.tar.xz + - name: Update Python Pip + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + python3 -m pip install --upgrade pip + - name: Install Qt uses: jurplel/install-qt-action@v2 with: - version: '5.14.2' + version: '5.15.2' - name: Setup branch and commit hash tags shell: bash @@ -220,7 +233,7 @@ jobs: shell: bash working-directory: ${{env.parentworkspace}} run: | - ./Qt/5.14.2/clang_64/bin/qmake -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec macx-clang "CONFIG+=x86_64" "CONFIG+=qtquickcompiler" + ./Qt/5.15.2/clang_64/bin/qmake -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec macx-clang "CONFIG+=x86_64" "CONFIG+=qtquickcompiler" - name: Run make shell: bash @@ -241,7 +254,7 @@ jobs: working-directory: "${{env.parentworkspace}}/Danganronpa Online" shell: bash run: | - ../Qt/5.14.2/clang_64/bin/macdeployqt "Danganronpa Online.app" + ../Qt/5.15.2/clang_64/bin/macdeployqt "Danganronpa Online.app" cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/Frameworks/" cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/MacOS/" cp ../libbass.dylib "./Danganronpa Online.app/Contents/Frameworks/" @@ -378,21 +391,16 @@ jobs: cp -R ../libqapng.so "./imageformats" echo "LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client" > "dro-client.sh" echo "Installation instructions - - 1. Download the Full Pack for Windows and extract on the same folder as this folder. - - 2. On your terminal, run + 1. On your terminal, run sudo apt install qt5-default - - 3. Change directory to this folder, and run + 2. Change directory to this folder, and run chmod +x dro-client.sh chmod +x dro-client - - 4. To launch, run + 3. To launch, run ./dro-client.sh" > "Readme (Ubuntu).txt" - name: Upload Artifact uses: actions/upload-artifact@v2 with: name: "Danganronpa Online (Ubuntu)" - path: "${{env.parentworkspace}}/**/Danganronpa Online" + path: "${{env.parentworkspace}}/**/Danganronpa Online" \ No newline at end of file diff --git a/.github/workflows/ubuntu-515.yml b/.github/workflows/ubuntu-515.yml new file mode 100644 index 000000000..f1ed6e5d3 --- /dev/null +++ b/.github/workflows/ubuntu-515.yml @@ -0,0 +1,136 @@ + + + ############################################################################### + # UBUNTU # + ############################################################################### + + ubuntu: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - name: Update worker + shell: bash + working-directory: ${{github.workspace}} + run: | + sudo apt-get update + sudo apt-get upgrade + + - name: Setup workspace + shell: bash + working-directory: ${{github.workspace}} + run: | + echo "parentworkspace=/home/runner/work/DRO-Client/" >> $GITHUB_ENV + mkdir "3rd" + mkdir "3rd/include" + mkdir "3rd/x86_64" + + - name: Fetch Discord external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip + unzip discord_rpc_linux.zip + cp ./discord-rpc/linux-dynamic/include/discord_register.h ./DRO-Client/3rd/include/discord_register.h + cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include/discord_rpc.h + cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./DRO-Client/3rd/x86_64/libdiscord-rpc.so + + - name: Fetch Bass external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip + unzip bass.zip + cp ./bass.h ./DRO-Client/3rd/include/bass.h + cp ./x64/libbass.so ./DRO-Client/3rd/x86_64/libbass.so + + - name: Fetch BassOpus external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip + unzip bassopus.zip + cp ./bassopus.h ./DRO-Client/3rd/include/bassopus.h + cp ./x64/libbassopus.so ./DRO-Client/3rd/x86_64/libbassopus.so + + - name: Update Python Pip + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + pip install --upgrade pip + + - name: Install AQt + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + pip install aqtinstall + + - name: Install Qt + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + aqt install-qt linux desktop 5.15.2 + sudo apt install libgl1-mesa-dev + + - name: Build QtApng external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + git clone https://github.com/Skycoder42/QtApng + cd QtApng + ../5.15.2/gcc_64/bin/qmake + make -j2 + cp plugins/imageformats/libqapng.so .. + cd .. + rm -r -f QtApng + + - name: Run qmake + shell: bash + working-directory: ${{github.workspace}} + run: | + ../5.15.2/gcc_64/bin/qmake + + - name: Run Make + shell: bash + working-directory: ${{github.workspace}} + run: | + make + + - name: Set up deploy folder + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + mkdir "Danganronpa Online" + cp -R ./DRO-Client/dro-client "./Danganronpa Online/dro-client" + + - name: Deploy + working-directory: "${{env.parentworkspace}}/Danganronpa Online" + shell: bash + run: | + mkdir depends + cp ../discord-rpc/linux-dynamic/lib/libdiscord-rpc.so "./depends" + cp ../x64/libbass.so "./depends" + cp ../x64/libbassopus.so "./depends" + mkdir imageformats + cp -R ../libqapng.so "./imageformats" + echo "LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client" > "dro-client.sh" + echo "Installation instructions + + 1. On your terminal, run these commands, one after the other. + pip install --upgrade pip + pip install aqtinstall + aqt install-qt linux desktop 5.15.2 + + 2. Change directory to this folder, and run + chmod +x dro-client.sh + chmod +x dro-client + + 3. To launch, run + ./dro-client.sh" > "Readme (Ubuntu).txt" + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: "Danganronpa Online (Ubuntu)" + path: "${{env.parentworkspace}}/**/Danganronpa Online" From d7534a8d11f479b605e45300cbf2ce0b9e6a2fa5 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 13 Dec 2021 09:45:49 -0500 Subject: [PATCH 572/842] Implement #251 --- src/courtroom.cpp | 34 ++++++++++++++++++++-------------- src/version.cpp | 2 +- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a1500106a..21142b8fd 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1355,15 +1355,18 @@ void Courtroom::next_chat_letter() if (m_message_color_stack.isEmpty()) m_message_color_stack.push(""); - for (const auto &col : qAsConst(m_chatbox_message_highlight_colors)) + if (!is_ignore_next_letter) { - if (f_character == col[0][0] && m_message_color_name != col[1]) + for (const auto &col : qAsConst(m_chatbox_message_highlight_colors)) { - m_message_color_stack.push(col[1]); - m_message_color_name = m_message_color_stack.top(); - highlight_found = true; - render_character = (col[2] != "0"); - break; + if (f_character == col[0][0] && m_message_color_name != col[1]) + { + m_message_color_stack.push(col[1]); + m_message_color_name = m_message_color_stack.top(); + highlight_found = true; + render_character = (col[2] != "0"); + break; + } } } @@ -1379,15 +1382,18 @@ void Courtroom::next_chat_letter() QString m_future_string_color = m_message_color_name; - for (const auto &col : qAsConst(m_chatbox_message_highlight_colors)) + if (!highlight_found && !is_ignore_next_letter) { - if (f_character == col[0][1] && !highlight_found) + for (const auto &col : qAsConst(m_chatbox_message_highlight_colors)) { - if (m_message_color_stack.size() > 1) - m_message_color_stack.pop(); - m_future_string_color = m_message_color_stack.top(); - render_character = (col[2] != "0"); - break; + if (f_character == col[0][1]) + { + if (m_message_color_stack.size() > 1) + m_message_color_stack.pop(); + m_future_string_color = m_message_color_stack.top(); + render_character = (col[2] != "0"); + break; + } } } diff --git a/src/version.cpp b/src/version.cpp index 727e1eb17..8d08e7d4b 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -59,7 +59,7 @@ QString get_about_message() "https://github.com/AttorneyOnline/AO2-Client" "

    Running on Qt version %2 with the BASS %3 audio engine.
    " "APNG plugin loaded: %4" - "

    Built on %4 %5
    ") + "

    Built on %5
    ") .arg(get_version_string()) .arg(QLatin1String(QT_VERSION_STR)) .arg(QLatin1String(BASSVERSIONTEXT)) From 42ce23e3b1d0284b1dad381736eb50b22cd6f982 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 13 Dec 2021 10:00:25 -0500 Subject: [PATCH 573/842] Fully comment out Ubuntu 5.15 workflow --- .github/workflows/ubuntu-515.yml | 224 +++++++++++++++---------------- 1 file changed, 112 insertions(+), 112 deletions(-) diff --git a/.github/workflows/ubuntu-515.yml b/.github/workflows/ubuntu-515.yml index f1ed6e5d3..21029c702 100644 --- a/.github/workflows/ubuntu-515.yml +++ b/.github/workflows/ubuntu-515.yml @@ -4,133 +4,133 @@ # UBUNTU # ############################################################################### - ubuntu: - runs-on: ubuntu-20.04 + # ubuntu: + # runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 + # steps: + # - uses: actions/checkout@v2 - - name: Update worker - shell: bash - working-directory: ${{github.workspace}} - run: | - sudo apt-get update - sudo apt-get upgrade + # - name: Update worker + # shell: bash + # working-directory: ${{github.workspace}} + # run: | + # sudo apt-get update + # sudo apt-get upgrade - - name: Setup workspace - shell: bash - working-directory: ${{github.workspace}} - run: | - echo "parentworkspace=/home/runner/work/DRO-Client/" >> $GITHUB_ENV - mkdir "3rd" - mkdir "3rd/include" - mkdir "3rd/x86_64" + # - name: Setup workspace + # shell: bash + # working-directory: ${{github.workspace}} + # run: | + # echo "parentworkspace=/home/runner/work/DRO-Client/" >> $GITHUB_ENV + # mkdir "3rd" + # mkdir "3rd/include" + # mkdir "3rd/x86_64" - - name: Fetch Discord external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip - unzip discord_rpc_linux.zip - cp ./discord-rpc/linux-dynamic/include/discord_register.h ./DRO-Client/3rd/include/discord_register.h - cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include/discord_rpc.h - cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./DRO-Client/3rd/x86_64/libdiscord-rpc.so + # - name: Fetch Discord external library + # shell: bash + # working-directory: ${{env.parentworkspace}} + # run: | + # curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip + # unzip discord_rpc_linux.zip + # cp ./discord-rpc/linux-dynamic/include/discord_register.h ./DRO-Client/3rd/include/discord_register.h + # cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include/discord_rpc.h + # cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./DRO-Client/3rd/x86_64/libdiscord-rpc.so - - name: Fetch Bass external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip - unzip bass.zip - cp ./bass.h ./DRO-Client/3rd/include/bass.h - cp ./x64/libbass.so ./DRO-Client/3rd/x86_64/libbass.so + # - name: Fetch Bass external library + # shell: bash + # working-directory: ${{env.parentworkspace}} + # run: | + # curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip + # unzip bass.zip + # cp ./bass.h ./DRO-Client/3rd/include/bass.h + # cp ./x64/libbass.so ./DRO-Client/3rd/x86_64/libbass.so - - name: Fetch BassOpus external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip - unzip bassopus.zip - cp ./bassopus.h ./DRO-Client/3rd/include/bassopus.h - cp ./x64/libbassopus.so ./DRO-Client/3rd/x86_64/libbassopus.so + # - name: Fetch BassOpus external library + # shell: bash + # working-directory: ${{env.parentworkspace}} + # run: | + # curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip + # unzip bassopus.zip + # cp ./bassopus.h ./DRO-Client/3rd/include/bassopus.h + # cp ./x64/libbassopus.so ./DRO-Client/3rd/x86_64/libbassopus.so - - name: Update Python Pip - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - pip install --upgrade pip + # - name: Update Python Pip + # shell: bash + # working-directory: ${{env.parentworkspace}} + # run: | + # pip install --upgrade pip - - name: Install AQt - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - pip install aqtinstall + # - name: Install AQt + # shell: bash + # working-directory: ${{env.parentworkspace}} + # run: | + # pip install aqtinstall - - name: Install Qt - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - aqt install-qt linux desktop 5.15.2 - sudo apt install libgl1-mesa-dev + # - name: Install Qt + # shell: bash + # working-directory: ${{env.parentworkspace}} + # run: | + # aqt install-qt linux desktop 5.15.2 + # sudo apt install libgl1-mesa-dev - - name: Build QtApng external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - git clone https://github.com/Skycoder42/QtApng - cd QtApng - ../5.15.2/gcc_64/bin/qmake - make -j2 - cp plugins/imageformats/libqapng.so .. - cd .. - rm -r -f QtApng + # - name: Build QtApng external library + # shell: bash + # working-directory: ${{env.parentworkspace}} + # run: | + # git clone https://github.com/Skycoder42/QtApng + # cd QtApng + # ../5.15.2/gcc_64/bin/qmake + # make -j2 + # cp plugins/imageformats/libqapng.so .. + # cd .. + # rm -r -f QtApng - - name: Run qmake - shell: bash - working-directory: ${{github.workspace}} - run: | - ../5.15.2/gcc_64/bin/qmake + # - name: Run qmake + # shell: bash + # working-directory: ${{github.workspace}} + # run: | + # ../5.15.2/gcc_64/bin/qmake - - name: Run Make - shell: bash - working-directory: ${{github.workspace}} - run: | - make + # - name: Run Make + # shell: bash + # working-directory: ${{github.workspace}} + # run: | + # make - - name: Set up deploy folder - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - mkdir "Danganronpa Online" - cp -R ./DRO-Client/dro-client "./Danganronpa Online/dro-client" + # - name: Set up deploy folder + # shell: bash + # working-directory: ${{env.parentworkspace}} + # run: | + # mkdir "Danganronpa Online" + # cp -R ./DRO-Client/dro-client "./Danganronpa Online/dro-client" - - name: Deploy - working-directory: "${{env.parentworkspace}}/Danganronpa Online" - shell: bash - run: | - mkdir depends - cp ../discord-rpc/linux-dynamic/lib/libdiscord-rpc.so "./depends" - cp ../x64/libbass.so "./depends" - cp ../x64/libbassopus.so "./depends" - mkdir imageformats - cp -R ../libqapng.so "./imageformats" - echo "LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client" > "dro-client.sh" - echo "Installation instructions + # - name: Deploy + # working-directory: "${{env.parentworkspace}}/Danganronpa Online" + # shell: bash + # run: | + # mkdir depends + # cp ../discord-rpc/linux-dynamic/lib/libdiscord-rpc.so "./depends" + # cp ../x64/libbass.so "./depends" + # cp ../x64/libbassopus.so "./depends" + # mkdir imageformats + # cp -R ../libqapng.so "./imageformats" + # echo "LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client" > "dro-client.sh" + # echo "Installation instructions - 1. On your terminal, run these commands, one after the other. - pip install --upgrade pip - pip install aqtinstall - aqt install-qt linux desktop 5.15.2 + # 1. On your terminal, run these commands, one after the other. + # pip install --upgrade pip + # pip install aqtinstall + # aqt install-qt linux desktop 5.15.2 - 2. Change directory to this folder, and run - chmod +x dro-client.sh - chmod +x dro-client + # 2. Change directory to this folder, and run + # chmod +x dro-client.sh + # chmod +x dro-client - 3. To launch, run - ./dro-client.sh" > "Readme (Ubuntu).txt" + # 3. To launch, run + # ./dro-client.sh" > "Readme (Ubuntu).txt" - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - name: "Danganronpa Online (Ubuntu)" - path: "${{env.parentworkspace}}/**/Danganronpa Online" + # - name: Upload Artifact + # uses: actions/upload-artifact@v2 + # with: + # name: "Danganronpa Online (Ubuntu)" + # path: "${{env.parentworkspace}}/**/Danganronpa Online" From b96e6715518309cafea097d037052c44e863dc1a Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Mon, 13 Dec 2021 18:51:21 -0500 Subject: [PATCH 574/842] Remove unnecessary workflow library --- .github/workflows/build-all.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 9085402b7..a66326873 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -337,7 +337,6 @@ jobs: sudo apt-get install qt5-default qttools5-dev sudo apt-get install libqt5designer5 sudo apt-get install git - sudo snap install discord - name: Build QtApng external library shell: bash @@ -403,4 +402,4 @@ jobs: uses: actions/upload-artifact@v2 with: name: "Danganronpa Online (Ubuntu)" - path: "${{env.parentworkspace}}/**/Danganronpa Online" \ No newline at end of file + path: "${{env.parentworkspace}}/**/Danganronpa Online" From 83f2a2c9cb587ab1273729c48107170c428f6285 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 28 Dec 2021 10:55:12 -0300 Subject: [PATCH 575/842] Fix emote on buttons lingering when switching to another emote --- src/emotes.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/emotes.cpp b/src/emotes.cpp index cda532841..42540bc40 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -173,14 +173,22 @@ void Courtroom::select_emote(int p_id) const DREmote &l_prev_emote = get_emote(m_emote_id); if (m_emote_id >= l_min && m_emote_id <= l_max) - ui_emote_list.at(m_emote_id % m_page_max_emote_count)->set_image(l_prev_emote, false); + { + AOEmoteButton* l_prev_button = ui_emote_list.at(m_emote_id % m_page_max_emote_count); + l_prev_button->set_image(l_prev_emote, false); + l_prev_button->repaint(); + } const int l_prev_emote_id = m_emote_id; m_emote_id = p_id; const DREmote &l_emote = get_emote(m_emote_id); if (m_emote_id >= l_min && m_emote_id <= l_max) - ui_emote_list.at(m_emote_id % m_page_max_emote_count)->set_image(l_emote, true); + { + AOEmoteButton* l_new_button = ui_emote_list.at(m_emote_id % m_page_max_emote_count); + l_new_button->set_image(l_emote, true); + l_new_button->repaint(); + } const int emote_mod = l_emote.modifier; if (l_prev_emote_id == m_emote_id) // toggle From 65055d72ca4a75ca7679d98ef957d6537c383d86 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 23 Jan 2022 16:22:19 -0500 Subject: [PATCH 576/842] Basic implementation for IC chatlog --- .github/{workflows => }/ubuntu-515.yml | 0 include/courtroom.h | 4 ++++ src/courtroom.cpp | 33 ++++++++++++++++++++++++++ src/courtroom_widgets.cpp | 9 ++++++- 4 files changed, 45 insertions(+), 1 deletion(-) rename .github/{workflows => }/ubuntu-515.yml (100%) diff --git a/.github/workflows/ubuntu-515.yml b/.github/ubuntu-515.yml similarity index 100% rename from .github/workflows/ubuntu-515.yml rename to .github/ubuntu-515.yml diff --git a/include/courtroom.h b/include/courtroom.h index 4f59788df..6a22c0808 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -374,6 +374,7 @@ class Courtroom : public QMainWindow DRTextEdit *ui_ic_chatlog = nullptr; QList m_ic_record_list; QQueue m_ic_record_queue; + AOButton *ui_ic_chatlog_scroll_td = nullptr; DRChatLog *ui_ooc_chatlog = nullptr; @@ -567,6 +568,9 @@ private slots: void on_ic_message_return_pressed(); void on_chat_config_changed(); + void on_ic_chatlog_scroll_changed(); + void on_ic_chatlog_scroll_td_clicked(); + void on_ooc_name_editing_finished(); void on_ooc_return_pressed(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 21142b8fd..c40c0f46c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -8,6 +8,7 @@ #include "aoconfig.h" #include "aoevidencedisplay.h" #include "aoimagedisplay.h" +#include "aolabel.h" #include "aomovie.h" #include "aomusicplayer.h" #include "aonotearea.h" @@ -1138,7 +1139,39 @@ void Courtroom::update_ic_log(bool p_reset_log) } if (l_is_end_scroll_pos) + { l_scrollbar->setValue(l_topdown_orientation ? l_scrollbar->maximum() : l_scrollbar->minimum()); + ui_ic_chatlog_scroll_td->hide(); + } + else + { + ui_ic_chatlog_scroll_td->show(); + } +} + +void Courtroom::on_ic_chatlog_scroll_changed() +{ + const bool l_topdown_orientation = ao_config->log_is_topdown_enabled(); + QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); + const int l_scroll_pos = l_scrollbar->value(); + const bool l_is_end_scroll_pos = (l_topdown_orientation ? + l_scroll_pos == l_scrollbar->maximum(): l_scroll_pos == l_scrollbar->minimum()); + if (l_is_end_scroll_pos) + { + ui_ic_chatlog_scroll_td->hide(); + } + else + { + ui_ic_chatlog_scroll_td->show(); + } + +} + +void Courtroom::on_ic_chatlog_scroll_td_clicked() +{ + const bool l_topdown_orientation = ao_config->log_is_topdown_enabled(); + QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); + l_scrollbar->setValue(l_topdown_orientation ? l_scrollbar->maximum() : l_scrollbar->minimum()); } void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, bool p_self) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 946e6222a..4318c25b3 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -111,6 +112,7 @@ void Courtroom::create_widgets() ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); ui_ic_chatlog->set_auto_align(false); + ui_ic_chatlog_scroll_td = new AOButton(this, ao_app); ui_ooc_chatlog = new DRChatLog(this); ui_ooc_chatlog->setReadOnly(true); @@ -272,7 +274,9 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(on_character_ini_changed())); connect(ui_ic_chat_showname, SIGNAL(editingFinished()), this, SLOT(on_ic_showname_editing_finished())); connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ic_message_return_pressed())); - + connect(ui_ic_chatlog->verticalScrollBar(), SIGNAL(valueChanged(int)), this, + SLOT(on_ic_chatlog_scroll_changed())); + connect(ui_ic_chatlog_scroll_td, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_td_clicked())); connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); connect(ui_ooc_chat_name, SIGNAL(editingFinished()), this, SLOT(on_ooc_name_editing_finished())); connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); @@ -361,6 +365,7 @@ void Courtroom::reset_widget_names() {"vp_objection", ui_vp_objection}, {"chat_arrow", ui_vp_chat_arrow}, {"ic_chatlog", ui_ic_chatlog}, + {"ic_chatlog_scroll_td", ui_ic_chatlog_scroll_td}, {"server_chatlog", ui_ooc_chatlog}, {"area_list", ui_area_list}, {"area_search", ui_area_search}, @@ -616,6 +621,8 @@ void Courtroom::set_widgets() ui_vp_objection->combo_resize(ui_viewport->width(), ui_viewport->height()); set_size_and_pos(ui_ic_chatlog, "ic_chatlog", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_ic_chatlog_scroll_td, "ic_chatlog_scroll_td", COURTROOM_DESIGN_INI, ao_app); + ui_ic_chatlog_scroll_td->set_image("ic_chatlog_scroll_td.png"); set_size_and_pos(ui_ooc_chatlog, "server_chatlog", COURTROOM_DESIGN_INI, ao_app); From 116a56e9721100b0a99a113b7b1599b22d5d0c7f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 23 Jan 2022 18:17:58 -0500 Subject: [PATCH 577/842] Move unused workflow out of folder --- .github/{workflows => }/ubuntu-515.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows => }/ubuntu-515.yml (100%) diff --git a/.github/workflows/ubuntu-515.yml b/.github/ubuntu-515.yml similarity index 100% rename from .github/workflows/ubuntu-515.yml rename to .github/ubuntu-515.yml From b8437712b76c1e3a3f7f0d4c104144818619eed5 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sun, 23 Jan 2022 20:10:05 -0500 Subject: [PATCH 578/842] Add bottom up scroll paused button --- include/courtroom.h | 2 ++ src/courtroom.cpp | 23 +++++++++++++++++------ src/courtroom_widgets.cpp | 7 +++++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 6a22c0808..a47739d7d 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -375,6 +375,7 @@ class Courtroom : public QMainWindow QList m_ic_record_list; QQueue m_ic_record_queue; AOButton *ui_ic_chatlog_scroll_td = nullptr; + AOButton *ui_ic_chatlog_scroll_bu = nullptr; DRChatLog *ui_ooc_chatlog = nullptr; @@ -570,6 +571,7 @@ private slots: void on_ic_chatlog_scroll_changed(); void on_ic_chatlog_scroll_td_clicked(); + void on_ic_chatlog_scroll_bu_clicked(); void on_ooc_name_editing_finished(); void on_ooc_return_pressed(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index c40c0f46c..64317b790 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -8,7 +8,6 @@ #include "aoconfig.h" #include "aoevidencedisplay.h" #include "aoimagedisplay.h" -#include "aolabel.h" #include "aomovie.h" #include "aomusicplayer.h" #include "aonotearea.h" @@ -1142,10 +1141,14 @@ void Courtroom::update_ic_log(bool p_reset_log) { l_scrollbar->setValue(l_topdown_orientation ? l_scrollbar->maximum() : l_scrollbar->minimum()); ui_ic_chatlog_scroll_td->hide(); + ui_ic_chatlog_scroll_bu->hide(); } else { - ui_ic_chatlog_scroll_td->show(); + if (l_topdown_orientation) + ui_ic_chatlog_scroll_td->show(); + else + ui_ic_chatlog_scroll_bu->show(); } } @@ -1159,19 +1162,27 @@ void Courtroom::on_ic_chatlog_scroll_changed() if (l_is_end_scroll_pos) { ui_ic_chatlog_scroll_td->hide(); + ui_ic_chatlog_scroll_bu->hide(); } else { - ui_ic_chatlog_scroll_td->show(); + if (l_topdown_orientation) + ui_ic_chatlog_scroll_td->show(); + else + ui_ic_chatlog_scroll_bu->show(); } - } void Courtroom::on_ic_chatlog_scroll_td_clicked() { - const bool l_topdown_orientation = ao_config->log_is_topdown_enabled(); QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); - l_scrollbar->setValue(l_topdown_orientation ? l_scrollbar->maximum() : l_scrollbar->minimum()); + l_scrollbar->setValue(l_scrollbar->maximum()); +} + +void Courtroom::on_ic_chatlog_scroll_bu_clicked() +{ + QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); + l_scrollbar->setValue(l_scrollbar->minimum()); } void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, bool p_self) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 4318c25b3..3dd85855e 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -113,6 +113,7 @@ void Courtroom::create_widgets() ui_ic_chatlog->setReadOnly(true); ui_ic_chatlog->set_auto_align(false); ui_ic_chatlog_scroll_td = new AOButton(this, ao_app); + ui_ic_chatlog_scroll_bu = new AOButton(this, ao_app); ui_ooc_chatlog = new DRChatLog(this); ui_ooc_chatlog->setReadOnly(true); @@ -277,6 +278,7 @@ void Courtroom::connect_widgets() connect(ui_ic_chatlog->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(on_ic_chatlog_scroll_changed())); connect(ui_ic_chatlog_scroll_td, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_td_clicked())); + connect(ui_ic_chatlog_scroll_bu, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_bu_clicked())); connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); connect(ui_ooc_chat_name, SIGNAL(editingFinished()), this, SLOT(on_ooc_name_editing_finished())); connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); @@ -366,6 +368,7 @@ void Courtroom::reset_widget_names() {"chat_arrow", ui_vp_chat_arrow}, {"ic_chatlog", ui_ic_chatlog}, {"ic_chatlog_scroll_td", ui_ic_chatlog_scroll_td}, + {"ic_chatlog_scroll_bu", ui_ic_chatlog_scroll_bu}, {"server_chatlog", ui_ooc_chatlog}, {"area_list", ui_area_list}, {"area_search", ui_area_search}, @@ -623,6 +626,10 @@ void Courtroom::set_widgets() set_size_and_pos(ui_ic_chatlog, "ic_chatlog", COURTROOM_DESIGN_INI, ao_app); set_size_and_pos(ui_ic_chatlog_scroll_td, "ic_chatlog_scroll_td", COURTROOM_DESIGN_INI, ao_app); ui_ic_chatlog_scroll_td->set_image("ic_chatlog_scroll_td.png"); + ui_ic_chatlog_scroll_td->hide(); + set_size_and_pos(ui_ic_chatlog_scroll_bu, "ic_chatlog_scroll_bu", COURTROOM_DESIGN_INI, ao_app); + ui_ic_chatlog_scroll_bu->set_image("ic_chatlog_scroll_bu.png"); + ui_ic_chatlog_scroll_bu->hide(); set_size_and_pos(ui_ooc_chatlog, "server_chatlog", COURTROOM_DESIGN_INI, ao_app); From 4e747968a65b7d21865308cdd3df43c4361ba452 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 28 Jan 2022 11:15:17 -0500 Subject: [PATCH 579/842] Redirect qt messages to base/logs/debug.log --- src/main.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 054aee296..ec7b842be 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,65 @@ #include "aoapplication.h" - #include "lobby.h" +#include #include +#include +#include +#include + +void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + QByteArray localMsg = msg.toLocal8Bit(); + const char *file = context.file ? context.file : ""; + const char *function = context.function ? context.function : ""; + + QString output; + switch (type) { + case QtDebugMsg: + output = QString("D"); + break; + case QtInfoMsg: + output = QString("I"); + break; + case QtWarningMsg: + output = QString("W"); + break; + case QtCriticalMsg: + output = QString("C"); + break; + case QtFatalMsg: + output = QString("F"); + break; + } + QString now = QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz"); + const QString raw_msg = localMsg.constData(); + QString final_msg; + // Censor HDID + if (raw_msg.startsWith("S/S: HI#")) + final_msg = "S/S: HI#HDID#%"; + else if (raw_msg.startsWith("M/S: HI#")) + final_msg = "M/S: HI#HDID#%"; + else + final_msg = raw_msg; + + output = QString("[%1] %2: %3").arg(now, output, final_msg); + // Set to true to get full output + bool super_debug = false; + if (super_debug) + output = QString("[%1] %2: %3 (%4:%5, %6)").arg( + now, output, final_msg, file, QString::number(context.line), function); + QFile outFile("base/logs/debug.log"); + outFile.open(QIODevice::WriteOnly | QIODevice::Append); + QTextStream ts(&outFile); + ts << output << Qt::endl; + outFile.close(); +} int main(int argc, char *argv[]) { + qInstallMessageHandler(myMessageOutput); + QFile outFile("base/logs/debug.log"); + outFile.resize(0); // High-DPI support is for Qt version >=5.6. #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) bool l_dpi_scaling = true; From 5a2ace41028bde1b004d295bab693b82b444b39a Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 28 Jan 2022 13:12:03 -0500 Subject: [PATCH 580/842] Readd original message handler as an addon to new one --- src/main.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index ec7b842be..157c2fd8d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,9 @@ #include #include +// Get the default Qt message handler. +static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(0); + void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QByteArray localMsg = msg.toLocal8Bit(); @@ -53,6 +56,9 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QS QTextStream ts(&outFile); ts << output << Qt::endl; outFile.close(); + + // Call the default handler. + (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg); } int main(int argc, char *argv[]) From ec5094e134d931a36ad26cd647f43d508f5ad2d4 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 28 Jan 2022 19:41:13 -0500 Subject: [PATCH 581/842] Move log code to new file --- dronline-client.pro | 2 ++ include/log.h | 7 +++++ src/log.cpp | 70 +++++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 62 ++------------------------------------- 4 files changed, 81 insertions(+), 60 deletions(-) create mode 100644 include/log.h create mode 100644 src/log.cpp diff --git a/dronline-client.pro b/dronline-client.pro index af54f6f2d..fd61b921e 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -62,6 +62,7 @@ HEADERS += \ include/file_functions.h \ include/hardware_functions.h \ include/lobby.h \ + include/log.h \ include/misc_functions.h \ include/theme.h \ include/version.h @@ -121,6 +122,7 @@ SOURCES += \ src/file_functions.cpp \ src/hardware_functions.cpp \ src/lobby.cpp \ + src/log.cpp \ src/main.cpp \ src/master_socket.cpp \ src/misc_functions.cpp \ diff --git a/include/log.h b/include/log.h new file mode 100644 index 000000000..0f3af5ff5 --- /dev/null +++ b/include/log.h @@ -0,0 +1,7 @@ +#ifndef LOG_H +#define LOG_H + +#include +void DROLogger(QtMsgType type, const QMessageLogContext &context, const QString &msg); + +#endif // LOG_H diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 000000000..65e86ba0b --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +#include "log.h" + +// Get the default Qt message handler. +static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(0); + +QString generate_message(QtMsgType type, const QMessageLogContext &context, const QString &msg, const bool super_debug) +{ + QByteArray localMsg = msg.toLocal8Bit(); + const char *file = context.file ? context.file : ""; + const char *function = context.function ? context.function : ""; + + QString output; + switch (type) { + case QtDebugMsg: + output = QString("D"); + break; + case QtInfoMsg: + output = QString("I"); + break; + case QtWarningMsg: + output = QString("W"); + break; + case QtCriticalMsg: + output = QString("C"); + break; + case QtFatalMsg: + output = QString("F"); + break; + } + QString now = QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz"); + const QString raw_msg = localMsg.constData(); + QString final_msg; + // Censor HDID + if (raw_msg.startsWith("S/S: HI#")) + final_msg = "S/S: HI#HDID#%"; + else if (raw_msg.startsWith("M/S: HI#")) + final_msg = "M/S: HI#HDID#%"; + else + final_msg = raw_msg; + + if (super_debug) + output = QString("[%1] %2: %3 (%4:%5, %6)").arg( + now, output, final_msg, file, QString::number(context.line), function); + else + output = QString("[%1] %2: %3").arg(now, output, final_msg); + return output; +} + +void save_log_line(QString log_line) +{ + QFile outFile("base/logs/debug.log"); + outFile.open(QIODevice::WriteOnly | QIODevice::Append); + QTextStream ts(&outFile); + ts << log_line << Qt::endl; + outFile.close(); +} + +void DROLogger(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + const bool super_debug = false; // Set to true for longer log lines + QString log_line = generate_message(type, context, msg, super_debug); + save_log_line(log_line); + // Call the default handler. + (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg); +} diff --git a/src/main.cpp b/src/main.cpp index 157c2fd8d..32822b8f5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,71 +1,13 @@ #include "aoapplication.h" #include "lobby.h" +#include "log.h" -#include #include -#include -#include -#include -// Get the default Qt message handler. -static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(0); - -void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) -{ - QByteArray localMsg = msg.toLocal8Bit(); - const char *file = context.file ? context.file : ""; - const char *function = context.function ? context.function : ""; - - QString output; - switch (type) { - case QtDebugMsg: - output = QString("D"); - break; - case QtInfoMsg: - output = QString("I"); - break; - case QtWarningMsg: - output = QString("W"); - break; - case QtCriticalMsg: - output = QString("C"); - break; - case QtFatalMsg: - output = QString("F"); - break; - } - QString now = QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz"); - const QString raw_msg = localMsg.constData(); - QString final_msg; - // Censor HDID - if (raw_msg.startsWith("S/S: HI#")) - final_msg = "S/S: HI#HDID#%"; - else if (raw_msg.startsWith("M/S: HI#")) - final_msg = "M/S: HI#HDID#%"; - else - final_msg = raw_msg; - - output = QString("[%1] %2: %3").arg(now, output, final_msg); - // Set to true to get full output - bool super_debug = false; - if (super_debug) - output = QString("[%1] %2: %3 (%4:%5, %6)").arg( - now, output, final_msg, file, QString::number(context.line), function); - QFile outFile("base/logs/debug.log"); - outFile.open(QIODevice::WriteOnly | QIODevice::Append); - QTextStream ts(&outFile); - ts << output << Qt::endl; - outFile.close(); - - // Call the default handler. - (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg); -} int main(int argc, char *argv[]) { - qInstallMessageHandler(myMessageOutput); - QFile outFile("base/logs/debug.log"); - outFile.resize(0); + qInstallMessageHandler(DROLogger); // High-DPI support is for Qt version >=5.6. #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) bool l_dpi_scaling = true; From a1b44944498fbc9f0de5ec871321e8d147f164c7 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 28 Jan 2022 21:30:13 -0500 Subject: [PATCH 582/842] Add truncated log support --- src/log.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/log.cpp b/src/log.cpp index 65e86ba0b..1699fda56 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -53,11 +54,26 @@ QString generate_message(QtMsgType type, const QMessageLogContext &context, cons void save_log_line(QString log_line) { - QFile outFile("base/logs/debug.log"); - outFile.open(QIODevice::WriteOnly | QIODevice::Append); - QTextStream ts(&outFile); + // Log should only be up to some size, then rename existing log file to something else + // At any point there should be at most two log files: the current one, and the one + // generated immediately before. + int max_size = 100; // 10 MB + QFile outFileA("base/logs/debug.log"); + if (outFileA.size() > max_size) + { + // Prevent concurrency issues + QLockFile lock_file("base/logs/debugLock"); + lock_file.lock(); + QFile outFileB("base/logs/debugB.log"); + if (outFileB.exists()) + outFileB.remove(); + outFileA.rename("base/logs/debugB.log"); + lock_file.unlock(); + } + outFileA.open(QIODevice::WriteOnly | QIODevice::Append); + QTextStream ts(&outFileA); ts << log_line << Qt::endl; - outFile.close(); + outFileA.close(); } void DROLogger(QtMsgType type, const QMessageLogContext &context, const QString &msg) From 6607d3699eba38fcb79beae0f47ed8db50a87c82 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 28 Jan 2022 22:13:27 -0500 Subject: [PATCH 583/842] Properly handle secondary log destruction --- src/charselect.cpp | 2 +- src/courtroom.cpp | 2 +- src/log.cpp | 55 +++++++++++++++++++++++++++++----------------- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/charselect.cpp b/src/charselect.cpp index 850e97832..8247e5768 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -198,7 +198,7 @@ void Courtroom::char_clicked(int n_char) } ao_app->send_server_packet( - DRPacket("CC", {QString::number(ao_app->get_client_id()), QString::number(n_real_char), get_hdid()})); + DRPacket("CC", {QString::number(ao_app->get_client_id()), QString::number(n_real_char), "HDID"})); } void Courtroom::char_mouse_entered(AOCharButton *p_caller) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 21142b8fd..66f3688c4 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2120,7 +2120,7 @@ void Courtroom::on_back_to_lobby_clicked() void Courtroom::on_spectator_clicked() { - ao_app->send_server_packet(DRPacket("CC", {QString::number(ao_app->get_client_id()), "-1", get_hdid()})); + ao_app->send_server_packet(DRPacket("CC", {QString::number(ao_app->get_client_id()), "-1", "HDID"})); } void Courtroom::on_call_mod_clicked() diff --git a/src/log.cpp b/src/log.cpp index 1699fda56..6cb76a71c 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -33,7 +34,6 @@ QString generate_message(QtMsgType type, const QMessageLogContext &context, cons output = QString("F"); break; } - QString now = QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz"); const QString raw_msg = localMsg.constData(); QString final_msg; // Censor HDID @@ -44,36 +44,51 @@ QString generate_message(QtMsgType type, const QMessageLogContext &context, cons else final_msg = raw_msg; + QString now = QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz"); + QString pid = QString::number(QCoreApplication::applicationPid()); + if (super_debug) - output = QString("[%1] %2: %3 (%4:%5, %6)").arg( - now, output, final_msg, file, QString::number(context.line), function); + output = QString("[%1] [%2] %3: %4 (%5:%6, %7)").arg( + now, pid, output, final_msg, file, QString::number(context.line), function); else - output = QString("[%1] %2: %3").arg(now, output, final_msg); + output = QString("[%1] [%2] %3: %4").arg(now, pid, output, final_msg); return output; } -void save_log_line(QString log_line) +void check_log_size() { // Log should only be up to some size, then rename existing log file to something else // At any point there should be at most two log files: the current one, and the one // generated immediately before. - int max_size = 100; // 10 MB + int max_size = 1e7; // 10 MB QFile outFileA("base/logs/debug.log"); - if (outFileA.size() > max_size) - { - // Prevent concurrency issues - QLockFile lock_file("base/logs/debugLock"); - lock_file.lock(); - QFile outFileB("base/logs/debugB.log"); - if (outFileB.exists()) - outFileB.remove(); - outFileA.rename("base/logs/debugB.log"); - lock_file.unlock(); - } - outFileA.open(QIODevice::WriteOnly | QIODevice::Append); - QTextStream ts(&outFileA); + if (outFileA.size() <= max_size) + return; + + // Prevent concurrency issues with the lock + QLockFile lock_file("base/logs/debugLock"); + lock_file.lock(); + QFile outFileB("base/logs/debugB.log"); + if (outFileB.exists()) + outFileB.remove(); + outFileA.rename("base/logs/debugB.log"); + + // Make sure there is an empty file available always + QFile outFileC("base/logs/debug.log"); + outFileC.open(QIODevice::WriteOnly | QIODevice::Append); + outFileC.close(); + lock_file.unlock(); +} + +void save_log_line(QString log_line) +{ + check_log_size(); + + QFile outFile("base/logs/debug.log"); + outFile.open(QIODevice::WriteOnly | QIODevice::Append); + QTextStream ts(&outFile); ts << log_line << Qt::endl; - outFileA.close(); + outFile.close(); } void DROLogger(QtMsgType type, const QMessageLogContext &context, const QString &msg) From 66079c4507b0ede80551f2aac0339b73d033eee7 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 28 Jan 2022 22:28:07 -0500 Subject: [PATCH 584/842] Fix Ubuntu newline compilation error --- src/log.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/log.cpp b/src/log.cpp index 6cb76a71c..fe45d0bf9 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -87,7 +87,11 @@ void save_log_line(QString log_line) QFile outFile("base/logs/debug.log"); outFile.open(QIODevice::WriteOnly | QIODevice::Append); QTextStream ts(&outFile); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) ts << log_line << Qt::endl; +#else + ts << log_line << "\n"; // Hopefully Windows users don't compile with Date: Fri, 28 Jan 2022 23:00:05 -0500 Subject: [PATCH 585/842] Fix version not listing time of compilation --- src/version.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.cpp b/src/version.cpp index 8d08e7d4b..4482cb026 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -59,7 +59,7 @@ QString get_about_message() "https://github.com/AttorneyOnline/AO2-Client" "

    Running on Qt version %2 with the BASS %3 audio engine.
    " "APNG plugin loaded: %4" - "

    Built on %5
    ") + "

    Built on %5 %6
    ") .arg(get_version_string()) .arg(QLatin1String(QT_VERSION_STR)) .arg(QLatin1String(BASSVERSIONTEXT)) From a261339307ad5a3170958c0fe804cbe806c7b790 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 29 Jan 2022 09:43:09 -0500 Subject: [PATCH 586/842] Add start and close logging messages --- include/log.h | 1 + src/aoapplication.cpp | 1 + src/log.cpp | 1 + src/main.cpp | 1 + 4 files changed, 4 insertions(+) diff --git a/include/log.h b/include/log.h index 0f3af5ff5..c8d9951f7 100644 --- a/include/log.h +++ b/include/log.h @@ -2,6 +2,7 @@ #define LOG_H #include + void DROLogger(QtMsgType type, const QMessageLogContext &context, const QString &msg); #endif // LOG_H diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 1110fe873..b145a38ea 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -66,6 +66,7 @@ AOApplication::~AOApplication() { destruct_lobby(); destruct_courtroom(); + qInfo() << "Closing Danganronpa Online..."; } int AOApplication::get_client_id() const diff --git a/src/log.cpp b/src/log.cpp index fe45d0bf9..5bbaa464d 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include diff --git a/src/main.cpp b/src/main.cpp index 32822b8f5..cd61a902b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ int main(int argc, char *argv[]) { qInstallMessageHandler(DROLogger); + qInfo() << "Starting Danganronpa Online..."; // High-DPI support is for Qt version >=5.6. #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) bool l_dpi_scaling = true; From 77cae276a8e5e675ff29f7f90fc45ceb0ff2a57e Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 4 Feb 2022 14:56:09 -0500 Subject: [PATCH 587/842] Make changing emotes not change current selected SFX --- src/emotes.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/emotes.cpp b/src/emotes.cpp index 42540bc40..fbd4e4c79 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -196,8 +196,6 @@ void Courtroom::select_emote(int p_id) else ui_pre->setChecked(emote_mod == 1 || ao_config->always_pre_enabled()); - select_default_sfx(); - ui_emote_dropdown->setCurrentIndex(m_emote_id); ui_ic_chat_message->setFocus(); From ce7faa00b5613a328dc06b1b78b7672b19b3eef3 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 5 Feb 2022 18:18:20 -0500 Subject: [PATCH 588/842] Make sticky sfx option toggleable in config panel --- include/aoconfig.h | 3 +++ include/aoconfigpanel.h | 1 + res/ui/config_panel.ui | 16 +++++++++++++++- src/aoconfig.cpp | 16 ++++++++++++++++ src/aoconfigpanel.cpp | 4 ++++ src/emotes.cpp | 3 +++ 6 files changed, 42 insertions(+), 1 deletion(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index 46fce7240..6a6767da9 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -41,6 +41,7 @@ class AOConfig : public QObject bool always_pre_enabled() const; int chat_tick_interval() const; bool emote_preview_enabled() const; + bool sticky_sfx_enabled() const; int log_max_lines() const; bool log_display_timestamp_enabled() const; bool log_display_self_highlight_enabled() const; @@ -91,6 +92,7 @@ public slots: void set_always_pre(bool p_enabled); void set_chat_tick_interval(int p_number); void set_emote_preview(bool p_enabled); + void set_sticky_sfx(bool p_enabled); void set_log_max_lines(int p_number); void set_log_display_timestamp(bool p_enabled); void set_log_display_self_highlight(bool p_enabled); @@ -141,6 +143,7 @@ public slots: void always_pre_changed(bool); void chat_tick_interval_changed(int); void emote_preview_changed(bool); + void sticky_sfx_changed(bool); // log void log_max_lines_changed(int); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index 032b418a7..df79cba53 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -93,6 +93,7 @@ private slots: QCheckBox *ui_always_pre = nullptr; QSpinBox *ui_chat_tick_interval = nullptr; QCheckBox *ui_emote_preview = nullptr; + QCheckBox *ui_sticky_sfx = nullptr; // IC Chatlog QSpinBox *ui_log_max_lines = nullptr; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index ca669f52f..4db6f2fac 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 4 + 1 @@ -436,6 +436,20 @@ + + + + Sticky SFX: + + + + + + + + + + diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 2fb58a8af..13c61be57 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -65,6 +65,7 @@ private slots: bool always_pre; int chat_tick_interval; bool emote_preview; + bool sticky_sfx; int log_max_lines; bool log_display_timestamp; bool log_display_self_highlight; @@ -134,6 +135,7 @@ void AOConfigPrivate::read_file() always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); emote_preview = cfg.value("emote_preview", true).toBool(); + sticky_sfx = cfg.value("sticky_sfx", false).toBool(); log_max_lines = cfg.value("chatlog_limit", 100).toInt(); log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); log_display_timestamp = cfg.value("chatlog_display_timestamp", true).toBool(); @@ -206,6 +208,7 @@ void AOConfigPrivate::save_file() cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("emote_preview", emote_preview); + cfg.setValue("sticky_sfx", sticky_sfx); cfg.setValue("chatlog_limit", log_max_lines); cfg.setValue("chatlog_display_timestamp", log_display_timestamp); cfg.setValue("chatlog_display_self_highlight", log_display_self_highlight); @@ -446,6 +449,11 @@ bool AOConfig::emote_preview_enabled() const return d->emote_preview; } +bool AOConfig::sticky_sfx_enabled() const +{ + return d->sticky_sfx; +} + int AOConfig::log_max_lines() const { return d->log_max_lines; @@ -728,6 +736,14 @@ void AOConfig::set_emote_preview(bool p_enabled) d->invoke_signal("emote_preview_changed", Q_ARG(bool, p_enabled)); } +void AOConfig::set_sticky_sfx(bool p_enabled) +{ + if (d->sticky_sfx == p_enabled) + return; + d->sticky_sfx = p_enabled; + d->invoke_signal("sticky_sfx_changed", Q_ARG(bool, p_enabled)); +} + void AOConfig::set_log_max_lines(int p_number) { if (d->log_max_lines == p_number) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index cca089ef1..a6cfe5266 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -62,6 +62,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); ui_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); ui_emote_preview = AO_GUI_WIDGET(QCheckBox, "emote_preview"); + ui_sticky_sfx = AO_GUI_WIDGET(QCheckBox, "sticky_sfx"); // IC Chatlog ui_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); @@ -130,6 +131,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(always_pre_changed(bool)), ui_always_pre, SLOT(setChecked(bool))); connect(m_config, SIGNAL(chat_tick_interval_changed(int)), ui_chat_tick_interval, SLOT(setValue(int))); connect(m_config, SIGNAL(emote_preview_changed(bool)), ui_emote_preview, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(sticky_sfx_changed(bool)), ui_sticky_sfx, SLOT(setChecked(bool))); // log connect(m_config, SIGNAL(log_max_lines_changed(int)), ui_log_max_lines, SLOT(setValue(int))); @@ -194,6 +196,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(ui_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); connect(ui_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); connect(ui_emote_preview, SIGNAL(toggled(bool)), m_config, SLOT(set_emote_preview(bool))); + connect(ui_sticky_sfx, SIGNAL(toggled(bool)), m_config, SLOT(set_sticky_sfx(bool))); // out, log connect(ui_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); @@ -242,6 +245,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_always_pre->setChecked(m_config->always_pre_enabled()); ui_chat_tick_interval->setValue(m_config->chat_tick_interval()); ui_emote_preview->setChecked(m_config->emote_preview_enabled()); + ui_sticky_sfx->setChecked(m_config->sticky_sfx_enabled()); // log ui_log_max_lines->setValue(m_config->log_max_lines()); diff --git a/src/emotes.cpp b/src/emotes.cpp index fbd4e4c79..cd7f40919 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -196,6 +196,9 @@ void Courtroom::select_emote(int p_id) else ui_pre->setChecked(emote_mod == 1 || ao_config->always_pre_enabled()); + if (!ao_config->sticky_sfx_enabled()) + select_default_sfx(); + ui_emote_dropdown->setCurrentIndex(m_emote_id); ui_ic_chat_message->setFocus(); From 45563822366686e5c87ef5e10fbfbc8182c641f1 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 5 Feb 2022 19:54:55 -0500 Subject: [PATCH 589/842] Fix unmaximizing giving wrong size if player launched with theme of different size to current one --- include/courtroom.h | 1 + include/lobby.h | 4 ++++ src/courtroom.cpp | 14 ++++++++++++++ src/lobby.cpp | 14 ++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/include/courtroom.h b/include/courtroom.h index 4f59788df..47778f6bb 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -739,6 +739,7 @@ public slots: // QWidget interface protected: void closeEvent(QCloseEvent *event) override; + virtual void changeEvent(QEvent *event) override; }; template void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) diff --git a/include/lobby.h b/include/lobby.h index 977fe72a7..6b047727d 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -83,6 +83,10 @@ private slots: void on_about_clicked(); void on_server_list_clicked(QModelIndex p_model); void on_chatfield_return_pressed(); + + // QWidget interface +protected: + virtual void changeEvent(QEvent *event) override; }; #endif // LOBBY_H diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 21142b8fd..ba6acca08 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -39,6 +39,7 @@ #include #include #include +#include const int Courtroom::DEFAULT_WIDTH = 714; const int Courtroom::DEFAULT_HEIGHT = 668; @@ -2233,6 +2234,19 @@ void Courtroom::closeEvent(QCloseEvent *event) QMainWindow::closeEvent(event); } +void Courtroom::changeEvent(QEvent* e ) +{ + if (e->type() == QEvent::WindowStateChange) + { + QWindowStateChangeEvent* event = static_cast< QWindowStateChangeEvent* >( e ); + const Qt::WindowStates oldState = event->oldState(); + const Qt::WindowStates newState = this->windowState(); + + if (oldState == Qt::WindowMaximized && newState == Qt::WindowNoState) + resize(size()); + } +} + void Courtroom::on_set_notes_clicked() { if (ui_note_scroll_area->isHidden()) diff --git a/src/lobby.cpp b/src/lobby.cpp index 3b4958fe3..6240f3bd5 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -19,6 +19,7 @@ #include #include #include +#include Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() { @@ -408,3 +409,16 @@ void Lobby::set_player_count(int players_online, int max_players) l_text.replace(l_regex, "\\1"); ui_description->setHtml(l_text.replace("\n", "
    ")); } + +void Lobby::changeEvent(QEvent* e) +{ + if (e->type() == QEvent::WindowStateChange) + { + QWindowStateChangeEvent* event = static_cast< QWindowStateChangeEvent* >( e ); + const Qt::WindowStates oldState = event->oldState(); + const Qt::WindowStates newState = this->windowState(); + + if (oldState == Qt::WindowMaximized && newState == Qt::WindowNoState) + resize(size()); + } +} From 037c26010361c000fa5cb868d3d5f664f288d103 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 11 Feb 2022 01:55:58 +0100 Subject: [PATCH 590/842] Cleaned up log code a bit, added verbose logging function --- include/log.h | 2 + src/log.cpp | 127 +++++++++++++++++++++++++----------------------- src/main.cpp | 1 - src/version.cpp | 2 +- 4 files changed, 69 insertions(+), 63 deletions(-) diff --git a/include/log.h b/include/log.h index c8d9951f7..86de1c006 100644 --- a/include/log.h +++ b/include/log.h @@ -5,4 +5,6 @@ void DROLogger(QtMsgType type, const QMessageLogContext &context, const QString &msg); +void set_verbose_logging(bool enabled); + #endif // LOG_H diff --git a/src/log.cpp b/src/log.cpp index 5bbaa464d..18cb3113f 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -1,3 +1,5 @@ +#include "log.h" + #include #include #include @@ -6,36 +8,41 @@ #include #include -#include "log.h" - // Get the default Qt message handler. static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(0); +static const int C_DEBUG_FILE_MAX_SIZE = 1e5; // 10 MB +static const QString C_DEBUG_FILE = "base/logs/debug.log"; +static const QString C_DEBUG_FILE_LOCK = "base/logs/debug.log.lock"; +static const QString C_DEBUG_B_FILE = "base/logs/debug_b.log"; +static bool s_verbose_logging = false; -QString generate_message(QtMsgType type, const QMessageLogContext &context, const QString &msg, const bool super_debug) +QString generate_message(QtMsgType p_type, const QMessageLogContext &p_context, const QString &p_message) { - QByteArray localMsg = msg.toLocal8Bit(); - const char *file = context.file ? context.file : ""; - const char *function = context.function ? context.function : ""; + QByteArray l_localMsg = p_message.toLocal8Bit(); + const QString l_fileName(p_context.file); + const QString l_function(p_context.function); - QString output; - switch (type) { + QString l_output; + switch (p_type) + { case QtDebugMsg: - output = QString("D"); - break; + l_output = QString("D"); + break; case QtInfoMsg: - output = QString("I"); - break; + l_output = QString("I"); + break; case QtWarningMsg: - output = QString("W"); - break; + l_output = QString("W"); + break; case QtCriticalMsg: - output = QString("C"); - break; + l_output = QString("C"); + break; case QtFatalMsg: - output = QString("F"); - break; + l_output = QString("F"); + break; } - const QString raw_msg = localMsg.constData(); + + const QString raw_msg = l_localMsg.constData(); QString final_msg; // Censor HDID if (raw_msg.startsWith("S/S: HI#")) @@ -45,62 +52,60 @@ QString generate_message(QtMsgType type, const QMessageLogContext &context, cons else final_msg = raw_msg; - QString now = QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz"); - QString pid = QString::number(QCoreApplication::applicationPid()); + const QString now = QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz"); + const QString pid = QString::number(QCoreApplication::applicationPid()); - if (super_debug) - output = QString("[%1] [%2] %3: %4 (%5:%6, %7)").arg( - now, pid, output, final_msg, file, QString::number(context.line), function); - else - output = QString("[%1] [%2] %3: %4").arg(now, pid, output, final_msg); - return output; -} + l_output = QString("[%1] [%2] %3: ").arg(now, pid, l_output); + l_output.append(final_msg); -void check_log_size() -{ - // Log should only be up to some size, then rename existing log file to something else - // At any point there should be at most two log files: the current one, and the one - // generated immediately before. - int max_size = 1e7; // 10 MB - QFile outFileA("base/logs/debug.log"); - if (outFileA.size() <= max_size) - return; + if (s_verbose_logging) + l_output.append(QString(" (%1:%2, %3)").arg(l_fileName, QString::number(p_context.line), l_function)); - // Prevent concurrency issues with the lock - QLockFile lock_file("base/logs/debugLock"); - lock_file.lock(); - QFile outFileB("base/logs/debugB.log"); - if (outFileB.exists()) - outFileB.remove(); - outFileA.rename("base/logs/debugB.log"); - - // Make sure there is an empty file available always - QFile outFileC("base/logs/debug.log"); - outFileC.open(QIODevice::WriteOnly | QIODevice::Append); - outFileC.close(); - lock_file.unlock(); + return l_output; } -void save_log_line(QString log_line) +void save_log_line(QString p_log_line) { - check_log_size(); + QLockFile l_log_lock(C_DEBUG_FILE_LOCK); + l_log_lock.lock(); - QFile outFile("base/logs/debug.log"); - outFile.open(QIODevice::WriteOnly | QIODevice::Append); - QTextStream ts(&outFile); + QFile l_log_file(C_DEBUG_FILE); + if (!l_log_file.open(QFile::WriteOnly | QFile::Append)) + { + qInstallMessageHandler(0); + qCritical().noquote() << QString("Failed to open debug.log! error: %1").arg(l_log_file.errorString()); + l_log_lock.unlock(); + return; + } + + QTextStream in(&l_log_file); #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - ts << log_line << Qt::endl; + in << p_log_line << Qt::endl; #else - ts << log_line << "\n"; // Hopefully Windows users don't compile with = 1e4) + { + if (l_log_file.exists(C_DEBUG_B_FILE)) + l_log_file.remove(C_DEBUG_B_FILE); + l_log_file.rename(C_DEBUG_B_FILE); + } + + l_log_lock.unlock(); } void DROLogger(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - const bool super_debug = false; // Set to true for longer log lines - QString log_line = generate_message(type, context, msg, super_debug); - save_log_line(log_line); + save_log_line(generate_message(type, context, msg)); + // Call the default handler. (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg); } + +void set_verbose_logging(bool enabled) +{ + s_verbose_logging = enabled; +} diff --git a/src/main.cpp b/src/main.cpp index cd61a902b..79d20bf98 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,7 +4,6 @@ #include - int main(int argc, char *argv[]) { qInstallMessageHandler(DROLogger); diff --git a/src/version.cpp b/src/version.cpp index 8d08e7d4b..4482cb026 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -59,7 +59,7 @@ QString get_about_message() "https://github.com/AttorneyOnline/AO2-Client" "

    Running on Qt version %2 with the BASS %3 audio engine.
    " "APNG plugin loaded: %4" - "

    Built on %5
    ") + "

    Built on %5 %6
    ") .arg(get_version_string()) .arg(QLatin1String(QT_VERSION_STR)) .arg(QLatin1String(BASSVERSIONTEXT)) From 3a301d6de3eabe6c0f00f7ce1322420f8c397162 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 11 Feb 2022 01:58:19 +0100 Subject: [PATCH 591/842] Max log size is now 10 MB once again. --- src/log.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/log.cpp b/src/log.cpp index 18cb3113f..c2b8bad2c 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -87,7 +87,7 @@ void save_log_line(QString p_log_line) l_log_file.close(); // rename the file if now too big - if (l_log_file.size() >= 1e4) + if (l_log_file.size() >= C_DEBUG_FILE_MAX_SIZE) { if (l_log_file.exists(C_DEBUG_B_FILE)) l_log_file.remove(C_DEBUG_B_FILE); From acb0ba3d35e826059bb7a2f4bd0d984393fa0294 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 11 Feb 2022 02:53:55 +0100 Subject: [PATCH 592/842] Replaced scrollbar orientation abbreviations with the actual words --- include/courtroom.h | 8 ++++---- src/courtroom.cpp | 28 ++++++++++------------------ src/courtroom_widgets.cpp | 27 +++++++++++++-------------- 3 files changed, 27 insertions(+), 36 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index a47739d7d..bc2ba0183 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -374,8 +374,8 @@ class Courtroom : public QMainWindow DRTextEdit *ui_ic_chatlog = nullptr; QList m_ic_record_list; QQueue m_ic_record_queue; - AOButton *ui_ic_chatlog_scroll_td = nullptr; - AOButton *ui_ic_chatlog_scroll_bu = nullptr; + AOButton *ui_ic_chatlog_scroll_topdown = nullptr; + AOButton *ui_ic_chatlog_scroll_bottomup = nullptr; DRChatLog *ui_ooc_chatlog = nullptr; @@ -570,8 +570,8 @@ private slots: void on_chat_config_changed(); void on_ic_chatlog_scroll_changed(); - void on_ic_chatlog_scroll_td_clicked(); - void on_ic_chatlog_scroll_bu_clicked(); + void on_ic_chatlog_scroll_topdown_clicked(); + void on_ic_chatlog_scroll_bottomup_clicked(); void on_ooc_name_editing_finished(); void on_ooc_return_pressed(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 64317b790..cc165e8ec 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1140,46 +1140,38 @@ void Courtroom::update_ic_log(bool p_reset_log) if (l_is_end_scroll_pos) { l_scrollbar->setValue(l_topdown_orientation ? l_scrollbar->maximum() : l_scrollbar->minimum()); - ui_ic_chatlog_scroll_td->hide(); - ui_ic_chatlog_scroll_bu->hide(); - } - else - { - if (l_topdown_orientation) - ui_ic_chatlog_scroll_td->show(); - else - ui_ic_chatlog_scroll_bu->show(); } } void Courtroom::on_ic_chatlog_scroll_changed() { - const bool l_topdown_orientation = ao_config->log_is_topdown_enabled(); QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); const int l_scroll_pos = l_scrollbar->value(); - const bool l_is_end_scroll_pos = (l_topdown_orientation ? - l_scroll_pos == l_scrollbar->maximum(): l_scroll_pos == l_scrollbar->minimum()); + const bool l_topdown_orientation = ao_config->log_is_topdown_enabled(); + const bool l_is_end_scroll_pos = + (l_topdown_orientation ? l_scroll_pos == l_scrollbar->maximum() : l_scroll_pos == l_scrollbar->minimum()); + if (l_is_end_scroll_pos) { - ui_ic_chatlog_scroll_td->hide(); - ui_ic_chatlog_scroll_bu->hide(); + ui_ic_chatlog_scroll_topdown->hide(); + ui_ic_chatlog_scroll_bottomup->hide(); } else { if (l_topdown_orientation) - ui_ic_chatlog_scroll_td->show(); + ui_ic_chatlog_scroll_topdown->show(); else - ui_ic_chatlog_scroll_bu->show(); + ui_ic_chatlog_scroll_bottomup->show(); } } -void Courtroom::on_ic_chatlog_scroll_td_clicked() +void Courtroom::on_ic_chatlog_scroll_topdown_clicked() { QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); l_scrollbar->setValue(l_scrollbar->maximum()); } -void Courtroom::on_ic_chatlog_scroll_bu_clicked() +void Courtroom::on_ic_chatlog_scroll_bottomup_clicked() { QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); l_scrollbar->setValue(l_scrollbar->minimum()); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 3dd85855e..362201507 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -112,8 +112,8 @@ void Courtroom::create_widgets() ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); ui_ic_chatlog->set_auto_align(false); - ui_ic_chatlog_scroll_td = new AOButton(this, ao_app); - ui_ic_chatlog_scroll_bu = new AOButton(this, ao_app); + ui_ic_chatlog_scroll_topdown = new AOButton(this, ao_app); + ui_ic_chatlog_scroll_bottomup = new AOButton(this, ao_app); ui_ooc_chatlog = new DRChatLog(this); ui_ooc_chatlog->setReadOnly(true); @@ -275,10 +275,9 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(on_character_ini_changed())); connect(ui_ic_chat_showname, SIGNAL(editingFinished()), this, SLOT(on_ic_showname_editing_finished())); connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ic_message_return_pressed())); - connect(ui_ic_chatlog->verticalScrollBar(), SIGNAL(valueChanged(int)), this, - SLOT(on_ic_chatlog_scroll_changed())); - connect(ui_ic_chatlog_scroll_td, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_td_clicked())); - connect(ui_ic_chatlog_scroll_bu, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_bu_clicked())); + connect(ui_ic_chatlog->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(on_ic_chatlog_scroll_changed())); + connect(ui_ic_chatlog_scroll_topdown, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_topdown_clicked())); + connect(ui_ic_chatlog_scroll_bottomup, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_bottomup_clicked())); connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); connect(ui_ooc_chat_name, SIGNAL(editingFinished()), this, SLOT(on_ooc_name_editing_finished())); connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); @@ -367,8 +366,8 @@ void Courtroom::reset_widget_names() {"vp_objection", ui_vp_objection}, {"chat_arrow", ui_vp_chat_arrow}, {"ic_chatlog", ui_ic_chatlog}, - {"ic_chatlog_scroll_td", ui_ic_chatlog_scroll_td}, - {"ic_chatlog_scroll_bu", ui_ic_chatlog_scroll_bu}, + {"ic_chatlog_scroll_topdown", ui_ic_chatlog_scroll_topdown}, + {"ic_chatlog_scroll_bottomup", ui_ic_chatlog_scroll_bottomup}, {"server_chatlog", ui_ooc_chatlog}, {"area_list", ui_area_list}, {"area_search", ui_area_search}, @@ -624,12 +623,12 @@ void Courtroom::set_widgets() ui_vp_objection->combo_resize(ui_viewport->width(), ui_viewport->height()); set_size_and_pos(ui_ic_chatlog, "ic_chatlog", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_ic_chatlog_scroll_td, "ic_chatlog_scroll_td", COURTROOM_DESIGN_INI, ao_app); - ui_ic_chatlog_scroll_td->set_image("ic_chatlog_scroll_td.png"); - ui_ic_chatlog_scroll_td->hide(); - set_size_and_pos(ui_ic_chatlog_scroll_bu, "ic_chatlog_scroll_bu", COURTROOM_DESIGN_INI, ao_app); - ui_ic_chatlog_scroll_bu->set_image("ic_chatlog_scroll_bu.png"); - ui_ic_chatlog_scroll_bu->hide(); + set_size_and_pos(ui_ic_chatlog_scroll_topdown, "ic_chatlog_scroll_topdown", COURTROOM_DESIGN_INI, ao_app); + ui_ic_chatlog_scroll_topdown->set_image("ic_chatlog_scroll_topdown.png"); + ui_ic_chatlog_scroll_topdown->hide(); + set_size_and_pos(ui_ic_chatlog_scroll_bottomup, "ic_chatlog_scroll_bottomup", COURTROOM_DESIGN_INI, ao_app); + ui_ic_chatlog_scroll_bottomup->set_image("ic_chatlog_scroll_bottomup.png"); + ui_ic_chatlog_scroll_bottomup->hide(); set_size_and_pos(ui_ooc_chatlog, "server_chatlog", COURTROOM_DESIGN_INI, ao_app); From 009d72bb32f249f894c6ca38463ebb602f060ec1 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 11 Feb 2022 15:39:34 +0100 Subject: [PATCH 593/842] Reworked entirely. * Changing/reloading the theme will now reset the window states, resize them and then center the main screens (Lobby and Courtroom). --- include/courtroom.h | 1 - include/lobby.h | 4 ---- include/theme.h | 1 + src/aoapplication.cpp | 46 ++++----------------------------------- src/courtroom.cpp | 14 ------------ src/courtroom_widgets.cpp | 15 ++++++------- src/lobby.cpp | 27 +++++------------------ src/theme.cpp | 39 ++++++++++++++++++++++++++++----- 8 files changed, 52 insertions(+), 95 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 47778f6bb..4f59788df 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -739,7 +739,6 @@ public slots: // QWidget interface protected: void closeEvent(QCloseEvent *event) override; - virtual void changeEvent(QEvent *event) override; }; template void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) diff --git a/include/lobby.h b/include/lobby.h index 6b047727d..977fe72a7 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -83,10 +83,6 @@ private slots: void on_about_clicked(); void on_server_list_clicked(QModelIndex p_model); void on_chatfield_return_pressed(); - - // QWidget interface -protected: - virtual void changeEvent(QEvent *event) override; }; #endif // LOBBY_H diff --git a/include/theme.h b/include/theme.h index f29051842..fe0f6bc2a 100644 --- a/include/theme.h +++ b/include/theme.h @@ -14,3 +14,4 @@ void set_text_alignment(QWidget *widget, QString identifier, QString ini_file, A void set_font(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); void set_drtextedit_font(DRTextEdit *widget, QString identifier, QString ini_file, AOApplication *ao_app); bool set_stylesheet(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); +void center_widget_to_screen(QWidget *widget); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 1110fe873..7bfb186af 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -8,18 +8,13 @@ #include "drpacket.h" #include "drserversocket.h" #include "lobby.h" +#include "theme.h" #include "version.h" #include #include #include -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) -#include -#else -#include -#endif - const QString AOApplication::MASTER_NAME = "Master"; const QString AOApplication::MASTER_HOST = "master.aceattorneyonline.com"; const int AOApplication::MASTER_PORT = 27016; @@ -93,19 +88,7 @@ void AOApplication::construct_lobby() m_lobby = new Lobby(this); is_lobby_constructed = true; - -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - QRect screen_geometry = QApplication::desktop()->screenGeometry(); -#else - QScreen *screen = QApplication::screenAt(m_lobby->pos()); - if (screen == nullptr) - return; - QRect screen_geometry = screen->geometry(); -#endif - int x = (screen_geometry.width() - m_lobby->width()) / 2; - int y = (screen_geometry.height() - m_lobby->height()) / 2; - m_lobby->move(x, y); - + center_widget_to_screen(m_lobby); m_lobby->show(); dr_discord->set_state(DRDiscord::State::Idle); @@ -141,18 +124,7 @@ void AOApplication::construct_courtroom() connect(m_courtroom, SIGNAL(closing()), this, SLOT(on_courtroom_closing())); connect(m_courtroom, SIGNAL(destroyed()), this, SLOT(on_courtroom_destroyed())); is_courtroom_constructed = true; - -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - QRect screen_geometry = QApplication::desktop()->screenGeometry(); -#else - QScreen *screen = QApplication::screenAt(m_courtroom->pos()); - if (screen == nullptr) - return; - QRect screen_geometry = screen->geometry(); -#endif - int x = (screen_geometry.width() - m_courtroom->width()) / 2; - int y = (screen_geometry.height() - m_courtroom->height()) / 2; - m_courtroom->move(x, y); + center_widget_to_screen(m_courtroom); } void AOApplication::destruct_courtroom() @@ -253,19 +225,9 @@ void AOApplication::toggle_config_panel() ao_config_panel->setVisible(!ao_config_panel->isVisible()); if (ao_config_panel->isVisible()) { -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - QRect screen_geometry = QApplication::desktop()->screenGeometry(); -#else - QScreen *screen = QApplication::screenAt(ao_config_panel->pos()); - if (screen == nullptr) - return; - QRect screen_geometry = screen->geometry(); -#endif - int x = (screen_geometry.width() - ao_config_panel->width()) / 2; - int y = (screen_geometry.height() - ao_config_panel->height()) / 2; ao_config_panel->setFocus(); ao_config_panel->raise(); - ao_config_panel->move(x, y); + center_widget_to_screen(ao_config_panel); } } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index ba6acca08..21142b8fd 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -39,7 +39,6 @@ #include #include #include -#include const int Courtroom::DEFAULT_WIDTH = 714; const int Courtroom::DEFAULT_HEIGHT = 668; @@ -2234,19 +2233,6 @@ void Courtroom::closeEvent(QCloseEvent *event) QMainWindow::closeEvent(event); } -void Courtroom::changeEvent(QEvent* e ) -{ - if (e->type() == QEvent::WindowStateChange) - { - QWindowStateChangeEvent* event = static_cast< QWindowStateChangeEvent* >( e ); - const Qt::WindowStates oldState = event->oldState(); - const Qt::WindowStates newState = this->windowState(); - - if (oldState == Qt::WindowMaximized && newState == Qt::WindowNoState) - resize(size()); - } -} - void Courtroom::on_set_notes_clicked() { if (ui_note_scroll_area->isHidden()) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 946e6222a..4c3608da3 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -552,18 +552,17 @@ void Courtroom::set_widget_layers() void Courtroom::set_widgets() { pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", COURTROOM_DESIGN_INI); - if (f_courtroom.width < 0 || f_courtroom.height < 0) { - qDebug() << "W: did not find courtroom width or height in " << COURTROOM_DESIGN_INI; - - resize(DEFAULT_WIDTH, DEFAULT_HEIGHT); - } - else - { - resize(f_courtroom.width, f_courtroom.height); + qWarning() << "W: did not find courtroom width or height in " << COURTROOM_DESIGN_INI; + f_courtroom.width = DEFAULT_WIDTH; + f_courtroom.height = DEFAULT_HEIGHT; } + setWindowState(Qt::WindowNoState); + resize(f_courtroom.width, f_courtroom.height); + center_widget_to_screen(this); + ui_background->move(0, 0); ui_background->resize(size()); ui_background->set_image("courtroombackground.png"); diff --git a/src/lobby.cpp b/src/lobby.cpp index 6240f3bd5..ff57191db 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -19,7 +19,6 @@ #include #include #include -#include Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() { @@ -92,7 +91,9 @@ void Lobby::set_widgets() if (f_lobby.width < 0 || f_lobby.height < 0) { - qDebug() << "W: did not find lobby width or height in " << LOBBY_DESIGN_INI; + qWarning() << "W: did not find lobby width or height in " << LOBBY_DESIGN_INI; + f_lobby.width = 517; + f_lobby.height = 666; // Most common symptom of bad config files, missing assets, or misnamed // theme folder @@ -107,13 +108,10 @@ void Lobby::set_widgets() "3. If it is there, check that your current theme folder exists in " "base/themes. According to base/config.ini, your current theme is " + ao_config->theme()); - - this->resize(517, 666); - } - else - { - this->resize(f_lobby.width, f_lobby.height); } + setWindowState(Qt::WindowNoState); + resize(f_lobby.width, f_lobby.height); + center_widget_to_screen(this); set_size_and_pos(ui_background, "lobby", LOBBY_DESIGN_INI, ao_app); ui_background->set_image("lobbybackground.png"); @@ -409,16 +407,3 @@ void Lobby::set_player_count(int players_online, int max_players) l_text.replace(l_regex, "\\1"); ui_description->setHtml(l_text.replace("\n", "
    ")); } - -void Lobby::changeEvent(QEvent* e) -{ - if (e->type() == QEvent::WindowStateChange) - { - QWindowStateChangeEvent* event = static_cast< QWindowStateChangeEvent* >( e ); - const Qt::WindowStates oldState = event->oldState(); - const Qt::WindowStates newState = this->windowState(); - - if (oldState == Qt::WindowMaximized && newState == Qt::WindowNoState) - resize(size()); - } -} diff --git a/src/theme.cpp b/src/theme.cpp index 00bba7278..1770dd6c0 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -1,15 +1,22 @@ #include "theme.h" -// src -#include "aoapplication.h" -#include "datatypes.h" -#include "drtextedit.h" +// std +#include // qt #include #include -#include +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) +#include +#else +#include +#endif + +// src +#include "aoapplication.h" +#include "datatypes.h" +#include "drtextedit.h" void set_size_and_pos(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) { @@ -106,3 +113,25 @@ bool set_stylesheet(QWidget *p_widget, QString p_identifier, QString p_ini_file, p_widget->setStyleSheet(p_style); return !p_style.isEmpty(); } + +/** + * @brief Center the widget on the screen it's resting in. + * @param widget The widget to center. + */ +void center_widget_to_screen(QWidget *p_widget) +{ + if (!p_widget || p_widget->parentWidget()) + return; + +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + QRect screen_geometry = QApplication::desktop()->screenGeometry(); +#else + QScreen *screen = QApplication::screenAt(p_widget->pos()); + if (screen == nullptr) + return; + QRect screen_geometry = screen->geometry(); +#endif + int x = (screen_geometry.width() - p_widget->width()) / 2; + int y = (screen_geometry.height() - p_widget->height()) / 2; + p_widget->move(x, y); +} From 4cdfdce4bc787075518c828bcd8b43209cfebbeb Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 15 Feb 2022 15:43:04 -0500 Subject: [PATCH 594/842] Add post version support --- include/version.h | 1 + src/version.cpp | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/version.h b/include/version.h index 5607d241a..430247c38 100644 --- a/include/version.h +++ b/include/version.h @@ -5,5 +5,6 @@ class QString; int get_release_version(); int get_major_version(); int get_minor_version(); +QString get_post_version(); QString get_version_string(); QString get_about_message(); diff --git a/src/version.cpp b/src/version.cpp index 4482cb026..7f8f01a49 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -21,10 +21,20 @@ int get_minor_version() return 2; } +QString get_post_version() +{ + return ""; +} + QString get_version_string() { - return QString::number(get_release_version()) + "." + QString::number(get_major_version()) + "." + - QString::number(get_minor_version()); + const QString post = get_post_version(); + const QString prefix = ( + QString::number(get_release_version()) + "." + QString::number(get_major_version()) + "." + + QString::number(get_minor_version())); + if (post.isEmpty()) + return prefix; + return prefix + "-" + post; } QString get_resource_file_text(QString filename) From e87527ab7851555888f5b53f22eb7480fba87417 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 15 Feb 2022 15:43:45 -0500 Subject: [PATCH 595/842] Bump to 1.1.0-b1 (beta 1) --- dronline-client.pro | 2 +- src/version.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index fd61b921e..206ee548e 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -3,7 +3,7 @@ QT += core gui widgets uitools network CONFIG += c++17 TEMPLATE = app -VERSION = 1.0.2.0 +VERSION = 1.1.0.0 TARGET = dro-client RC_ICONS = icon.ico diff --git a/src/version.cpp b/src/version.cpp index 7f8f01a49..c368c21b1 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -13,17 +13,17 @@ int get_release_version() int get_major_version() { - return 0; + return 1; } int get_minor_version() { - return 2; + return 0; } QString get_post_version() { - return ""; + return "b1"; } QString get_version_string() From 923a468c666cbf1a08ee414ff3297984d4f4ade8 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 16 Feb 2022 18:55:05 +0100 Subject: [PATCH 596/842] Reworked entirely the movie widgets --- dronline-client.pro | 20 +++- include/DRStickerMovie.h | 20 ++++ include/aocharmovie.h | 48 -------- include/aomovie.h | 47 -------- include/aoscene.h | 25 ----- include/courtroom.h | 28 +++-- include/drcharactermovie.cpp | 79 ++++++++++++++ include/drcharactermovie.h | 22 ++++ include/dreffectmovie.cpp | 7 ++ include/dreffectmovie.h | 12 ++ include/drmovie.h | 50 +++++++++ include/drscenemovie.h | 19 ++++ include/drshoutmovie.h | 19 ++++ include/drsplashmovie.cpp | 10 ++ include/drsplashmovie.h | 12 ++ src/aocharmovie.cpp | 151 ------------------------- src/aomovie.cpp | 193 -------------------------------- src/aoscene.cpp | 39 ------- src/courtroom.cpp | 23 ++-- src/courtroom_widgets.cpp | 39 ++++--- src/drmovie.cpp | 206 +++++++++++++++++++++++++++++++++++ src/drscenemovie.cpp | 25 +++++ src/drshoutmovie.cpp | 40 +++++++ src/drstickermovie.cpp | 46 ++++++++ src/emotes.cpp | 8 +- 25 files changed, 631 insertions(+), 557 deletions(-) create mode 100644 include/DRStickerMovie.h delete mode 100644 include/aocharmovie.h delete mode 100644 include/aomovie.h delete mode 100644 include/aoscene.h create mode 100644 include/drcharactermovie.cpp create mode 100644 include/drcharactermovie.h create mode 100644 include/dreffectmovie.cpp create mode 100644 include/dreffectmovie.h create mode 100644 include/drmovie.h create mode 100644 include/drscenemovie.h create mode 100644 include/drshoutmovie.h create mode 100644 include/drsplashmovie.cpp create mode 100644 include/drsplashmovie.h delete mode 100644 src/aocharmovie.cpp delete mode 100644 src/aomovie.cpp delete mode 100644 src/aoscene.cpp create mode 100644 src/drmovie.cpp create mode 100644 src/drscenemovie.cpp create mode 100644 src/drshoutmovie.cpp create mode 100644 src/drstickermovie.cpp diff --git a/dronline-client.pro b/dronline-client.pro index fd61b921e..8d05fadb0 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -15,11 +15,13 @@ DEFINES += DRO_ACKMS CONFIG(debug, debug|release):DEFINES += DR_DEV HEADERS += \ + include/drscenemovie.h \ + include/drsplashmovie.h \ + include/drstickermovie.h \ include/aoapplication.h \ include/aoblipplayer.h \ include/aobutton.h \ include/aocharbutton.h \ - include/aocharmovie.h \ include/aoconfig.h \ include/aoconfigpanel.h \ include/aoemotebutton.h \ @@ -30,14 +32,12 @@ HEADERS += \ include/aoimagedisplay.h \ include/aolabel.h \ include/aolineedit.h \ - include/aomovie.h \ include/aomusicplayer.h \ include/aonotearea.h \ include/aonotepad.h \ include/aonotepicker.h \ include/aoobject.h \ include/aopixmap.h \ - include/aoscene.h \ include/aosfxplayer.h \ include/aoshoutplayer.h \ include/aosystemplayer.h \ @@ -53,10 +53,14 @@ HEADERS += \ include/draudioerror.h \ include/draudiostream.h \ include/draudiostreamfamily.h \ + include/drcharactermovie.h \ include/drchatlog.h \ + include/dreffectmovie.h \ + include/drmovie.h \ include/drpacket.h \ include/drpather.h \ include/drserversocket.h \ + include/drshoutmovie.h \ include/drtextedit.h \ include/drdiscord.h \ include/file_functions.h \ @@ -68,11 +72,13 @@ HEADERS += \ include/version.h SOURCES += \ + include/drcharactermovie.cpp \ + include/dreffectmovie.cpp \ + include/drsplashmovie.cpp \ src/aoapplication.cpp \ src/aoblipplayer.cpp \ src/aobutton.cpp \ src/aocharbutton.cpp \ - src/aocharmovie.cpp \ src/aoconfig.cpp \ src/aoconfigpanel.cpp \ src/aoemotebutton.cpp \ @@ -83,14 +89,12 @@ SOURCES += \ src/aoimagedisplay.cpp \ src/aolabel.cpp \ src/aolineedit.cpp \ - src/aomovie.cpp \ src/aomusicplayer.cpp \ src/aonotearea.cpp \ src/aonotepad.cpp \ src/aonotepicker.cpp \ src/aoobject.cpp \ src/aopixmap.cpp \ - src/aoscene.cpp \ src/aosfxplayer.cpp \ src/aoshoutplayer.cpp \ src/aosystemplayer.cpp \ @@ -112,9 +116,13 @@ SOURCES += \ src/draudiostream.cpp \ src/draudiostreamfamily.cpp \ src/drchatlog.cpp \ + src/drmovie.cpp \ src/drpacket.cpp \ src/drpather.cpp \ + src/drscenemovie.cpp \ src/drserversocket.cpp \ + src/drshoutmovie.cpp \ + src/drstickermovie.cpp \ src/drtextedit.cpp \ src/drdiscord.cpp \ src/emotes.cpp \ diff --git a/include/DRStickerMovie.h b/include/DRStickerMovie.h new file mode 100644 index 000000000..55b8c130a --- /dev/null +++ b/include/DRStickerMovie.h @@ -0,0 +1,20 @@ +#pragma once + +#include "drmovie.h" + +class AOApplication; + +class DRStickerMovie : public DRMovie +{ + Q_OBJECT + +public: + explicit DRStickerMovie(QWidget *parent = nullptr); + ~DRStickerMovie(); + + void play(QString p_file_name, QString p_character); + void play(QString p_file_name); + +private: + AOApplication *ao_app = nullptr; +}; diff --git a/include/aocharmovie.h b/include/aocharmovie.h deleted file mode 100644 index 7040c70e4..000000000 --- a/include/aocharmovie.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef AOCHARMOVIE_H -#define AOCHARMOVIE_H - -class AOApplication; - -#include - -class QMovie; -class QTimer; - -class AOCharMovie : public QLabel -{ - Q_OBJECT - -public: - AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app); - - QString file_name(); - - bool play(QString character, QString emote, QString prefix, bool play_once); - bool play_pre(QString character, QString emote); - bool play_talk(QString character, QString emote); - bool play_idle(QString character, QString emote); - void set_mirrored(bool enabled); - void combo_resize(QSize size); - void stop(); - -signals: - void done(); - -private: - AOApplication *ao_app = nullptr; - - QMovie *m_movie = nullptr; - QTimer *m_frame_timer = nullptr; - QImage m_current_frame; - - bool is_mirrored = false; - bool is_play_once = false; - - void paint_frame(); - -private slots: - void on_frame_changed(int n_frame); - void on_timer_timeout(); -}; - -#endif // AOCHARMOVIE_H diff --git a/include/aomovie.h b/include/aomovie.h deleted file mode 100644 index 6053d956b..000000000 --- a/include/aomovie.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef AOMOVIE_H -#define AOMOVIE_H - -class AOApplication; - -#include -#include - -class AOMovie : public QLabel -{ - Q_OBJECT - -public: - AOMovie(QWidget *p_parent, AOApplication *p_ao_app); - ~AOMovie(); - - bool is_play_once(); - void set_play_once(bool p_play_once); - - bool is_hide_on_done(); - void set_hide_on_done(bool p_hide_on_done); - - void play_file_name(QString file); - void play(QString p_file, QString p_char = ""); - void play_interjection(QString p_char_name, QString p_interjection_name); - void restart(); - void combo_resize(int w, int h); - QMovie::MovieState state(); - void stop(); - -signals: - void done(); - -private: - AOApplication *ao_app = nullptr; - - QMovie *m_movie = nullptr; - bool m_first_loop = false; - bool m_play_once = true; - bool m_hide_on_done = false; - -private slots: - void frame_change(int n_frame); - void handle_state(QMovie::MovieState); -}; - -#endif // AOMOVIE_H diff --git a/include/aoscene.h b/include/aoscene.h deleted file mode 100644 index 25ef697cb..000000000 --- a/include/aoscene.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef AOSCENE_H -#define AOSCENE_H - -class AOApplication; -class Courtroom; - -#include - -class AOScene : public QLabel -{ - Q_OBJECT - -public: - AOScene(QWidget *parent, AOApplication *p_ao_app); - - void set_image(QString p_image); - void combo_resize(QSize p_size); - -private: - AOApplication *ao_app = nullptr; - - QMovie *m_reader = nullptr; -}; - -#endif // AOSCENE_H diff --git a/include/courtroom.h b/include/courtroom.h index 4f59788df..54afa6f8b 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -7,7 +7,6 @@ class AOApplication; class AOBlipPlayer; class AOButton; class AOCharButton; -class AOCharMovie; class AOConfig; class AOEmoteButton; class AOEvidenceButton; @@ -20,12 +19,17 @@ class AOMovie; class AOMusicPlayer; class AONoteArea; class AONotepad; -class AOScene; class AOSfxPlayer; class AOShoutPlayer; class AOSystemPlayer; class AOTimer; +class DRCharacterMovie; class DRChatLog; +class DREffectMovie; +class DRSceneMovie; +class DRShoutMovie; +class DRSplashMovie; +class DRStickerMovie; class DRTextEdit; #include @@ -340,9 +344,9 @@ class Courtroom : public QMainWindow AOImageDisplay *ui_background = nullptr; QWidget *ui_viewport = nullptr; - AOScene *ui_vp_background = nullptr; - AOCharMovie *ui_vp_player_char = nullptr; - AOScene *ui_vp_desk = nullptr; + DRSceneMovie *ui_vp_background = nullptr; + DRCharacterMovie *ui_vp_player_char = nullptr; + DRSceneMovie *ui_vp_desk = nullptr; AOEvidenceDisplay *ui_vp_evidence_display = nullptr; AONoteArea *ui_note_area = nullptr; @@ -353,10 +357,10 @@ class Courtroom : public QMainWindow AOImageDisplay *ui_vp_chatbox = nullptr; DRTextEdit *ui_vp_showname = nullptr; DRTextEdit *ui_vp_message = nullptr; - AOMovie *ui_vp_effect = nullptr; - AOMovie *ui_vp_wtce = nullptr; - AOMovie *ui_vp_objection = nullptr; - AOMovie *ui_vp_chat_arrow = nullptr; + DREffectMovie *ui_vp_effect = nullptr; + DRSplashMovie *ui_vp_wtce = nullptr; + DRShoutMovie *ui_vp_objection = nullptr; + DRStickerMovie *ui_vp_chat_arrow = nullptr; AOImageDisplay *ui_vp_music_display_a = nullptr; AOImageDisplay *ui_vp_music_display_b = nullptr; @@ -368,7 +372,7 @@ class Courtroom : public QMainWindow QWidget *ui_vp_music_area = nullptr; - AOMovie *ui_vp_clock = nullptr; + DRStickerMovie *ui_vp_clock = nullptr; QVector ui_timers; DRTextEdit *ui_ic_chatlog = nullptr; @@ -401,7 +405,7 @@ class Courtroom : public QMainWindow AOButton *ui_emote_left = nullptr; AOButton *ui_emote_right = nullptr; AOImageDisplay *ui_emote_preview = nullptr; - AOCharMovie *ui_emote_preview_character = nullptr; + DRCharacterMovie *ui_emote_preview_character = nullptr; QComboBox *ui_emote_dropdown = nullptr; QComboBox *ui_iniswap_dropdown = nullptr; @@ -427,7 +431,7 @@ class Courtroom : public QMainWindow // holds all the shout buttons objects QVector ui_wtce; // holds all the free block objects - QVector ui_free_blocks; + QVector ui_free_blocks; // holds all the names for sound files for the shouts QVector shout_names; diff --git a/include/drcharactermovie.cpp b/include/drcharactermovie.cpp new file mode 100644 index 000000000..fb184d3e9 --- /dev/null +++ b/include/drcharactermovie.cpp @@ -0,0 +1,79 @@ +#include "drcharactermovie.h" + +#include "aoapplication.h" +#include "file_functions.h" + +DRCharacterMovie::DRCharacterMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_cast(qApp)) +{ + Q_ASSERT(ao_app); + set_scale_to_height(true); +} + +DRCharacterMovie::~DRCharacterMovie() +{} + +void DRCharacterMovie::play(QString p_character, QString p_emote, QString p_prefix, bool p_play_once) +{ + QStringList l_filelist; + QStringList l_blacklist; + for (const QString &i_characterName : ao_app->get_char_include_tree(p_character)) + { + l_blacklist.append({ + ao_app->get_character_path(i_characterName, "char_icon.png"), + ao_app->get_character_path(i_characterName, "showname.png"), + ao_app->get_character_path(i_characterName, "emotions"), + }); + + if (!p_play_once) + { + l_filelist.append(ao_app->get_character_path(i_characterName, QString("%1%2").arg(p_prefix, p_emote))); + } + + l_filelist.append(ao_app->get_character_path(i_characterName, p_emote)); + } + + QString l_file = ao_app->find_asset_path(l_filelist, animated_or_static_extensions()); + if (l_file.isEmpty() && !p_play_once) + { + l_file = ao_app->find_theme_asset_path("placeholder", animated_extensions()); + } + + for (const QString &i_blackened : qAsConst(l_blacklist)) + { + if (l_file == i_blackened) + { + l_file.clear(); + break; + } + } + + if (l_file.isEmpty()) + { + qWarning() << "error: character animation not found" + << "character:" << p_character << "emote:" << p_emote << "prefix:" << p_prefix; + } + + set_file_name(l_file); + set_play_once(p_play_once); + start(); + + if (p_character == "") + { + hide(); + } +} + +void DRCharacterMovie::play_pre(QString p_character, QString p_emote) +{ + play(p_character, p_emote, nullptr, true); +} + +void DRCharacterMovie::play_talk(QString p_character, QString p_emote) +{ + play(p_character, p_emote, "(b)", false); +} + +void DRCharacterMovie::play_idle(QString p_character, QString p_emote) +{ + play(p_character, p_emote, "(a)", false); +} diff --git a/include/drcharactermovie.h b/include/drcharactermovie.h new file mode 100644 index 000000000..aeac0f092 --- /dev/null +++ b/include/drcharactermovie.h @@ -0,0 +1,22 @@ +#pragma once + +#include "drmovie.h" + +class AOApplication; + +class DRCharacterMovie : public DRMovie +{ + Q_OBJECT + +public: + explicit DRCharacterMovie(QWidget *parent = nullptr); + ~DRCharacterMovie(); + + void play(QString character, QString emote, QString prefix, bool play_once); + void play_pre(QString character, QString emote); + void play_talk(QString character, QString emote); + void play_idle(QString character, QString emote); + +private: + AOApplication *ao_app = nullptr; +}; diff --git a/include/dreffectmovie.cpp b/include/dreffectmovie.cpp new file mode 100644 index 000000000..6b281941f --- /dev/null +++ b/include/dreffectmovie.cpp @@ -0,0 +1,7 @@ +#include "dreffectmovie.h" + +DREffectMovie::DREffectMovie(QWidget *parent) : DRStickerMovie(parent) +{} + +DREffectMovie::~DREffectMovie() +{} diff --git a/include/dreffectmovie.h b/include/dreffectmovie.h new file mode 100644 index 000000000..8ecfef210 --- /dev/null +++ b/include/dreffectmovie.h @@ -0,0 +1,12 @@ +#pragma once + +#include "drstickermovie.h" + +class DREffectMovie : public DRStickerMovie +{ + Q_OBJECT + +public: + explicit DREffectMovie(QWidget *parent = nullptr); + ~DREffectMovie(); +}; diff --git a/include/drmovie.h b/include/drmovie.h new file mode 100644 index 000000000..9217b5c9e --- /dev/null +++ b/include/drmovie.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +class DRMovie : public QLabel +{ + Q_OBJECT + +public: + explicit DRMovie(QWidget *parent = nullptr); + ~DRMovie(); + + QString file_name(); + void set_file_name(QString file_name); + + void set_play_once(bool); + void set_mirrored(bool); + void set_scale_to_height(bool); + void set_hide_on_done(bool); + + bool isRunning(); + void start(); + void stop(); + +signals: + void done(); + +protected: + virtual void resizeEvent(QResizeEvent *event) override; + +private: + bool m_play_once = false; + bool m_mirrored = false; + bool m_scale_to_height = false; + bool m_hide_when_done = false; + + QString m_file_name; + QMovie m_movie; + int m_frame_count = 0; + int m_frame_number = 0; + QTimer m_frame_timer; + QPixmap m_current_pixmap; + +private slots: + void update_frame(int); + void jump_next_frame(); + void paint_frame(); +}; diff --git a/include/drscenemovie.h b/include/drscenemovie.h new file mode 100644 index 000000000..0742d5d51 --- /dev/null +++ b/include/drscenemovie.h @@ -0,0 +1,19 @@ +#pragma once + +#include "drmovie.h" + +class AOApplication; + +class DRSceneMovie : public DRMovie +{ + Q_OBJECT + +public: + explicit DRSceneMovie(QWidget *parent = nullptr); + ~DRSceneMovie(); + + void set_image(QString p_image); + +private: + AOApplication *ao_app; +}; diff --git a/include/drshoutmovie.h b/include/drshoutmovie.h new file mode 100644 index 000000000..37f027a64 --- /dev/null +++ b/include/drshoutmovie.h @@ -0,0 +1,19 @@ +#pragma once + +#include "drmovie.h" + +class AOApplication; + +class DRShoutMovie : public DRMovie +{ + Q_OBJECT + +public: + explicit DRShoutMovie(QWidget *parent = nullptr); + ~DRShoutMovie(); + + void play_interjection(QString p_char_name, QString p_interjection_name); + +private: + AOApplication *ao_app = nullptr; +}; diff --git a/include/drsplashmovie.cpp b/include/drsplashmovie.cpp new file mode 100644 index 000000000..5e43738fa --- /dev/null +++ b/include/drsplashmovie.cpp @@ -0,0 +1,10 @@ +#include "drsplashmovie.h" + +DRSplashMovie::DRSplashMovie(QWidget *parent) : DRStickerMovie(parent) +{ + set_play_once(true); + set_hide_on_done(true); +} + +DRSplashMovie::~DRSplashMovie() +{} diff --git a/include/drsplashmovie.h b/include/drsplashmovie.h new file mode 100644 index 000000000..ebcb379e2 --- /dev/null +++ b/include/drsplashmovie.h @@ -0,0 +1,12 @@ +#pragma once + +#include "drstickermovie.h" + +class DRSplashMovie : public DRStickerMovie +{ + Q_OBJECT + +public: + explicit DRSplashMovie(QWidget *parent = nullptr); + ~DRSplashMovie(); +}; diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp deleted file mode 100644 index 6736982cc..000000000 --- a/src/aocharmovie.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include "aocharmovie.h" - -#include "aoapplication.h" -#include "aopixmap.h" -#include "file_functions.h" - -#include -#include - -AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) -{ - ao_app = p_ao_app; - - // center even images, not just text - setAlignment(Qt::AlignCenter); - - m_movie = new QMovie(this); - - m_frame_timer = new QTimer(this); - m_frame_timer->setSingleShot(true); - - connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(on_frame_changed(int))); - connect(m_frame_timer, SIGNAL(timeout()), this, SLOT(on_timer_timeout())); -} - -QString AOCharMovie::file_name() -{ - return m_movie->fileName(); -} - -bool AOCharMovie::play(QString p_chr, QString p_emote, QString p_prefix, bool p_play_once) -{ - // Asset lookup order - // 1. In the character folder, look for - // `p_emote_prefix+p_emote` + extensions in `exts` in order - // 2. In the character folder, look for - // `p_emote` + extensions in `exts` in order - // 3. In the theme folder (gamemode-timeofday/main/default), look for - // "placeholder" + extensions in `exts` in order - - bool r_exist = true; - - const bool l_is_anim = p_prefix.isEmpty(); - - QStringList l_file_list; - QStringList l_blacklist; - for (const QString &i_chr : ao_app->get_char_include_tree(p_chr)) - { - l_blacklist.append(QStringList{ - ao_app->get_character_path(i_chr, "char_icon.png"), - ao_app->get_character_path(i_chr, "showname.png"), - ao_app->get_character_path(i_chr, "emotions"), - }); - - if (!l_is_anim) - l_file_list.append(ao_app->get_character_path(i_chr, QString("%1%2").arg(p_prefix, p_emote))); - l_file_list.append(ao_app->get_character_path(i_chr, p_emote)); - } - - QString l_file = ao_app->find_asset_path(l_file_list, animated_or_static_extensions()); - if (l_file.isEmpty()) - { - if (!l_is_anim) - l_file = ao_app->find_theme_asset_path("placeholder", animated_extensions()); - r_exist = false; - } - - for (const QString &i_blacklisted_file : qAsConst(l_blacklist)) - { - if (!l_file.startsWith(i_blacklisted_file, Qt::CaseInsensitive)) - continue; - l_file.clear(); - r_exist = false; - break; - } - - stop(); - is_play_once = p_play_once; - m_movie->setFileName(l_file); - if (m_movie->isValid()) - m_movie->start(); - - return r_exist; -} - -bool AOCharMovie::play_pre(QString p_chr, QString p_emote) -{ - return play(p_chr, p_emote, nullptr, true); -} - -bool AOCharMovie::play_talk(QString p_chr, QString p_emote) -{ - return play(p_chr, p_emote, "(b)", false); -} - -bool AOCharMovie::play_idle(QString p_chr, QString p_emote) -{ - return play(p_chr, p_emote, "(a)", false); -} - -void AOCharMovie::set_mirrored(bool p_enabled) -{ - is_mirrored = p_enabled; -} - -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 - m_movie->stop(); - m_frame_timer->stop(); - m_current_frame = QImage(); - paint_frame(); -} - -void AOCharMovie::combo_resize(QSize p_size) -{ - resize(p_size); - paint_frame(); -} - -void AOCharMovie::paint_frame() -{ - AOPixmap l_pixmap(QPixmap::fromImage(m_movie->currentImage().mirrored(is_mirrored, false))); - this->setPixmap(l_pixmap.scale_to_height(this->size())); -} - -void AOCharMovie::on_frame_changed(int p_frame_num) -{ - m_current_frame = m_movie->currentImage(); - - paint_frame(); - - if (is_play_once) - { - const int f_frame_count = m_movie->frameCount(); - if (f_frame_count == 0 || p_frame_num == (f_frame_count - 1)) - { - int f_frame_delay = m_movie->nextFrameDelay(); - if (f_frame_delay < 0) - f_frame_delay = 0; - m_movie->stop(); - m_frame_timer->start(f_frame_delay); - } - } -} - -void AOCharMovie::on_timer_timeout() -{ - Q_EMIT done(); -} diff --git a/src/aomovie.cpp b/src/aomovie.cpp deleted file mode 100644 index 9092c76b9..000000000 --- a/src/aomovie.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#include "aomovie.h" - -#include "aoapplication.h" -#include "aopixmap.h" -#include "file_functions.h" -#include "misc_functions.h" - -#include - -AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) -{ - ao_app = p_ao_app; - - m_movie = new QMovie(); - - connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); - connect(m_movie, SIGNAL(stateChanged(QMovie::MovieState)), this, SLOT(handle_state(QMovie::MovieState))); -} - -AOMovie::~AOMovie() -{ - delete m_movie; -} - -bool AOMovie::is_play_once() -{ - return m_play_once; -} - -void AOMovie::set_play_once(bool p_play_once) -{ - m_play_once = p_play_once; -} - -bool AOMovie::is_hide_on_done() -{ - return m_hide_on_done; -} - -void AOMovie::set_hide_on_done(bool p_hide_on_done) -{ - m_hide_on_done = p_hide_on_done; -} - -void AOMovie::play_file_name(QString p_file_name) -{ - m_movie->stop(); - m_movie->setFileName(p_file_name); - m_first_loop = true; - qDebug() << "playing" << p_file_name; - m_movie->start(); - this->show(); -} - -void AOMovie::play(QString p_file, QString p_char) -{ - QString file_path = ""; - - QString char_p_file; - // FIXME: When looking in the character folder, append "_bubble" except for - // custom We probably should drop this - if (p_file != "custom") - char_p_file = p_file + "_bubble"; - else - char_p_file = p_file; - - // Asset lookup order - // 1. If p_char is not empty, in the character folder, look for - // `char_p_file` + extensions in `exts` in order - // 2. If p_char is not empty, in the character folder, look for - // `overlay/char_p_file` + extensions in `exts` in order - // 3. In the theme folder (gamemode-timeofday/main/default), look for - // `p_file` + extensions in `exts` in order - // 4. In the theme folder (gamemode-timeofday/main/default), look for - // "placeholder" + extensions in `exts` in order - - if (!p_char.isEmpty()) - { - file_path = ao_app->find_asset_path( - { - ao_app->get_character_path(p_char, char_p_file), - ao_app->get_character_path(p_char, "overlay/" + char_p_file), - }, - animated_or_static_extensions()); - } - - if (file_path.isEmpty()) - { - file_path = ao_app->find_theme_asset_path(p_file, animated_or_static_extensions()); - if (file_path.isEmpty()) - file_path = ao_app->find_theme_asset_path("placeholder", animated_or_static_extensions()); - } - - play_file_name(file_path); -} - -/// -/// \brief Searches and play the first interjection file it can find based on -/// the provided character name and interjection name. -/// -void AOMovie::play_interjection(QString p_char_name, QString p_interjection_name) -{ - QString p_char_interjection_name; - // FIXME: When looking in the character folder, append "_bubble" except for - // custom We probably should drop this - if (p_interjection_name.toLower() != "custom") - p_char_interjection_name = p_interjection_name + "_bubble"; - else - p_char_interjection_name = p_interjection_name; - - // Asset lookup order - // 1. In the character folder, look for - // `p_char_interjection_name` - // 2. In the theme folder (gamemode-timeofday/main/default), look for - // `p_char_name` - - QString interjection_filepath = ao_app->find_asset_path( - {ao_app->get_character_path(p_char_name, p_char_interjection_name)}, animated_extensions()); - if (interjection_filepath.isEmpty()) - interjection_filepath = ao_app->find_theme_asset_path(p_interjection_name, animated_extensions()); - - if (interjection_filepath.isEmpty()) - { - Q_EMIT done(); - return; - } - - qDebug() << "playing interjection" << (p_interjection_name.isEmpty() ? "(none)" : p_interjection_name) - << "for character" << (p_char_name.isEmpty() ? "(none)" : p_char_name) << "at" - << (interjection_filepath.isEmpty() ? "(not found)" : interjection_filepath); - play_file_name(interjection_filepath); -} - -void AOMovie::restart() -{ - m_movie->stop(); - m_movie->start(); -} - -void AOMovie::stop() -{ - m_movie->stop(); - // free up resources - m_movie->setFileName(nullptr); - if (m_hide_on_done) - this->hide(); -} - -void AOMovie::frame_change(int n_frame) -{ - const QImage l_frame = m_movie->currentImage(); - bool l_paint_frame = true; - - if (m_play_once && n_frame == 0 && !m_first_loop) - { - l_paint_frame = false; - stop(); - } - - if (l_paint_frame || m_first_loop) - { - m_first_loop = false; - // paint - AOPixmap l_pixmap(QPixmap::fromImage(l_frame)); - this->setPixmap(l_pixmap.scale(this->size())); - } -} - -void AOMovie::handle_state(QMovie::MovieState p_state) -{ - if (m_play_once && p_state == QMovie::NotRunning) - { - stop(); - Q_EMIT done(); - } -} - -void AOMovie::combo_resize(int w, int h) -{ - QSize f_size(w, h); - this->resize(f_size); - m_movie->setScaledSize(f_size); -} - -/* - * @brief Returns the state of the current movie. Refer to QMovie::state() - * for more details. - * @returns Current movie status - */ -QMovie::MovieState AOMovie::state() -{ - return m_movie->state(); -} diff --git a/src/aoscene.cpp b/src/aoscene.cpp deleted file mode 100644 index c795af3aa..000000000 --- a/src/aoscene.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "aoscene.h" - -#include "aoapplication.h" -#include "file_functions.h" - -#include - -AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent), ao_app(p_ao_app) -{ - m_reader = new QMovie(this); - setMovie(m_reader); -} - -void AOScene::set_image(QString p_image) -{ - const QString l_file_path = - ao_app->find_asset_path(ao_app->get_current_background_path() + "/" + p_image, animated_or_static_extensions()); - - // do not update the movie if we're using the same file - if (m_reader->fileName() == l_file_path) - return; - - m_reader->stop(); - delete m_reader; - - m_reader = new QMovie(this); - m_reader->setScaledSize(size()); - m_reader->setFileName(l_file_path); - setMovie(m_reader); - m_reader->start(); -} - -void AOScene::combo_resize(QSize p_size) -{ - resize(p_size); - m_reader->stop(); - m_reader->setScaledSize(p_size); - m_reader->start(); -} diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 66f3688c4..1e6c547af 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -4,24 +4,27 @@ #include "aoblipplayer.h" #include "aobutton.h" #include "aocharbutton.h" -#include "aocharmovie.h" #include "aoconfig.h" #include "aoevidencedisplay.h" #include "aoimagedisplay.h" -#include "aomovie.h" #include "aomusicplayer.h" #include "aonotearea.h" #include "aonotepicker.h" -#include "aoscene.h" #include "aosfxplayer.h" #include "aoshoutplayer.h" #include "aosystemplayer.h" #include "aotimer.h" #include "commondefs.h" #include "debug_functions.h" +#include "drcharactermovie.h" #include "drchatlog.h" #include "drdiscord.h" +#include "dreffectmovie.h" #include "drpacket.h" +#include "drscenemovie.h" +#include "drshoutmovie.h" +#include "drsplashmovie.h" +#include "drstickermovie.h" #include "file_functions.h" #include "hardware_functions.h" #include "lobby.h" @@ -1190,17 +1193,9 @@ void Courtroom::play_preanim() const QString l_chr_name = m_chatmessage[CMChrName]; const QString l_anim_name = m_chatmessage[CMPreAnim]; - - if (!ui_vp_player_char->play_pre(l_chr_name, l_anim_name)) - { - qDebug() << "Unable to play animation: missing or invalid file; character:" << l_chr_name - << "animation:" << l_anim_name; - preanim_done(); - return; - } - qDebug() << "Playing character animation; character:" << l_chr_name << "animation: " << l_anim_name << "file:" << ui_vp_player_char->file_name(); + ui_vp_player_char->play_pre(l_chr_name, l_anim_name); } void Courtroom::preanim_done() @@ -1446,7 +1441,7 @@ void Courtroom::post_chat() if (ui_vp_chatbox->isVisible()) { - ui_vp_chat_arrow->restart(); + ui_vp_chat_arrow->start(); ui_vp_chat_arrow->show(); } } @@ -2097,7 +2092,7 @@ void Courtroom::on_change_character_clicked() void Courtroom::reload_theme() { - if (ui_vp_objection->state() == QMovie::Running) + if (ui_vp_objection->isRunning()) { m_shout_reload_theme = true; return; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 946e6222a..fe0dfdcca 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -3,24 +3,27 @@ #include "aoapplication.h" #include "aoblipplayer.h" #include "aobutton.h" -#include "aocharmovie.h" #include "aoconfig.h" #include "aoevidencedescription.h" #include "aoevidencedisplay.h" #include "aoimagedisplay.h" #include "aolabel.h" #include "aolineedit.h" -#include "aomovie.h" #include "aomusicplayer.h" #include "aonotearea.h" #include "aonotepicker.h" -#include "aoscene.h" #include "aosfxplayer.h" #include "aoshoutplayer.h" #include "aosystemplayer.h" #include "aotimer.h" #include "commondefs.h" +#include "drcharactermovie.h" #include "drchatlog.h" +#include "dreffectmovie.h" +#include "drscenemovie.h" +#include "drshoutmovie.h" +#include "drsplashmovie.h" +#include "drstickermovie.h" #include "drtextedit.h" #include "file_functions.h" #include "theme.h" @@ -62,9 +65,9 @@ void Courtroom::create_widgets() ui_background = new AOImageDisplay(this, ao_app); ui_viewport = new QWidget(this); - ui_vp_background = new AOScene(ui_viewport, ao_app); - ui_vp_player_char = new AOCharMovie(ui_viewport, ao_app); - ui_vp_desk = new AOScene(ui_viewport, ao_app); + ui_vp_background = new DRSceneMovie(ui_viewport); + ui_vp_player_char = new DRCharacterMovie(ui_viewport); + ui_vp_desk = new DRSceneMovie(ui_viewport); ui_vp_music_display_a = new AOImageDisplay(this, ao_app); ui_vp_music_display_b = new AOImageDisplay(this, ao_app); @@ -77,7 +80,7 @@ void Courtroom::create_widgets() ui_vp_music_name->setReadOnly(true); music_anim = new QPropertyAnimation(ui_vp_music_name, "geometry", this); - ui_vp_clock = new AOMovie(this, ao_app); + ui_vp_clock = new DRStickerMovie(this); ui_vp_clock->set_play_once(true); ui_vp_evidence_display = new AOEvidenceDisplay(this, ao_app); @@ -96,14 +99,14 @@ void Courtroom::create_widgets() ui_vp_showname_image = new AOImageDisplay(this, ao_app); - ui_vp_effect = new AOMovie(this, ao_app); + ui_vp_effect = new DREffectMovie(this); ui_vp_effect->set_hide_on_done(true); - ui_vp_wtce = new AOMovie(this, ao_app); + ui_vp_wtce = new DRSplashMovie(this); ui_vp_wtce->set_hide_on_done(true); - ui_vp_objection = new AOMovie(this, ao_app); + ui_vp_objection = new DRShoutMovie(this); ui_vp_objection->set_hide_on_done(true); - ui_vp_chat_arrow = new AOMovie(this, ao_app); + ui_vp_chat_arrow = new DRStickerMovie(this); ui_vp_chat_arrow->set_play_once(false); ui_iniswap_dropdown = new QComboBox(this); @@ -571,14 +574,14 @@ void Courtroom::set_widgets() set_size_and_pos(ui_viewport, "viewport", COURTROOM_DESIGN_INI, ao_app); ui_vp_background->move(0, 0); - ui_vp_background->combo_resize(ui_viewport->size()); + ui_vp_background->resize(ui_viewport->size()); ui_vp_player_char->move(0, 0); - ui_vp_player_char->combo_resize(ui_viewport->size()); + ui_vp_player_char->resize(ui_viewport->size()); // the AO2 desk element ui_vp_desk->move(0, 0); - ui_vp_desk->combo_resize(ui_viewport->size()); + ui_vp_desk->resize(ui_viewport->size()); ui_vp_evidence_display->move(0, 0); ui_vp_evidence_display->resize(ui_viewport->width(), ui_viewport->height()); @@ -610,10 +613,10 @@ void Courtroom::set_widgets() ui_vp_effect->hide(); ui_vp_wtce->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_wtce->combo_resize(ui_viewport->width(), ui_viewport->height()); + ui_vp_wtce->resize(ui_viewport->width(), ui_viewport->height()); ui_vp_objection->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_objection->combo_resize(ui_viewport->width(), ui_viewport->height()); + ui_vp_objection->resize(ui_viewport->width(), ui_viewport->height()); set_size_and_pos(ui_ic_chatlog, "ic_chatlog", COURTROOM_DESIGN_INI, ao_app); @@ -1241,7 +1244,7 @@ void Courtroom::load_free_blocks() for (int i = 0; i < ui_free_blocks.size(); ++i) { - ui_free_blocks[i] = new AOMovie(this, ao_app); + ui_free_blocks[i] = new DRStickerMovie(this); // ui_free_blocks[i]->setProperty("free_block_id", i+1); ui_free_blocks[i]->set_play_once(false); ui_free_blocks[i]->stackUnder(ui_vp_player_char); @@ -1414,7 +1417,7 @@ void Courtroom::set_free_blocks() { for (int i = 0; i < ui_free_blocks.size(); i++) { - AOMovie *free_block = ui_free_blocks[i]; + DRStickerMovie *free_block = ui_free_blocks[i]; free_block->play(free_block_names[i]); } } diff --git a/src/drmovie.cpp b/src/drmovie.cpp new file mode 100644 index 000000000..1e883bf70 --- /dev/null +++ b/src/drmovie.cpp @@ -0,0 +1,206 @@ +#include "drmovie.h" + +#include + +DRMovie::DRMovie(QWidget *parent) : QLabel(parent) +{ + setAlignment(Qt::AlignCenter); + + m_movie.setCacheMode(QMovie::CacheNone); + m_frame_timer.setSingleShot(true); + m_frame_timer.setTimerType(Qt::PreciseTimer); + + connect(&m_movie, SIGNAL(frameChanged(int)), this, SLOT(update_frame(int))); + connect(&m_frame_timer, SIGNAL(timeout()), this, SLOT(jump_next_frame())); +} + +DRMovie::~DRMovie() +{} + +QString DRMovie::file_name() +{ + return m_file_name; +} + +/** + * @brief Sets the property filename. + * + * stop() will be called prior to updating setting the property. + */ +void DRMovie::set_file_name(QString p_file_name) +{ + stop(); + m_file_name = p_file_name; +} + +/** + * @brief Sets the property play_once on if true or off if false. + * + * If on, the movie will repeat until either conditions are met: + * - setting play_once to off + * - calling stop() + * + * The signal done() will not be emitted while play_once is on. + * + * By default, play_once is off. + */ +void DRMovie::set_play_once(bool p_on) +{ + m_play_once = p_on; +} + +/** + * @brief Sets the property hide_when_done on if on true or off if false. + * + * If on, the widget will hide automatically right before signaling done() + * + * By default, hide_when_done is off. + */ +void DRMovie::set_hide_on_done(bool p_on) +{ + m_hide_when_done = p_on; +} + +/** + * @brief Sets the property mirrored on if true or off if false. + * + * If on, painted frames will be mirrored horizontally. + * + * The current frame will NOT be updated. + * + * By default, mirrored is off. + */ +void DRMovie::set_mirrored(bool p_on) +{ + m_mirrored = p_on; +} + +/** + * @brief Sets the property scale_to_height on if true or off if false. + * + * If on, painted frames will preserve their aspect ratio and scale based + * on the height of the widget. + * + * The current frame will NOT be updated. + * + * By default, scale_to_height is off. + */ +void DRMovie::set_scale_to_height(bool p_on) +{ + m_scale_to_height = p_on; +} + +/** + * @brief Starts (or restarts) the movie. + * + * If the movie isn't valid and the property play_once is on, + * the movie will immediately emit the signal done() + */ +void DRMovie::start() +{ + m_frame_timer.stop(); + m_movie.stop(); + m_movie.setFileName(m_file_name); + m_frame_count = m_movie.frameCount(); + m_frame_number = m_movie.currentFrameNumber(); + if (m_movie.isValid()) + { + m_movie.start(); + m_movie.setPaused(true); + show(); + } + else if (m_play_once) + { + emit done(); + } +} + +bool DRMovie::isRunning() +{ + return m_movie.state() != QMovie::NotRunning; +} + +void DRMovie::stop() +{ + m_frame_timer.stop(); + m_movie.stop(); + m_movie.setFileName(""); // clean up QMovie's cache + + if (m_hide_when_done) + { + hide(); + } +} + +void DRMovie::resizeEvent(QResizeEvent *event) +{ + QLabel::resizeEvent(event); + + paint_frame(); +} + +void DRMovie::paint_frame() +{ + QPixmap l_frame = m_current_pixmap; + + const bool l_is_larger = l_frame.width() > width() || l_frame.height() > height(); + const Qt::TransformationMode l_transform = l_is_larger ? Qt::SmoothTransformation : Qt::FastTransformation; + if (m_scale_to_height) + { + l_frame = l_frame.scaledToHeight(height(), l_transform); + } + else + { + l_frame = l_frame.scaled(size(), Qt::IgnoreAspectRatio, l_transform); + } + + setPixmap(l_frame); +} + +void DRMovie::update_frame(int p_frame_number) +{ + m_frame_number = p_frame_number; + m_frame_timer.start(m_movie.nextFrameDelay()); + + if (m_mirrored) + { + m_current_pixmap = QPixmap::fromImage(m_movie.currentImage().mirrored(m_mirrored, false)); + } + else + { + m_current_pixmap = m_movie.currentPixmap(); + } + + paint_frame(); +} + +void DRMovie::jump_next_frame() +{ + const int l_next_frame_number = m_frame_number + 1; + if (l_next_frame_number >= m_frame_count) + { + m_movie.stop(); + m_frame_number = m_movie.currentFrameNumber(); + + if (m_play_once) + { + if (m_hide_when_done) + { + hide(); + } + + emit done(); + return; + } + + if (m_frame_count > 1) + { + m_movie.start(); + m_movie.setPaused(true); + } + } + else + { + m_movie.jumpToNextFrame(); + } +} diff --git a/src/drscenemovie.cpp b/src/drscenemovie.cpp new file mode 100644 index 000000000..bf0ceefbf --- /dev/null +++ b/src/drscenemovie.cpp @@ -0,0 +1,25 @@ +#include "drscenemovie.h" + +#include "aoapplication.h" +#include "file_functions.h" + +DRSceneMovie::DRSceneMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_cast(qApp)) +{ + Q_ASSERT(ao_app); + set_scale_to_height(true); +} + +DRSceneMovie::~DRSceneMovie() +{} + +void DRSceneMovie::set_image(QString p_image) +{ + const QString l_file_path = + ao_app->find_asset_path(ao_app->get_current_background_path() + "/" + p_image, animated_or_static_extensions()); + + if (file_name() == l_file_path) + return; + + set_file_name(l_file_path); + start(); +} diff --git a/src/drshoutmovie.cpp b/src/drshoutmovie.cpp new file mode 100644 index 000000000..04189f779 --- /dev/null +++ b/src/drshoutmovie.cpp @@ -0,0 +1,40 @@ +#include "drshoutmovie.h" + +#include "aoapplication.h" +#include "file_functions.h" + +DRShoutMovie::DRShoutMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_cast(qApp)) +{ + Q_ASSERT(ao_app); + set_play_once(true); + set_hide_on_done(true); +} + +DRShoutMovie::~DRShoutMovie() +{} + +void DRShoutMovie::play_interjection(QString p_character, QString p_shout) +{ + if (p_shout.toLower() == "custom") + { + p_shout.append("_bubble"); + } + + QString l_file_name = + ao_app->find_asset_path({ao_app->get_character_path(p_character, p_shout)}, animated_extensions()); + + if (l_file_name.isEmpty()) + { + l_file_name = ao_app->find_theme_asset_path(p_shout, animated_extensions()); + } + + if (l_file_name.isEmpty()) + { + qWarning() << "error: shout not found" + << "character:" << p_character << "shout:" << p_shout; + } + + set_file_name(l_file_name); + set_play_once(true); + start(); +} diff --git a/src/drstickermovie.cpp b/src/drstickermovie.cpp new file mode 100644 index 000000000..0f816b6bc --- /dev/null +++ b/src/drstickermovie.cpp @@ -0,0 +1,46 @@ +#include "drstickermovie.h" + +#include "aoapplication.h" +#include "file_functions.h" + +DRStickerMovie::DRStickerMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_cast(qApp)) +{} + +DRStickerMovie::~DRStickerMovie() +{} + +void DRStickerMovie::play(QString p_file_name, QString p_character) +{ + QString l_file_path; + if (!p_character.isEmpty()) + { + QString l_character_file_name = p_file_name; + if (l_character_file_name == "custom") + { + l_character_file_name.append("_bubble"); + } + + QStringList l_path_list{ + ao_app->get_character_path(p_character, l_character_file_name), + ao_app->get_character_path(p_character, "overlay/" + l_character_file_name), + }; + l_file_path = ao_app->find_asset_path(l_path_list, animated_or_static_extensions()); + } + + if (l_file_path.isEmpty()) + { + l_file_path = ao_app->find_theme_asset_path(p_file_name, animated_or_static_extensions()); + if (l_file_path.isEmpty()) + { + l_file_path = ao_app->find_theme_asset_path("placeholder", animated_or_static_extensions()); + } + } + + set_file_name(l_file_path); + start(); +} + +void DRStickerMovie::play(QString p_file_name) +{ + play(p_file_name, ""); +} diff --git a/src/emotes.cpp b/src/emotes.cpp index 42540bc40..d050f10f8 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -2,11 +2,11 @@ #include "aoapplication.h" #include "aobutton.h" -#include "aocharmovie.h" #include "aoconfig.h" #include "aoemotebutton.h" #include "aoimagedisplay.h" #include "commondefs.h" +#include "drcharactermovie.h" #include "theme.h" #include @@ -30,7 +30,7 @@ void Courtroom::construct_emotes() ui_emote_preview = new AOImageDisplay(nullptr, ao_app); ui_emote_preview->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::BypassGraphicsProxyWidget); ui_emote_preview->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_emote_preview_character = new AOCharMovie(ui_emote_preview, ao_app); + ui_emote_preview_character = new DRCharacterMovie(ui_emote_preview); ui_emote_preview_character->setAttribute(Qt::WA_TransparentForMouseEvents); ui_emote_dropdown = new QComboBox(this); @@ -174,7 +174,7 @@ void Courtroom::select_emote(int p_id) const DREmote &l_prev_emote = get_emote(m_emote_id); if (m_emote_id >= l_min && m_emote_id <= l_max) { - AOEmoteButton* l_prev_button = ui_emote_list.at(m_emote_id % m_page_max_emote_count); + AOEmoteButton *l_prev_button = ui_emote_list.at(m_emote_id % m_page_max_emote_count); l_prev_button->set_image(l_prev_emote, false); l_prev_button->repaint(); } @@ -185,7 +185,7 @@ void Courtroom::select_emote(int p_id) if (m_emote_id >= l_min && m_emote_id <= l_max) { - AOEmoteButton* l_new_button = ui_emote_list.at(m_emote_id % m_page_max_emote_count); + AOEmoteButton *l_new_button = ui_emote_list.at(m_emote_id % m_page_max_emote_count); l_new_button->set_image(l_emote, true); l_new_button->repaint(); } From 43ae8ab6a3ff685adf6a8bbc0cee34caa14fdfbf Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 16 Feb 2022 19:00:59 +0100 Subject: [PATCH 597/842] [LINUX] Missing headers --- include/drcharactermovie.cpp | 2 ++ src/drshoutmovie.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/include/drcharactermovie.cpp b/include/drcharactermovie.cpp index fb184d3e9..6546759df 100644 --- a/include/drcharactermovie.cpp +++ b/include/drcharactermovie.cpp @@ -3,6 +3,8 @@ #include "aoapplication.h" #include "file_functions.h" +#include + DRCharacterMovie::DRCharacterMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_cast(qApp)) { Q_ASSERT(ao_app); diff --git a/src/drshoutmovie.cpp b/src/drshoutmovie.cpp index 04189f779..08705f8db 100644 --- a/src/drshoutmovie.cpp +++ b/src/drshoutmovie.cpp @@ -3,6 +3,8 @@ #include "aoapplication.h" #include "file_functions.h" +#include + DRShoutMovie::DRShoutMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_cast(qApp)) { Q_ASSERT(ao_app); From 00b1bee3b8f52bb757f70b2fb152829487ccbb26 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 16 Feb 2022 19:05:59 +0100 Subject: [PATCH 598/842] Lower filename case. --- dronline-client.pro | 8 ++++---- include/{DRStickerMovie.h => drstickermovie_lower.h} | 0 {include => src}/drcharactermovie.cpp | 0 {include => src}/dreffectmovie.cpp | 0 {include => src}/drsplashmovie.cpp | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename include/{DRStickerMovie.h => drstickermovie_lower.h} (100%) rename {include => src}/drcharactermovie.cpp (100%) rename {include => src}/dreffectmovie.cpp (100%) rename {include => src}/drsplashmovie.cpp (100%) diff --git a/dronline-client.pro b/dronline-client.pro index 8d05fadb0..0f202281f 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -17,7 +17,6 @@ CONFIG(debug, debug|release):DEFINES += DR_DEV HEADERS += \ include/drscenemovie.h \ include/drsplashmovie.h \ - include/drstickermovie.h \ include/aoapplication.h \ include/aoblipplayer.h \ include/aobutton.h \ @@ -61,6 +60,7 @@ HEADERS += \ include/drpather.h \ include/drserversocket.h \ include/drshoutmovie.h \ + include/drstickermovie_lower.h \ include/drtextedit.h \ include/drdiscord.h \ include/file_functions.h \ @@ -72,9 +72,6 @@ HEADERS += \ include/version.h SOURCES += \ - include/drcharactermovie.cpp \ - include/dreffectmovie.cpp \ - include/drsplashmovie.cpp \ src/aoapplication.cpp \ src/aoblipplayer.cpp \ src/aobutton.cpp \ @@ -115,13 +112,16 @@ SOURCES += \ src/draudioerror.cpp \ src/draudiostream.cpp \ src/draudiostreamfamily.cpp \ + src/drcharactermovie.cpp \ src/drchatlog.cpp \ + src/dreffectmovie.cpp \ src/drmovie.cpp \ src/drpacket.cpp \ src/drpather.cpp \ src/drscenemovie.cpp \ src/drserversocket.cpp \ src/drshoutmovie.cpp \ + src/drsplashmovie.cpp \ src/drstickermovie.cpp \ src/drtextedit.cpp \ src/drdiscord.cpp \ diff --git a/include/DRStickerMovie.h b/include/drstickermovie_lower.h similarity index 100% rename from include/DRStickerMovie.h rename to include/drstickermovie_lower.h diff --git a/include/drcharactermovie.cpp b/src/drcharactermovie.cpp similarity index 100% rename from include/drcharactermovie.cpp rename to src/drcharactermovie.cpp diff --git a/include/dreffectmovie.cpp b/src/dreffectmovie.cpp similarity index 100% rename from include/dreffectmovie.cpp rename to src/dreffectmovie.cpp diff --git a/include/drsplashmovie.cpp b/src/drsplashmovie.cpp similarity index 100% rename from include/drsplashmovie.cpp rename to src/drsplashmovie.cpp From 8b616537e6e70b6413cccfba4f0bc0d4073c588c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 16 Feb 2022 19:06:21 +0100 Subject: [PATCH 599/842] Fixed header name. --- dronline-client.pro | 2 +- include/{drstickermovie_lower.h => drstickermovie.h} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename include/{drstickermovie_lower.h => drstickermovie.h} (100%) diff --git a/dronline-client.pro b/dronline-client.pro index 0f202281f..9f7c6db87 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -60,7 +60,7 @@ HEADERS += \ include/drpather.h \ include/drserversocket.h \ include/drshoutmovie.h \ - include/drstickermovie_lower.h \ + include/drstickermovie.h \ include/drtextedit.h \ include/drdiscord.h \ include/file_functions.h \ diff --git a/include/drstickermovie_lower.h b/include/drstickermovie.h similarity index 100% rename from include/drstickermovie_lower.h rename to include/drstickermovie.h From 5b6557f954b3e969658564170ae347563c2cb6d7 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 16 Feb 2022 19:46:18 +0100 Subject: [PATCH 600/842] Renamed method to fit name convention --- include/drmovie.h | 2 +- src/courtroom.cpp | 2 +- src/drmovie.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/drmovie.h b/include/drmovie.h index 9217b5c9e..30f24d2d7 100644 --- a/include/drmovie.h +++ b/include/drmovie.h @@ -20,7 +20,7 @@ class DRMovie : public QLabel void set_scale_to_height(bool); void set_hide_on_done(bool); - bool isRunning(); + bool is_running(); void start(); void stop(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 1e6c547af..42418ed06 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2092,7 +2092,7 @@ void Courtroom::on_change_character_clicked() void Courtroom::reload_theme() { - if (ui_vp_objection->isRunning()) + if (ui_vp_objection->is_running()) { m_shout_reload_theme = true; return; diff --git a/src/drmovie.cpp b/src/drmovie.cpp index 1e883bf70..9f372e09f 100644 --- a/src/drmovie.cpp +++ b/src/drmovie.cpp @@ -115,7 +115,7 @@ void DRMovie::start() } } -bool DRMovie::isRunning() +bool DRMovie::is_running() { return m_movie.state() != QMovie::NotRunning; } From 86f74df5498d2ad888c96ab863c72ed44819196a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 16 Feb 2022 20:51:10 +0100 Subject: [PATCH 601/842] Tweaks --- src/version.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/version.cpp b/src/version.cpp index c368c21b1..a04e272fd 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -28,13 +28,15 @@ QString get_post_version() QString get_version_string() { - const QString post = get_post_version(); - const QString prefix = ( - QString::number(get_release_version()) + "." + QString::number(get_major_version()) + "." + - QString::number(get_minor_version())); - if (post.isEmpty()) - return prefix; - return prefix + "-" + post; + QString l_version = QString("%1.%2.%3").arg(get_release_version()).arg(get_major_version()).arg(get_minor_version()); + + const QString l_post = get_post_version(); + if (!l_post.isEmpty()) + { + l_version += QString("-" + l_post); + } + + return l_version; } QString get_resource_file_text(QString filename) From 34a76b6d4d7e897e91a2bec971e58eefcc57cab5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 21 Feb 2022 00:09:36 +0100 Subject: [PATCH 602/842] Added Video playing feature Resolve #263 * Added new feature: playable videos for emotes Videos will be played before pre-animations. Other audio sources will NOT be muted when playing a video file. Files without video track will be ignored. In order to play a video file, it must be located within a `videos` folder of the character you're using. Example of a video playable through character ini: ```ini [emotions] 5 = Scrum Debate#some_pre#some_dialog#1 [media] 5 = scrum_debate.mp4``` You must include the FULL filename, including extension for said file. --- dronline-client.pro | 4 +- include/aoapplication.h | 2 + include/aoconfig.h | 6 ++ include/aoconfigpanel.h | 4 + include/courtroom.h | 5 +- include/datatypes.h | 43 +++------- include/draudio.h | 1 + include/drvideoscreen.h | 46 +++++++++++ res/ui/config_panel.ui | 97 +++++++++++++++++----- src/aoapplication.cpp | 5 ++ src/aoconfig.cpp | 40 ++++++++- src/aoconfigpanel.cpp | 16 ++++ src/courtroom.cpp | 15 +++- src/courtroom_widgets.cpp | 8 ++ src/draudioengine.cpp | 2 + src/draudiostreamfamily.cpp | 5 ++ src/drvideoscreen.cpp | 156 ++++++++++++++++++++++++++++++++++++ src/server_socket.cpp | 2 + src/text_file_functions.cpp | 4 + 19 files changed, 406 insertions(+), 55 deletions(-) create mode 100644 include/drvideoscreen.h create mode 100644 src/drvideoscreen.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 2d5257092..0ae34ac66 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -1,4 +1,4 @@ -QT += core gui widgets uitools network +QT += core gui widgets uitools network multimedia multimediawidgets CONFIG += c++17 @@ -63,6 +63,7 @@ HEADERS += \ include/drstickermovie.h \ include/drtextedit.h \ include/drdiscord.h \ + include/drvideoscreen.h \ include/file_functions.h \ include/hardware_functions.h \ include/lobby.h \ @@ -125,6 +126,7 @@ SOURCES += \ src/drstickermovie.cpp \ src/drtextedit.cpp \ src/drdiscord.cpp \ + src/drvideoscreen.cpp \ src/emotes.cpp \ src/evidence.cpp \ src/file_functions.cpp \ diff --git a/include/aoapplication.h b/include/aoapplication.h index 97c702762..3155e01bf 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -53,6 +53,7 @@ class AOApplication : public QApplication bool has_showname_declaration_feature() const; bool has_chat_speed_feature() const; bool has_character_availability_request_feature() const; + bool has_playable_video_feature() const; /////////////////////////////////////////// @@ -232,6 +233,7 @@ public slots: bool feature_chrini = false; bool feature_chat_speed = false; bool feature_charscheck = false; + bool feature_playable_video = false; ///////////////loading info/////////////////// // player number, it's hardly used but might be needed for some old servers diff --git a/include/aoconfig.h b/include/aoconfig.h index 6a6767da9..1fc481a9d 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -60,6 +60,8 @@ class AOConfig : public QObject bool effect_ignore_suppression() const; int music_volume() const; bool music_ignore_suppression() const; + int video_volume() const; + bool video_ignore_suppression() const; int blip_volume() const; bool blip_ignore_suppression() const; int blip_rate() const; @@ -111,6 +113,8 @@ public slots: void set_effect_ignore_suppression(bool p_enabled); void set_music_volume(int p_number); void set_music_ignore_suppression(bool p_enabled); + void set_video_volume(int p_number); + void set_video_ignore_suppression(bool p_enabled); void set_blip_volume(int p_number); void set_blip_ignore_suppression(bool p_enabled); void set_blip_rate(int p_number); @@ -164,6 +168,8 @@ public slots: void effect_ignore_suppression_changed(bool); void music_volume_changed(int); void music_ignore_suppression_changed(bool); + void video_volume_changed(int); + void video_ignore_suppression_changed(bool); void blip_volume_changed(int); void blip_ignore_suppression_changed(bool); void blip_rate_changed(int); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index df79cba53..cdbdbed1a 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -61,6 +61,7 @@ private slots: void on_system_value_changed(int p_num); void on_effect_value_changed(int p_num); void on_music_value_changed(int p_num); + void on_video_value_changed(int p_num); void on_blip_value_changed(int p_num); private: @@ -119,6 +120,9 @@ private slots: QSlider *ui_music = nullptr; QCheckBox *ui_music_ignore_suppression = nullptr; QLabel *ui_music_value = nullptr; + QSlider *ui_video = nullptr; + QCheckBox *ui_video_ignore_suppression = nullptr; + QLabel *ui_video_value = nullptr; QSlider *ui_blip = nullptr; QCheckBox *ui_blip_ignore_suppression = nullptr; QLabel *ui_blip_value = nullptr; diff --git a/include/courtroom.h b/include/courtroom.h index dba8d8124..907056a04 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -24,6 +24,7 @@ class AOShoutPlayer; class AOSystemPlayer; class AOTimer; class DRCharacterMovie; +class DRVideoWidget; class DRChatLog; class DREffectMovie; class DRSceneMovie; @@ -266,7 +267,7 @@ class Courtroom : public QMainWindow // Generate a File Name based on the time you launched the client QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); - static const int MESSAGE_SIZE = 16; + static const int MESSAGE_SIZE = 17; QString m_chatmessage[MESSAGE_SIZE]; bool chatmessage_is_empty = false; @@ -344,6 +345,7 @@ class Courtroom : public QMainWindow AOImageDisplay *ui_background = nullptr; QWidget *ui_viewport = nullptr; + DRVideoWidget *ui_vp_video = nullptr; DRSceneMovie *ui_vp_background = nullptr; DRCharacterMovie *ui_vp_player_char = nullptr; DRSceneMovie *ui_vp_desk = nullptr; @@ -549,6 +551,7 @@ class Courtroom : public QMainWindow bool is_spectating(); public slots: + void video_done(); void objection_done(); void preanim_done(); diff --git a/include/datatypes.h b/include/datatypes.h index 0fb3037ea..fbf48768c 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -16,6 +16,7 @@ class DREmote int desk_modifier = -1; QString sound_file; int sound_delay = 0; + QString video_file; }; class DRAreaBackground @@ -30,33 +31,14 @@ class DRChatRecord public: using list = QVector; - DRChatRecord(QString p_name, QString p_message) : name(p_name), message(p_message) - {} + DRChatRecord(QString p_name, QString p_message) : name(p_name), message(p_message) {} - QDateTime get_timestamp() const - { - return timestamp; - } - QString get_name() const - { - return name; - } - QString get_message() const - { - return message; - } - bool is_self() const - { - return self; - } - bool is_system() const - { - return system; - } - bool is_music() const - { - return music; - } + QDateTime get_timestamp() const { return timestamp; } + QString get_name() const { return name; } + QString get_message() const { return message; } + bool is_self() const { return self; } + bool is_system() const { return system; } + bool is_music() const { return music; } // set void set_self(const bool p_enabled) @@ -123,10 +105,7 @@ struct server_type return r_info; } - QString to_address() const - { - return ip + ":" + QString::number(port); - } + QString to_address() const { return ip + ":" + QString::number(port); } }; struct char_type @@ -167,6 +146,7 @@ enum ChatMessage : int32_t CMFlipState, CMEffectState, CMTextColor, + CMVideoName, CMShowName, }; @@ -214,8 +194,7 @@ struct ColorInfo { public: ColorInfo() = default; - ColorInfo(QString p_showname, QString p_code) : name(p_showname.toLower()), showname(p_showname), code(p_code) - {} + ColorInfo(QString p_showname, QString p_code) : name(p_showname.toLower()), showname(p_showname), code(p_code) {} QString name; QString showname; diff --git a/include/draudio.h b/include/draudio.h index 4c60b3389..8941ba836 100644 --- a/include/draudio.h +++ b/include/draudio.h @@ -9,6 +9,7 @@ enum class Family FSystem, FEffect, FMusic, + FVideo, FBlip, }; diff --git a/include/drvideoscreen.h b/include/drvideoscreen.h new file mode 100644 index 000000000..3e5620c30 --- /dev/null +++ b/include/drvideoscreen.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +class QVideoWidget; + +#include "draudiodevice.h" +#include "draudiostreamfamily.h" + +class AOApplication; +class AOConfig; +class DRAudioEngine; + +class DRVideoWidget : public QWidget +{ + Q_OBJECT + +public: + DRVideoWidget(QWidget *parent = nullptr); + ~DRVideoWidget(); + + void play(QString character, QString video); + void stop(); + +signals: + void done(); + +protected: + void resizeEvent(QResizeEvent *event) final; + +private: + AOConfig *m_config; + AOApplication *ao_app; + DRAudioEngine *m_engine; + DRAudioStreamFamily::ptr m_family; + + QMediaPlayer *m_player; + QVideoWidget *m_screen; + +private slots: + void update_volume(); + void on_state_changed(QMediaPlayer::State); + void on_video_availability_changed(bool); + void on_device_changed(DRAudioDevice); +}; diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 4db6f2fac..6bf2309d4 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -29,7 +29,7 @@ - 1 + 3 @@ -805,14 +805,14 @@ - + Effects: - + @@ -854,14 +854,14 @@ - + Blips: - + @@ -903,6 +903,55 @@ + + + + Video: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + @@ -920,6 +969,26 @@ false + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<span style=" color:#0000ff;">Suppress Background Audio</span>] is enabled and activated.</p></body></html> + + + Ignore Music + + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + Ignore Blips + + + @@ -939,16 +1008,6 @@ - - - - <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<span style=" color:#0000ff;">Suppress Background Audio</span>] is enabled and activated.</p></body></html> - - - Ignore Music - - - @@ -959,13 +1018,13 @@ - - + + - <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<span style=" color:#0000ff;">Suppress Background Audio</span>] is enabled and activated.</p></body></html> - Ignore Blips + Ignore Video diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index b145a38ea..3be2187a6 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -205,6 +205,11 @@ bool AOApplication::has_character_availability_request_feature() const return feature_charscheck; } +bool AOApplication::has_playable_video_feature() const +{ + return feature_playable_video; +} + void AOApplication::handle_theme_modification() { load_fonts(); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 13c61be57..230318924 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -84,6 +84,8 @@ private slots: bool effect_ignore_suppression; int music_volume; bool music_ignore_suppression; + int video_volume; + bool video_ignore_suppression; int blip_volume; bool blip_ignore_suppression; int blip_rate; @@ -155,6 +157,8 @@ void AOConfigPrivate::read_file() effect_ignore_suppression = cfg.value("effect_ignore_suppression", false).toBool(); music_volume = cfg.value("default_music", 50).toInt(); music_ignore_suppression = cfg.value("music_ignore_suppression", false).toBool(); + video_volume = cfg.value("default_video", 50).toInt(); + video_ignore_suppression = cfg.value("video_ignore_suppression", false).toBool(); blip_volume = cfg.value("default_blip", 50).toInt(); blip_ignore_suppression = cfg.value("blip_ignore_suppression", false).toBool(); blip_rate = cfg.value("blip_rate", 1000000000).toInt(); @@ -167,6 +171,8 @@ void AOConfigPrivate::read_file() audio_engine->get_family(DRAudio::Family::FEffect)->set_ignore_suppression(effect_ignore_suppression); audio_engine->get_family(DRAudio::Family::FMusic)->set_volume(music_volume); audio_engine->get_family(DRAudio::Family::FMusic)->set_ignore_suppression(effect_ignore_suppression); + audio_engine->get_family(DRAudio::Family::FVideo)->set_volume(video_volume); + audio_engine->get_family(DRAudio::Family::FVideo)->set_ignore_suppression(effect_ignore_suppression); audio_engine->get_family(DRAudio::Family::FBlip)->set_volume(blip_volume); audio_engine->get_family(DRAudio::Family::FBlip)->set_ignore_suppression(effect_ignore_suppression); @@ -184,6 +190,7 @@ void AOConfigPrivate::read_file() cfg.endGroup(); } + // audio device update_favorite_device(); } @@ -229,6 +236,8 @@ void AOConfigPrivate::save_file() cfg.setValue("effect_ignore_suppression", effect_ignore_suppression); cfg.setValue("default_music", music_volume); cfg.setValue("music_ignore_suppression", music_ignore_suppression); + cfg.setValue("default_video", video_volume); + cfg.setValue("video_ignore_suppression", video_ignore_suppression); cfg.setValue("default_blip", blip_volume); cfg.setValue("blip_ignore_suppression", blip_ignore_suppression); cfg.setValue("blip_rate", blip_rate); @@ -533,6 +542,16 @@ bool AOConfig::music_ignore_suppression() const return d->music_ignore_suppression; } +int AOConfig::video_volume() const +{ + return d->video_volume; +} + +bool AOConfig::video_ignore_suppression() const +{ + return d->video_ignore_suppression; +} + int AOConfig::blip_volume() const { return d->blip_volume; @@ -857,7 +876,7 @@ void AOConfig::set_effect_ignore_suppression(bool p_enabled) if (d->music_ignore_suppression == p_enabled) return; d->music_ignore_suppression = p_enabled; - d->audio_engine->get_family(DRAudio::Family::FMusic)->set_ignore_suppression(p_enabled); + d->audio_engine->get_family(DRAudio::Family::FEffect)->set_ignore_suppression(p_enabled); d->invoke_signal("music_ignore_suppression_changed", Q_ARG(bool, p_enabled)); } @@ -879,6 +898,24 @@ void AOConfig::set_music_ignore_suppression(bool p_enabled) d->invoke_signal("music_ignore_suppression_changed", Q_ARG(bool, p_enabled)); } +void AOConfig::set_video_volume(int p_number) +{ + if (d->video_volume == p_number) + return; + d->video_volume = p_number; + d->audio_engine->get_family(DRAudio::Family::FVideo)->set_volume(p_number); + d->invoke_signal("video_volume_changed", Q_ARG(int, p_number)); +} + +void AOConfig::set_video_ignore_suppression(bool p_enabled) +{ + if (d->video_ignore_suppression == p_enabled) + return; + d->video_ignore_suppression = p_enabled; + d->audio_engine->get_family(DRAudio::Family::FVideo)->set_ignore_suppression(p_enabled); + d->invoke_signal("video_ignore_suppression_changed", Q_ARG(bool, p_enabled)); +} + void AOConfig::set_blip_volume(int p_number) { if (d->blip_volume == p_number) @@ -910,6 +947,7 @@ void AOConfig::set_blank_blips(bool p_enabled) if (d->blank_blips == p_enabled) return; d->blank_blips = p_enabled; + d->audio_engine->get_family(DRAudio::Family::FVideo)->set_ignore_suppression(p_enabled); d->invoke_signal("blank_blips_changed", Q_ARG(bool, p_enabled)); } diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index a6cfe5266..09b7610d4 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -89,6 +89,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_music = AO_GUI_WIDGET(QSlider, "music"); ui_music_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "music_ignore_suppression"); ui_music_value = AO_GUI_WIDGET(QLabel, "music_value"); + ui_video = AO_GUI_WIDGET(QSlider, "video"); + ui_video_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "video_ignore_suppression"); + ui_video_value = AO_GUI_WIDGET(QLabel, "video_value"); ui_blip = AO_GUI_WIDGET(QSlider, "blip"); ui_blip_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "blip_ignore_suppression"); ui_blip_value = AO_GUI_WIDGET(QLabel, "blip_value"); @@ -157,6 +160,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(music_volume_changed(int)), ui_music, SLOT(setValue(int))); connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), ui_music_ignore_suppression, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(video_volume_changed(int)), ui_video, SLOT(setValue(int))); + connect(m_config, SIGNAL(video_ignore_suppression_changed(bool)), ui_video_ignore_suppression, + SLOT(setChecked(bool))); connect(m_config, SIGNAL(blip_volume_changed(int)), ui_blip, SLOT(setValue(int))); connect(m_config, SIGNAL(blip_ignore_suppression_changed(bool)), ui_blip_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(blip_rate_changed(int)), ui_blip_rate, SLOT(setValue(int))); @@ -219,6 +225,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(ui_music, SIGNAL(valueChanged(int)), m_config, SLOT(set_music_volume(int))); connect(ui_music, SIGNAL(valueChanged(int)), this, SLOT(on_music_value_changed(int))); connect(ui_music_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_music_ignore_suppression(bool))); + connect(ui_video, SIGNAL(valueChanged(int)), m_config, SLOT(set_video_volume(int))); + connect(ui_video, SIGNAL(valueChanged(int)), this, SLOT(on_video_value_changed(int))); + connect(ui_video_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_video_ignore_suppression(bool))); connect(ui_blip, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_volume(int))); connect(ui_blip, SIGNAL(valueChanged(int)), this, SLOT(on_blip_value_changed(int))); connect(ui_blip_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_blip_ignore_suppression(bool))); @@ -279,6 +288,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_effect_ignore_suppression->setChecked(m_config->effect_ignore_suppression()); ui_music->setValue(m_config->music_volume()); ui_music_ignore_suppression->setChecked(m_config->music_ignore_suppression()); + ui_video->setValue(m_config->video_volume()); + ui_video_ignore_suppression->setChecked(m_config->video_ignore_suppression()); ui_blip->setValue(m_config->blip_volume()); ui_blip_ignore_suppression->setChecked(m_config->blip_ignore_suppression()); ui_blip_rate->setValue(m_config->blip_rate()); @@ -539,6 +550,11 @@ void AOConfigPanel::on_music_value_changed(int p_num) ui_music_value->setText(QString::number(p_num) + "%"); } +void AOConfigPanel::on_video_value_changed(int p_num) +{ + ui_video_value->setText(QString::number(p_num) + "%"); +} + void AOConfigPanel::on_blip_value_changed(int p_num) { ui_blip_value->setText(QString::number(p_num) + "%"); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2c657d086..f5dfff8a3 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -25,6 +25,7 @@ #include "drshoutmovie.h" #include "drsplashmovie.h" #include "drstickermovie.h" +#include "drvideoscreen.h" #include "file_functions.h" #include "hardware_functions.h" #include "lobby.h" @@ -647,6 +648,10 @@ void Courtroom::on_ic_message_return_pressed() f_text_color = QString::number(m_text_color); packet_contents.append(f_text_color); + + if (ao_app->has_playable_video_feature()) + packet_contents.append(!l_emote.video_file.isEmpty() ? l_emote.video_file : "0"); + ao_app->send_server_packet(DRPacket("MS", packet_contents)); } @@ -670,7 +675,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) { if (p_contents.size() < 15) return; - else if (p_contents.size() == 15) + while (p_contents.length() < MESSAGE_SIZE) p_contents.append(nullptr); for (int i = 0; i < MESSAGE_SIZE; ++i) @@ -760,6 +765,14 @@ void Courtroom::handle_chatmessage(QStringList p_contents) save_textlog(f_showname + ": " + l_message); } + ui_vp_video->show(); + ui_vp_video->play(m_chatmessage[CMChrName], m_chatmessage[CMVideoName]); +} + +void Courtroom::video_done() +{ + ui_vp_video->hide(); + int objection_mod = m_chatmessage[CMShoutModifier].toInt(); QString f_char = m_chatmessage[CMChrName]; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 1727a6781..52c91d264 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -25,6 +25,7 @@ #include "drsplashmovie.h" #include "drstickermovie.h" #include "drtextedit.h" +#include "drvideoscreen.h" #include "file_functions.h" #include "theme.h" @@ -66,6 +67,8 @@ void Courtroom::create_widgets() ui_background = new AOImageDisplay(this, ao_app); ui_viewport = new QWidget(this); + ui_vp_video = new DRVideoWidget(ui_viewport); + ui_vp_video->hide(); ui_vp_background = new DRSceneMovie(ui_viewport); ui_vp_player_char = new DRCharacterMovie(ui_viewport); ui_vp_desk = new DRSceneMovie(ui_viewport); @@ -255,6 +258,7 @@ void Courtroom::connect_widgets() { connect(m_keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); + connect(ui_vp_video, SIGNAL(done()), this, SLOT(video_done())); connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); @@ -350,6 +354,7 @@ void Courtroom::reset_widget_names() widget_names = { {"courtroom", this}, {"viewport", ui_viewport}, + {"video", ui_vp_video}, {"background", ui_vp_background}, //* {"player_char", ui_vp_player_char}, //* {"desk", ui_vp_desk}, //* @@ -580,6 +585,9 @@ void Courtroom::set_widgets() set_size_and_pos(ui_viewport, "viewport", COURTROOM_DESIGN_INI, ao_app); + ui_vp_video->move(0, 0); + ui_vp_video->resize(ui_viewport->size()); + ui_vp_background->move(0, 0); ui_vp_background->resize(ui_viewport->size()); diff --git a/src/draudioengine.cpp b/src/draudioengine.cpp index 882f9b5fd..f34651bc5 100644 --- a/src/draudioengine.cpp +++ b/src/draudioengine.cpp @@ -27,6 +27,8 @@ static class DRAudioEngineData DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FEffect))); d->family_map.insert(DRAudio::Family::FMusic, DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FMusic))); + d->family_map.insert(DRAudio::Family::FVideo, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FVideo))); d->family_map.insert(DRAudio::Family::FBlip, DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FBlip))); diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp index 4b0e72657..d9b15ac62 100644 --- a/src/draudiostreamfamily.cpp +++ b/src/draudiostreamfamily.cpp @@ -112,6 +112,11 @@ DRAudioStreamFamily::stream_list DRAudioStreamFamily::get_stream_list() const return m_stream_list; } +int32_t DRAudioStreamFamily::get_volume() const +{ + return m_volume; +} + float DRAudioStreamFamily::calculate_volume() { float volume = float(m_volume) * 0.01f; diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp new file mode 100644 index 000000000..6b5830187 --- /dev/null +++ b/src/drvideoscreen.cpp @@ -0,0 +1,156 @@ +#include "drvideoscreen.h" + +#include +#include +#include +#include +#include + +#include "aoapplication.h" +#include "aoconfig.h" +#include "draudiodevice.h" +#include "draudioengine.h" +#include "draudiostreamfamily.h" + +DRVideoWidget::DRVideoWidget(QWidget *parent) : QWidget(parent), ao_app(dynamic_cast(qApp)) +{ + Q_ASSERT(ao_app); + + m_config = new AOConfig(this); + m_engine = new DRAudioEngine(this); + m_family = m_engine->get_family(DRAudio::Family::FVideo); + m_player = new QMediaPlayer(this); + m_screen = new QVideoWidget(this); + m_player->setVideoOutput(m_screen); + + connect(m_config, SIGNAL(video_volume_changed(int)), this, SLOT(update_volume())); + connect(m_engine, SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); + connect(m_engine, SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); + connect(m_family.get(), SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); + connect(m_family.get(), SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); + connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(on_state_changed(QMediaPlayer::State))); + connect(m_player, SIGNAL(videoAvailableChanged(bool)), this, SLOT(on_video_availability_changed(bool))); +} + +DRVideoWidget::~DRVideoWidget() +{} + +/** + * @brief Attempts to play a video file located in a character's videos directory. + * + * If the file isn't valid for any reason (not found, error, file has no video track, etc) the signal done() will be + * emitted. + * + * If a video file is already playing, it will immediately stop it + * without emitting done() before playing the newly designated file + */ +void DRVideoWidget::play(QString p_character, QString p_video) +{ + if (m_player->state() != QMediaPlayer::StoppedState) + stop(); + + qInfo() << "DRVideoWidget::play" + << "starting:" + << "character:" << p_character << "video:" << p_video; + + QStringList l_filelist; + const QString l_video_path = QString("videos/%1").arg(p_video); + for (const QString &i_character_name : ao_app->get_char_include_tree(p_character)) + { + l_filelist.append(ao_app->get_character_path(i_character_name, l_video_path)); + qDebug() << l_filelist.last(); + } + + const QString l_file = ao_app->find_asset_path(l_filelist); + if (l_file.isEmpty()) + { + qWarning() << "DRVideoWidget::play" + << "error: file could not be found"; + emit done(); + return; + } + + m_player->setMedia(QUrl(l_file)); + m_player->setMuted(true); + m_player->play(); + if (m_player->error()) + { + qWarning() << "DRVideoWidget::play" + << "error:" << m_player->errorString(); + emit done(); + } +} + +void DRVideoWidget::stop() +{ + m_player->stop(); +} + +void DRVideoWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + m_screen->resize(size()); +} + +void DRVideoWidget::update_volume() +{ + int l_volume = (m_family->get_volume() * m_engine->get_volume()) / 100; + + if (!m_family->is_ignore_suppression() && (m_family->is_suppressed() || m_engine->is_suppressed())) + { + l_volume = 0; + } + + if (m_player->volume() == l_volume) + return; + m_player->setVolume(l_volume); +} + +void DRVideoWidget::on_state_changed(QMediaPlayer::State p_state) +{ + switch (p_state) + { + case QMediaPlayer::StoppedState: + emit done(); + break; + + default: + break; + } +} + +void DRVideoWidget::on_video_availability_changed(bool p_state) +{ + m_player->setMuted(!p_state); + if (!p_state) + { + qWarning() << "DRVideoWidget::play" + << "error: video track not available"; + m_player->stop(); + emit done(); + } +} + +void DRVideoWidget::on_device_changed(DRAudioDevice p_device) +{ + const QString l_new_device_name = p_device.get_name(); + const QList l_device_list = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); + for (const QAudioDeviceInfo &i_device : l_device_list) + { + const QString l_device_name = i_device.deviceName(); + if (l_device_name == l_new_device_name) + { + QMediaService *l_service = m_player->service(); + QAudioOutputSelectorControl *l_control = l_service->requestControl(); + if (!l_control) + { + qCritical() << "DRVideoWidget" + << "error: failed to change device; QAudioOutputSelectorControl is null"; + return; + } + l_control->setActiveOutput(i_device.deviceName()); + l_service->releaseControl(l_control); + break; + } + } +} diff --git a/src/server_socket.cpp b/src/server_socket.cpp index d2fdf08cc..decbdccf6 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -62,6 +62,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) feature_chrini = false; feature_chat_speed = false; feature_charscheck = false; + feature_playable_video = false; send_server_packet(DRPacket("HI", {get_hdid()})); } @@ -92,6 +93,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) feature_chrini = l_content.contains("chrini", Qt::CaseInsensitive); feature_chat_speed = l_content.contains("chat_speed", Qt::CaseInsensitive); feature_charscheck = l_content.contains("charscheck", Qt::CaseInsensitive); + feature_playable_video = l_content.contains("playable_video", Qt::CaseInsensitive); } else if (l_header == "PN") { diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index a51e4f916..8f7f88909 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -683,6 +683,10 @@ QVector AOApplication::get_emote_list(QString p_chr) l_emote.sound_delay = qMax(l_chrini.value(i_key).toInt(), 0); l_chrini.endGroup(); + l_chrini.beginGroup(drLookupKey(l_group_list, "videos")); + l_emote.video_file = l_chrini.value(i_key).toString(); + l_chrini.endGroup(); + // add the emote r_emote_list.append(l_emote); } From e5a66b0b4bfb9d8a74eca04b2d64ecbdd4466703 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 21 Feb 2022 00:15:22 +0100 Subject: [PATCH 603/842] Various fixes for audio systems --- src/aoconfig.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 230318924..5695e9fba 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -873,11 +873,11 @@ void AOConfig::set_effect_volume(int p_number) void AOConfig::set_effect_ignore_suppression(bool p_enabled) { - if (d->music_ignore_suppression == p_enabled) + if (d->effect_ignore_suppression == p_enabled) return; - d->music_ignore_suppression = p_enabled; + d->effect_ignore_suppression = p_enabled; d->audio_engine->get_family(DRAudio::Family::FEffect)->set_ignore_suppression(p_enabled); - d->invoke_signal("music_ignore_suppression_changed", Q_ARG(bool, p_enabled)); + d->invoke_signal("effect_ignore_suppression_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_music_volume(int p_number) @@ -947,7 +947,7 @@ void AOConfig::set_blank_blips(bool p_enabled) if (d->blank_blips == p_enabled) return; d->blank_blips = p_enabled; - d->audio_engine->get_family(DRAudio::Family::FVideo)->set_ignore_suppression(p_enabled); + d->audio_engine->get_family(DRAudio::Family::FBlip)->set_ignore_suppression(p_enabled); d->invoke_signal("blank_blips_changed", Q_ARG(bool, p_enabled)); } From 3ad7262571a2f8d55f6d55538726101d0659b826 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 21 Feb 2022 00:18:16 +0100 Subject: [PATCH 604/842] Removed unnecessary output --- src/drvideoscreen.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index 6b5830187..dd26cf6c5 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -58,7 +58,6 @@ void DRVideoWidget::play(QString p_character, QString p_video) for (const QString &i_character_name : ao_app->get_char_include_tree(p_character)) { l_filelist.append(ao_app->get_character_path(i_character_name, l_video_path)); - qDebug() << l_filelist.last(); } const QString l_file = ao_app->find_asset_path(l_filelist); From b8759af9d2e1f7dd9495e8700d55233c4cc8cda4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Mon, 21 Feb 2022 00:22:13 +0100 Subject: [PATCH 605/842] [LINUX] Install missing dependencies (libqt5multimedia5, libqt5multimediawidgets5) --- .github/workflows/build-all.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index a66326873..bbd19d21a 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -336,6 +336,8 @@ jobs: sudo apt-get install build-essential sudo apt-get install qt5-default qttools5-dev sudo apt-get install libqt5designer5 + sudo apt-get install libqt5multimedia5 + sudo apt-get install libqt5multimediawidgets5 sudo apt-get install git - name: Build QtApng external library From 60468c198b0b17de896bfea3de56da6ba087038d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Mon, 21 Feb 2022 01:41:14 +0100 Subject: [PATCH 606/842] [LINUX] Install dependency packages: libqt5multimedia5-plugins, qtmultimedia5-dev, --- .github/workflows/build-all.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index bbd19d21a..7c41d984c 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -337,7 +337,9 @@ jobs: sudo apt-get install qt5-default qttools5-dev sudo apt-get install libqt5designer5 sudo apt-get install libqt5multimedia5 + sudo apt-get install libqt5multimedia5-plugins sudo apt-get install libqt5multimediawidgets5 + sudo apt-get install qtmultimedia5-dev sudo apt-get install git - name: Build QtApng external library From 424214ac5d2e137842e1d6eef27a8c4402016a21 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 21 Feb 2022 02:12:02 +0100 Subject: [PATCH 607/842] Clear cache --- src/drvideoscreen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index dd26cf6c5..d2d0cbe0a 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -46,8 +46,8 @@ DRVideoWidget::~DRVideoWidget() */ void DRVideoWidget::play(QString p_character, QString p_video) { - if (m_player->state() != QMediaPlayer::StoppedState) - stop(); + m_player->stop(); + m_player->setMedia(nullptr); qInfo() << "DRVideoWidget::play" << "starting:" From 381e200bca3edf34e1c532ee58ac4ed8ec809d05 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 21 Feb 2022 04:13:20 +0100 Subject: [PATCH 608/842] Update volume before playing video --- src/drvideoscreen.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index d2d0cbe0a..4f1fc9e49 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -69,6 +69,7 @@ void DRVideoWidget::play(QString p_character, QString p_video) return; } + update_volume(); m_player->setMedia(QUrl(l_file)); m_player->setMuted(true); m_player->play(); From 8ee5eab5992b30e9a59cb40476394b6c7be7e76d Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 12 Mar 2022 16:16:22 -0500 Subject: [PATCH 609/842] Use witness background/desk if positional background/desk unavailable --- include/drmovie.h | 1 + src/courtroom.cpp | 10 ++++++++-- src/drmovie.cpp | 7 ++++++- src/drscenemovie.cpp | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/include/drmovie.h b/include/drmovie.h index 30f24d2d7..131b68045 100644 --- a/include/drmovie.h +++ b/include/drmovie.h @@ -21,6 +21,7 @@ class DRMovie : public QLabel void set_hide_on_done(bool); bool is_running(); + bool is_valid(); void start(); void stop(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2c657d086..69ab82812 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -250,8 +250,10 @@ void Courtroom::set_window_title(QString p_title) void Courtroom::update_background_scene() { // witness is default if pos is invalid - QString f_background = "witnessempty"; - QString f_desk_image = "stand"; + QString f_default_background = "witnessempty"; + QString f_default_desk_image = "stand"; + QString f_background = f_default_background; + QString f_desk_image = f_default_desk_image; QString f_desk_mod = m_chatmessage[CMDeskModifier]; QString f_side = m_chatmessage[CMPosition]; @@ -289,9 +291,13 @@ void Courtroom::update_background_scene() { ui_vp_desk->show(); ui_vp_desk->set_image(f_desk_image); + if (!ui_vp_desk->is_valid()) + ui_vp_desk->set_image(f_default_desk_image); } ui_vp_background->set_image(f_background); + if (!ui_vp_background->is_valid()) + ui_vp_background->set_image(f_default_background); } DRAreaBackground Courtroom::get_background() diff --git a/src/drmovie.cpp b/src/drmovie.cpp index 9f372e09f..4b713562b 100644 --- a/src/drmovie.cpp +++ b/src/drmovie.cpp @@ -103,7 +103,7 @@ void DRMovie::start() m_movie.setFileName(m_file_name); m_frame_count = m_movie.frameCount(); m_frame_number = m_movie.currentFrameNumber(); - if (m_movie.isValid()) + if (is_valid()) { m_movie.start(); m_movie.setPaused(true); @@ -204,3 +204,8 @@ void DRMovie::jump_next_frame() m_movie.jumpToNextFrame(); } } + +bool DRMovie::is_valid() +{ + return !m_movie.fileName().isEmpty() && m_movie.isValid(); +} diff --git a/src/drscenemovie.cpp b/src/drscenemovie.cpp index bf0ceefbf..ce52c789b 100644 --- a/src/drscenemovie.cpp +++ b/src/drscenemovie.cpp @@ -1,3 +1,4 @@ +#include "commondefs.h" #include "drscenemovie.h" #include "aoapplication.h" From 3724a0d22b8e253e19d85ed600929c973036cfec Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 17 Mar 2022 01:27:42 +0100 Subject: [PATCH 610/842] Added DRAudiotrackMetadata, separated character loading, ... * Added DRAudiotrackMetadata Allowing a content manager to provide title, loop points and so on for songs * Made character reloading separate from theme reloading --- dronline-client.pro | 4 + include/aoapplication.h | 7 +- include/aoconfig.h | 16 +- include/aoconfigpanel.h | 34 +- include/aomusicplayer.h | 2 +- include/courtroom.h | 4 +- include/draudiodevice.h | 25 +- include/draudioengine.h | 18 +- include/draudioengine_p.h | 41 +- include/draudiostream.h | 74 ++- include/draudiostreamfamily.h | 1 - include/draudiotrackmetadata.h | 24 + include/utils.h | 21 + res/ui/config_panel.ui | 1010 +++++++++++++++++--------------- src/aoapplication.cpp | 13 +- src/aoconfig.cpp | 2 +- src/aoconfigpanel.cpp | 70 ++- src/aomusicplayer.cpp | 16 +- src/courtroom.cpp | 31 +- src/courtroom_character.cpp | 1 + src/draudiodevice.cpp | 49 +- src/draudioengine.cpp | 58 +- src/draudioengine_p.cpp | 156 ++--- src/draudiostream.cpp | 308 ++++------ src/draudiostreamfamily.cpp | 14 +- src/draudiotrackmetadata.cpp | 114 ++++ src/path_functions.cpp | 10 +- src/server_socket.cpp | 2 +- src/text_file_functions.cpp | 43 +- src/utils.cpp | 27 + 30 files changed, 1206 insertions(+), 989 deletions(-) create mode 100644 include/draudiotrackmetadata.h create mode 100644 include/utils.h create mode 100644 src/draudiotrackmetadata.cpp create mode 100644 src/utils.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 2d5257092..4241fb55d 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -15,6 +15,7 @@ DEFINES += DRO_ACKMS CONFIG(debug, debug|release):DEFINES += DR_DEV HEADERS += \ + include/draudiotrackmetadata.h \ include/drscenemovie.h \ include/drsplashmovie.h \ include/aoapplication.h \ @@ -69,6 +70,7 @@ HEADERS += \ include/log.h \ include/misc_functions.h \ include/theme.h \ + include/utils.h \ include/version.h SOURCES += \ @@ -112,6 +114,7 @@ SOURCES += \ src/draudioerror.cpp \ src/draudiostream.cpp \ src/draudiostreamfamily.cpp \ + src/draudiotrackmetadata.cpp \ src/drcharactermovie.cpp \ src/drchatlog.cpp \ src/dreffectmovie.cpp \ @@ -138,6 +141,7 @@ SOURCES += \ src/server_socket.cpp \ src/text_file_functions.cpp \ src/theme.cpp \ + src/utils.cpp \ src/version.cpp # 1. You need to get BASS and put the x86 bass DLL/headers in the project root folder diff --git a/include/aoapplication.h b/include/aoapplication.h index 97c702762..52de5e3ad 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -71,6 +71,7 @@ class AOApplication : public QApplication QString get_character_path(QString p_character, QString p_file); // QString get_demothings_path(); QString get_sounds_path(QString p_file); + QString get_music_folder_path(); QString get_music_path(QString p_song); QString format_background_path(QString p_identifier); QStringList get_available_background_identifier_list(); @@ -207,7 +208,9 @@ public slots: void loading_cancelled(); signals: - void theme_reloaded(); + void reload_theme(); + void reload_character(); + void reload_audiotracks(); private: AOConfig *ao_config = nullptr; @@ -258,6 +261,8 @@ private slots: void on_courtroom_closing(); void on_courtroom_destroyed(); void handle_theme_modification(); + void handle_character_reloading(); + void handle_audiotracks_reloading(); }; #endif // AOAPPLICATION_H diff --git a/include/aoconfig.h b/include/aoconfig.h index 6a6767da9..80841f4bc 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -130,13 +130,6 @@ public slots: void discord_hide_character_changed(bool); // game - void theme_changed(QString); - void gamemode_changed(QString); - void manual_gamemode_changed(QString); - void manual_gamemode_selection_changed(bool); - void timeofday_changed(QString); - void manual_timeofday_changed(QString); - void manual_timeofday_selection_changed(bool); void showname_changed(QString); void showname_placeholder_changed(QString); void character_ini_changed(QString base_character); @@ -145,6 +138,15 @@ public slots: void emote_preview_changed(bool); void sticky_sfx_changed(bool); + // theme + void theme_changed(QString); + void gamemode_changed(QString); + void manual_gamemode_changed(QString); + void manual_gamemode_selection_changed(bool); + void timeofday_changed(QString); + void manual_timeofday_changed(QString); + void manual_timeofday_selection_changed(bool); + // log void log_max_lines_changed(int); void log_display_timestamp_changed(bool); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index df79cba53..a62e32476 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -30,6 +30,8 @@ public slots: signals: void reload_theme(); + void reload_character(); + void reload_audiotracks(); protected: void showEvent(QShowEvent *event) override; @@ -42,6 +44,8 @@ public slots: private slots: void on_reload_theme_clicked(); + void on_reload_character_clicked(); + void on_reload_audiotracks_clicked(); void on_theme_changed(QString); void on_gamemode_changed(QString); void on_manual_gamemode_selection_changed(bool); @@ -56,7 +60,7 @@ private slots: void on_device_current_index_changed(int p_index); void on_audio_device_changed(DRAudioDevice p_device); void on_favorite_audio_device_changed(DRAudioDevice p_device); - void on_audio_device_list_changed(QList p_device_list); + void on_audio_device_list_changed(QVector p_device_list); void on_master_value_changed(int p_num); void on_system_value_changed(int p_num); void on_effect_value_changed(int p_num); @@ -69,10 +73,12 @@ private slots: // driver AOConfig *m_config = nullptr; DRAudioEngine *m_engine = nullptr; + // behaviour QPushButton *ui_save = nullptr; QPushButton *ui_close = nullptr; QCheckBox *ui_autosave = nullptr; + // general QLineEdit *ui_username = nullptr; QLineEdit *ui_callwords = nullptr; @@ -80,6 +86,21 @@ private slots: QGroupBox *ui_discord_presence = nullptr; QCheckBox *ui_discord_hide_server = nullptr; QCheckBox *ui_discord_hide_character = nullptr; + + // character + QLineEdit *ui_showname = nullptr; + QPushButton *ui_reload_character = nullptr; + + // emotes + QCheckBox *ui_emote_preview = nullptr; + QCheckBox *ui_always_pre = nullptr; + QCheckBox *ui_sticky_sfx = nullptr; + + // dialog + QSpinBox *ui_chat_tick_interval = nullptr; + QSpinBox *ui_blip_rate = nullptr; + QCheckBox *ui_blank_blips = nullptr; + // game QComboBox *ui_theme = nullptr; QPushButton *ui_reload_theme = nullptr; @@ -89,13 +110,8 @@ private slots: QLineEdit *ui_timeofday = nullptr; QComboBox *ui_manual_timeofday = nullptr; QCheckBox *ui_manual_timeofday_selection = nullptr; - QLineEdit *ui_showname = nullptr; - QCheckBox *ui_always_pre = nullptr; - QSpinBox *ui_chat_tick_interval = nullptr; - QCheckBox *ui_emote_preview = nullptr; - QCheckBox *ui_sticky_sfx = nullptr; - // IC Chatlog + // chatlog QSpinBox *ui_log_max_lines = nullptr; QCheckBox *ui_log_display_timestamp = nullptr; QCheckBox *ui_log_display_self_highlight = nullptr; @@ -105,6 +121,7 @@ private slots: QRadioButton *ui_log_orientation_top_down = nullptr; QRadioButton *ui_log_orientation_bottom_up = nullptr; QCheckBox *ui_log_is_recording = nullptr; + // audio QComboBox *ui_device = nullptr; QCheckBox *ui_favorite_device = nullptr; @@ -122,8 +139,7 @@ private slots: QSlider *ui_blip = nullptr; QCheckBox *ui_blip_ignore_suppression = nullptr; QLabel *ui_blip_value = nullptr; - QSpinBox *ui_blip_rate = nullptr; - QCheckBox *ui_blank_blips = nullptr; + QPushButton *ui_reload_audiotracks = nullptr; // about QLabel *ui_about = nullptr; diff --git a/include/aomusicplayer.h b/include/aomusicplayer.h index 0b081dda3..af9b1c910 100644 --- a/include/aomusicplayer.h +++ b/include/aomusicplayer.h @@ -16,5 +16,5 @@ public slots: private: DRAudioStreamFamily::ptr m_family; - QString m_song; + QString m_file_name; }; diff --git a/include/courtroom.h b/include/courtroom.h index dba8d8124..89c8ee817 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -653,7 +653,9 @@ private slots: void on_wtce_clicked(); void on_change_character_clicked(); - void reload_theme(); + void load_theme(); + void load_character(); + void load_audiotracks(); void on_call_mod_clicked(); void on_switch_area_music_clicked(); diff --git a/include/draudiodevice.h b/include/draudiodevice.h index 2cd78965b..215cc057d 100644 --- a/include/draudiodevice.h +++ b/include/draudiodevice.h @@ -1,18 +1,18 @@ #pragma once -#include - #include #include +#include #include -class DRAudioEngine; -class DRAudioEnginePrivate; +#include class DRAudioDevice { public: + static QVector get_device_list(); + enum State : DWORD { SEnabled = BASS_DEVICE_ENABLED, @@ -22,27 +22,24 @@ class DRAudioDevice Q_DECLARE_FLAGS(States, State) DRAudioDevice(); - DRAudioDevice(DWORD p_device, BASS_DEVICEINFO p_device_info); + ~DRAudioDevice(); - std::optional get_id() const; + DWORD get_id() const; QString get_name() const; QString get_driver() const; States get_states() const; - // condition check - bool is_state(State p_state) const; + bool is_state(State state) const; bool is_enabled() const; bool is_default() const; bool is_init() const; -private: - friend class DRAudioEngine; - friend class DRAudioEnginePrivate; + bool operator==(const DRAudioDevice &other) const; + bool operator!=(const DRAudioDevice &other) const; - std::optional m_id; +private: + DWORD m_id; QString m_name; QString m_driver; States m_states; - - bool merge(DRAudioDevice &p_device); }; Q_DECLARE_METATYPE(DRAudioDevice) diff --git a/include/draudioengine.h b/include/draudioengine.h index 62bbac66d..91d35666f 100644 --- a/include/draudioengine.h +++ b/include/draudioengine.h @@ -7,9 +7,10 @@ class DRAudioEngine : public QObject Q_OBJECT public: - static std::optional get_device(); + static QVector get_device_list(); + static std::optional get_current_device(); + static QString get_favorite_device_driver(); static std::optional get_favorite_device(); - static QList get_device_list(); static DRAudioStreamFamily::ptr get_family(DRAudio::Family p_family); static QList get_family_list(); static int32_t get_volume(); @@ -23,9 +24,7 @@ class DRAudioEngine : public QObject ~DRAudioEngine(); public slots: - void set_device(DRAudioDevice p_device); - void set_favorite_device(DRAudioDevice p_device); - void set_favorite_device_by_driver(QString p_device_driver); + void set_favorite_device_driver(QString driver); void set_volume(int32_t p_volume); void set_options(DRAudio::Options p_options); // option set @@ -34,14 +33,9 @@ public slots: void set_suppress_background_audio(bool p_enabled); signals: - void device_changed(DRAudioDevice); + void device_list_changed(QVector); + void current_device_changed(DRAudioDevice); void favorite_device_changed(DRAudioDevice); - void device_list_changed(QList); void volume_changed(int32_t); void options_changed(DRAudio::Options); - -private: - friend class DRAudioStream; - - static void check_stream_error(); }; diff --git a/include/draudioengine_p.h b/include/draudioengine_p.h index 7eb8f394d..e6129fe97 100644 --- a/include/draudioengine_p.h +++ b/include/draudioengine_p.h @@ -1,19 +1,16 @@ #pragma once -#include "draudio.h" -#include "draudiostreamfamily.h" - #include #include #include +#include "draudio.h" +#include "draudiostreamfamily.h" + class QTimer; #include -class DRAudioEngine; -class DRAudioEngineData; - class DRAudioEnginePrivate : public QObject { Q_OBJECT @@ -21,37 +18,27 @@ class DRAudioEnginePrivate : public QObject public: using ptr = QPointer; - DRAudioEnginePrivate(); - ~DRAudioEnginePrivate(); - - void invoke_signal(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)); - -private: - friend class DRAudioEngine; - friend class DRAudioEngineData; - QObjectList children; QPointer engine; - QPointer event_timer; - bool check_device = false; - + QPointer update_timer; + QVector device_list; std::optional device; - std::optional previous_device; + QString favorite_device_driver; std::optional favorite_device; int32_t volume = 0; DRAudio::Options options; - - QMap device_map; QMap family_map; - void update_device(); + DRAudioEnginePrivate(); + ~DRAudioEnginePrivate(); + + void invoke_signal(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)); + +public slots: void update_device_list(); - void update_options(); + void update_current_device(); void update_volume(); - void check_stream_error(); - -private slots: - void on_event_tick(); + void update_options(); }; diff --git a/include/draudiostream.h b/include/draudiostream.h index f3a5355e3..d454ca6f8 100644 --- a/include/draudiostream.h +++ b/include/draudiostream.h @@ -34,62 +34,56 @@ class DRAudioStream : public QObject ~DRAudioStream(); DRAudio::Family get_family() const; - std::optional get_file() const; - - // state + QString get_file_name() const; + bool is_repeatable() const; bool is_playing() const; - QWORD loop_start(); - QWORD loop_end(); - public slots: - std::optional set_file(QString m_file); + std::optional set_file_name(QString m_file); void set_volume(float p_volume); + void set_repeatable(bool); + void set_loop(quint64 start, quint64 end); void play(); void stop(); + signals: - void file_changed(QString p_file); + void file_name_changed(QString p_file); void finished(); + void looped(); public: - static void CALLBACK on_sync_callback(HSYNC hsync, DWORD ch, DWORD data, void *userdata); + static void CALLBACK end_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata); + static void CALLBACK loop_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata); private: - friend class DRAudioEngine; - friend class DRAudioEnginePrivate; friend class DRAudioStreamFamily; - // static method + enum InitState + { + InitError, + InitNotDone, + InitFinished, + }; + + DRAudioEngine *m_engine = nullptr; DRAudio::Family m_family; - std::optional m_file; - QWORD m_loop_start = {}; - QWORD m_loop_end = {}; - float m_volume; - - int m_loop_sync; - - // bass - std::optional m_hstream; - QStack m_hsync_stack; - std::optional m_position; - - void cache_position(); - void update_device(); - - /** - * @brief Sets up looping. - * - * Right now we only support files that satisfy the following: - * 1. They are OGG files - * 2. They indicate a positive sample rate - * 3. They have, as OGG comments, positive values of LoopStart and LoopEnd (denoted as an int in samples) - * 4. LoopStart <= LoopEnd - */ - void setup_looping(); - -private slots: - void on_device_error(); + QString m_file_name; + InitState m_init_state = InitNotDone; + HSTREAM m_hstream = 0; + float m_volume = 0.0f; + bool m_repeatable = false; + QWORD m_loop_start = 0; + QWORD m_loop_start_pos = 0; + QWORD m_loop_end = 0; + QWORD m_loop_end_pos = 0; + HSYNC m_loop_sync = 0; + + bool ensure_init(); + bool ensure_init() const; + void update_device(DRAudioDevice); + void init_loop(); + void seek_loop_start(); signals: void device_error(QPrivateSignal); diff --git a/include/draudiostreamfamily.h b/include/draudiostreamfamily.h index bbf6bbab3..17f485c12 100644 --- a/include/draudiostreamfamily.h +++ b/include/draudiostreamfamily.h @@ -60,7 +60,6 @@ public slots: float calculate_volume(); - void update_device(); void update_capacity(); void update_options(); void update_volume(); diff --git a/include/draudiotrackmetadata.h b/include/draudiotrackmetadata.h new file mode 100644 index 000000000..366ede7f5 --- /dev/null +++ b/include/draudiotrackmetadata.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +class DRAudiotrackMetadata +{ +public: + static void update_cache(); + + DRAudiotrackMetadata(); + DRAudiotrackMetadata(QString file_name); + ~DRAudiotrackMetadata(); + + QString file_name(); + QString title(); + quint64 loop_start(); + quint64 loop_end(); + +private: + QString m_file_name; + QString m_title; + quint64 m_loop_start = 0; + quint64 m_loop_end = 0; +}; diff --git a/include/utils.h b/include/utils.h new file mode 100644 index 000000000..5031a82a4 --- /dev/null +++ b/include/utils.h @@ -0,0 +1,21 @@ +#pragma once + +class QSettings; +class QString; + +namespace utils +{ + +class QSettingsKeyFetcher +{ +public: + explicit QSettingsKeyFetcher(QSettings &settings); + + QString lookup_group(QString key_name); + QString lookup_value(QString key_name); + +private: + QSettings &m_settings; +}; + +} // namespace utils diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 4db6f2fac..763dae916 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -7,7 +7,7 @@ 0 0 487 - 387 + 439 @@ -19,13 +19,16 @@ 487 - 300 + 0 Form + + QLayout::SetMaximumSize + @@ -35,42 +38,48 @@ General - - - - - Username: - - - - - - - - - - Callwords: - - - true - - - - - - - Separate words by space - - - - - - - Qt::Horizontal + + + + + User + + + + + Name: + + + + + + + Your username + + + + + + + Callwords: + + + true + + + + + + + Separate words by space + + + + - + @@ -108,508 +117,389 @@ - - - - Server alerts: - - - - + - + 0 0 - + Server alerts + + + + Qt::Vertical + + + + 20 + 40 + + + + - + Game - - - - - Theme: + + + + + Character + + + + + Name: + + + + + + + The name of your character + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p>Reload the currently selected character. If a character is not selected, nothing happens.</p></body></html> + + + Reload + + + + + + + + + + 0 + 121 + + + + Emote options + + + + + + Sticky SFX + + + + + + + Emote preview tooltip + + + + + + + + 0 + 0 + + + + Always-on Anim + + + false + + + + + + + - - - - - 0 - 0 - + + + + + 0 + 101 + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - + + Dialog + + + + + + Chat-tick interval: + + - - - - - 0 - 0 - + + + + ms + + + 0 + + + 999 + + + + - Reload + Blip Rate: + + + + + + 1 + + + 1000000000 + + + 1000000000 + + + + + + + + 0 + 0 + + + + Blanks + + + + + - - - - Gamemode: + + + + Qt::Vertical - + + + 20 + 40 + + + - - - - - - - - false - - - - true - - - - <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> gamemode.</p></body></html> - - - <default> - - - false - - - true - - - - - - - true - - - <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> gamemode.</p></body></html> - - - - - - - + + + + + Theme + + + + + + 0 0 - - <html><head/><body><p>Allows you to manually switch to a gamemode interface.</p></body></html> - Manual - - - - - - Time of day: - - - - - - - - - - - false - - - - true - - - - <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> time of day.</p></body></html> - - - <default> - - - false - - - true - - - - - - - <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> time of day.</p></body></html> - - - - - - - + + - + 0 0 - Manual + Gamemode: - - - - - - Qt::Horizontal - - - - - - - Showname: - - - - - - - - - - Chat-tick interval: - - - - - - - ms - - - 0 - - - 999 - - - - - - - Blip Rate: - - - - - - - - - 1 - - - 1000000000 + + + + + 0 + 0 + - - 1000000000 + + Time of day: - - + + 0 0 - - Blanks + + <html><head/><body><p>Allows you to manually switch to a gamemode interface.</p></body></html> - - - - - - - - Always-anim: - - - true - - - - - - - - 0 - 0 - - - - - - - false - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - Emote preview: - - - - - - - Sticky SFX: - - - - - - - - - - - - - - - Log - - - - - - - Max length: - - - - - - - lines - - - 1 - - - 10000 - - - 200 + Manual - - - - - - Options - - - - - - - 0 - 0 - - - - Display timestamp - - - - - - - Display self-identification highlight - - - - - - - - 0 - 0 - - - - Display empty messages - - - - - - - - 0 - 0 - - - - Display music switch - - - - - - - - 0 - 0 - - - - <html><head/><body><p>Extra newlines are added between the name and message.</p></body></html> - - - Format use newline - - - - - - - - - - - 0 - 0 - - - - Orientation - - - - - - Top-down - - - - - - - Bottom-up - - - - - - - - - + + + + 0 + 0 + + - Save to disk: + Folder: - - + + + + + + false + + + + true + + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> gamemode.</p></body></html> + + + <default> + + + false + + + true + + + + + + + true + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> gamemode.</p></body></html> + + + + + + + + + + + false + + + + true + + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> time of day.</p></body></html> + + + <default> + + + false + + + true + + + + + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> time of day.</p></body></html> + + + + + + + + + + - + 0 0 - + Reload - + Qt::Vertical @@ -627,8 +517,8 @@ Audio - - + + @@ -905,7 +795,7 @@ - + <html><head/><body><p>If enabled, audio will be temporarily suppressed when the client is not in focus.<br/><br/>This option is never applied to the System audio channel.</p></body></html> @@ -972,7 +862,31 @@ - + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reload + + + + + + Qt::Vertical @@ -985,6 +899,184 @@ + + + + Audiotracks: + + + + + + + + Log + + + + + + + + Max length: + + + + + + + lines + + + 1 + + + 10000 + + + 200 + + + + + + + + + Options + + + + + + + 0 + 0 + + + + Display timestamp + + + + + + + Display self-identification highlight + + + + + + + + 0 + 0 + + + + Display empty messages + + + + + + + + 0 + 0 + + + + Display music switch + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Extra newlines are added between the name and message.</p></body></html> + + + Format use newline + + + + + + + + + + + 0 + 0 + + + + Orientation + + + + + + Top-down + + + + + + + Bottom-up + + + + + + + + + + + + Save to disk: + + + + + + + + 0 + 0 + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index b145a38ea..08d6d67ca 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -41,6 +41,8 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(ao_config, SIGNAL(manual_timeofday_changed(QString)), this, SLOT(handle_theme_modification())); connect(ao_config, SIGNAL(manual_timeofday_selection_changed(bool)), this, SLOT(handle_theme_modification())); connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(handle_theme_modification())); + connect(ao_config_panel, SIGNAL(reload_character()), this, SLOT(handle_character_reloading())); + connect(ao_config_panel, SIGNAL(reload_audiotracks()), this, SLOT(handle_audiotracks_reloading())); ao_config_panel->hide(); dr_discord->set_presence(ao_config->discord_presence()); @@ -208,8 +210,17 @@ bool AOApplication::has_character_availability_request_feature() const void AOApplication::handle_theme_modification() { load_fonts(); + emit reload_theme(); +} - Q_EMIT theme_reloaded(); +void AOApplication::handle_character_reloading() +{ + emit reload_character(); +} + +void AOApplication::handle_audiotracks_reloading() +{ + emit reload_audiotracks(); } void AOApplication::set_favorite_list() diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 13c61be57..6c1e08984 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -259,7 +259,7 @@ void AOConfigPrivate::update_favorite_device() { if (!favorite_device_driver.has_value()) return; - audio_engine->set_favorite_device_by_driver(favorite_device_driver.value()); + audio_engine->set_favorite_device_driver(favorite_device_driver.value()); } void AOConfigPrivate::on_application_state_changed(Qt::ApplicationState p_state) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index a6cfe5266..5ec89c8e1 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -59,6 +59,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_manual_timeofday = AO_GUI_WIDGET(QComboBox, "manual_timeofday"); ui_manual_timeofday_selection = AO_GUI_WIDGET(QCheckBox, "manual_timeofday_selection"); ui_showname = AO_GUI_WIDGET(QLineEdit, "showname"); + ui_reload_character = AO_GUI_WIDGET(QPushButton, "reload_character"); ui_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); ui_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); ui_emote_preview = AO_GUI_WIDGET(QCheckBox, "emote_preview"); @@ -94,6 +95,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_blip_value = AO_GUI_WIDGET(QLabel, "blip_value"); ui_blip_rate = AO_GUI_WIDGET(QSpinBox, "blip_rate"); ui_blank_blips = AO_GUI_WIDGET(QCheckBox, "blank_blips"); + ui_reload_audiotracks = AO_GUI_WIDGET(QPushButton, "reload_audiotracks"); // about ui_about = AO_GUI_WIDGET(QLabel, "about_label"); @@ -162,9 +164,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(blip_rate_changed(int)), ui_blip_rate, SLOT(setValue(int))); connect(m_config, SIGNAL(blank_blips_changed(bool)), ui_blank_blips, SLOT(setChecked(bool))); - connect(m_engine, SIGNAL(device_changed(DRAudioDevice)), this, SLOT(on_audio_device_changed(DRAudioDevice))); - connect(m_engine, SIGNAL(device_list_changed(QList)), this, - SLOT(on_audio_device_list_changed(QList))); + connect(m_engine, SIGNAL(current_device_changed(DRAudioDevice)), this, SLOT(on_audio_device_changed(DRAudioDevice))); + connect(m_engine, SIGNAL(device_list_changed(QVector)), this, + SLOT(on_audio_device_list_changed(QVector))); connect(m_engine, SIGNAL(favorite_device_changed(DRAudioDevice)), this, SLOT(on_favorite_audio_device_changed(DRAudioDevice))); @@ -184,6 +186,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // game connect(ui_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); connect(ui_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); + connect(ui_reload_character, SIGNAL(clicked()), this, SLOT(on_reload_character_clicked())); + connect(ui_reload_audiotracks, SIGNAL(clicked()), this, SLOT(on_reload_audiotracks_clicked())); connect(ui_manual_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_manual_gamemode_index_changed(QString))); connect(ui_manual_gamemode_selection, SIGNAL(toggled(bool)), m_config, @@ -375,36 +379,38 @@ void AOConfigPanel::refresh_timeofday_list() void AOConfigPanel::update_audio_device_list() { - const auto current_device = m_engine->get_device(); - const auto favorite_device = m_engine->get_favorite_device(); - - QSignalBlocker device_blocker(ui_device); + const std::optional l_current_device = m_engine->get_current_device(); + const std::optional l_favorite_device = m_engine->get_favorite_device(); + + std::optional l_prev_driver; + std::optional l_prev_driver_index; + if (ui_device->currentIndex() != -1) + l_prev_driver = ui_device->currentData().toString(); + QSignalBlocker l_blocker(ui_device); ui_device->clear(); - std::optional current_device_index; + std::optional l_current_driver_index; + for (const DRAudioDevice &i_device : m_engine->get_device_list()) { - for (DRAudioDevice &i_device : m_engine->get_device_list()) - { - if (!i_device.is_enabled()) - continue; - ui_device->addItem(i_device.get_name(), i_device.get_driver()); - int item_index = ui_device->count() - 1; + if (!i_device.is_enabled()) + continue; + ui_device->addItem(i_device.get_name(), i_device.get_driver()); + int l_item_index = ui_device->count() - 1; - if (current_device.has_value() && current_device.value().get_driver() == i_device.get_driver()) - current_device_index = item_index; + const QString l_device_driver = i_device.get_driver(); + if (l_prev_driver.has_value() && l_prev_driver == l_device_driver) + l_prev_driver_index = l_item_index; - if (favorite_device.has_value() && favorite_device.value().get_driver() == i_device.get_driver()) - ui_device->setItemData(item_index, QColor(Qt::green), Qt::BackgroundRole); + if (l_current_device.has_value() && l_current_device->get_driver() == l_device_driver) + { + ui_device->setItemData(l_item_index, QColor(Qt::lightGray), Qt::BackgroundRole); + l_current_driver_index = l_item_index; } - const bool has_items(ui_device->count()); - if (!has_items) - ui_device->addItem(tr("")); - ui_device->setEnabled(has_items); + if (l_favorite_device.has_value() && l_favorite_device->get_driver() == l_device_driver) + ui_device->setItemData(l_item_index, QColor(Qt::green), Qt::BackgroundRole); } - - if (current_device_index.has_value()) - ui_device->setCurrentIndex(current_device_index.value()); + ui_device->setCurrentIndex(l_prev_driver_index.value_or(l_current_driver_index.value_or(0))); } void AOConfigPanel::on_reload_theme_clicked() @@ -412,6 +418,16 @@ void AOConfigPanel::on_reload_theme_clicked() Q_EMIT reload_theme(); } +void AOConfigPanel::on_reload_character_clicked() +{ + Q_EMIT reload_character(); +} + +void AOConfigPanel::on_reload_audiotracks_clicked() +{ + Q_EMIT reload_audiotracks(); +} + void AOConfigPanel::on_theme_changed(QString p_name) { Q_UNUSED(p_name); @@ -494,8 +510,6 @@ void AOConfigPanel::on_device_current_index_changed(int p_index) for (DRAudioDevice &i_device : m_engine->get_device_list()) if (target_device_driver == i_device.get_driver()) { - m_engine->set_device(i_device); - m_engine->set_favorite_device(i_device); m_config->set_favorite_device_driver(i_device.get_driver()); break; } @@ -513,7 +527,7 @@ void AOConfigPanel::on_favorite_audio_device_changed(DRAudioDevice p_device) update_audio_device_list(); } -void AOConfigPanel::on_audio_device_list_changed(QList p_device_list) +void AOConfigPanel::on_audio_device_list_changed(QVector p_device_list) { Q_UNUSED(p_device_list); update_audio_device_list(); diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index 6250021dd..5b8a31848 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -2,6 +2,9 @@ #include "aoapplication.h" +#include "draudiotrackmetadata.h" +#include + AOMusicPlayer::AOMusicPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) { m_family = DRAudioEngine::get_family(DRAudio::Family::FMusic); @@ -11,8 +14,17 @@ AOMusicPlayer::AOMusicPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObj void AOMusicPlayer::play(QString p_song) { stop(); - m_song = p_song; - m_family->play_stream(ao_app->get_music_path(p_song)); + + m_file_name = p_song; + auto l_maybe_stream = m_family->create_stream(ao_app->get_music_path(p_song)); + if (l_maybe_stream) + { + auto l_stream = l_maybe_stream.value(); + DRAudiotrackMetadata l_audiotrack(p_song); + l_stream->set_repeatable(true); + l_stream->set_loop(l_audiotrack.loop_start(), l_audiotrack.loop_end()); + l_stream->play(); + } } void AOMusicPlayer::stop() diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2c657d086..35e7b1187 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -16,6 +16,7 @@ #include "aotimer.h" #include "commondefs.h" #include "debug_functions.h" +#include "draudiotrackmetadata.h" #include "drcharactermovie.h" #include "drchatlog.h" #include "drdiscord.h" @@ -51,13 +52,16 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ao_app = p_ao_app; ao_config = new AOConfig(this); - connect(ao_app, SIGNAL(theme_reloaded()), this, SLOT(reload_theme())); + connect(ao_app, SIGNAL(reload_theme()), this, SLOT(load_theme())); + connect(ao_app, SIGNAL(reload_character()), this, SLOT(load_character())); + connect(ao_app, SIGNAL(reload_audiotracks()), this, SLOT(load_audiotracks())); create_widgets(); connect_widgets(); setup_courtroom(); set_char_select(); + load_audiotracks(); } Courtroom::~Courtroom() @@ -385,15 +389,19 @@ void Courtroom::list_music() ui_music_list->clear(); for (const QString &i_song : qAsConst(m_music_list)) { - QListWidgetItem *l_item = new QListWidgetItem(i_song, ui_music_list); + DRAudiotrackMetadata l_track(i_song); + QListWidgetItem *l_item = new QListWidgetItem(l_track.title(), ui_music_list); + l_item->setData(Qt::UserRole, l_track.file_name()); + if (l_track.title() != l_track.file_name()) + l_item->setToolTip(l_track.file_name()); const QString l_song_path = ao_app->find_asset_path({ao_app->get_music_path(i_song)}, audio_extensions()); l_item->setBackground(l_song_path.isEmpty() ? l_missing_song_brush : l_song_brush); } + filter_list_widget(ui_music_list, ui_music_search->text()); } void Courtroom::list_areas() { - ui_area_list->clear(); const QBrush l_area_brush(ao_app->get_color("area_free_color", COURTROOM_DESIGN_INI)); ui_area_list->clear(); for (const QString &i_item_name : qAsConst(m_area_list)) @@ -401,6 +409,7 @@ void Courtroom::list_areas() QListWidgetItem *l_item = new QListWidgetItem(i_item_name, ui_area_list); l_item->setBackground(l_area_brush); } + filter_list_widget(ui_area_list, ui_area_search->text()); } void Courtroom::list_note_files() @@ -796,7 +805,7 @@ void Courtroom::handle_chatmessage_2() // handles IC if (m_shout_reload_theme) { m_shout_reload_theme = false; - reload_theme(); + load_theme(); } QString real_name = m_chr_list.at(m_chatmessage[CMChrId].toInt()).name; @@ -2126,7 +2135,7 @@ void Courtroom::on_change_character_clicked() ao_app->send_server_packet(DRPacket("CharsCheck")); } -void Courtroom::reload_theme() +void Courtroom::load_theme() { if (ui_vp_objection->is_running()) { @@ -2138,6 +2147,18 @@ void Courtroom::reload_theme() update_background_scene(); } +void Courtroom::load_character() +{ + update_iniswap_list(); + enter_courtroom(get_character_id()); +} + +void Courtroom::load_audiotracks() +{ + DRAudiotrackMetadata::update_cache(); + list_music(); +} + void Courtroom::on_back_to_lobby_clicked() { // hide so we don't get the 'disconnected from server' prompt diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index 112873c7f..c0748e0da 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -22,6 +22,7 @@ void Courtroom::set_character_id(const int p_chr_id) if (m_chr_id == p_chr_id) return; m_chr_id = p_chr_id; + load_character(); Q_EMIT character_id_changed(m_chr_id); } diff --git a/src/draudiodevice.cpp b/src/draudiodevice.cpp index d25b1e9b1..ca3844d30 100644 --- a/src/draudiodevice.cpp +++ b/src/draudiodevice.cpp @@ -1,13 +1,34 @@ #include "draudiodevice.h" -DRAudioDevice::DRAudioDevice() : m_name("") +#include + +#include + +QVector DRAudioDevice::get_device_list() +{ + QVector r_device_list; + + BASS_DEVICEINFO l_device_info; + for (int i = 0; BASS_GetDeviceInfo(i, &l_device_info); ++i) + { + DRAudioDevice l_device; + l_device.m_id = i; + l_device.m_name = l_device_info.name; + l_device.m_driver = l_device_info.driver; + l_device.m_states = State(l_device_info.flags); + r_device_list.append(l_device); + } + + return r_device_list; +} + +DRAudioDevice::DRAudioDevice() : m_id(0), m_name("") {} -DRAudioDevice::DRAudioDevice(DWORD p_device, BASS_DEVICEINFO p_device_info) - : m_id(p_device), m_name(p_device_info.name), m_driver(p_device_info.driver), m_states(State(p_device_info.flags)) +DRAudioDevice::~DRAudioDevice() {} -std::optional DRAudioDevice::get_id() const +DWORD DRAudioDevice::get_id() const { return m_id; } @@ -47,18 +68,12 @@ bool DRAudioDevice::is_init() const return is_state(SInit); } -bool DRAudioDevice::merge(DRAudioDevice &p_device) +bool DRAudioDevice::operator==(const DRAudioDevice &other) const +{ + return m_id == other.m_id && m_name == other.m_name && m_driver == other.m_driver && m_states == other.m_states; +} + +bool DRAudioDevice::operator!=(const DRAudioDevice &other) const { - bool is_changed = false; - auto check_and_set = [&](auto &a, auto &b) { - if (a == b) - return; - is_changed = true; - a = b; - }; - - check_and_set(m_name, p_device.m_name); - check_and_set(m_driver, p_device.m_driver); - check_and_set(m_states, p_device.m_states); - return is_changed; + return !operator==(other); } diff --git a/src/draudioengine.cpp b/src/draudioengine.cpp index 882f9b5fd..59e191b81 100644 --- a/src/draudioengine.cpp +++ b/src/draudioengine.cpp @@ -4,7 +4,7 @@ #include -static const int EVENT_TIMER_INTERVAL_DEFAULT = 100; +static const int UPDATE_TIMER_INTERVAL = 100; static class DRAudioEngineData { @@ -34,14 +34,12 @@ static class DRAudioEngineData d->family_map.value(DRAudio::Family::FSystem)->set_ignore_suppression(true); // create and init event loop - d->event_timer->setInterval(EVENT_TIMER_INTERVAL_DEFAULT); - d->event_timer->setSingleShot(false); + d->update_timer->setInterval(UPDATE_TIMER_INTERVAL); + d->update_timer->setSingleShot(false); - QObject::connect(d->event_timer, SIGNAL(timeout()), d, SLOT(on_event_tick())); - - d->event_timer->start(); - - d->on_event_tick(); + QObject::connect(d->update_timer, SIGNAL(timeout()), d, SLOT(update_current_device())); + d->update_timer->start(); + d->update_current_device(); } return d; @@ -61,19 +59,24 @@ DRAudioEngine::~DRAudioEngine() d->children.removeAll(this); } -std::optional DRAudioEngine::get_device() +std::optional DRAudioEngine::get_current_device() { return d->device; } +QString DRAudioEngine::get_favorite_device_driver() +{ + return d->favorite_device_driver; +} + std::optional DRAudioEngine::get_favorite_device() { return d->favorite_device; } -QList DRAudioEngine::get_device_list() +QVector DRAudioEngine::get_device_list() { - return d->device_map.values(); + return d->device_list; } DRAudioStreamFamily::ptr DRAudioEngine::get_family(DRAudio::Family p_family) @@ -111,32 +114,10 @@ bool DRAudioEngine::is_suppress_background_audio() return is_option(DRAudio::OEngineSuppressBackgroundAudio); } -void DRAudioEngine::set_device(DRAudioDevice p_device) +void DRAudioEngine::set_favorite_device_driver(QString p_driver) { - if (!d->device_map.contains(p_device.get_driver())) - return; - if (d->device.has_value() && d->device->get_driver() == p_device.get_driver()) - return; - d->previous_device = d->device; - d->device = d->device_map.value(p_device.get_driver()); - d->update_device(); - Q_EMIT d->invoke_signal("device_changed", Q_ARG(DRAudioDevice, d->device.value())); -} - -void DRAudioEngine::set_favorite_device(DRAudioDevice p_device) -{ - if (d->favorite_device.has_value() && d->favorite_device.value().get_driver() == p_device.get_driver()) - return; - d->favorite_device = p_device; - Q_EMIT d->invoke_signal("favorite_device_changed", Q_ARG(DRAudioDevice, d->favorite_device.value())); -} - -void DRAudioEngine::set_favorite_device_by_driver(QString p_device_driver) -{ - DRAudioDevice favorite_device; - favorite_device.m_driver = p_device_driver; - d->check_device = true; - set_favorite_device(favorite_device); + d->favorite_device_driver = p_driver; + d->update_current_device(); } void DRAudioEngine::set_volume(int32_t p_volume) @@ -174,8 +155,3 @@ void DRAudioEngine::set_suppress_background_audio(bool p_enabled) { set_option(DRAudio::OEngineSuppressBackgroundAudio, p_enabled); } - -void DRAudioEngine::check_stream_error() -{ - d->check_stream_error(); -} diff --git a/src/draudioengine_p.cpp b/src/draudioengine_p.cpp index 8a7742d09..2ebae8e82 100644 --- a/src/draudioengine_p.cpp +++ b/src/draudioengine_p.cpp @@ -13,14 +13,14 @@ #include #include -static const int EVENT_TIMER_INTERVAL_DEFAULT = 100; - -DRAudioEnginePrivate::DRAudioEnginePrivate() : QObject(nullptr), event_timer(new QTimer) -{} +DRAudioEnginePrivate::DRAudioEnginePrivate() : QObject(nullptr), update_timer(new QTimer(this)) +{ + BASS_SetConfig(BASS_CONFIG_DEV_DEFAULT, FALSE); +} DRAudioEnginePrivate::~DRAudioEnginePrivate() { - event_timer->stop(); + update_timer->stop(); BASS_Free(); } @@ -33,73 +33,74 @@ void DRAudioEnginePrivate::invoke_signal(QString p_method_name, QGenericArgument } } -void DRAudioEnginePrivate::update_device() +void DRAudioEnginePrivate::update_current_device() { - // cache the stream's position before we make the switch - for (DRAudioStreamFamily::ptr &i_family : family_map.values()) - for (DRAudioStream::ptr &i_stream : i_family->get_stream_list()) - i_stream->cache_position(); + update_device_list(); - if (previous_device.has_value()) + DRAudioDevice l_new_device; + for (const DRAudioDevice &i_device : qAsConst(device_list)) { - qInfo().noquote() << QString("Shutting down current device: %1").arg(previous_device->get_name()); - if (BASS_Free() == FALSE) - qCritical() << DRAudioError(QString("Failed to free: %1").arg(DRAudio::get_last_bass_error())).what(); - previous_device.reset(); - } + if (i_device.get_driver() == favorite_device_driver) + { + if (!favorite_device.has_value() || favorite_device.value() != i_device) + { + favorite_device = i_device; + invoke_signal("favorite_device_changed", Q_ARG(DRAudioDevice, favorite_device.value())); + } - if (!device.has_value()) - { - qCritical() << DRAudioError("Failed to initialize, device not set!").what(); - return; - } + if (i_device.is_enabled()) + { + l_new_device = i_device; + break; + } + } - qInfo().noquote() << QString("Initializing device: %1").arg(device->get_name()); - if (BASS_Init(device->get_id().value_or(0), 48000, BASS_DEVICE_LATENCY, 0, NULL) == FALSE) - { - qCritical() << DRAudioError(QString("Failed to initialize: %1").arg(DRAudio::get_last_bass_error())).what(); - return; + if (i_device.is_default()) + { + l_new_device = i_device; + } } - qInfo() << "Switching to initiliazed device"; - if (BASS_SetDevice(device->get_id().value_or(0)) == FALSE) - { - qCritical() << DRAudioError(QString("Failed to set device: %1").arg(DRAudio::get_last_bass_error())).what(); + if (device.has_value() && device.value() == l_new_device) return; - } + const std::optional l_prev_device = device; + device = l_new_device; - for (DRAudioStreamFamily::ptr &i_family : family_map.values()) - i_family->update_device(); -} + if (l_prev_device.has_value() && l_prev_device->get_id() == device->get_id()) + return; -void DRAudioEnginePrivate::update_device_list() -{ - QVector updated_device_list; + if (!BASS_IsStarted()) + BASS_Start(); + if (!device->is_init()) { - BASS_DEVICEINFO device_info; - for (int device = 1; BASS_GetDeviceInfo(device, &device_info); ++device) - updated_device_list.append(DRAudioDevice(device, device_info)); + if (!BASS_Init(device->get_id(), 44100, 0, 0, NULL)) + { + qWarning() << "Error: failed to initialize audio device:" << DRAudio::get_last_bass_error() + << "(device:" << device->get_name() << ")"; + return; + } } + qInfo() << "Audio device changed to" << device->get_name(); + invoke_signal("current_device_changed", Q_ARG(DRAudioDevice, device.value())); - bool is_changed = false; - for (DRAudioDevice &device : updated_device_list) + if (l_prev_device.has_value()) { - if (device_map.contains(device.get_driver())) + if (BASS_SetDevice(l_prev_device->get_id())) { - if (device_map[device.get_driver()].merge(device)) - is_changed = true; - continue; + BASS_Free(); } - - device_map.insert(device.get_driver(), device); - is_changed = true; } +} - if (!is_changed) - return; - check_device = true; - Q_EMIT invoke_signal("device_list_changed", Q_ARG(QList, device_map.values())); +void DRAudioEnginePrivate::update_device_list() +{ + const QVector l_new_device_list = DRAudioDevice::get_device_list(); + if (l_new_device_list != device_list) + { + device_list = std::move(l_new_device_list); + invoke_signal("device_list_changed", Q_ARG(QVector, device_list)); + } } void DRAudioEnginePrivate::update_options() @@ -112,52 +113,3 @@ void DRAudioEnginePrivate::update_volume() for (auto &i_group : family_map.values()) i_group->update_volume(); } - -void DRAudioEnginePrivate::check_stream_error() -{ - event_timer->start(EVENT_TIMER_INTERVAL_DEFAULT); - check_device = true; -} - -void DRAudioEnginePrivate::on_event_tick() -{ - update_device_list(); - - if (device.has_value() && !check_device) - return; - check_device = false; - - std::optional target_device; - for (DRAudioDevice &i_device : device_map.values()) - { - if (!i_device.is_enabled()) - continue; - - if (favorite_device && favorite_device.value().get_driver() == i_device.get_driver()) - { - // if our favorite device doesn't have an id then - // it was most likely set through the driver - // in which case we need to update it - if (!favorite_device->get_id().has_value()) - { - favorite_device.reset(); - engine->set_favorite_device(i_device); - } - - target_device = i_device; - break; - } - - if (!target_device.has_value() || i_device.is_default()) - target_device = i_device; - } - - if (!target_device.has_value()) - { -#ifdef QT_DEBUG - qWarning().noquote() << DRAudioError(QString("NO DEVICE AVAILABLE!")).what(); -#endif - return; - } - engine->set_device(target_device.value()); -} diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index 82c74576a..50c44f20c 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -1,32 +1,25 @@ #include "draudiostream.h" -#include "draudioengine.h" -#include "draudiostreamfamily.h" - -#include - #include #include #include -DRAudioStream::DRAudioStream(DRAudio::Family p_family) : m_family(p_family) +#include +#include + +#include "draudioengine.h" +#include "draudiostreamfamily.h" + +DRAudioStream::DRAudioStream(DRAudio::Family p_family) : m_engine(new DRAudioEngine(this)), m_family(p_family) { - connect(this, &DRAudioStream::device_error, this, &DRAudioStream::on_device_error, Qt::QueuedConnection); + connect(m_engine, &DRAudioEngine::current_device_changed, this, &DRAudioStream::update_device); } DRAudioStream::~DRAudioStream() { - if (m_hstream.has_value()) + if (m_hstream) { - const HSTREAM hstream = m_hstream.value(); - - while (!m_hsync_stack.empty()) - { - BASS_ChannelRemoveSync(hstream, m_hsync_stack.top().sync); - m_hsync_stack.pop(); - } - - BASS_StreamFree(hstream); + BASS_StreamFree(m_hstream); } } @@ -35,242 +28,185 @@ DRAudio::Family DRAudioStream::get_family() const return m_family; } -std::optional DRAudioStream::get_file() const +QString DRAudioStream::get_file_name() const { - return m_file; + return m_file_name; } bool DRAudioStream::is_playing() const { - if (!m_hstream.has_value()) + if (!ensure_init()) return false; - return BASS_ChannelIsActive(m_hstream.value()) == BASS_ACTIVE_PLAYING; + return BASS_ChannelIsActive(m_hstream) == BASS_ACTIVE_PLAYING; +} + +bool DRAudioStream::is_repeatable() const +{ + return m_repeatable; } void DRAudioStream::play() { - if (!m_hstream.has_value()) + if (!ensure_init()) return; - const BOOL result = BASS_ChannelPlay(m_hstream.value(), FALSE); - if (result == FALSE) + if (!BASS_ChannelPlay(m_hstream, FALSE)) { - qWarning() << DRAudioError( - QString("failed to play file %1: %2").arg(m_file.value()).arg(DRAudio::get_last_bass_error())) - .what(); + qWarning() + << QString("error: failed to play file: %1 (file: \"%1\")").arg(DRAudio::get_last_bass_error(), m_file_name); Q_EMIT finished(); } - setup_looping(); } void DRAudioStream::stop() { - if (!m_hstream.has_value()) + if (!ensure_init()) return; - BASS_ChannelStop(m_hstream.value()); + BASS_ChannelStop(m_hstream); Q_EMIT finished(); } -QWORD DRAudioStream::loop_start() +std::optional DRAudioStream::set_file_name(QString p_file_name) { - return m_loop_start; + m_file_name = p_file_name; + m_init_state = InitNotDone; + if (!ensure_init()) + { + return DRAudioError("failed to set file"); + } + emit file_name_changed(m_file_name); + return std::nullopt; } -QWORD DRAudioStream::loop_end() +void DRAudioStream::set_volume(float p_volume) { - return m_loop_end; + if (!ensure_init()) + return; + m_volume = p_volume; + BASS_ChannelSetAttribute(m_hstream, BASS_ATTRIB_VOL, float(p_volume) * 0.01f); } -// the sync callback -static void CALLBACK loop_sync(HSYNC syncHandle, DWORD channel, DWORD data, void *user) +void DRAudioStream::set_repeatable(bool p_enabled) { - Q_UNUSED(syncHandle); - Q_UNUSED(data); - - // move the position to the loopStart - DRAudioStream *stream = static_cast(user); - QWORD loop_start = stream->loop_start(); - BASS_ChannelSetPosition(channel, loop_start, BASS_POS_BYTE); + if (m_repeatable == p_enabled) + return; + m_repeatable = p_enabled; + init_loop(); } -void DRAudioStream::setup_looping() +void DRAudioStream::set_loop(quint64 p_start, quint64 p_end) { - // Remove all previously set loop information - m_loop_start = {}; - m_loop_end = {}; - - if (m_loop_sync > 0) - { - BASS_ChannelRemoveSync(m_hstream.value(), m_loop_sync); - m_loop_sync = 0; - } - - // Now decide whether the track we are playing now is loopable. - if (!m_file->endsWith("ogg", Qt::CaseInsensitive)) + if (!ensure_init()) return; - double l_sample_rate = 0.0; - if (float l_float_sample_rate = 0.0f; - BASS_ChannelGetAttribute(m_hstream.value(), BASS_ATTRIB_FREQ, &l_float_sample_rate)) - l_sample_rate = double(l_float_sample_rate); - if (qFabs(l_sample_rate) == 0.0) + if (m_loop_start == p_start && m_loop_end == p_end) return; - // Now sample_rate holds the sample rate in hertz + m_loop_start = p_start; + m_loop_end = p_end; + init_loop(); +} - const char *ogg_value = BASS_ChannelGetTags(m_hstream.value(), BASS_TAG_OGG); - QStringList ogg_comments; - while (*ogg_value) - { - ogg_comments.push_back(QString(ogg_value)); - ogg_value += ogg_comments.back().size() + 1; - } +void DRAudioStream::end_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata) +{ + Q_UNUSED(hsync); + Q_UNUSED(ch); + Q_UNUSED(data); - double loop_start = 0; - double loop_end = 0; - for (const QString &ogg_comment : ogg_comments) + DRAudioStream *l_stream = static_cast(userdata); + if (!l_stream->is_repeatable()) { - QStringList split = ogg_comment.split('='); - if (split.size() != 2) - continue; - if (split.at(0) == "LoopStart") - loop_start = split.at(1).toDouble(); - else if (split.at(0) == "LoopEnd") - loop_end = split.at(1).toDouble(); + emit l_stream->finished(); } - if (loop_start > loop_end || (loop_start == 0 && loop_end == 0)) - return; - - // If we are at this point, we are successful in fetching all required values - - m_loop_start = BASS_ChannelSeconds2Bytes(m_hstream.value(), loop_start / l_sample_rate); - m_loop_end = BASS_ChannelSeconds2Bytes(m_hstream.value(), loop_end / l_sample_rate); - - m_loop_sync = BASS_ChannelSetSync(m_hstream.value(), BASS_SYNC_POS | BASS_SYNC_MIXTIME, m_loop_end, &loop_sync, this); } -std::optional DRAudioStream::set_file(QString p_file) +void DRAudioStream::loop_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata) { - if (m_file.has_value()) - return DRAudioError("file already set"); + Q_UNUSED(hsync); + Q_UNUSED(data); - const QFileInfo file(p_file); - if (!file.exists()) - return DRAudioError(QString("file does not exist: %1").arg(p_file.isEmpty() ? "" : p_file)); + // move the position to the loopStart + DRAudioStream *l_stream = static_cast(userdata); + qDebug() << l_stream->m_file_name << l_stream->m_loop_start_pos; + if (l_stream->is_repeatable()) + { + BASS_ChannelSetPosition(ch, l_stream->m_loop_start_pos, BASS_POS_BYTE); + emit l_stream->looped(); + } +} - if (m_file == p_file) - return std::nullopt; +bool DRAudioStream::ensure_init() +{ + if (m_init_state != InitNotDone) + return m_init_state == InitFinished; + m_init_state = InitError; - m_file = p_file; + if (m_file_name.isEmpty()) + return false; - HSTREAM stream_handle; - if (m_file->endsWith("opus", Qt::CaseInsensitive)) - stream_handle = BASS_OPUS_StreamCreateFile(FALSE, m_file->utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); + HSTREAM l_hstream; + if (m_file_name.endsWith("opus", Qt::CaseInsensitive)) + l_hstream = BASS_OPUS_StreamCreateFile(FALSE, m_file_name.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); else - stream_handle = BASS_StreamCreateFile(FALSE, m_file->utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); - - if (stream_handle == 0) - return DRAudioError( - QString("failed to create stream for file %1: %2").arg(p_file).arg(DRAudio::get_last_bass_error())); - m_hstream = stream_handle; + l_hstream = + BASS_StreamCreateFile(FALSE, m_file_name.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE | BASS_STREAM_PRESCAN); - // bass events - for (const DWORD type : {BASS_SYNC_END, BASS_SYNC_DEV_FAIL}) + if (!l_hstream) { - const DWORD final_type = type == BASS_SYNC_DEV_FAIL ? type | BASS_SYNC_MIXTIME : type; - HSYNC sync_handle = BASS_ChannelSetSync(stream_handle, final_type, 0, &DRAudioStream::on_sync_callback, this); - if (sync_handle == 0) - return DRAudioError( - QString("failed to create sync %1 for file %2: %3").arg(type).arg(p_file, DRAudio::get_last_bass_error())); - m_hsync_stack.push(DRAudioStreamSync{sync_handle, type}); + qWarning() << "error: failed to create audio stream (file:" << m_file_name << ")"; + return false; } + m_hstream = l_hstream; - Q_EMIT file_changed(p_file); - return std::nullopt; + BASS_ChannelSetSync(l_hstream, BASS_SYNC_END | BASS_SYNC_MIXTIME, 0, &end_sync, this); + m_init_state = InitFinished; + init_loop(); + return true; } -void DRAudioStream::set_volume(float p_volume) +bool DRAudioStream::ensure_init() const { - if (!m_hstream.has_value()) - return; - m_volume = p_volume; - BASS_ChannelSetAttribute(m_hstream.value(), BASS_ATTRIB_VOL, float(p_volume) * 0.01f); + return const_cast(this)->ensure_init(); } -#include - -void DRAudioStream::on_sync_callback(HSYNC hsync, DWORD ch, DWORD data, void *userdata) +void DRAudioStream::init_loop() { - Q_UNUSED(hsync) - Q_UNUSED(ch) - Q_UNUSED(data) - - /* - * BASS only provides what we fed it when we first created our synch even if - * the pointer we provided get deleted mid-way through the program lifetime - */ - DRAudioStream *self = static_cast(userdata); - for (auto &v : self->m_hsync_stack) + if (m_loop_sync) + { + BASS_ChannelRemoveSync(m_hstream, m_loop_sync); + m_loop_sync = 0; + } + + if (m_repeatable) { - if (v.sync != hsync) - continue; - switch (v.type) - { - case BASS_SYNC_END: - Q_EMIT self->finished(); - break; + float l_sample_rate; + BASS_ChannelGetAttribute(m_hstream, BASS_ATTRIB_FREQ, &l_sample_rate); - case BASS_SYNC_DEV_FAIL: - Q_EMIT self->device_error(QPrivateSignal()); - break; + const QWORD l_length = BASS_ChannelGetLength(m_hstream, BASS_POS_BYTE); + m_loop_start_pos = BASS_ChannelSeconds2Bytes(m_hstream, m_loop_start / double(l_sample_rate)); + if (m_loop_start_pos >= l_length) + { + m_loop_start_pos = 0; } - } -} -void DRAudioStream::cache_position() -{ - if (!m_hstream.has_value()) - return; - if (m_position.has_value()) - return; - m_position = BASS_ChannelGetPosition(m_hstream.value(), BASS_POS_BYTE); - BASS_ChannelStop(m_hstream.value()); -} + m_loop_end_pos = BASS_ChannelSeconds2Bytes(m_hstream, m_loop_end / double(l_sample_rate)); + if (m_loop_end_pos <= m_loop_start_pos || m_loop_end_pos > l_length) + { + m_loop_end_pos = l_length; + } -void DRAudioStream::on_device_error() -{ - cache_position(); - DRAudioEngine::check_stream_error(); + m_loop_sync = BASS_ChannelSetSync(m_hstream, BASS_SYNC_POS | BASS_SYNC_MIXTIME, m_loop_end_pos, &loop_sync, this); + } } -void DRAudioStream::update_device() +void DRAudioStream::update_device(DRAudioDevice p_device) { - if (!m_hstream.has_value()) - { - Q_EMIT finished(); + if (!ensure_init()) return; - } - - if (is_playing()) - return; - const QString file = m_file.value(); - - m_file.reset(); - m_hstream.reset(); - m_hsync_stack.clear(); - - blockSignals(true); - set_file(file); - set_volume(m_volume); - blockSignals(false); - - if (m_position.has_value()) + if (BASS_ChannelGetDevice(m_hstream) != p_device.get_id()) { - if (BASS_ChannelSetPosition(m_hstream.value(), m_position.value(), BASS_POS_BYTE) == FALSE) - qWarning() << DRAudioError( - QString("failed to set position for %1: %2").arg(file).arg(DRAudio::get_last_bass_error())) - .what(); - m_position.reset(); + if (!BASS_ChannelSetDevice(m_hstream, p_device.get_id())) + { + qDebug() << "error: failed to switch stream device;" << DRAudio::get_last_bass_error() << p_device.get_name(); + } } - - play(); } diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp index 4b0e72657..31f7ec483 100644 --- a/src/draudiostreamfamily.cpp +++ b/src/draudiostreamfamily.cpp @@ -81,7 +81,7 @@ std::optional DRAudioStreamFamily::create_stream(QString p_f { DRAudioStream::ptr stream(new DRAudioStream(m_family)); - if (auto err = stream->set_file(p_file); err) + if (auto err = stream->set_file_name(p_file); err) { qWarning() << err->what(); return std::nullopt; @@ -101,7 +101,7 @@ std::optional DRAudioStreamFamily::play_stream(QString p_fil if (r_stream.has_value()) { auto stream = r_stream.value(); - qWarning() << "playing" << stream->get_file().value(); + qInfo() << "Playing" << stream->get_file_name(); stream->play(); } return r_stream; @@ -129,12 +129,6 @@ float DRAudioStreamFamily::calculate_volume() return volume * 100.0f; } -void DRAudioStreamFamily::update_device() -{ - for (DRAudioStream::ptr &i_stream : m_stream_list) - i_stream->update_device(); -} - void DRAudioStreamFamily::update_capacity() { if (m_capacity == 0) @@ -162,8 +156,8 @@ void DRAudioStreamFamily::on_stream_finished() if (invoker == nullptr) return; - if (auto file = invoker->get_file(); file) - qInfo() << "removing" << file.value(); + if (const QString l_file = invoker->get_file_name(); !l_file.isEmpty()) + qInfo() << "removing" << l_file; else qWarning() << "removing unspecified stream"; diff --git a/src/draudiotrackmetadata.cpp b/src/draudiotrackmetadata.cpp new file mode 100644 index 000000000..343e24e3f --- /dev/null +++ b/src/draudiotrackmetadata.cpp @@ -0,0 +1,114 @@ +#include "draudiotrackmetadata.h" + +#include +#include +#include +#include +#include +#include + +#include "aoapplication.h" +#include "utils.h" + +static QMap s_audiotrack_cache; + +void DRAudiotrackMetadata::update_cache() +{ + AOApplication *ao_app = dynamic_cast(qApp); + if (!ao_app) + { + qCritical() << "error: AOApplication has not been created"; + return; + } + + QList l_ini_list; + { // fetch ini files + const QDir l_dir(ao_app->get_music_folder_path()); + const QList l_file_list = l_dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::IgnoreCase); + for (const QFileInfo &i_file : l_file_list) + { + if (i_file.suffix().toLower() != "ini") + continue; + l_ini_list.append(i_file); + } + } + + QMap l_new_audiotrack_cache; + QMap l_new_audiotrack_origin; + for (const QFileInfo &i_ini_file : l_ini_list) + { + const QString l_ini_path = i_ini_file.absoluteFilePath(); + QSettings l_settings(l_ini_path, QSettings::IniFormat); + l_settings.setIniCodec("UTF-8"); + if (l_settings.status() != QSettings::NoError) + { + qWarning() << "error: failed to read audiotrack metadata" << l_settings.status() << "file:" << l_ini_path; + continue; + } + qDebug() << "reading audiotrack metadata" << l_ini_path; + utils::QSettingsKeyFetcher l_fetcher(l_settings); + + const QStringList l_audiotrack_name_list = l_settings.childGroups(); + for (const QString &i_track_name : l_audiotrack_name_list) + { + if (!QFileInfo::exists(ao_app->get_music_path(i_track_name))) + { + qWarning() << "error: audiotrack not found" << i_track_name; + continue; + } + + const QString l_lower_track_name = i_track_name.toLower(); + if (l_new_audiotrack_origin.contains(l_lower_track_name)) + { + qWarning() << "warning: replacing track" << i_track_name << "; previously defined in" + << l_new_audiotrack_origin[l_lower_track_name]; + } + l_new_audiotrack_origin.insert(l_lower_track_name, l_ini_path); + + l_settings.beginGroup(i_track_name); + DRAudiotrackMetadata l_audiotrack; + l_audiotrack.m_file_name = i_track_name; + l_audiotrack.m_title = l_settings.value(l_fetcher.lookup_value("title")).toString(); + l_audiotrack.m_loop_start = l_settings.value(l_fetcher.lookup_value("loop_start")).toULongLong(); + l_audiotrack.m_loop_end = l_settings.value(l_fetcher.lookup_value("loop_end")).toULongLong(); + l_new_audiotrack_cache.insert(l_lower_track_name, std::move(l_audiotrack)); + l_settings.endGroup(); + } + } + s_audiotrack_cache = std::move(l_new_audiotrack_cache); +} + +DRAudiotrackMetadata::DRAudiotrackMetadata() +{} + +DRAudiotrackMetadata::DRAudiotrackMetadata(QString p_file_name) : m_file_name(p_file_name) +{ + const QString l_lower_file_name = p_file_name.toLower(); + if (s_audiotrack_cache.contains(l_lower_file_name)) + { + *this = s_audiotrack_cache[l_lower_file_name]; + } +} + +DRAudiotrackMetadata::~DRAudiotrackMetadata() +{} + +QString DRAudiotrackMetadata::file_name() +{ + return m_file_name; +} + +QString DRAudiotrackMetadata::title() +{ + return m_title.isEmpty() ? m_file_name : m_title; +} + +quint64 DRAudiotrackMetadata::loop_start() +{ + return m_loop_start; +} + +quint64 DRAudiotrackMetadata::loop_end() +{ + return m_loop_end; +} diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 0da76ceb4..94912d40e 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -43,10 +43,16 @@ QString AOApplication::get_sounds_path(QString p_file) return get_case_sensitive_path(path); } +QString AOApplication::get_music_folder_path() +{ + const QString l_path = get_base_path() + "sounds/music/"; + return get_case_sensitive_path(l_path); +} + QString AOApplication::get_music_path(QString p_song) { - QString path = get_base_path() + "sounds/music/" + p_song; - return get_case_sensitive_path(path); + const QString l_path = get_music_folder_path() + p_song; + return get_case_sensitive_path(l_path); } QString AOApplication::format_background_path(QString p_identifier) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index d2fdf08cc..54339b7e4 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -272,7 +272,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) return; if (is_courtroom_constructed) - m_courtroom->enter_courtroom(l_content.at(2).toInt()); + m_courtroom->set_character_id(l_content.at(2).toInt()); } else if (l_header == "MS") { diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index a51e4f916..1ebd99e8d 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -1,15 +1,16 @@ #include "aoapplication.h" -#include "aoconfig.h" -#include "commondefs.h" -#include "file_functions.h" - #include #include #include #include #include +#include "aoconfig.h" +#include "commondefs.h" +#include "file_functions.h" +#include "utils.h" + QStringList AOApplication::get_callwords() { return ao_config->callwords().split(" ", DR::SkipEmptyParts); @@ -512,15 +513,6 @@ QStringList AOApplication::get_sfx_list() return r_sfx_list; } -QString drLookupKey(const QStringList &keyList, const QString &targetKey) -{ - const QString finalTargetKey = targetKey.toLower(); - for (const QString &i_key : qAsConst(keyList)) - if (i_key.toLower() == finalTargetKey) - return i_key; - return targetKey; -} - // returns whatever is to the right of "search_line =" within target_tag and // terminator_tag, trimmed returns the empty string if the search line couldnt // be found @@ -528,8 +520,9 @@ QVariant AOApplication::read_char_ini(QString p_chr, QString p_group, QString p_ { QSettings s(get_character_path(p_chr, CHARACTER_CHAR_INI), QSettings::IniFormat); s.setIniCodec("UTF-8"); - s.beginGroup(drLookupKey(s.childGroups(), p_group)); - return s.value(drLookupKey(s.childKeys(), p_key), p_def); + utils::QSettingsKeyFetcher l_fetcher(s); + s.beginGroup(l_fetcher.lookup_group(p_group)); + return s.value(l_fetcher.lookup_value(p_key), p_def); } QVariant AOApplication::read_char_ini(QString p_chr, QString p_group, QString p_key) @@ -589,6 +582,15 @@ QString AOApplication::get_chat(QString p_chr) return read_char_ini(p_chr, "options", "chat").toString().toLower(); } +QString drLookupKey(const QStringList &keyList, const QString &targetKey) +{ + const QString finalTargetKey = targetKey.toLower(); + for (const QString &i_key : qAsConst(keyList)) + if (i_key.toLower() == finalTargetKey) + return i_key; + return targetKey; +} + QVector AOApplication::get_emote_list(QString p_chr) { QVector r_emote_list; @@ -613,11 +615,11 @@ QVector AOApplication::get_emote_list(QString p_chr) QSettings l_chrini(get_character_path(i_chr, CHARACTER_CHAR_INI), QSettings::IniFormat); l_chrini.setIniCodec("UTF-8"); + utils::QSettingsKeyFetcher l_fetcher(l_chrini); QStringList l_keys; { // recover all numbered keys, ignore words - const QStringList l_group_list = l_chrini.childGroups(); - l_chrini.beginGroup(drLookupKey(l_group_list, "emotions")); + l_chrini.beginGroup(l_fetcher.lookup_group("emotions")); l_keys = l_chrini.childKeys(); l_chrini.endGroup(); @@ -646,8 +648,7 @@ QVector AOApplication::get_emote_list(QString p_chr) for (const QString &i_key : qAsConst(l_keys)) { - const QStringList l_group_list = l_chrini.childGroups(); - l_chrini.beginGroup(drLookupKey(l_group_list, "emotions")); + l_chrini.beginGroup(l_fetcher.lookup_group("emotions")); const QStringList l_emotions = l_chrini.value(i_key).toString().split("#", DR::KeepEmptyParts); l_chrini.endGroup(); @@ -675,11 +676,11 @@ QVector AOApplication::get_emote_list(QString p_chr) if (DeskModifier < l_emotions.length()) l_emote.desk_modifier = l_emotions.at(DeskModifier).toInt(); - l_chrini.beginGroup(drLookupKey(l_group_list, "soundn")); + l_chrini.beginGroup(l_fetcher.lookup_group("soundn")); l_emote.sound_file = l_chrini.value(i_key).toString(); l_chrini.endGroup(); - l_chrini.beginGroup(drLookupKey(l_group_list, "soundt")); + l_chrini.beginGroup(l_fetcher.lookup_group("soundt")); l_emote.sound_delay = qMax(l_chrini.value(i_key).toInt(), 0); l_chrini.endGroup(); diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 000000000..9eeb28f3c --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,27 @@ +#include "utils.h" + +#include + +using namespace utils; + +static QString lookupKey(const QStringList &p_key_list, const QString &p_target_key) +{ + const QString l_lower_target_key = p_target_key.toLower(); + for (const QString &i_key : qAsConst(p_key_list)) + if (i_key.toLower() == l_lower_target_key) + return i_key; + return p_target_key; +} + +QSettingsKeyFetcher::QSettingsKeyFetcher(QSettings &settings) : m_settings(settings) +{} + +QString QSettingsKeyFetcher::lookup_group(QString p_name) +{ + return lookupKey(m_settings.childGroups(), p_name); +} + +QString QSettingsKeyFetcher::lookup_value(QString p_name) +{ + return lookupKey(m_settings.childKeys(), p_name); +} From 1a3f254f81236d299942caa8dbcebada91a969a1 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 17 Mar 2022 15:57:38 +0100 Subject: [PATCH 611/842] Added field `play_once`, ... * Added field `play_once` to Audiotrack * Restored play song debug information --- include/draudiotrackmetadata.h | 2 ++ src/aomusicplayer.cpp | 12 ++++++++++-- src/draudiostream.cpp | 1 - src/draudiotrackmetadata.cpp | 6 ++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/include/draudiotrackmetadata.h b/include/draudiotrackmetadata.h index 366ede7f5..188060928 100644 --- a/include/draudiotrackmetadata.h +++ b/include/draudiotrackmetadata.h @@ -13,12 +13,14 @@ class DRAudiotrackMetadata QString file_name(); QString title(); + bool play_once(); quint64 loop_start(); quint64 loop_end(); private: QString m_file_name; QString m_title; + bool m_play_once = false; quint64 m_loop_start = 0; quint64 m_loop_end = 0; }; diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index 5b8a31848..166333b00 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -21,9 +21,17 @@ void AOMusicPlayer::play(QString p_song) { auto l_stream = l_maybe_stream.value(); DRAudiotrackMetadata l_audiotrack(p_song); - l_stream->set_repeatable(true); - l_stream->set_loop(l_audiotrack.loop_start(), l_audiotrack.loop_end()); + if (!l_audiotrack.play_once()) + { + l_stream->set_repeatable(true); + l_stream->set_loop(l_audiotrack.loop_start(), l_audiotrack.loop_end()); + } l_stream->play(); + + if (l_stream->is_playing()) + { + qDebug() << "playing" << l_stream->get_file_name(); + } } } diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index 50c44f20c..2d32e8fe6 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -125,7 +125,6 @@ void DRAudioStream::loop_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata) // move the position to the loopStart DRAudioStream *l_stream = static_cast(userdata); - qDebug() << l_stream->m_file_name << l_stream->m_loop_start_pos; if (l_stream->is_repeatable()) { BASS_ChannelSetPosition(ch, l_stream->m_loop_start_pos, BASS_POS_BYTE); diff --git a/src/draudiotrackmetadata.cpp b/src/draudiotrackmetadata.cpp index 343e24e3f..686522503 100644 --- a/src/draudiotrackmetadata.cpp +++ b/src/draudiotrackmetadata.cpp @@ -69,6 +69,7 @@ void DRAudiotrackMetadata::update_cache() DRAudiotrackMetadata l_audiotrack; l_audiotrack.m_file_name = i_track_name; l_audiotrack.m_title = l_settings.value(l_fetcher.lookup_value("title")).toString(); + l_audiotrack.m_play_once = l_settings.value(l_fetcher.lookup_value("play_once")).toBool(); l_audiotrack.m_loop_start = l_settings.value(l_fetcher.lookup_value("loop_start")).toULongLong(); l_audiotrack.m_loop_end = l_settings.value(l_fetcher.lookup_value("loop_end")).toULongLong(); l_new_audiotrack_cache.insert(l_lower_track_name, std::move(l_audiotrack)); @@ -103,6 +104,11 @@ QString DRAudiotrackMetadata::title() return m_title.isEmpty() ? m_file_name : m_title; } +bool DRAudiotrackMetadata::play_once() +{ + return m_play_once; +} + quint64 DRAudiotrackMetadata::loop_start() { return m_loop_start; From 5fd31a15f862d24ec077d5c85a4acf1764120502 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 22 Mar 2022 11:30:00 +0100 Subject: [PATCH 612/842] Add warning for missing background files, misc optimization --- include/drmovie.h | 1 + src/courtroom.cpp | 22 ++++++++++++++-------- src/drmovie.cpp | 5 +++-- src/drscenemovie.cpp | 1 - 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/include/drmovie.h b/include/drmovie.h index 131b68045..87abff789 100644 --- a/include/drmovie.h +++ b/include/drmovie.h @@ -38,6 +38,7 @@ class DRMovie : public QLabel bool m_hide_when_done = false; QString m_file_name; + bool m_file_exists = false; QMovie m_movie; int m_frame_count = 0; int m_frame_number = 0; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 69ab82812..3a472c1bc 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -250,12 +250,12 @@ void Courtroom::set_window_title(QString p_title) void Courtroom::update_background_scene() { // witness is default if pos is invalid - QString f_default_background = "witnessempty"; - QString f_default_desk_image = "stand"; - QString f_background = f_default_background; - QString f_desk_image = f_default_desk_image; - QString f_desk_mod = m_chatmessage[CMDeskModifier]; - QString f_side = m_chatmessage[CMPosition]; + const QString L_DEFAULT_BACK = "witnessempty"; + const QString L_DEFAULT_FRONT = "stand"; + const QString f_desk_mod = m_chatmessage[CMDeskModifier]; + const QString f_side = m_chatmessage[CMPosition]; + QString f_background = L_DEFAULT_BACK; + QString f_desk_image = L_DEFAULT_FRONT; if (f_side == "def") { @@ -292,12 +292,18 @@ void Courtroom::update_background_scene() ui_vp_desk->show(); ui_vp_desk->set_image(f_desk_image); if (!ui_vp_desk->is_valid()) - ui_vp_desk->set_image(f_default_desk_image); + { + qWarning() << "warning: background missing file (" << m_background.background << f_side << f_desk_image << ")"; + ui_vp_desk->set_image(L_DEFAULT_FRONT); + } } ui_vp_background->set_image(f_background); if (!ui_vp_background->is_valid()) - ui_vp_background->set_image(f_default_background); + { + qWarning() << "warning: background missing file (" << m_background.background << f_side << f_background << ")"; + ui_vp_background->set_image(L_DEFAULT_BACK); + } } DRAreaBackground Courtroom::get_background() diff --git a/src/drmovie.cpp b/src/drmovie.cpp index 4b713562b..f0968e860 100644 --- a/src/drmovie.cpp +++ b/src/drmovie.cpp @@ -1,6 +1,7 @@ #include "drmovie.h" #include +#include DRMovie::DRMovie(QWidget *parent) : QLabel(parent) { @@ -31,6 +32,7 @@ void DRMovie::set_file_name(QString p_file_name) { stop(); m_file_name = p_file_name; + m_file_exists = QFile::exists(m_file_name); } /** @@ -135,7 +137,6 @@ void DRMovie::stop() void DRMovie::resizeEvent(QResizeEvent *event) { QLabel::resizeEvent(event); - paint_frame(); } @@ -207,5 +208,5 @@ void DRMovie::jump_next_frame() bool DRMovie::is_valid() { - return !m_movie.fileName().isEmpty() && m_movie.isValid(); + return m_file_exists && m_movie.isValid(); } diff --git a/src/drscenemovie.cpp b/src/drscenemovie.cpp index ce52c789b..bf0ceefbf 100644 --- a/src/drscenemovie.cpp +++ b/src/drscenemovie.cpp @@ -1,4 +1,3 @@ -#include "commondefs.h" #include "drscenemovie.h" #include "aoapplication.h" From 59c0c111551a87ede49c5ad136d10819d72bb32e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 22 Mar 2022 16:35:33 +0100 Subject: [PATCH 613/842] Extra checks for maximizing --- include/courtroom.h | 5 +++++ src/charselect.cpp | 8 +------- src/courtroom.cpp | 13 ++++++++++++- src/courtroom_widgets.cpp | 16 ++++++++++++---- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 4f59788df..75ed9556b 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -220,6 +220,10 @@ class Courtroom : public QMainWindow void closing(); private: + bool m_first_theme_loading = true; + QSize m_default_size; + bool m_is_maximized = false; + AOApplication *ao_app = nullptr; AOConfig *ao_config = nullptr; @@ -738,6 +742,7 @@ public slots: // QWidget interface protected: + void changeEvent(QEvent *) override; void closeEvent(QCloseEvent *event) override; }; diff --git a/src/charselect.cpp b/src/charselect.cpp index 850e97832..1ea8f8e68 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -107,14 +107,8 @@ void Courtroom::reset_char_select() void Courtroom::set_char_select() { pos_size_type f_charselect = ao_app->get_element_dimensions("char_select", COURTROOM_DESIGN_INI); - if (f_charselect.width < 0 || f_charselect.height < 0) - { - qDebug() << "W: did not find courtroom width or height in courtroom_design.ini!"; - this->resize(714, 668); - } - else - this->resize(f_charselect.width, f_charselect.height); + qWarning() << "warning: char_select not found or invalid within courtroom_design.ini"; ui_char_select_background->resize(f_charselect.width, f_charselect.height); ui_char_select_background->set_image("charselect_background.png"); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 21142b8fd..869bc6759 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2227,10 +2227,21 @@ void Courtroom::ping_server() ao_app->send_server_packet(DRPacket("CH", {QString::number(m_chr_id)})); } +void Courtroom::changeEvent(QEvent *event) +{ + QMainWindow::changeEvent(event); + if (event->type() == QEvent::WindowStateChange) + { + m_is_maximized = windowState().testFlag(Qt::WindowMaximized); + if (!m_is_maximized) + resize(m_default_size); + } +} + void Courtroom::closeEvent(QCloseEvent *event) { - Q_EMIT closing(); QMainWindow::closeEvent(event); + Q_EMIT closing(); } void Courtroom::on_set_notes_clicked() diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 4c3608da3..e61e70f4a 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -559,12 +559,20 @@ void Courtroom::set_widgets() f_courtroom.height = DEFAULT_HEIGHT; } - setWindowState(Qt::WindowNoState); - resize(f_courtroom.width, f_courtroom.height); - center_widget_to_screen(this); + m_default_size = QSize(f_courtroom.width, f_courtroom.height); + if (!m_is_maximized) + { + resize(m_default_size); + } + + if (m_first_theme_loading) + { + m_first_theme_loading = false; + center_widget_to_screen(this); + } ui_background->move(0, 0); - ui_background->resize(size()); + ui_background->resize(m_default_size); ui_background->set_image("courtroombackground.png"); set_size_and_pos(ui_viewport, "viewport", COURTROOM_DESIGN_INI, ao_app); From d01341e73c97f2cab10278b14dff7dc1fa4d8704 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 23 Mar 2022 20:46:00 +0100 Subject: [PATCH 614/842] Reworked DRVideoScreen logic, removed unused widget * Reworked DRVideoScreen logic to always scan the file before attempting to play it. Play commands is queued and will be executed as soon as the media file is valid, failing that the file is considered to be done playing * Removed AOEvidenceDisplay as it is unused --- dronline-client.pro | 2 - include/aoevidencedisplay.h | 31 -------- include/courtroom.h | 2 +- include/drvideoscreen.h | 31 +++++--- src/aoevidencedisplay.cpp | 83 -------------------- src/courtroom.cpp | 20 +---- src/courtroom_widgets.cpp | 18 ++--- src/drvideoscreen.cpp | 152 ++++++++++++++++++++++++------------ 8 files changed, 130 insertions(+), 209 deletions(-) delete mode 100644 include/aoevidencedisplay.h delete mode 100644 src/aoevidencedisplay.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 0ae34ac66..8b0de33cf 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -26,7 +26,6 @@ HEADERS += \ include/aoemotebutton.h \ include/aoevidencebutton.h \ include/aoevidencedescription.h \ - include/aoevidencedisplay.h \ include/aoguiloader.h \ include/aoimagedisplay.h \ include/aolabel.h \ @@ -82,7 +81,6 @@ SOURCES += \ src/aoemotebutton.cpp \ src/aoevidencebutton.cpp \ src/aoevidencedescription.cpp \ - src/aoevidencedisplay.cpp \ src/aoguiloader.cpp \ src/aoimagedisplay.cpp \ src/aolabel.cpp \ diff --git a/include/aoevidencedisplay.h b/include/aoevidencedisplay.h deleted file mode 100644 index 4813219c3..000000000 --- a/include/aoevidencedisplay.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef AOEVIDENCEDISPLAY_H -#define AOEVIDENCEDISPLAY_H - -class AOApplication; -class AOSfxPlayer; - -#include - -class AOEvidenceDisplay : public QLabel -{ - Q_OBJECT - -public: - AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app); - - void show_evidence(QString p_evidence_image, bool is_left_side); - void reset(); - -private: - AOApplication *ao_app = nullptr; - AOSfxPlayer *dr_sfx = nullptr; - - QMovie *m_movie = nullptr; - - QLabel *ui_icon = nullptr; - -private slots: - void frame_change(int p_frame); -}; - -#endif // AOEVIDENCEDISPLAY_H diff --git a/include/courtroom.h b/include/courtroom.h index 907056a04..302bb7c6b 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -345,7 +345,7 @@ class Courtroom : public QMainWindow AOImageDisplay *ui_background = nullptr; QWidget *ui_viewport = nullptr; - DRVideoWidget *ui_vp_video = nullptr; + DRVideoWidget *ui_video = nullptr; DRSceneMovie *ui_vp_background = nullptr; DRCharacterMovie *ui_vp_player_char = nullptr; DRSceneMovie *ui_vp_desk = nullptr; diff --git a/include/drvideoscreen.h b/include/drvideoscreen.h index 3e5620c30..4919c16ca 100644 --- a/include/drvideoscreen.h +++ b/include/drvideoscreen.h @@ -1,9 +1,7 @@ #pragma once #include -#include - -class QVideoWidget; +#include #include "draudiodevice.h" #include "draudiostreamfamily.h" @@ -12,7 +10,7 @@ class AOApplication; class AOConfig; class DRAudioEngine; -class DRVideoWidget : public QWidget +class DRVideoWidget : public QVideoWidget { Q_OBJECT @@ -20,27 +18,36 @@ class DRVideoWidget : public QWidget DRVideoWidget(QWidget *parent = nullptr); ~DRVideoWidget(); - void play(QString character, QString video); + QString file_name(); + void set_file_name(QString file_name); + + void play_character_video(QString character, QString video); + void play(); void stop(); + bool is_playable(); + signals: void done(); -protected: - void resizeEvent(QResizeEvent *event) final; - private: AOConfig *m_config; AOApplication *ao_app; DRAudioEngine *m_engine; DRAudioStreamFamily::ptr m_family; + QString m_file_name; + bool m_scanned = true; + bool m_readable = false; QMediaPlayer *m_player; - QVideoWidget *m_screen; + bool m_running = false; + + void handle_scan_error(); private slots: + void update_device(DRAudioDevice); void update_volume(); - void on_state_changed(QMediaPlayer::State); - void on_video_availability_changed(bool); - void on_device_changed(DRAudioDevice); + void check_media_status(QMediaPlayer::MediaStatus); + void check_video_availability(bool); + void check_state(QMediaPlayer::State); }; diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp deleted file mode 100644 index fbae69643..000000000 --- a/src/aoevidencedisplay.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "aoevidencedisplay.h" - -#include "aoapplication.h" -#include "aopixmap.h" -#include "aosfxplayer.h" -#include "commondefs.h" -#include "courtroom.h" -#include "datatypes.h" -#include "file_functions.h" -#include "misc_functions.h" - -#include - -AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) -{ - ao_app = p_ao_app; - - m_movie = new QMovie(this); - ui_icon = new QLabel(this); - dr_sfx = new AOSfxPlayer(ao_app, this); - - connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); -} - -void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_side) -{ - this->reset(); - - QString l_evidence = ao_app->get_evidence_path(p_evidence_image); - - AOPixmap l_pixmap(l_evidence); - - QString l_icon_animation; - QString l_icon_identifier; - - if (is_left_side) - { - l_icon_identifier = "left_evidence_icon"; - l_icon_animation = "evidence_appear_left.gif"; - } - else - { - l_icon_identifier = "right_evidence_icon"; - l_icon_animation = "evidence_appear_right.gif"; - } - - pos_size_type l_icon_dimensions = ao_app->get_element_dimensions(l_icon_identifier, COURTROOM_DESIGN_INI); - - ui_icon->move(l_icon_dimensions.x, l_icon_dimensions.y); - ui_icon->resize(l_icon_dimensions.width, l_icon_dimensions.height); - ui_icon->setPixmap(l_pixmap.scale(ui_icon->size())); - - QString f_path = ao_app->find_theme_asset_path(l_icon_animation); - m_movie->setFileName(f_path); - if (m_movie->frameCount() < 1) - return; - - this->setMovie(m_movie); - - m_movie->start(); - dr_sfx->play_effect(ao_app->get_sfx("evidence_present")); -} - -void AOEvidenceDisplay::frame_change(int p_frame) -{ - if (p_frame == (m_movie->frameCount() - 1)) - { - // we need this or else the last frame wont show - delay(m_movie->nextFrameDelay()); - - m_movie->stop(); - this->clear(); - - ui_icon->show(); - } -} - -void AOEvidenceDisplay::reset() -{ - m_movie->stop(); - ui_icon->hide(); - this->clear(); -} diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f5dfff8a3..a6219e479 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -5,7 +5,6 @@ #include "aobutton.h" #include "aocharbutton.h" #include "aoconfig.h" -#include "aoevidencedisplay.h" #include "aoimagedisplay.h" #include "aomusicplayer.h" #include "aonotearea.h" @@ -747,7 +746,6 @@ void Courtroom::handle_chatmessage(QStringList p_contents) anim_state = 0; stop_chat_timer(); ui_vp_objection->stop(); - ui_vp_evidence_display->reset(); m_message_color_name = ""; m_message_color_stack.clear(); @@ -765,13 +763,13 @@ void Courtroom::handle_chatmessage(QStringList p_contents) save_textlog(f_showname + ": " + l_message); } - ui_vp_video->show(); - ui_vp_video->play(m_chatmessage[CMChrName], m_chatmessage[CMVideoName]); + ui_video->show(); + ui_video->play_character_video(m_chatmessage[CMChrName], m_chatmessage[CMVideoName]); } void Courtroom::video_done() { - ui_vp_video->hide(); + ui_video->hide(); int objection_mod = m_chatmessage[CMShoutModifier].toInt(); QString f_char = m_chatmessage[CMChrName]; @@ -885,17 +883,7 @@ void Courtroom::handle_chatmessage_3() qDebug() << "handle_chatmessage_3"; setup_chat(); - int f_evi_id = m_chatmessage[CMEvidenceId].toInt(); - QString f_side = m_chatmessage[CMPosition]; - - 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; - // 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"); - ui_vp_evidence_display->show_evidence(f_image, is_left_side); - } + const QString f_side = m_chatmessage[CMPosition]; int f_anim_state = 0; // BLUE is from an enum in datatypes.h diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 52c91d264..4bf5b9635 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -5,7 +5,6 @@ #include "aobutton.h" #include "aoconfig.h" #include "aoevidencedescription.h" -#include "aoevidencedisplay.h" #include "aoimagedisplay.h" #include "aolabel.h" #include "aolineedit.h" @@ -67,8 +66,8 @@ void Courtroom::create_widgets() ui_background = new AOImageDisplay(this, ao_app); ui_viewport = new QWidget(this); - ui_vp_video = new DRVideoWidget(ui_viewport); - ui_vp_video->hide(); + ui_video = new DRVideoWidget(this); + ui_video->hide(); ui_vp_background = new DRSceneMovie(ui_viewport); ui_vp_player_char = new DRCharacterMovie(ui_viewport); ui_vp_desk = new DRSceneMovie(ui_viewport); @@ -87,8 +86,6 @@ void Courtroom::create_widgets() ui_vp_clock = new DRStickerMovie(this); ui_vp_clock->set_play_once(true); - ui_vp_evidence_display = new AOEvidenceDisplay(this, ao_app); - ui_vp_chatbox = new AOImageDisplay(this, ao_app); ui_vp_showname = new DRTextEdit(ui_vp_chatbox); ui_vp_showname->setFrameStyle(QFrame::NoFrame); @@ -258,7 +255,7 @@ void Courtroom::connect_widgets() { connect(m_keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); - connect(ui_vp_video, SIGNAL(done()), this, SLOT(video_done())); + connect(ui_video, SIGNAL(done()), this, SLOT(video_done())); connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); @@ -354,7 +351,7 @@ void Courtroom::reset_widget_names() widget_names = { {"courtroom", this}, {"viewport", ui_viewport}, - {"video", ui_vp_video}, + {"video", ui_video}, {"background", ui_vp_background}, //* {"player_char", ui_vp_player_char}, //* {"desk", ui_vp_desk}, //* @@ -585,8 +582,8 @@ void Courtroom::set_widgets() set_size_and_pos(ui_viewport, "viewport", COURTROOM_DESIGN_INI, ao_app); - ui_vp_video->move(0, 0); - ui_vp_video->resize(ui_viewport->size()); + ui_video->move(ui_viewport->pos()); + ui_video->resize(ui_viewport->size()); ui_vp_background->move(0, 0); ui_vp_background->resize(ui_viewport->size()); @@ -598,9 +595,6 @@ void Courtroom::set_widgets() ui_vp_desk->move(0, 0); ui_vp_desk->resize(ui_viewport->size()); - ui_vp_evidence_display->move(0, 0); - ui_vp_evidence_display->resize(ui_viewport->width(), ui_viewport->height()); - set_size_and_pos(ui_vp_notepad_image, "notepad_image", COURTROOM_DESIGN_INI, ao_app); ui_vp_notepad_image->set_image("notepad_image.png"); ui_vp_notepad_image->hide(); diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index 4f1fc9e49..d25bbedc6 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include "aoapplication.h" #include "aoconfig.h" @@ -12,7 +11,7 @@ #include "draudioengine.h" #include "draudiostreamfamily.h" -DRVideoWidget::DRVideoWidget(QWidget *parent) : QWidget(parent), ao_app(dynamic_cast(qApp)) +DRVideoWidget::DRVideoWidget(QWidget *parent) : QVideoWidget(parent), ao_app(dynamic_cast(qApp)) { Q_ASSERT(ao_app); @@ -20,21 +19,47 @@ DRVideoWidget::DRVideoWidget(QWidget *parent) : QWidget(parent), ao_app(dynamic_ m_engine = new DRAudioEngine(this); m_family = m_engine->get_family(DRAudio::Family::FVideo); m_player = new QMediaPlayer(this); - m_screen = new QVideoWidget(this); - m_player->setVideoOutput(m_screen); + m_player->setVideoOutput(this); + connect(m_engine, SIGNAL(device_changed(DRAudioDevice)), this, SLOT(update_device(DRAudioDevice))); connect(m_config, SIGNAL(video_volume_changed(int)), this, SLOT(update_volume())); connect(m_engine, SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); connect(m_engine, SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); connect(m_family.get(), SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); connect(m_family.get(), SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); - connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(on_state_changed(QMediaPlayer::State))); - connect(m_player, SIGNAL(videoAvailableChanged(bool)), this, SLOT(on_video_availability_changed(bool))); + connect(m_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, + SLOT(check_media_status(QMediaPlayer::MediaStatus))); + connect(m_player, SIGNAL(videoAvailableChanged(bool)), this, SLOT(check_video_availability(bool))); + connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(check_state(QMediaPlayer::State))); } DRVideoWidget::~DRVideoWidget() {} +QString DRVideoWidget::file_name() +{ + return m_file_name; +} + +void DRVideoWidget::set_file_name(QString p_file_name) +{ + if (m_file_name == p_file_name) + return; + m_file_name = p_file_name; + m_scanned = false; + if (p_file_name.isEmpty()) + m_scanned = true; + m_readable = false; + m_running = false; + stop(); + m_player->setMuted(true); + m_player->setMedia(QUrl(p_file_name)); + if (m_player->error()) + { + qDebug() << "error: failed to load media file" << (m_file_name.isEmpty() ? "" : m_file_name); + } +} + /** * @brief Attempts to play a video file located in a character's videos directory. * @@ -44,14 +69,9 @@ DRVideoWidget::~DRVideoWidget() * If a video file is already playing, it will immediately stop it * without emitting done() before playing the newly designated file */ -void DRVideoWidget::play(QString p_character, QString p_video) +void DRVideoWidget::play_character_video(QString p_character, QString p_video) { - m_player->stop(); - m_player->setMedia(nullptr); - - qInfo() << "DRVideoWidget::play" - << "starting:" - << "character:" << p_character << "video:" << p_video; + qInfo() << "loading character media file" << p_character << p_video; QStringList l_filelist; const QString l_video_path = QString("videos/%1").arg(p_video); @@ -61,77 +81,92 @@ void DRVideoWidget::play(QString p_character, QString p_video) } const QString l_file = ao_app->find_asset_path(l_filelist); - if (l_file.isEmpty()) + set_file_name(l_file); + play(); +} + +void DRVideoWidget::play() +{ + if (!m_scanned) { - qWarning() << "DRVideoWidget::play" - << "error: file could not be found"; - emit done(); + m_running = true; return; } - - update_volume(); - m_player->setMedia(QUrl(l_file)); - m_player->setMuted(true); - m_player->play(); - if (m_player->error()) + else if (!m_readable) { - qWarning() << "DRVideoWidget::play" - << "error:" << m_player->errorString(); emit done(); + return; } + + stop(); + m_running = true; + m_player->setMuted(false); + m_player->blockSignals(true); // prevents scan results from being altered + m_player->setMedia(QUrl(m_file_name)); + m_player->blockSignals(false); + m_player->play(); } void DRVideoWidget::stop() { + m_running = false; + m_player->blockSignals(true); m_player->stop(); + m_player->setMedia(nullptr); + m_player->blockSignals(false); } -void DRVideoWidget::resizeEvent(QResizeEvent *event) +bool DRVideoWidget::is_playable() { - QWidget::resizeEvent(event); - m_screen->resize(size()); + return m_readable; } -void DRVideoWidget::update_volume() +void DRVideoWidget::check_media_status(QMediaPlayer::MediaStatus p_status) { - int l_volume = (m_family->get_volume() * m_engine->get_volume()) / 100; - - if (!m_family->is_ignore_suppression() && (m_family->is_suppressed() || m_engine->is_suppressed())) + if (p_status == QMediaPlayer::InvalidMedia) { - l_volume = 0; + qWarning() << "error: invalid media file" << m_file_name; + m_scanned = true; + handle_scan_error(); } +} - if (m_player->volume() == l_volume) - return; - m_player->setVolume(l_volume); +void DRVideoWidget::check_video_availability(bool p_state) +{ + m_scanned = true; + m_readable = p_state; + m_player->setMuted(!m_readable); + if (!m_readable) + { + qWarning() << "error: media file is not a video" << m_file_name; + handle_scan_error(); + } + else if (m_running && m_player->state() != QMediaPlayer::PlayingState) + { + m_player->play(); + } } -void DRVideoWidget::on_state_changed(QMediaPlayer::State p_state) +void DRVideoWidget::check_state(QMediaPlayer::State p_state) { - switch (p_state) + if (m_readable && m_running && p_state == QMediaPlayer::StoppedState) { - case QMediaPlayer::StoppedState: + qInfo() << "finished media file playback" << m_file_name; + stop(); emit done(); - break; - - default: - break; } } -void DRVideoWidget::on_video_availability_changed(bool p_state) +void DRVideoWidget::handle_scan_error() { - m_player->setMuted(!p_state); - if (!p_state) + if (m_running) { - qWarning() << "DRVideoWidget::play" - << "error: video track not available"; - m_player->stop(); + stop(); emit done(); } } -void DRVideoWidget::on_device_changed(DRAudioDevice p_device) +void DRVideoWidget::update_device(DRAudioDevice p_device) { const QString l_new_device_name = p_device.get_name(); const QList l_device_list = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); @@ -144,8 +179,7 @@ void DRVideoWidget::on_device_changed(DRAudioDevice p_device) QAudioOutputSelectorControl *l_control = l_service->requestControl(); if (!l_control) { - qCritical() << "DRVideoWidget" - << "error: failed to change device; QAudioOutputSelectorControl is null"; + qCritical() << "error: failed to change device; QAudioOutputSelectorControl is null"; return; } l_control->setActiveOutput(i_device.deviceName()); @@ -154,3 +188,17 @@ void DRVideoWidget::on_device_changed(DRAudioDevice p_device) } } } + +void DRVideoWidget::update_volume() +{ + int l_volume = (m_family->get_volume() * m_engine->get_volume()) / 100; + + if (!m_family->is_ignore_suppression() && (m_family->is_suppressed() || m_engine->is_suppressed())) + { + l_volume = 0; + } + + if (m_player->volume() == l_volume) + return; + m_player->setVolume(l_volume); +} From e2d053b1991de26d51287bada01c1e51a972e47a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 23 Mar 2022 21:25:22 +0100 Subject: [PATCH 615/842] Removed evidence --- dronline-client.pro | 5 - include/aoapplication.h | 1 - include/aoevidencebutton.h | 45 ---- include/aoevidencedescription.h | 23 --- include/courtroom.h | 54 ----- include/datatypes.h | 7 - src/aoevidencebutton.cpp | 103 ---------- src/aoevidencedescription.cpp | 18 -- src/courtroom.cpp | 28 +-- src/courtroom_widgets.cpp | 46 ----- src/evidence.cpp | 354 -------------------------------- src/path_functions.cpp | 11 - src/server_socket.cpp | 24 --- 13 files changed, 2 insertions(+), 717 deletions(-) delete mode 100644 include/aoevidencebutton.h delete mode 100644 include/aoevidencedescription.h delete mode 100644 src/aoevidencebutton.cpp delete mode 100644 src/aoevidencedescription.cpp delete mode 100644 src/evidence.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 8b0de33cf..30126eea6 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -24,8 +24,6 @@ HEADERS += \ include/aoconfig.h \ include/aoconfigpanel.h \ include/aoemotebutton.h \ - include/aoevidencebutton.h \ - include/aoevidencedescription.h \ include/aoguiloader.h \ include/aoimagedisplay.h \ include/aolabel.h \ @@ -79,8 +77,6 @@ SOURCES += \ src/aoconfig.cpp \ src/aoconfigpanel.cpp \ src/aoemotebutton.cpp \ - src/aoevidencebutton.cpp \ - src/aoevidencedescription.cpp \ src/aoguiloader.cpp \ src/aoimagedisplay.cpp \ src/aolabel.cpp \ @@ -126,7 +122,6 @@ SOURCES += \ src/drdiscord.cpp \ src/drvideoscreen.cpp \ src/emotes.cpp \ - src/evidence.cpp \ src/file_functions.cpp \ src/hardware_functions.cpp \ src/lobby.cpp \ diff --git a/include/aoapplication.h b/include/aoapplication.h index 3155e01bf..b275ea94d 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -76,7 +76,6 @@ class AOApplication : public QApplication QString format_background_path(QString p_identifier); QStringList get_available_background_identifier_list(); QString get_current_background_path(); - QString get_evidence_path(QString p_file); bool is_safe_path(QString p_file); diff --git a/include/aoevidencebutton.h b/include/aoevidencebutton.h deleted file mode 100644 index b50954097..000000000 --- a/include/aoevidencebutton.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef AOEVIDENCEBUTTON_H -#define AOEVIDENCEBUTTON_H - -class AOApplication; -class AOImageDisplay; - -#include - -class AOEvidenceButton : public QPushButton -{ - Q_OBJECT - -public: - AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y); - - void reset(); - void set_image(QString p_image); - void set_theme_image(QString p_image); - void set_id(int p_id); - - void set_selected(bool p_selected); - -signals: - void evidence_clicked(int p_id); - void evidence_double_clicked(int p_id); - void on_hover(int p_id, bool p_state); - -protected: - void enterEvent(QEvent *e); - void leaveEvent(QEvent *e); - void mouseDoubleClickEvent(QMouseEvent *e); - -private: - AOApplication *ao_app = nullptr; - - int m_index = 0; - - AOImageDisplay *ui_selected = nullptr; - AOImageDisplay *ui_selector = nullptr; - -private slots: - void on_clicked(); -}; - -#endif // AOEVIDENCEBUTTON_H diff --git a/include/aoevidencedescription.h b/include/aoevidencedescription.h deleted file mode 100644 index 343bfd7b7..000000000 --- a/include/aoevidencedescription.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef AOEVIDENCEDESCRIPTION_H -#define AOEVIDENCEDESCRIPTION_H - -#include - -class AOEvidenceDescription : public QPlainTextEdit -{ - Q_OBJECT - -public: - AOEvidenceDescription(QWidget *parent); - -signals: - void double_clicked(); - -protected: - void mouseDoubleClickEvent(QMouseEvent *e); - -private slots: - void on_enter_pressed(); -}; - -#endif // AOEVIDENCEDESCRIPTION_H diff --git a/include/courtroom.h b/include/courtroom.h index 302bb7c6b..53bc328a3 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -9,9 +9,6 @@ class AOButton; class AOCharButton; class AOConfig; class AOEmoteButton; -class AOEvidenceButton; -class AOEvidenceDescription; -class AOEvidenceDisplay; class AOImageDisplay; class AOLabel; class AOLineEdit; @@ -81,9 +78,6 @@ class Courtroom : public QMainWindow void set_tick_rate(const int tick_rate); - // sets the evidence list member variable to argument - void set_evidence_list(QVector &p_evi_list); - // sets the character position void set_character_position(QString p_pos); @@ -229,7 +223,6 @@ class Courtroom : public QMainWindow AOConfig *ao_config = nullptr; QVector m_chr_list; - QVector m_evidence_list; QStringList m_area_list; QStringList m_music_list; @@ -307,7 +300,6 @@ class Courtroom : public QMainWindow int m_shout_current = 0; int m_effect_current = 0; int m_wtce_current = 0; - bool is_presenting_evidence = false; bool is_judge = false; bool is_system_speaking = false; @@ -328,16 +320,6 @@ class Courtroom : public QMainWindow int m_page_max_emote_count = 10; int m_emote_preview_id = -1; - // inmchatlog_changed; - - QVector local_evidence_list; - - int current_evidence_page = 0; - int current_evidence = 0; - int evidence_columns = 6; - int evidence_rows = 3; - int max_evidence_on_page = 18; - int m_current_clock = -1; DRAreaBackground m_background; @@ -349,7 +331,6 @@ class Courtroom : public QMainWindow DRSceneMovie *ui_vp_background = nullptr; DRCharacterMovie *ui_vp_player_char = nullptr; DRSceneMovie *ui_vp_desk = nullptr; - AOEvidenceDisplay *ui_vp_evidence_display = nullptr; AONoteArea *ui_note_area = nullptr; @@ -487,21 +468,6 @@ class Courtroom : public QMainWindow AOButton *ui_note_button = nullptr; - AOButton *ui_evidence_button = nullptr; - AOImageDisplay *ui_evidence = nullptr; - AOLineEdit *ui_evidence_name = nullptr; - QWidget *ui_evidence_buttons = nullptr; - QVector ui_evidence_list; - AOButton *ui_evidence_left = nullptr; - AOButton *ui_evidence_right = nullptr; - AOButton *ui_evidence_present = nullptr; - AOImageDisplay *ui_evidence_overlay = nullptr; - AOButton *ui_evidence_delete = nullptr; - AOLineEdit *ui_evidence_image_name = nullptr; - AOButton *ui_evidence_image_button = nullptr; - AOButton *ui_evidence_x = nullptr; - AOEvidenceDescription *ui_evidence_description = nullptr; - AOImageDisplay *ui_char_select_background = nullptr; // abstract widget to hold char buttons @@ -541,9 +507,6 @@ class Courtroom : public QMainWindow DREmote get_emote(const int id); DREmote get_current_emote(); - void construct_evidence(); - void set_evidence_page(); - void load_note(); void save_note(); void save_textlog(QString p_text); @@ -607,18 +570,6 @@ private slots: void on_iniswap_dropdown_changed(int p_index); void on_pos_dropdown_changed(int p_index); - void on_evidence_name_edited(); - void on_evidence_image_name_edited(); - void on_evidence_image_button_clicked(); - void on_evidence_clicked(int p_id); - void on_evidence_double_clicked(int p_id); - - void on_evidence_hover(int p_id, bool p_state); - - void on_evidence_left_clicked(); - void on_evidence_right_clicked(); - void on_evidence_present_clicked(); - void on_cycle_clicked(); void cycle_shout(int p_delta); @@ -672,11 +623,6 @@ private slots: void on_flip_clicked(); void on_hidden_clicked(); - void on_evidence_button_clicked(); - - void on_evidence_delete_clicked(); - void on_evidence_x_clicked(); - void on_back_to_lobby_clicked(); void on_char_select_left_clicked(); diff --git a/include/datatypes.h b/include/datatypes.h index fbf48768c..1fcb9f637 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -114,13 +114,6 @@ struct char_type bool taken = false; }; -struct evi_type -{ - QString name; - QString description; - QString image; -}; - struct pos_size_type { int x = 0; diff --git a/src/aoevidencebutton.cpp b/src/aoevidencebutton.cpp deleted file mode 100644 index cbc685adb..000000000 --- a/src/aoevidencebutton.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include "aoevidencebutton.h" - -#include "aoapplication.h" -#include "aoimagedisplay.h" -#include "file_functions.h" - -#include - -AOEvidenceButton::AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) -{ - ao_app = p_ao_app; - - ui_selected = new AOImageDisplay(p_parent, ao_app); - ui_selected->resize(70, 70); - ui_selected->move(p_x, p_y); - ui_selected->set_image("evidence_selected.png"); - ui_selected->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_selected->hide(); - - ui_selector = new AOImageDisplay(p_parent, ao_app); - ui_selector->resize(71, 71); - ui_selector->move(p_x - 1, p_y - 1); - ui_selector->set_image("evidence_selector.png"); - ui_selector->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_selector->hide(); - - this->move(p_x, p_y); - this->resize(70, 70); - this->setAcceptDrops(true); - - connect(this, SIGNAL(clicked()), this, SLOT(on_clicked())); -} -void AOEvidenceButton::reset() -{ - this->hide(); - ui_selected->hide(); - ui_selector->hide(); -} - -void AOEvidenceButton::set_image(QString p_image) -{ - QString image_path = ao_app->get_evidence_path(p_image); - - if (file_exists(image_path)) - { - this->setText(""); - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - } - else - { - this->setText(p_image); - this->setStyleSheet(""); - } -} - -void AOEvidenceButton::set_theme_image(QString p_image) -{ - QString path = ao_app->find_theme_asset_path(p_image); - this->setText(""); - this->setStyleSheet("border-image:url(\"" + path + "\")"); -} - -void AOEvidenceButton::set_id(int p_id) -{ - m_index = p_id; -} - -void AOEvidenceButton::set_selected(bool p_selected) -{ - if (p_selected) - ui_selected->show(); - else - ui_selected->hide(); -} - -void AOEvidenceButton::on_clicked() -{ - Q_EMIT evidence_clicked(m_index); -} - -void AOEvidenceButton::mouseDoubleClickEvent(QMouseEvent *e) -{ - QPushButton::mouseDoubleClickEvent(e); - Q_EMIT evidence_double_clicked(m_index); -} - -void AOEvidenceButton::enterEvent(QEvent *e) -{ - ui_selector->show(); - - Q_EMIT on_hover(m_index, true); - - setFlat(false); - QPushButton::enterEvent(e); -} - -void AOEvidenceButton::leaveEvent(QEvent *e) -{ - ui_selector->hide(); - - Q_EMIT on_hover(m_index, false); - QPushButton::leaveEvent(e); -} diff --git a/src/aoevidencedescription.cpp b/src/aoevidencedescription.cpp deleted file mode 100644 index 0a3c4caec..000000000 --- a/src/aoevidencedescription.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "aoevidencedescription.h" - -AOEvidenceDescription::AOEvidenceDescription(QWidget *parent) : QPlainTextEdit(parent) -{ - this->setReadOnly(true); -} - -void AOEvidenceDescription::mouseDoubleClickEvent(QMouseEvent *e) -{ - QPlainTextEdit::mouseDoubleClickEvent(e); - - this->setReadOnly(false); -} - -void AOEvidenceDescription::on_enter_pressed() -{ - this->setReadOnly(true); -} diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a6219e479..c1650ee21 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -98,10 +98,6 @@ void Courtroom::setup_courtroom() load_free_blocks(); load_sfx_list_theme(); - current_evidence_page = 0; - current_evidence = 0; - set_evidence_page(); - // Update widgets first, then check if everything is valid // This will also handle showing the correct shouts, effects and wtce buttons, // and cycling through them if the buttons that are supposed to be displayed @@ -625,12 +621,8 @@ void Courtroom::on_ic_message_return_pressed() packet_contents.append(f_obj_state); - if (is_presenting_evidence) - // the evidence index is shifted by 1 because 0 is no evidence per legacy - // standards besides, older clients crash if we pass -1 - packet_contents.append(QString::number(current_evidence + 1)); - else - packet_contents.append("0"); + // evidence + packet_contents.append("0"); QString f_flip = ui_flip->isChecked() ? "1" : "0"; packet_contents.append(f_flip); @@ -665,9 +657,6 @@ void Courtroom::handle_acknowledged_ms() reset_effect_buttons(); reset_wtce_buttons(); clear_sfx_selection(); - - is_presenting_evidence = false; - ui_evidence_present->set_image("present_disabled.png"); } void Courtroom::handle_chatmessage(QStringList p_contents) @@ -2210,19 +2199,6 @@ void Courtroom::on_hidden_clicked() ui_ic_chat_message->setFocus(); } -void Courtroom::on_evidence_button_clicked() -{ - if (ui_evidence->isHidden()) - { - ui_evidence->show(); - ui_evidence_overlay->hide(); - } - else - { - ui_evidence->hide(); - } -} - void Courtroom::on_config_panel_clicked() { ao_app->toggle_config_panel(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 4bf5b9635..e2d8b9c09 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -4,7 +4,6 @@ #include "aoblipplayer.h" #include "aobutton.h" #include "aoconfig.h" -#include "aoevidencedescription.h" #include "aoimagedisplay.h" #include "aolabel.h" #include "aolineedit.h" @@ -234,9 +233,6 @@ void Courtroom::create_widgets() ui_text_color->addItem("Yellow"); ui_text_color->addItem("Purple"); ui_text_color->addItem("Pink"); - - ui_evidence_button = new AOButton(this, ao_app); - ui_vp_notepad_image = new AOImageDisplay(this, ao_app); ui_vp_notepad = new DRTextEdit(this); ui_vp_notepad->setFrameStyle(QFrame::NoFrame); @@ -244,8 +240,6 @@ void Courtroom::create_widgets() ui_timers.resize(1); ui_timers[0] = new AOTimer(this); - construct_evidence(); - load_free_blocks(); // Done last so they are guaranteed to be at bottom construct_char_select(); @@ -339,8 +333,6 @@ void Courtroom::connect_widgets() connect(ui_sfx_list, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(_p_sfxCurrentItemChanged(QListWidgetItem *, QListWidgetItem *))); - connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); - connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, SLOT(on_add_button_clicked())); connect(ui_set_notes, SIGNAL(clicked(bool)), this, SLOT(on_set_notes_clicked())); } @@ -361,7 +353,6 @@ void Courtroom::reset_widget_names() {"music_name", ui_vp_music_name}, // music_anim {"clock", ui_vp_clock}, - // ui_vp_evidence_display {"ao2_chatbox", ui_vp_chatbox}, {"showname", ui_vp_showname}, {"message", ui_vp_message}, @@ -421,12 +412,9 @@ void Courtroom::reset_widget_names() {"prosecution_plus", ui_prosecution_plus}, {"prosecution_minus", ui_prosecution_minus}, {"text_color", ui_text_color}, - {"evidence_button", ui_evidence_button}, {"notepad_image", ui_vp_notepad_image}, {"notepad", ui_vp_notepad}, // Each ui_timers[i] - {"evidence_background", ui_evidence}, - {"evidence_buttons", ui_evidence_buttons}, {"char_select", ui_char_select_background}, {"back_to_lobby", ui_back_to_lobby}, {"char_buttons", ui_char_buttons}, @@ -951,40 +939,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_text_color, "text_color", COURTROOM_DESIGN_INI, ao_app); set_stylesheet(ui_text_color, "[TEXT COLOR]", COURTROOM_STYLESHEETS_CSS, ao_app); - set_size_and_pos(ui_evidence_button, "evidence_button", COURTROOM_DESIGN_INI, ao_app); - ui_evidence_button->set_image("evidencebutton.png"); - - set_size_and_pos(ui_evidence, "evidence_background", COURTROOM_DESIGN_INI, ao_app); - ui_evidence->set_image("evidencebackground.png"); - - set_size_and_pos(ui_evidence_name, "evidence_name", COURTROOM_DESIGN_INI, ao_app); - - set_size_and_pos(ui_evidence_buttons, "evidence_buttons", COURTROOM_DESIGN_INI, ao_app); - - set_size_and_pos(ui_evidence_left, "evidence_left", COURTROOM_DESIGN_INI, ao_app); - ui_evidence_left->set_image("arrow_left.png"); - - set_size_and_pos(ui_evidence_right, "evidence_right", COURTROOM_DESIGN_INI, ao_app); - ui_evidence_right->set_image("arrow_right.png"); - - set_size_and_pos(ui_evidence_present, "evidence_present", COURTROOM_DESIGN_INI, ao_app); - ui_evidence_present->set_image("present_disabled.png"); - - set_size_and_pos(ui_evidence_overlay, "evidence_overlay", COURTROOM_DESIGN_INI, ao_app); - ui_evidence_overlay->set_image("evidenceoverlay.png"); - - set_size_and_pos(ui_evidence_delete, "evidence_delete", COURTROOM_DESIGN_INI, ao_app); - ui_evidence_delete->set_image("deleteevidence.png"); - - set_size_and_pos(ui_evidence_image_name, "evidence_image_name", COURTROOM_DESIGN_INI, ao_app); - - set_size_and_pos(ui_evidence_image_button, "evidence_image_button", COURTROOM_DESIGN_INI, ao_app); - - set_size_and_pos(ui_evidence_x, "evidence_x", COURTROOM_DESIGN_INI, ao_app); - ui_evidence_x->set_image("evidencex.png"); - - set_size_and_pos(ui_evidence_description, "evidence_description", COURTROOM_DESIGN_INI, ao_app); - ui_char_button_selector->set_image("char_selector.png"); ui_char_button_selector->hide(); diff --git a/src/evidence.cpp b/src/evidence.cpp deleted file mode 100644 index 80ce78805..000000000 --- a/src/evidence.cpp +++ /dev/null @@ -1,354 +0,0 @@ -#include "courtroom.h" - -#include "aoapplication.h" -#include "aobutton.h" -#include "aoevidencebutton.h" -#include "aoevidencedescription.h" -#include "aoimagedisplay.h" -#include "aolineedit.h" -#include "commondefs.h" -#include "drpacket.h" -#include "theme.h" - -#include - -void Courtroom::construct_evidence() -{ - ui_evidence = new AOImageDisplay(this, ao_app); - - // ui_evidence_name = new QLabel(ui_evidence); - ui_evidence_name = new AOLineEdit(ui_evidence); - ui_evidence_name->setAlignment(Qt::AlignCenter); - ui_evidence_name->setFont(QFont("Arial", 14, QFont::Bold)); - ui_evidence_name->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: rgba(255, 128, 0, 255);"); - - ui_evidence_buttons = new QWidget(ui_evidence); - - ui_evidence_left = new AOButton(ui_evidence, ao_app); - ui_evidence_right = new AOButton(ui_evidence, ao_app); - ui_evidence_present = new AOButton(ui_evidence, ao_app); - - ui_evidence_overlay = new AOImageDisplay(ui_evidence, ao_app); - - ui_evidence_delete = new AOButton(ui_evidence_overlay, ao_app); - ui_evidence_image_name = new AOLineEdit(ui_evidence_overlay); - ui_evidence_image_button = new AOButton(ui_evidence_overlay, ao_app); - ui_evidence_image_button->setText("Choose.."); - ui_evidence_x = new AOButton(ui_evidence_overlay, ao_app); - - ui_evidence_description = new AOEvidenceDescription(ui_evidence_overlay); - ui_evidence_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: white;"); - - set_size_and_pos(ui_evidence, "evidence_background", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_evidence_buttons, "evidence_buttons", COURTROOM_DESIGN_INI, ao_app); - - QPoint f_spacing = ao_app->get_button_spacing("evidence_button_spacing", COURTROOM_DESIGN_INI); - - const int button_width = 70; - int x_spacing = f_spacing.x(); - int x_mod_count = 0; - - const int button_height = 70; - int y_spacing = f_spacing.y(); - int y_mod_count = 0; - - evidence_columns = ((ui_evidence_buttons->width() - button_width) / (x_spacing + button_width)) + 1; - evidence_rows = ((ui_evidence_buttons->height() - button_height) / (y_spacing + button_height)) + 1; - - max_evidence_on_page = evidence_columns * evidence_rows; - - for (int n = 0; n < max_evidence_on_page; ++n) - { - int x_pos = (button_width + x_spacing) * x_mod_count; - int y_pos = (button_height + y_spacing) * y_mod_count; - - AOEvidenceButton *f_evidence = new AOEvidenceButton(ui_evidence_buttons, ao_app, x_pos, y_pos); - - ui_evidence_list.append(f_evidence); - - f_evidence->set_id(n); - - connect(f_evidence, SIGNAL(evidence_clicked(int)), this, SLOT(on_evidence_clicked(int))); - connect(f_evidence, SIGNAL(evidence_double_clicked(int)), this, SLOT(on_evidence_double_clicked(int))); - connect(f_evidence, SIGNAL(on_hover(int, bool)), this, SLOT(on_evidence_hover(int, bool))); - - ++x_mod_count; - - if (x_mod_count == evidence_columns) - { - ++y_mod_count; - x_mod_count = 0; - } - } - - connect(ui_evidence_name, SIGNAL(returnPressed()), this, SLOT(on_evidence_name_edited())); - connect(ui_evidence_left, SIGNAL(clicked()), this, SLOT(on_evidence_left_clicked())); - connect(ui_evidence_right, SIGNAL(clicked()), this, SLOT(on_evidence_right_clicked())); - connect(ui_evidence_present, SIGNAL(clicked()), this, SLOT(on_evidence_present_clicked())); - connect(ui_evidence_delete, SIGNAL(clicked()), this, SLOT(on_evidence_delete_clicked())); - connect(ui_evidence_image_name, SIGNAL(returnPressed()), this, SLOT(on_evidence_image_name_edited())); - connect(ui_evidence_image_button, SIGNAL(clicked()), this, SLOT(on_evidence_image_button_clicked())); - connect(ui_evidence_x, SIGNAL(clicked()), this, SLOT(on_evidence_x_clicked())); - - ui_evidence->hide(); -} - -void Courtroom::set_evidence_list(QVector &p_evi_list) -{ - local_evidence_list.clear(); - local_evidence_list = p_evi_list; - - set_evidence_page(); -} - -void Courtroom::set_evidence_page() -{ - int total_evidence = local_evidence_list.size(); - - ui_evidence_left->hide(); - ui_evidence_right->hide(); - - for (AOEvidenceButton *i_button : qAsConst(ui_evidence_list)) - { - i_button->reset(); - } - - // to account for the "add evidence" button - ++total_evidence; - - int total_pages = total_evidence / max_evidence_on_page; - int evidence_on_page = 0; - - if ((total_evidence % max_evidence_on_page) != 0) - { - ++total_pages; - // i. e. not on the last page - if (total_pages > current_evidence_page + 1) - evidence_on_page = max_evidence_on_page; - else - evidence_on_page = total_evidence % max_evidence_on_page; - } - else - evidence_on_page = max_evidence_on_page; - - if (total_pages > current_evidence_page + 1) - ui_evidence_right->show(); - - if (current_evidence_page > 0) - ui_evidence_left->show(); - - for (int n_evidence_button = 0; n_evidence_button < evidence_on_page; ++n_evidence_button) - { - int n_real_evidence = n_evidence_button + current_evidence_page * max_evidence_on_page; - AOEvidenceButton *f_evidence_button = ui_evidence_list.at(n_evidence_button); - - // ie. the add evidence button - if (n_real_evidence == (total_evidence - 1)) - f_evidence_button->set_theme_image("addevidence.png"); - else if (n_real_evidence < (total_evidence - 1)) - { - f_evidence_button->set_image(local_evidence_list.at(n_real_evidence).image); - - if (n_real_evidence == current_evidence) - f_evidence_button->set_selected(true); - else - f_evidence_button->set_selected(false); - } - else - f_evidence_button->set_image(""); - - f_evidence_button->show(); - } -} - -void Courtroom::on_evidence_name_edited() -{ - if (current_evidence >= local_evidence_list.size()) - return; - - QStringList f_contents; - - evi_type f_evi = local_evidence_list.at(current_evidence); - - f_contents.append(QString::number(current_evidence)); - f_contents.append(ui_evidence_name->text()); - f_contents.append(f_evi.description); - f_contents.append(f_evi.image); - - ao_app->send_server_packet(DRPacket("EE", f_contents)); -} - -void Courtroom::on_evidence_image_name_edited() -{ - if (current_evidence >= local_evidence_list.size()) - return; - - QStringList f_contents; - - evi_type f_evi = local_evidence_list.at(current_evidence); - - f_contents.append(QString::number(current_evidence)); - f_contents.append(f_evi.name); - f_contents.append(f_evi.description); - f_contents.append(ui_evidence_image_name->text()); - - ao_app->send_server_packet(DRPacket("EE", f_contents)); -} - -void Courtroom::on_evidence_image_button_clicked() -{ - QFileDialog dialog(this); - dialog.setFileMode(QFileDialog::ExistingFile); - dialog.setNameFilter(tr("Images (*.png)")); - dialog.setViewMode(QFileDialog::List); - dialog.setDirectory(ao_app->get_evidence_path("")); - - QStringList filenames; - - if (dialog.exec()) - filenames = dialog.selectedFiles(); - - if (filenames.size() != 1) - return; - - QString filename = filenames.at(0); - - QStringList split_filename = filename.split("/"); - - filename = split_filename.at(split_filename.size() - 1); - - ui_evidence_image_name->setText(filename); - - on_evidence_image_name_edited(); -} - -void Courtroom::on_evidence_clicked(int p_id) -{ - ui_evidence_name->setReadOnly(true); - - int f_real_id = p_id + max_evidence_on_page * current_evidence_page; - - if (f_real_id == local_evidence_list.size()) - { - ao_app->send_server_packet(DRPacket("PE", {"", "", "empty.png"})); - return; - } - else if (f_real_id > local_evidence_list.size()) - return; - - ui_evidence_name->setText(local_evidence_list.at(f_real_id).name); - - for (AOEvidenceButton *i_button : qAsConst(ui_evidence_list)) - i_button->set_selected(false); - - ui_evidence_list.at(p_id)->set_selected(true); - - current_evidence = f_real_id; - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_evidence_double_clicked(int p_id) -{ - int f_real_id = p_id + max_evidence_on_page * current_evidence_page; - - if (f_real_id >= local_evidence_list.size()) - return; - - current_evidence = f_real_id; - - evi_type f_evi = local_evidence_list.at(f_real_id); - - ui_evidence_description->clear(); - ui_evidence_description->appendPlainText(f_evi.description); - - ui_evidence_image_name->setText(f_evi.image); - - ui_evidence_overlay->show(); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_evidence_hover(int p_id, bool p_state) -{ - ui_evidence_name->setReadOnly(true); - int final_id = p_id + max_evidence_on_page * current_evidence_page; - - if (p_state) - { - if (final_id == local_evidence_list.size()) - ui_evidence_name->setText("Add new evidence..."); - else if (final_id < local_evidence_list.size()) - ui_evidence_name->setText(local_evidence_list.at(final_id).name); - } - else if (current_evidence < local_evidence_list.size()) - ui_evidence_name->setText(local_evidence_list.at(current_evidence).name); - else - ui_evidence_name->setText(""); -} - -void Courtroom::on_evidence_left_clicked() -{ - --current_evidence_page; - - set_evidence_page(); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_evidence_right_clicked() -{ - ++current_evidence_page; - - set_evidence_page(); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_evidence_present_clicked() -{ - if (is_presenting_evidence) - ui_evidence_present->set_image("present_disabled.png"); - else - ui_evidence_present->set_image("present.png"); - - is_presenting_evidence = !is_presenting_evidence; - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_evidence_delete_clicked() -{ - ui_evidence_description->setReadOnly(true); - ui_evidence_overlay->hide(); - - ao_app->send_server_packet(DRPacket("DE", {QString::number(current_evidence)})); - - current_evidence = 0; - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_evidence_x_clicked() -{ - ui_evidence_description->setReadOnly(true); - ui_evidence_overlay->hide(); - - if (current_evidence >= local_evidence_list.size()) - return; - - QStringList f_contents; - - evi_type f_evi = local_evidence_list.at(current_evidence); - - f_contents.append(QString::number(current_evidence)); - f_contents.append(f_evi.name); - f_contents.append(ui_evidence_description->toPlainText()); - f_contents.append(f_evi.image); - - ao_app->send_server_packet(DRPacket("EE", f_contents)); - - ui_ic_chat_message->setFocus(); -} diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 0da76ceb4..7ec8f9c54 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -105,17 +105,6 @@ QString AOApplication::get_current_background_path() return l_bg_path; } -QString AOApplication::get_evidence_path(QString p_file) -{ - QString default_path = get_case_sensitive_path(get_base_path() + "evidence/" + p_file); - QString alt_path = get_case_sensitive_path(get_base_path() + "items/" + p_file); - - if (QFile(default_path).exists()) - return default_path; - else - return alt_path; -} - /** * @brief Returns the 'correct' path for the file given as the parameter by * trying to match the case of the actual path. diff --git a/src/server_socket.cpp b/src/server_socket.cpp index decbdccf6..08be38c17 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -303,30 +303,6 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) if (is_courtroom_constructed && l_content.size() > 1) m_courtroom->set_hp_bar(l_content.at(0).toInt(), l_content.at(1).toInt()); } - else if (l_header == "LE") - { - if (is_courtroom_constructed) - { - QVector f_evi_list; - - for (const QString &f_string : l_content) - { - QStringList sub_contents = f_string.split("&"); - - if (sub_contents.size() < 3) - continue; - - evi_type f_evi; - f_evi.name = sub_contents.at(0); - f_evi.description = sub_contents.at(1); - f_evi.image = sub_contents.at(2); - - f_evi_list.append(f_evi); - } - - m_courtroom->set_evidence_list(f_evi_list); - } - } else if (l_header == "KK") { if (is_courtroom_constructed && l_content.size() > 0) From df8133521cef548f22b0544565f0d35ceb690ea3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 23 Mar 2022 21:46:13 +0100 Subject: [PATCH 616/842] DRVideoScreen now emit started signal --- include/courtroom.h | 2 +- include/drvideoscreen.h | 3 ++- src/courtroom.cpp | 5 +---- src/courtroom_widgets.cpp | 4 +++- src/drvideoscreen.cpp | 12 ++++++++---- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/include/courtroom.h b/include/courtroom.h index 53bc328a3..db4e54ca0 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -514,7 +514,7 @@ class Courtroom : public QMainWindow bool is_spectating(); public slots: - void video_done(); + void video_finished(); void objection_done(); void preanim_done(); diff --git a/include/drvideoscreen.h b/include/drvideoscreen.h index 4919c16ca..46613a554 100644 --- a/include/drvideoscreen.h +++ b/include/drvideoscreen.h @@ -28,7 +28,8 @@ class DRVideoWidget : public QVideoWidget bool is_playable(); signals: - void done(); + void started(); + void finished(); private: AOConfig *m_config; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index c1650ee21..beaa58437 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -752,14 +752,11 @@ void Courtroom::handle_chatmessage(QStringList p_contents) save_textlog(f_showname + ": " + l_message); } - ui_video->show(); ui_video->play_character_video(m_chatmessage[CMChrName], m_chatmessage[CMVideoName]); } -void Courtroom::video_done() +void Courtroom::video_finished() { - ui_video->hide(); - int objection_mod = m_chatmessage[CMShoutModifier].toInt(); QString f_char = m_chatmessage[CMChrName]; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index e2d8b9c09..3d74d7a91 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -249,7 +249,9 @@ void Courtroom::connect_widgets() { connect(m_keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); - connect(ui_video, SIGNAL(done()), this, SLOT(video_done())); + connect(ui_video, SIGNAL(started()), ui_video, SLOT(show())); + connect(ui_video, SIGNAL(finished()), ui_video, SLOT(hide())); + connect(ui_video, SIGNAL(finished()), this, SLOT(video_finished())); connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index d25bbedc6..43eeac0d9 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -94,7 +94,7 @@ void DRVideoWidget::play() } else if (!m_readable) { - emit done(); + emit finished(); return; } @@ -149,11 +149,15 @@ void DRVideoWidget::check_video_availability(bool p_state) void DRVideoWidget::check_state(QMediaPlayer::State p_state) { - if (m_readable && m_running && p_state == QMediaPlayer::StoppedState) + if (p_state == QMediaPlayer::PlayingState) + { + emit started(); + } + else if (m_readable && m_running && p_state == QMediaPlayer::StoppedState) { qInfo() << "finished media file playback" << m_file_name; stop(); - emit done(); + emit finished(); } } @@ -162,7 +166,7 @@ void DRVideoWidget::handle_scan_error() if (m_running) { stop(); - emit done(); + emit finished(); } } From f37629c1305a717399716a949cfa20cbab78a1f9 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 23 Mar 2022 22:02:50 +0100 Subject: [PATCH 617/842] Tweaked media file scanning The media file scan will automatically be skipped if the filename provided is empty --- src/drvideoscreen.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index 43eeac0d9..7f90f0e16 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -47,10 +47,13 @@ void DRVideoWidget::set_file_name(QString p_file_name) return; m_file_name = p_file_name; m_scanned = false; - if (p_file_name.isEmpty()) - m_scanned = true; m_readable = false; m_running = false; + if (m_file_name.isEmpty()) + { + m_scanned = true; + return; + } stop(); m_player->setMuted(true); m_player->setMedia(QUrl(p_file_name)); From 44ca22b402dafbccc18cfea99ddd43a2d1421c14 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 23 Mar 2022 22:15:16 +0100 Subject: [PATCH 618/842] Fix merge conflicts --- src/text_file_functions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index dd4522a31..cb249237b 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -684,7 +684,7 @@ QVector AOApplication::get_emote_list(QString p_chr) l_emote.sound_delay = qMax(l_chrini.value(i_key).toInt(), 0); l_chrini.endGroup(); - l_chrini.beginGroup(drLookupKey(l_group_list, "videos")); + l_chrini.beginGroup(l_fetcher.lookup_group("videos")); l_emote.video_file = l_chrini.value(i_key).toString(); l_chrini.endGroup(); From 8c2c6792a59dcc26631cf1a709cad014f431a8fc Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 24 Mar 2022 06:14:51 +0100 Subject: [PATCH 619/842] Fix previous media file not being stopped before playing new one --- src/drvideoscreen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index 7f90f0e16..9eca6582b 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -49,12 +49,12 @@ void DRVideoWidget::set_file_name(QString p_file_name) m_scanned = false; m_readable = false; m_running = false; + stop(); if (m_file_name.isEmpty()) { m_scanned = true; return; } - stop(); m_player->setMuted(true); m_player->setMedia(QUrl(p_file_name)); if (m_player->error()) From 8ddf15dee82cf86e03bd0745401823495da6ebf9 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 24 Mar 2022 09:05:40 +0100 Subject: [PATCH 620/842] Fix audio blips clipping --- src/draudiostream.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index 2d32e8fe6..a8f84c9ee 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -155,7 +155,7 @@ bool DRAudioStream::ensure_init() } m_hstream = l_hstream; - BASS_ChannelSetSync(l_hstream, BASS_SYNC_END | BASS_SYNC_MIXTIME, 0, &end_sync, this); + BASS_ChannelSetSync(l_hstream, BASS_SYNC_END, 0, &end_sync, this); m_init_state = InitFinished; init_loop(); return true; @@ -176,7 +176,6 @@ void DRAudioStream::init_loop() if (m_repeatable) { - float l_sample_rate; BASS_ChannelGetAttribute(m_hstream, BASS_ATTRIB_FREQ, &l_sample_rate); From 365287dd83d2936798d651bf7fdb6f10c5817cc9 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 24 Mar 2022 14:18:14 +0100 Subject: [PATCH 621/842] DRVideoScreen will now always update its audio output device on creation --- src/drvideoscreen.cpp | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index 9eca6582b..fce783236 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -21,7 +21,7 @@ DRVideoWidget::DRVideoWidget(QWidget *parent) : QVideoWidget(parent), ao_app(dyn m_player = new QMediaPlayer(this); m_player->setVideoOutput(this); - connect(m_engine, SIGNAL(device_changed(DRAudioDevice)), this, SLOT(update_device(DRAudioDevice))); + connect(m_engine, SIGNAL(current_device_changed(DRAudioDevice)), this, SLOT(update_device(DRAudioDevice))); connect(m_config, SIGNAL(video_volume_changed(int)), this, SLOT(update_volume())); connect(m_engine, SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); connect(m_engine, SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); @@ -31,6 +31,10 @@ DRVideoWidget::DRVideoWidget(QWidget *parent) : QVideoWidget(parent), ao_app(dyn SLOT(check_media_status(QMediaPlayer::MediaStatus))); connect(m_player, SIGNAL(videoAvailableChanged(bool)), this, SLOT(check_video_availability(bool))); connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(check_state(QMediaPlayer::State))); + + const std::optional l_maybe_device = m_engine->get_current_device(); + if (l_maybe_device.has_value()) + update_device(l_maybe_device.value()); } DRVideoWidget::~DRVideoWidget() @@ -176,24 +180,24 @@ void DRVideoWidget::handle_scan_error() void DRVideoWidget::update_device(DRAudioDevice p_device) { const QString l_new_device_name = p_device.get_name(); - const QList l_device_list = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); - for (const QAudioDeviceInfo &i_device : l_device_list) + bool l_device_changed = false; + QMediaService *l_service = m_player->service(); + QAudioOutputSelectorControl *l_control = l_service->requestControl(); + const QStringList l_device_id_list = l_control->availableOutputs(); + for (const QString &i_device_id : l_device_id_list) { - const QString l_device_name = i_device.deviceName(); - if (l_device_name == l_new_device_name) + if (l_control->outputDescription(i_device_id) == l_new_device_name) { - QMediaService *l_service = m_player->service(); - QAudioOutputSelectorControl *l_control = l_service->requestControl(); - if (!l_control) - { - qCritical() << "error: failed to change device; QAudioOutputSelectorControl is null"; - return; - } - l_control->setActiveOutput(i_device.deviceName()); - l_service->releaseControl(l_control); + qDebug() << "media player changed audio device;" << l_new_device_name; + l_device_changed = true; + l_control->setActiveOutput(i_device_id); break; } } + + if (!l_device_changed) + qWarning() << "error: audio device not found;" << l_new_device_name; + l_service->releaseControl(l_control); } void DRVideoWidget::update_volume() From 494d55619a5416e3c9e1756301edd93172767dbd Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Thu, 24 Mar 2022 16:53:43 -0400 Subject: [PATCH 622/842] Create build-all-merge-master.yml --- .github/workflows/build-all-merge-master.yml | 425 +++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 .github/workflows/build-all-merge-master.yml diff --git a/.github/workflows/build-all-merge-master.yml b/.github/workflows/build-all-merge-master.yml new file mode 100644 index 000000000..46fa3cca7 --- /dev/null +++ b/.github/workflows/build-all-merge-master.yml @@ -0,0 +1,425 @@ +# Based from code from skyedeving and oldmud0 +# at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml +name: build-all-merge-master + +on: push + branches-ignore: + - 'master' + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + ############################################################################### + # WINDOWS # + ############################################################################### + + windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Merge master + shell: bash + working-directory: ${{github.workspace}} + run: | + git merge master + + - name: Setup workspace + shell: bash + working-directory: ${{github.workspace}} + run: | + echo "parentworkspace=D:\\a\\DRO-Client\\" >> $GITHUB_ENV + mkdir "3rd" + mkdir "3rd/include" + mkdir "3rd/x86" + + - name: Fetch Discord external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o discord_rpc_win.zip + unzip discord_rpc_win.zip + cp ./discord-rpc/win32-dynamic/include/discord_register.h ./DRO-Client/3rd/include + cp ./discord-rpc/win32-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include + cp ./discord-rpc/win32-dynamic/bin/discord-rpc.dll ./DRO-Client/3rd/x86 + cp ./discord-rpc/win32-dynamic/lib/discord-rpc.lib ./DRO-Client/3rd/x86 + + - name: Fetch Bass external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bass24.zip -o bass.zip + unzip bass.zip + cp ./c/bass.h ./DRO-Client/3rd/include + cp ./bass.dll ./DRO-Client/3rd/x86 + cp ./c/bass.lib ./DRO-Client/3rd/x86 + + - name: Fetch BassOpus external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bassopus24.zip -o bassopus.zip + unzip bassopus.zip + cp ./c/bassopus.h ./DRO-Client/3rd/include + cp ./bassopus.dll ./DRO-Client/3rd/x86 + cp ./c/bassopus.lib ./DRO-Client/3rd/x86 + + - name: Fetch QtApng external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-mingw73_32-5.14.1.zip -o apng.zip + unzip apng.zip + + - name: Update Python Pip + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + python -m pip install --upgrade pip + + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: '5.15.2' + arch: 'win32_mingw81' + + - name: Install AQt + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + pip install aqtinstall + + - name: Install MinGW + shell: bash + working-directory: ${{env.parentworkspace}} + run: aqt tool -O ./Qt windows tools_mingw 8.1.0-1-202004170606 qt.tools.win32_mingw810 + + - name: Setup MinGW + shell: bash + working-directory: ${{env.parentworkspace}} + # For whatever reason, qmake insists on using the MinGW64 installation that came with the host machine rather than the newly installed MinGW32 + # I could not find any way of making it use that MinGW32 folder, it always insisted on using MinGW64. + # Therefore, I just brought the MinGW32 folder to the MinGW64 folder. + # Tom Scott would be proud of this bodge + run: | + export PATH=/d/a/DRO-Client/Qt/Tools/mingw810_32/bin:$PATH + rm -r /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ + cp -r /d/a/DRO-Client/Qt/Tools/mingw810_32/ /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ + + - name: Setup branch and commit hash tags + shell: bash + working-directory: ${{github.workspace}} + run: | + git_hash=$(git rev-parse --short "$GITHUB_SHA") + git_branch=${GITHUB_REF##*/} + echo "$git_hash" >> ./res/git/git_hash.txt + echo "$git_branch" >> ./res/git/git_branch.txt + + - name: Run qmake + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + ./Qt/5.15.2/mingw81_32/bin/qmake.exe -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec win32-g++ "CONFIG+=qtquickcompiler" + + - name: Run Make + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + ./Qt/Tools/mingw810_32/bin/mingw32-make.exe -f ./Makefile qmake_all + ./Qt/Tools/mingw810_32/bin/mingw32-make.exe -j8 + + - name: Set up deploy folder + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + mkdir "Danganronpa Online" + cp ./release/dro-client.exe "./Danganronpa Online/dro-client.exe" + + - name: Deploy + working-directory: "${{env.parentworkspace}}/Danganronpa Online" + shell: bash + run: | + windeployqt.exe --quick --compiler-runtime . + cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . + cp ../bass.dll . + cp ../bassopus.dll . + cp ../mingw73_32/plugins/imageformats/qapng.dll ./imageformats/ + cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libgcc_s_dw2-1.dll . + cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libwinpthread-1.dll . + cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libstdc++-6.dll . + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: "Danganronpa Online (Windows) (+master)" + path: "${{env.parentworkspace}}/**/Danganronpa Online" + + - name: Upload Artifact (just .exe) + uses: actions/upload-artifact@v2 + with: + name: "dro-client.exe" + path: "${{env.parentworkspace}}/Danganronpa Online/dro-client-and-master.exe" + + ############################################################################### + # MACOS # + ############################################################################### + + macos: + runs-on: macos-10.15 + + steps: + - uses: actions/checkout@v2 + + - name: Merge master + shell: bash + working-directory: ${{github.workspace}} + run: | + git merge master + + - name: Setup workspace + shell: bash + working-directory: ${{github.workspace}} + run: | + echo "parentworkspace=/Users/runner/work/DRO-Client/" >> $GITHUB_ENV + mkdir "3rd" + mkdir "3rd/include" + mkdir "3rd/x86_64" + + - name: Fetch Discord external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip -o discord_rpc_osx.zip + unzip discord_rpc_osx.zip + cp ./discord-rpc/osx-dynamic/include/discord_register.h ./DRO-Client/3rd/include + cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include + cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./DRO-Client/3rd/x86_64 + + - name: Fetch Bass external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bass24-osx.zip -o bass.zip + unzip bass.zip + cp ./bass.h ./DRO-Client/3rd/include + cp ./libbass.dylib ./DRO-Client/3rd/x86_64 + + - name: Fetch BassOpus external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bassopus24-osx.zip -o bassopus.zip + unzip bassopus.zip + cp ./bassopus.h ./DRO-Client/3rd/include + cp ./libbassopus.dylib ./DRO-Client/3rd/x86_64 + + - name: Fetch QtApng external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-clang_64-5.14.1.tar.xz -o qtapng.tar.xz + tar -xvf qtapng.tar.xz + + - name: Update Python Pip + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + python3 -m pip install --upgrade pip + + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: '5.15.2' + + - name: Setup branch and commit hash tags + shell: bash + working-directory: ${{github.workspace}} + run: | + git_hash=$(git rev-parse --short "$GITHUB_SHA") + git_branch=${GITHUB_REF##*/} + echo "$git_hash" >> ./res/git/git_hash.txt + echo "$git_branch" >> ./res/git/git_branch.txt + + - name: Run qmake + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + ./Qt/5.15.2/clang_64/bin/qmake -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec macx-clang "CONFIG+=x86_64" "CONFIG+=qtquickcompiler" + + - name: Run make + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + /usr/bin/make -f ./Makefile qmake_all + /usr/bin/make -j12 + + - name: Set up deploy folder + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + mkdir "Danganronpa Online" + cp -R ./dro-client.app "./Danganronpa Online" + mv "./Danganronpa Online/dro-client.app" "./Danganronpa Online/Danganronpa Online.app" + + - name: Deploy + working-directory: "${{env.parentworkspace}}/Danganronpa Online" + shell: bash + run: | + ../Qt/5.15.2/clang_64/bin/macdeployqt "Danganronpa Online.app" + cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/Frameworks/" + cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/MacOS/" + cp ../libbass.dylib "./Danganronpa Online.app/Contents/Frameworks/" + cp ../libbass.dylib "./Danganronpa Online.app/Contents/MacOS/" + cp ../libbassopus.dylib "./Danganronpa Online.app/Contents/Frameworks/" + cp ../libbassopus.dylib "./Danganronpa Online.app/Contents/MacOS/" + cp -R ../clang_64/plugins/imageformats "./Danganronpa Online.app/Contents/MacOS/" + + - name: Make DMG + working-directory: ${{env.parentworkspace}} + shell: bash + run: | + hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online" -ov -format UDZO "Danganronpa Online.dmg" + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: "Danganronpa Online (MacOS) (+master)" + path: "${{env.parentworkspace}}/Danganronpa Online.dmg" + + ############################################################################### + # UBUNTU # + ############################################################################### + + ubuntu: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - name: Update worker + shell: bash + working-directory: ${{github.workspace}} + run: | + sudo apt-get update + sudo apt-get upgrade + + - name: Merge master + shell: bash + working-directory: ${{github.workspace}} + run: | + git merge master + + - name: Setup workspace + shell: bash + working-directory: ${{github.workspace}} + run: | + echo "parentworkspace=/home/runner/work/DRO-Client/" >> $GITHUB_ENV + mkdir "3rd" + mkdir "3rd/include" + mkdir "3rd/x86_64" + + - name: Fetch Discord external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip + unzip discord_rpc_linux.zip + cp ./discord-rpc/linux-dynamic/include/discord_register.h ./DRO-Client/3rd/include/discord_register.h + cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include/discord_rpc.h + cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./DRO-Client/3rd/x86_64/libdiscord-rpc.so + + - name: Fetch Bass external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip + unzip bass.zip + cp ./bass.h ./DRO-Client/3rd/include/bass.h + cp ./x64/libbass.so ./DRO-Client/3rd/x86_64/libbass.so + + - name: Fetch BassOpus external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip + unzip bassopus.zip + cp ./bassopus.h ./DRO-Client/3rd/include/bassopus.h + cp ./x64/libbassopus.so ./DRO-Client/3rd/x86_64/libbassopus.so + + - name: Install Qt + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + sudo apt-get install build-essential + sudo apt-get install qt5-default qttools5-dev + sudo apt-get install libqt5designer5 + sudo apt-get install git + + - name: Build QtApng external library + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + git clone https://github.com/Skycoder42/QtApng + cd QtApng + qmake + make -j2 + cp plugins/imageformats/libqapng.so .. + cd .. + rm -r -f QtApng + + - name: Setup branch and commit hash tags + shell: bash + working-directory: ${{github.workspace}} + run: | + git_hash=$(git rev-parse --short "$GITHUB_SHA") + git_branch=${GITHUB_REF##*/} + echo "$git_hash" >> ./res/git/git_hash.txt + echo "$git_branch" >> ./res/git/git_branch.txt + + - name: Run qmake + shell: bash + working-directory: ${{github.workspace}} + run: | + qmake + + - name: Run Make + shell: bash + working-directory: ${{github.workspace}} + run: | + make + + - name: Set up deploy folder + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + mkdir "Danganronpa Online" + cp -R ./DRO-Client/dro-client "./Danganronpa Online/dro-client" + + - name: Deploy + working-directory: "${{env.parentworkspace}}/Danganronpa Online" + shell: bash + run: | + mkdir depends + cp ../discord-rpc/linux-dynamic/lib/libdiscord-rpc.so "./depends" + cp ../x64/libbass.so "./depends" + cp ../x64/libbassopus.so "./depends" + mkdir imageformats + cp -R ../libqapng.so "./imageformats" + echo "LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client" > "dro-client.sh" + echo "Installation instructions + 1. On your terminal, run + sudo apt install qt5-default + 2. Change directory to this folder, and run + chmod +x dro-client.sh + chmod +x dro-client + 3. To launch, run + ./dro-client.sh" > "Readme (Ubuntu).txt" + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: "Danganronpa Online (Ubuntu) (+master)" + path: "${{env.parentworkspace}}/**/Danganronpa Online" From fcd77298b7f4f500b6fcb9f6dfc5ba01c0d909b7 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Thu, 24 Mar 2022 16:54:45 -0400 Subject: [PATCH 623/842] Update build-all-merge-master.yml --- .github/workflows/build-all-merge-master.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-all-merge-master.yml b/.github/workflows/build-all-merge-master.yml index 46fa3cca7..ffc8005d0 100644 --- a/.github/workflows/build-all-merge-master.yml +++ b/.github/workflows/build-all-merge-master.yml @@ -2,9 +2,10 @@ # at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml name: build-all-merge-master -on: push - branches-ignore: - - 'master' +on: + push: + branches-ignore: + - 'master' env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) From 18e5c1d7dbe6ffd210f30ead08e309af3671d452 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Thu, 24 Mar 2022 17:04:06 -0400 Subject: [PATCH 624/842] Manually fetch master branch --- .github/workflows/build-all-merge-master.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/build-all-merge-master.yml b/.github/workflows/build-all-merge-master.yml index ffc8005d0..d76c641bf 100644 --- a/.github/workflows/build-all-merge-master.yml +++ b/.github/workflows/build-all-merge-master.yml @@ -21,6 +21,10 @@ jobs: steps: - uses: actions/checkout@v2 + - run: | + git fetch --no-tags --depth=1 origin master + git checkout -b master + git checkout ${{ github.event.pull_request.head.sha }} - name: Merge master shell: bash @@ -173,6 +177,10 @@ jobs: steps: - uses: actions/checkout@v2 + - run: | + git fetch --no-tags --depth=1 origin master + git checkout -b master + git checkout ${{ github.event.pull_request.head.sha }} - name: Merge master shell: bash @@ -299,6 +307,10 @@ jobs: steps: - uses: actions/checkout@v2 + - run: | + git fetch --no-tags --depth=1 origin master + git checkout -b master + git checkout ${{ github.event.pull_request.head.sha }} - name: Update worker shell: bash From 181bea17808cf096309800e5926c63a61a97fab9 Mon Sep 17 00:00:00 2001 From: Chrezm <42015761+Chrezm@users.noreply.github.com> Date: Thu, 24 Mar 2022 17:24:56 -0400 Subject: [PATCH 625/842] Fix exe name for Windows --- .github/workflows/build-all-merge-master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-all-merge-master.yml b/.github/workflows/build-all-merge-master.yml index d76c641bf..36c7085fc 100644 --- a/.github/workflows/build-all-merge-master.yml +++ b/.github/workflows/build-all-merge-master.yml @@ -141,7 +141,7 @@ jobs: working-directory: ${{env.parentworkspace}} run: | mkdir "Danganronpa Online" - cp ./release/dro-client.exe "./Danganronpa Online/dro-client.exe" + cp ./release/dro-client.exe "./Danganronpa Online/dro-client-and-master.exe" - name: Deploy working-directory: "${{env.parentworkspace}}/Danganronpa Online" From 5b96bc56d15313989e244f16ba264cb25abea3f8 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 25 Mar 2022 07:36:54 +0100 Subject: [PATCH 626/842] Fixed media audio disappearing after first play --- include/drvideoscreen.h | 2 ++ src/drvideoscreen.cpp | 41 ++++++++++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/include/drvideoscreen.h b/include/drvideoscreen.h index 46613a554..4a3194d2b 100644 --- a/include/drvideoscreen.h +++ b/include/drvideoscreen.h @@ -35,6 +35,7 @@ class DRVideoWidget : public QVideoWidget AOConfig *m_config; AOApplication *ao_app; DRAudioEngine *m_engine; + DRAudioDevice m_device; DRAudioStreamFamily::ptr m_family; QString m_file_name; @@ -47,6 +48,7 @@ class DRVideoWidget : public QVideoWidget private slots: void update_device(DRAudioDevice); + void update_audio_output(); void update_volume(); void check_media_status(QMediaPlayer::MediaStatus); void check_video_availability(bool); diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index fce783236..83868fd98 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -32,9 +32,8 @@ DRVideoWidget::DRVideoWidget(QWidget *parent) : QVideoWidget(parent), ao_app(dyn connect(m_player, SIGNAL(videoAvailableChanged(bool)), this, SLOT(check_video_availability(bool))); connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(check_state(QMediaPlayer::State))); - const std::optional l_maybe_device = m_engine->get_current_device(); - if (l_maybe_device.has_value()) - update_device(l_maybe_device.value()); + const std::optional l_device = m_engine->get_current_device(); + update_device(l_device.value_or(m_device)); } DRVideoWidget::~DRVideoWidget() @@ -158,6 +157,7 @@ void DRVideoWidget::check_state(QMediaPlayer::State p_state) { if (p_state == QMediaPlayer::PlayingState) { + update_audio_output(); emit started(); } else if (m_readable && m_running && p_state == QMediaPlayer::StoppedState) @@ -179,24 +179,35 @@ void DRVideoWidget::handle_scan_error() void DRVideoWidget::update_device(DRAudioDevice p_device) { - const QString l_new_device_name = p_device.get_name(); - bool l_device_changed = false; + if (m_device == p_device) + return; + m_device = p_device; + update_audio_output(); +} + +void DRVideoWidget::update_audio_output() +{ + const QString l_new_device_name = m_device.get_name(); QMediaService *l_service = m_player->service(); QAudioOutputSelectorControl *l_control = l_service->requestControl(); - const QStringList l_device_id_list = l_control->availableOutputs(); - for (const QString &i_device_id : l_device_id_list) + + if (l_control->outputDescription(l_control->activeOutput()) != l_new_device_name) { - if (l_control->outputDescription(i_device_id) == l_new_device_name) + bool l_device_changed = false; + const QStringList l_device_id_list = l_control->availableOutputs(); + for (const QString &i_device_id : l_device_id_list) { - qDebug() << "media player changed audio device;" << l_new_device_name; - l_device_changed = true; - l_control->setActiveOutput(i_device_id); - break; + if (l_control->outputDescription(i_device_id) == l_new_device_name) + { + qDebug() << "media player changed audio device;" << l_new_device_name; + l_device_changed = true; + l_control->setActiveOutput(i_device_id); + break; + } } + if (!l_device_changed) + qWarning() << "audio device not found;" << l_new_device_name; } - - if (!l_device_changed) - qWarning() << "error: audio device not found;" << l_new_device_name; l_service->releaseControl(l_control); } From b677ecb504363283cd7135268292149ecc6b0562 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 25 Mar 2022 07:17:11 -0400 Subject: [PATCH 627/842] Allow "contains" filtering of the iniswap dropdown contents --- src/courtroom_widgets.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 88ccbed28..f6ecb000d 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -28,6 +28,7 @@ #include "theme.h" #include +#include #include #include #include @@ -110,6 +111,10 @@ void Courtroom::create_widgets() ui_vp_chat_arrow->set_play_once(false); ui_iniswap_dropdown = new QComboBox(this); + ui_iniswap_dropdown->setEditable(true); + ui_iniswap_dropdown->setInsertPolicy(QComboBox::NoInsert); + ui_iniswap_dropdown->completer()->setCompletionMode(QCompleter::PopupCompletion); + ui_iniswap_dropdown->completer()->setFilterMode(Qt::MatchContains); ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); @@ -732,6 +737,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_iniswap_dropdown, "iniswap_dropdown", COURTROOM_DESIGN_INI, ao_app); set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); + set_stylesheet(ui_iniswap_dropdown->completer()->popup(), "[INISWAP DROPDOWN POPUP]", COURTROOM_STYLESHEETS_CSS, ao_app); set_size_and_pos(ui_pos_dropdown, "pos_dropdown", COURTROOM_DESIGN_INI, ao_app); set_stylesheet(ui_pos_dropdown, "[POS DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); From 234fd0c428878002a04fd5897a90acced7d547f4 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 24 Jan 2022 10:47:08 -0500 Subject: [PATCH 628/842] Remove text eliding in iniswap dropdown+Add horizontall scrollbar --- src/courtroom_widgets.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index f6ecb000d..c48d37083 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -110,11 +110,17 @@ void Courtroom::create_widgets() ui_vp_chat_arrow = new DRStickerMovie(this); ui_vp_chat_arrow->set_play_once(false); + QListView* view = new QListView(ui_iniswap_dropdown); + view->setTextElideMode(Qt::TextElideMode::ElideNone); + view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); ui_iniswap_dropdown = new QComboBox(this); + ui_iniswap_dropdown->setView(view); ui_iniswap_dropdown->setEditable(true); ui_iniswap_dropdown->setInsertPolicy(QComboBox::NoInsert); ui_iniswap_dropdown->completer()->setCompletionMode(QCompleter::PopupCompletion); ui_iniswap_dropdown->completer()->setFilterMode(Qt::MatchContains); + ui_iniswap_dropdown->completer()->popup()->setTextElideMode(Qt::TextElideMode::ElideNone); + ui_iniswap_dropdown->completer()->popup()->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); From 0c0ff9cb152e6374bcfcd9e35bab880b9e0eb6f6 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 25 Mar 2022 18:29:08 +0100 Subject: [PATCH 629/842] Reworked logger to be faster and safe --- dronline-client.pro | 4 +- include/log.h | 10 --- include/logger.h | 10 +++ src/log.cpp | 111 --------------------------- src/logger.cpp | 173 ++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 11 ++- src/master_socket.cpp | 1 + 7 files changed, 194 insertions(+), 126 deletions(-) delete mode 100644 include/log.h create mode 100644 include/logger.h delete mode 100644 src/log.cpp create mode 100644 src/logger.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 4241fb55d..e79e8447c 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -67,7 +67,7 @@ HEADERS += \ include/file_functions.h \ include/hardware_functions.h \ include/lobby.h \ - include/log.h \ + include/logger.h \ include/misc_functions.h \ include/theme.h \ include/utils.h \ @@ -133,7 +133,7 @@ SOURCES += \ src/file_functions.cpp \ src/hardware_functions.cpp \ src/lobby.cpp \ - src/log.cpp \ + src/logger.cpp \ src/main.cpp \ src/master_socket.cpp \ src/misc_functions.cpp \ diff --git a/include/log.h b/include/log.h deleted file mode 100644 index 86de1c006..000000000 --- a/include/log.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef LOG_H -#define LOG_H - -#include - -void DROLogger(QtMsgType type, const QMessageLogContext &context, const QString &msg); - -void set_verbose_logging(bool enabled); - -#endif // LOG_H diff --git a/include/logger.h b/include/logger.h new file mode 100644 index 000000000..915ccf2dc --- /dev/null +++ b/include/logger.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace logger +{ +void log(QtMsgType type, const QMessageLogContext &context, const QString &msg); +void shutdown(); +void set_verbose_logging(bool enabled); +} // namespace logger diff --git a/src/log.cpp b/src/log.cpp deleted file mode 100644 index c2b8bad2c..000000000 --- a/src/log.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "log.h" - -#include -#include -#include -#include -#include -#include -#include - -// Get the default Qt message handler. -static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(0); -static const int C_DEBUG_FILE_MAX_SIZE = 1e5; // 10 MB -static const QString C_DEBUG_FILE = "base/logs/debug.log"; -static const QString C_DEBUG_FILE_LOCK = "base/logs/debug.log.lock"; -static const QString C_DEBUG_B_FILE = "base/logs/debug_b.log"; -static bool s_verbose_logging = false; - -QString generate_message(QtMsgType p_type, const QMessageLogContext &p_context, const QString &p_message) -{ - QByteArray l_localMsg = p_message.toLocal8Bit(); - const QString l_fileName(p_context.file); - const QString l_function(p_context.function); - - QString l_output; - switch (p_type) - { - case QtDebugMsg: - l_output = QString("D"); - break; - case QtInfoMsg: - l_output = QString("I"); - break; - case QtWarningMsg: - l_output = QString("W"); - break; - case QtCriticalMsg: - l_output = QString("C"); - break; - case QtFatalMsg: - l_output = QString("F"); - break; - } - - const QString raw_msg = l_localMsg.constData(); - QString final_msg; - // Censor HDID - if (raw_msg.startsWith("S/S: HI#")) - final_msg = "S/S: HI#HDID#%"; - else if (raw_msg.startsWith("M/S: HI#")) - final_msg = "M/S: HI#HDID#%"; - else - final_msg = raw_msg; - - const QString now = QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz"); - const QString pid = QString::number(QCoreApplication::applicationPid()); - - l_output = QString("[%1] [%2] %3: ").arg(now, pid, l_output); - l_output.append(final_msg); - - if (s_verbose_logging) - l_output.append(QString(" (%1:%2, %3)").arg(l_fileName, QString::number(p_context.line), l_function)); - - return l_output; -} - -void save_log_line(QString p_log_line) -{ - QLockFile l_log_lock(C_DEBUG_FILE_LOCK); - l_log_lock.lock(); - - QFile l_log_file(C_DEBUG_FILE); - if (!l_log_file.open(QFile::WriteOnly | QFile::Append)) - { - qInstallMessageHandler(0); - qCritical().noquote() << QString("Failed to open debug.log! error: %1").arg(l_log_file.errorString()); - l_log_lock.unlock(); - return; - } - - QTextStream in(&l_log_file); -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - in << p_log_line << Qt::endl; -#else - in << p_log_line << "\n"; // Hopefully Windows users don't compile with = C_DEBUG_FILE_MAX_SIZE) - { - if (l_log_file.exists(C_DEBUG_B_FILE)) - l_log_file.remove(C_DEBUG_B_FILE); - l_log_file.rename(C_DEBUG_B_FILE); - } - - l_log_lock.unlock(); -} - -void DROLogger(QtMsgType type, const QMessageLogContext &context, const QString &msg) -{ - save_log_line(generate_message(type, context, msg)); - - // Call the default handler. - (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg); -} - -void set_verbose_logging(bool enabled) -{ - s_verbose_logging = enabled; -} diff --git a/src/logger.cpp b/src/logger.cpp new file mode 100644 index 000000000..7bed223d2 --- /dev/null +++ b/src/logger.cpp @@ -0,0 +1,173 @@ +#include "logger.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// Get the default Qt message handler. +static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(0); +static const int C_MAX_FILE_SIZE = 1e7; // 10 MB +static const QString C_FILE_NAME = "base/logs/debug.log"; +static const QString C_FILE_NAME_BACKUP = "base/logs/debug_b.log"; +static bool s_verbose_logging = false; + +static bool s_initialized = false; +static QMutex s_msg_list_lock; +static QStringList s_msg_list; +static std::thread s_logger; +static std::atomic_bool s_continue = false; + +void check_file_size_and_rename() +{ + QFile l_log(C_FILE_NAME); + if (l_log.exists() && l_log.size() >= C_MAX_FILE_SIZE) + { + if (l_log.exists(C_FILE_NAME_BACKUP)) + { + if (!l_log.remove(C_FILE_NAME_BACKUP)) + { + s_continue = false; + qInstallMessageHandler(0); + qWarning() << "error: failed to remove log file" << l_log.errorString(); + return; + } + } + + if (!l_log.rename(C_FILE_NAME_BACKUP)) + { + s_continue = false; + qInstallMessageHandler(0); + qWarning() << "error: failed to rename log file" << l_log.errorString(); + } + } +} + +void task() +{ + s_continue = true; + QSystemSemaphore l_system_lock("dro-logger-lock", 1); + QStringList l_msg_list; + while (s_continue) + { + if (s_msg_list_lock.tryLock()) + { + QStringList l_msg_list(std::move(s_msg_list)); + s_msg_list_lock.unlock(); + + l_system_lock.acquire(); + check_file_size_and_rename(); + QFile l_log(C_FILE_NAME); + if (l_log.open(QFile::WriteOnly | QFile::Append)) + { + QTextStream in(&l_log); + for (const QString &i_msg : l_msg_list) + { + in << i_msg +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + << Qt::endl; +#elif Q_WINDOWS + << "\n"; +#endif + } + } + l_log.close(); + + l_system_lock.release(); + } + + QThread::msleep(100); + } +} + +QString generate_message(QtMsgType p_type, const QMessageLogContext &p_context, const QString &p_message) +{ + QString l_output; + switch (p_type) + { + case QtDebugMsg: + l_output = QString("D"); + break; + case QtInfoMsg: + l_output = QString("I"); + break; + case QtWarningMsg: + l_output = QString("W"); + break; + case QtCriticalMsg: + l_output = QString("C"); + break; + case QtFatalMsg: + l_output = QString("F"); + break; + } + + const QString l_timestamp = QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz"); + const QString l_pid = QString::number(QCoreApplication::applicationPid()); + + QString l_final_message = p_message; + // Censor HDID + if (p_message.startsWith("S/S: HI#")) + l_final_message = "S/S: HI#HDID#%"; + else if (p_message.startsWith("M/S: HI#")) + l_final_message = "M/S: HI#HDID#%"; + else + l_final_message = p_message; + + l_output = QString("[%1][%2][%3] %4").arg(l_timestamp, l_pid, l_output, l_final_message); + if (s_verbose_logging) + l_output.append(QString(" (%1:%2, %3)").arg(p_context.file).arg(p_context.line).arg(p_context.function)); + + return l_output; +} + +void save_log_line(QString p_log_line) +{ + s_msg_list_lock.lock(); + s_msg_list.append(p_log_line); + s_msg_list_lock.unlock(); +} + +void logger::log(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + if (!s_initialized) + { + s_initialized = true; + s_logger = std::thread(task); + } + + save_log_line(generate_message(type, context, msg)); + + // call the default handler. + (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg); +} + +void logger::shutdown() +{ + s_continue = false; + try + { + if (s_logger.joinable()) + s_logger.join(); + } + catch (const std::exception &e) + { + qWarning() << "error:" << e.what(); + } + catch (...) + { + qWarning() << "error: unknown exception"; + } +} + +void logger::set_verbose_logging(bool enabled) +{ + s_verbose_logging = enabled; +} diff --git a/src/main.cpp b/src/main.cpp index 79d20bf98..abf36084a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,13 @@ #include "aoapplication.h" #include "lobby.h" -#include "log.h" +#include "logger.h" #include int main(int argc, char *argv[]) { - qInstallMessageHandler(DROLogger); + qInstallMessageHandler(logger::log); + qInfo() << "Starting Danganronpa Online..."; // High-DPI support is for Qt version >=5.6. #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) @@ -49,5 +50,9 @@ int main(int argc, char *argv[]) app.construct_lobby(); app.get_lobby()->show(); - return app.exec(); + const int code = app.exec(); + + logger::shutdown(); + + return code; } diff --git a/src/master_socket.cpp b/src/master_socket.cpp index 29f8abbc1..3cf2b42a2 100644 --- a/src/master_socket.cpp +++ b/src/master_socket.cpp @@ -25,6 +25,7 @@ void AOApplication::send_master_packet(DRPacket p_packet) qDebug() << "Failed to send packet: not connected to master"; return; } + qDebug().noquote() << "M/S:" << p_packet.to_string(); m_master_socket->send_packet(p_packet); } From 69e9f26c251146771f62e91a6645be43e73e3f1b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 25 Mar 2022 19:29:25 +0100 Subject: [PATCH 630/842] Added config option to disable searchable iniswap * Added config option to disable searchable iniswap Making it editable is fine but it makes it a lot more annoying to click on it as a result. --- include/aoconfig.h | 3 + include/aoconfigpanel.h | 1 + include/courtroom.h | 1 + res/ui/config_panel.ui | 137 ++++++++++++++++++++++++------------ src/aoconfig.cpp | 16 +++++ src/aoconfigpanel.cpp | 4 ++ src/courtroom_character.cpp | 19 +++++ src/courtroom_widgets.cpp | 17 ++--- 8 files changed, 142 insertions(+), 56 deletions(-) diff --git a/include/aoconfig.h b/include/aoconfig.h index feefedcfd..75f6cfb3e 100644 --- a/include/aoconfig.h +++ b/include/aoconfig.h @@ -38,6 +38,7 @@ class AOConfig : public QObject QString timeofday() const; QString manual_timeofday() const; bool is_manual_timeofday_selection_enabled() const; + bool searchable_iniswap_enabled() const; bool always_pre_enabled() const; int chat_tick_interval() const; bool emote_preview_enabled() const; @@ -91,6 +92,7 @@ public slots: void set_timeofday(QString p_string); void set_manual_timeofday(QString p_string); void set_manual_timeofday_selection_enabled(bool p_enabled); + void set_searchable_iniswap(bool); void set_always_pre(bool p_enabled); void set_chat_tick_interval(int p_number); void set_emote_preview(bool p_enabled); @@ -137,6 +139,7 @@ public slots: void showname_changed(QString); void showname_placeholder_changed(QString); void character_ini_changed(QString base_character); + void searchable_iniswap_changed(bool); void always_pre_changed(bool); void chat_tick_interval_changed(int); void emote_preview_changed(bool); diff --git a/include/aoconfigpanel.h b/include/aoconfigpanel.h index f4d686bb3..9a13b482c 100644 --- a/include/aoconfigpanel.h +++ b/include/aoconfigpanel.h @@ -91,6 +91,7 @@ private slots: // character QLineEdit *ui_showname = nullptr; QPushButton *ui_reload_character = nullptr; + QCheckBox *ui_searchable_iniswap = nullptr; // emotes QCheckBox *ui_emote_preview = nullptr; diff --git a/include/courtroom.h b/include/courtroom.h index 806687927..a20570e11 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -572,6 +572,7 @@ private slots: void on_emote_dropdown_changed(int p_index); void on_iniswap_dropdown_changed(int p_index); + void set_iniswap_dropdown_searchable(bool); void on_pos_dropdown_changed(int p_index); void on_cycle_clicked(); diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 422520924..8aaa93856 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -32,7 +32,7 @@ - 3 + 1 @@ -155,15 +155,15 @@ Character - - + + Name: - + The name of your character @@ -171,60 +171,82 @@ - + - - - Qt::Horizontal + + + Character options - - - 40 - 20 - - - + + + + + Searchable iniswap + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + - - - <html><head/><body><p>Reload the currently selected character. If a character is not selected, nothing happens.</p></body></html> + + + + 0 + 121 + - - Reload + + Emote options - - - - - - - - - 0 - 121 - - - - Emote options - - - + + + 12 + 87 + 78 + 22 + + Sticky SFX - - + + + 12 + 31 + 141 + 22 + + Emote preview tooltip - - + + + 12 + 59 + 113 + 22 + + 0 @@ -238,9 +260,36 @@ false - - - + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p>Reload the currently selected character. If a character is not selected, nothing happens.</p></body></html> + + + Reload + + + + diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index f639f0c2e..1180389ab 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -62,6 +62,7 @@ private slots: QString showname; QString showname_placeholder; QMap ini_map; + bool searchable_iniswap; bool always_pre; int chat_tick_interval; bool emote_preview; @@ -134,6 +135,7 @@ void AOConfigPrivate::read_file() manual_gamemode_selection = cfg.value("manual_gamemode", false).toBool(); manual_timeofday = cfg.value("timeofday").toString(); manual_timeofday_selection = cfg.value("manual_timeofday", false).toBool(); + searchable_iniswap = cfg.value("searchable_iniswap", true).toBool(); always_pre = cfg.value("always_pre", true).toBool(); chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); emote_preview = cfg.value("emote_preview", true).toBool(); @@ -212,6 +214,7 @@ void AOConfigPrivate::save_file() cfg.setValue("manual_gamemode", manual_gamemode_selection); cfg.setValue("timeofday", manual_timeofday); cfg.setValue("manual_timeofday", manual_timeofday_selection); + cfg.setValue("searchable_iniswap", searchable_iniswap); cfg.setValue("always_pre", always_pre); cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("emote_preview", emote_preview); @@ -443,6 +446,11 @@ bool AOConfig::is_manual_timeofday_selection_enabled() const return d->manual_timeofday_selection; } +bool AOConfig::searchable_iniswap_enabled() const +{ + return d->searchable_iniswap; +} + bool AOConfig::always_pre_enabled() const { return d->always_pre; @@ -731,6 +739,14 @@ void AOConfig::set_manual_timeofday_selection_enabled(bool p_enabled) d->invoke_signal("manual_timeofday_selection_changed", Q_ARG(bool, p_enabled)); } +void AOConfig::set_searchable_iniswap(bool p_on) +{ + if (d->searchable_iniswap == p_on) + return; + d->searchable_iniswap = p_on; + d->invoke_signal("searchable_iniswap_changed", Q_ARG(bool, p_on)); +} + void AOConfig::set_always_pre(bool p_enabled) { if (d->always_pre == p_enabled) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 045271619..aee0144da 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -60,6 +60,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_manual_timeofday_selection = AO_GUI_WIDGET(QCheckBox, "manual_timeofday_selection"); ui_showname = AO_GUI_WIDGET(QLineEdit, "showname"); ui_reload_character = AO_GUI_WIDGET(QPushButton, "reload_character"); + ui_searchable_iniswap = AO_GUI_WIDGET(QCheckBox, "searchable_iniswap"); ui_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); ui_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); ui_emote_preview = AO_GUI_WIDGET(QCheckBox, "emote_preview"); @@ -133,6 +134,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(showname_changed(QString)), ui_showname, SLOT(setText(QString))); connect(m_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); + connect(m_config, SIGNAL(searchable_iniswap_changed(bool)), ui_searchable_iniswap, SLOT(setChecked(bool))); connect(m_config, SIGNAL(always_pre_changed(bool)), ui_always_pre, SLOT(setChecked(bool))); connect(m_config, SIGNAL(chat_tick_interval_changed(int)), ui_chat_tick_interval, SLOT(setValue(int))); connect(m_config, SIGNAL(emote_preview_changed(bool)), ui_emote_preview, SLOT(setChecked(bool))); @@ -203,6 +205,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(ui_manual_timeofday_selection, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_timeofday_selection_enabled(bool))); connect(ui_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); + connect(ui_searchable_iniswap, SIGNAL(toggled(bool)), m_config, SLOT(set_searchable_iniswap(bool))); connect(ui_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); connect(ui_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); connect(ui_emote_preview, SIGNAL(toggled(bool)), m_config, SLOT(set_emote_preview(bool))); @@ -255,6 +258,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_manual_timeofday_selection->setChecked(m_config->is_manual_timeofday_selection_enabled()); ui_showname->setText(m_config->showname()); on_showname_placeholder_changed(m_config->showname_placeholder()); + ui_searchable_iniswap->setChecked(m_config->searchable_iniswap_enabled()); ui_always_pre->setChecked(m_config->always_pre_enabled()); ui_chat_tick_interval->setValue(m_config->chat_tick_interval()); ui_emote_preview->setChecked(m_config->emote_preview_enabled()); diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index c0748e0da..77ffd2467 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -4,8 +4,11 @@ #include "aoconfig.h" #include "commondefs.h" #include "file_functions.h" +#include "theme.h" +#include #include +#include #include #include #include @@ -123,3 +126,19 @@ void Courtroom::on_iniswap_dropdown_changed(int p_index) ao_config->set_character_ini(get_character(), p_index == 0 ? get_character() : ui_iniswap_dropdown->itemText(p_index)); } + +void Courtroom::set_iniswap_dropdown_searchable(bool p_enabled) +{ + ui_iniswap_dropdown->setEditable(p_enabled); + set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); + if (p_enabled) + { + QCompleter *l_completer = ui_iniswap_dropdown->completer(); + l_completer->setCompletionMode(QCompleter::PopupCompletion); + l_completer->setFilterMode(Qt::MatchContains); + QAbstractItemView *l_popup = l_completer->popup(); + l_popup->setTextElideMode(Qt::TextElideMode::ElideNone); + l_popup->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + set_stylesheet(l_popup, "[INISWAP DROPDOWN POPUP]", COURTROOM_STYLESHEETS_CSS, ao_app); + } +} diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index c48d37083..9c1aabbbe 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -28,7 +28,6 @@ #include "theme.h" #include -#include #include #include #include @@ -110,17 +109,11 @@ void Courtroom::create_widgets() ui_vp_chat_arrow = new DRStickerMovie(this); ui_vp_chat_arrow->set_play_once(false); - QListView* view = new QListView(ui_iniswap_dropdown); - view->setTextElideMode(Qt::TextElideMode::ElideNone); - view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); ui_iniswap_dropdown = new QComboBox(this); - ui_iniswap_dropdown->setView(view); - ui_iniswap_dropdown->setEditable(true); ui_iniswap_dropdown->setInsertPolicy(QComboBox::NoInsert); - ui_iniswap_dropdown->completer()->setCompletionMode(QCompleter::PopupCompletion); - ui_iniswap_dropdown->completer()->setFilterMode(Qt::MatchContains); - ui_iniswap_dropdown->completer()->popup()->setTextElideMode(Qt::TextElideMode::ElideNone); - ui_iniswap_dropdown->completer()->popup()->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + QAbstractItemView *l_view = ui_iniswap_dropdown->view(); + l_view->setTextElideMode(Qt::TextElideMode::ElideNone); + l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); @@ -272,6 +265,7 @@ void Courtroom::connect_widgets() connect(m_flash_timer, SIGNAL(timeout()), this, SLOT(realization_done())); + connect(ao_config, SIGNAL(searchable_iniswap_changed(bool)), this, SLOT(set_iniswap_dropdown_searchable(bool))); connect(ao_config, SIGNAL(emote_preview_changed(bool)), this, SLOT(on_emote_preview_toggled(bool))); connect(ui_emote_left, SIGNAL(clicked()), this, SLOT(on_emote_left_clicked())); connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); @@ -742,8 +736,7 @@ void Courtroom::set_widgets() set_stylesheet(ui_emote_dropdown, "[EMOTE DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); set_size_and_pos(ui_iniswap_dropdown, "iniswap_dropdown", COURTROOM_DESIGN_INI, ao_app); - set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); - set_stylesheet(ui_iniswap_dropdown->completer()->popup(), "[INISWAP DROPDOWN POPUP]", COURTROOM_STYLESHEETS_CSS, ao_app); + set_iniswap_dropdown_searchable(ao_config->searchable_iniswap_enabled()); set_size_and_pos(ui_pos_dropdown, "pos_dropdown", COURTROOM_DESIGN_INI, ao_app); set_stylesheet(ui_pos_dropdown, "[POS DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); From 844bf667404cebc1ae3e0bcf8353cab2c43b46ae Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 25 Mar 2022 19:40:46 +0100 Subject: [PATCH 631/842] Fix macro condition --- src/logger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logger.cpp b/src/logger.cpp index 7bed223d2..f602c7623 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -73,7 +73,7 @@ void task() in << i_msg #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) << Qt::endl; -#elif Q_WINDOWS +#else << "\n"; #endif } From c1b3fca523afe64737b8b7648eeb26e77e94c748 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 25 Mar 2022 19:58:50 +0100 Subject: [PATCH 632/842] Iniswap dropdown stylesheet always applied --- src/courtroom_character.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index 77ffd2467..924444166 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -130,7 +130,6 @@ void Courtroom::on_iniswap_dropdown_changed(int p_index) void Courtroom::set_iniswap_dropdown_searchable(bool p_enabled) { ui_iniswap_dropdown->setEditable(p_enabled); - set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); if (p_enabled) { QCompleter *l_completer = ui_iniswap_dropdown->completer(); @@ -139,6 +138,6 @@ void Courtroom::set_iniswap_dropdown_searchable(bool p_enabled) QAbstractItemView *l_popup = l_completer->popup(); l_popup->setTextElideMode(Qt::TextElideMode::ElideNone); l_popup->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); - set_stylesheet(l_popup, "[INISWAP DROPDOWN POPUP]", COURTROOM_STYLESHEETS_CSS, ao_app); } + set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); } From 39d05bb1197e47fcea1d40e6ecb7f09a5c7cd9d7 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 25 Mar 2022 20:39:57 +0100 Subject: [PATCH 633/842] Bumped speed at which logs are saved --- src/logger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logger.cpp b/src/logger.cpp index f602c7623..e114db4ef 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -83,7 +83,7 @@ void task() l_system_lock.release(); } - QThread::msleep(100); + QThread::msleep(10); } } From 6b44e8b2f92e8cccef757520b4bb4950edc44be9 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 25 Mar 2022 20:42:16 +0100 Subject: [PATCH 634/842] Avoid needlessly acquire the system lock if our msg list is empty --- src/logger.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/logger.cpp b/src/logger.cpp index e114db4ef..91089cc52 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -62,25 +62,27 @@ void task() QStringList l_msg_list(std::move(s_msg_list)); s_msg_list_lock.unlock(); - l_system_lock.acquire(); - check_file_size_and_rename(); - QFile l_log(C_FILE_NAME); - if (l_log.open(QFile::WriteOnly | QFile::Append)) + if (!l_msg_list.isEmpty()) { - QTextStream in(&l_log); - for (const QString &i_msg : l_msg_list) + l_system_lock.acquire(); + check_file_size_and_rename(); + QFile l_log(C_FILE_NAME); + if (l_log.open(QFile::WriteOnly | QFile::Append)) { - in << i_msg + QTextStream in(&l_log); + for (const QString &i_msg : l_msg_list) + { + in << i_msg #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - << Qt::endl; + << Qt::endl; #else - << "\n"; + << "\n"; #endif + } } + l_log.close(); + l_system_lock.release(); } - l_log.close(); - - l_system_lock.release(); } QThread::msleep(10); From 10287948dbdd6b28f2bf264f78eb44dff304efb6 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 26 Mar 2022 15:54:45 +0100 Subject: [PATCH 635/842] Moved all headers to src folder --- dronline-client.pro | 110 +++---- {include => src}/aoapplication.h | 0 {include => src}/aoblipplayer.h | 0 {include => src}/aobutton.h | 0 {include => src}/aocharbutton.h | 0 {include => src}/aoconfig.h | 368 ++++++++++++------------ {include => src}/aoconfigpanel.h | 316 ++++++++++---------- {include => src}/aoemotebutton.h | 0 {include => src}/aoguiloader.h | 40 +-- {include => src}/aoimagedisplay.h | 0 {include => src}/aolabel.h | 6 +- {include => src}/aolineedit.h | 0 {include => src}/aomusicplayer.h | 0 {include => src}/aonotearea.h | 54 ++-- {include => src}/aonotepad.h | 38 +-- {include => src}/aonotepicker.h | 70 ++--- {include => src}/aoobject.h | 32 +-- {include => src}/aopixmap.h | 42 +-- {include => src}/aosfxplayer.h | 0 {include => src}/aoshoutplayer.h | 32 +-- {include => src}/aosystemplayer.h | 26 +- {include => src}/aotimer.h | 0 {include => src}/commondefs.h | 0 {include => src}/courtroom.h | 0 {include => src}/datatypes.h | 0 {include => src}/debug_functions.h | 0 {include => src}/draudio.h | 66 ++--- {include => src}/draudiodevice.h | 90 +++--- {include => src}/draudioengine.h | 82 +++--- {include => src}/draudioengine_p.h | 88 +++--- {include => src}/draudioerror.h | 30 +- {include => src}/draudiostream.h | 180 ++++++------ {include => src}/draudiostreamfamily.h | 138 ++++----- {include => src}/draudiotrackmetadata.h | 0 {include => src}/drcharactermovie.h | 0 {include => src}/drchatlog.h | 0 {include => src}/drdiscord.h | 0 {include => src}/dreffectmovie.h | 0 {include => src}/drmovie.h | 0 {include => src}/drpacket.h | 0 {include => src}/drpather.h | 0 {include => src}/drscenemovie.h | 0 {include => src}/drserversocket.h | 0 {include => src}/drshoutmovie.h | 0 {include => src}/drsplashmovie.h | 0 {include => src}/drstickermovie.h | 0 {include => src}/drtextedit.h | 0 {include => src}/drvideoscreen.h | 0 {include => src}/file_functions.h | 0 {include => src}/hardware_functions.h | 0 {include => src}/lobby.h | 0 {include => src}/logger.h | 0 {include => src}/misc_functions.h | 0 {include => src}/theme.h | 0 {include => src}/utils.h | 0 {include => src}/version.h | 0 56 files changed, 904 insertions(+), 904 deletions(-) rename {include => src}/aoapplication.h (100%) rename {include => src}/aoblipplayer.h (100%) rename {include => src}/aobutton.h (100%) rename {include => src}/aocharbutton.h (100%) rename {include => src}/aoconfig.h (97%) rename {include => src}/aoconfigpanel.h (96%) rename {include => src}/aoemotebutton.h (100%) rename {include => src}/aoguiloader.h (95%) rename {include => src}/aoimagedisplay.h (100%) rename {include => src}/aolabel.h (78%) rename {include => src}/aolineedit.h (100%) rename {include => src}/aomusicplayer.h (100%) rename {include => src}/aonotearea.h (78%) rename {include => src}/aonotepad.h (93%) rename {include => src}/aonotepicker.h (82%) rename {include => src}/aoobject.h (93%) rename {include => src}/aopixmap.h (93%) rename {include => src}/aosfxplayer.h (100%) rename {include => src}/aoshoutplayer.h (68%) rename {include => src}/aosystemplayer.h (93%) rename {include => src}/aotimer.h (100%) rename {include => src}/commondefs.h (100%) rename {include => src}/courtroom.h (100%) rename {include => src}/datatypes.h (100%) rename {include => src}/debug_functions.h (100%) rename {include => src}/draudio.h (94%) rename {include => src}/draudiodevice.h (94%) rename {include => src}/draudioengine.h (96%) rename {include => src}/draudioengine_p.h (95%) rename {include => src}/draudioerror.h (91%) rename {include => src}/draudiostream.h (95%) rename {include => src}/draudiostreamfamily.h (95%) rename {include => src}/draudiotrackmetadata.h (100%) rename {include => src}/drcharactermovie.h (100%) rename {include => src}/drchatlog.h (100%) rename {include => src}/drdiscord.h (100%) rename {include => src}/dreffectmovie.h (100%) rename {include => src}/drmovie.h (100%) rename {include => src}/drpacket.h (100%) rename {include => src}/drpather.h (100%) rename {include => src}/drscenemovie.h (100%) rename {include => src}/drserversocket.h (100%) rename {include => src}/drshoutmovie.h (100%) rename {include => src}/drsplashmovie.h (100%) rename {include => src}/drstickermovie.h (100%) rename {include => src}/drtextedit.h (100%) rename {include => src}/drvideoscreen.h (100%) rename {include => src}/file_functions.h (100%) rename {include => src}/hardware_functions.h (100%) rename {include => src}/lobby.h (100%) rename {include => src}/logger.h (100%) rename {include => src}/misc_functions.h (100%) rename {include => src}/theme.h (100%) rename {include => src}/utils.h (100%) rename {include => src}/version.h (100%) diff --git a/dronline-client.pro b/dronline-client.pro index 11abacd52..e130b1f8a 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -15,61 +15,61 @@ DEFINES += DRO_ACKMS CONFIG(debug, debug|release):DEFINES += DR_DEV HEADERS += \ - include/draudiotrackmetadata.h \ - include/drscenemovie.h \ - include/drsplashmovie.h \ - include/aoapplication.h \ - include/aoblipplayer.h \ - include/aobutton.h \ - include/aocharbutton.h \ - include/aoconfig.h \ - include/aoconfigpanel.h \ - include/aoemotebutton.h \ - include/aoguiloader.h \ - include/aoimagedisplay.h \ - include/aolabel.h \ - include/aolineedit.h \ - include/aomusicplayer.h \ - include/aonotearea.h \ - include/aonotepad.h \ - include/aonotepicker.h \ - include/aoobject.h \ - include/aopixmap.h \ - include/aosfxplayer.h \ - include/aoshoutplayer.h \ - include/aosystemplayer.h \ - include/aotimer.h \ - include/commondefs.h \ - include/courtroom.h \ - include/datatypes.h \ - include/debug_functions.h \ - include/draudio.h \ - include/draudiodevice.h \ - include/draudioengine.h \ - include/draudioengine_p.h \ - include/draudioerror.h \ - include/draudiostream.h \ - include/draudiostreamfamily.h \ - include/drcharactermovie.h \ - include/drchatlog.h \ - include/dreffectmovie.h \ - include/drmovie.h \ - include/drpacket.h \ - include/drpather.h \ - include/drserversocket.h \ - include/drshoutmovie.h \ - include/drstickermovie.h \ - include/drtextedit.h \ - include/drdiscord.h \ - include/drvideoscreen.h \ - include/file_functions.h \ - include/hardware_functions.h \ - include/lobby.h \ - include/logger.h \ - include/misc_functions.h \ - include/theme.h \ - include/utils.h \ - include/version.h + src/aoapplication.h \ + src/aoblipplayer.h \ + src/aobutton.h \ + src/aocharbutton.h \ + src/aoconfig.h \ + src/aoconfigpanel.h \ + src/aoemotebutton.h \ + src/aoguiloader.h \ + src/aoimagedisplay.h \ + src/aolabel.h \ + src/aolineedit.h \ + src/aomusicplayer.h \ + src/aonotearea.h \ + src/aonotepad.h \ + src/aonotepicker.h \ + src/aoobject.h \ + src/aopixmap.h \ + src/aosfxplayer.h \ + src/aoshoutplayer.h \ + src/aosystemplayer.h \ + src/aotimer.h \ + src/commondefs.h \ + src/courtroom.h \ + src/datatypes.h \ + src/debug_functions.h \ + src/draudio.h \ + src/draudiodevice.h \ + src/draudioengine.h \ + src/draudioengine_p.h \ + src/draudioerror.h \ + src/draudiostream.h \ + src/draudiostreamfamily.h \ + src/draudiotrackmetadata.h \ + src/drcharactermovie.h \ + src/drchatlog.h \ + src/drdiscord.h \ + src/dreffectmovie.h \ + src/drmovie.h \ + src/drpacket.h \ + src/drpather.h \ + src/drscenemovie.h \ + src/drserversocket.h \ + src/drshoutmovie.h \ + src/drsplashmovie.h \ + src/drstickermovie.h \ + src/drtextedit.h \ + src/drvideoscreen.h \ + src/file_functions.h \ + src/hardware_functions.h \ + src/lobby.h \ + src/logger.h \ + src/misc_functions.h \ + src/theme.h \ + src/utils.h \ + src/version.h SOURCES += \ src/aoapplication.cpp \ diff --git a/include/aoapplication.h b/src/aoapplication.h similarity index 100% rename from include/aoapplication.h rename to src/aoapplication.h diff --git a/include/aoblipplayer.h b/src/aoblipplayer.h similarity index 100% rename from include/aoblipplayer.h rename to src/aoblipplayer.h diff --git a/include/aobutton.h b/src/aobutton.h similarity index 100% rename from include/aobutton.h rename to src/aobutton.h diff --git a/include/aocharbutton.h b/src/aocharbutton.h similarity index 100% rename from include/aocharbutton.h rename to src/aocharbutton.h diff --git a/include/aoconfig.h b/src/aoconfig.h similarity index 97% rename from include/aoconfig.h rename to src/aoconfig.h index 75f6cfb3e..97457ed9b 100644 --- a/include/aoconfig.h +++ b/src/aoconfig.h @@ -1,184 +1,184 @@ -#ifndef AOCONFIG_H -#define AOCONFIG_H - -#include "datatypes.h" - -#include - -#include - -class AOConfig : public QObject -{ - Q_OBJECT - -public: - AOConfig(QObject *p_parent = nullptr); - ~AOConfig(); - - // generic getters - QString get_string(QString p_name, QString p_default = nullptr) const; - bool get_bool(QString p_name, bool p_default = false) const; - int get_number(QString p_name, int p_default = 0) const; - - // getters - bool autosave() const; - QString username() const; - QString showname() const; - QString showname_placeholder() const; - QString character_ini(QString base_character) const; - QString callwords() const; - bool server_alerts_enabled() const; - bool discord_presence() const; - bool discord_hide_server() const; - bool discord_hide_character() const; - QString theme() const; - QString gamemode() const; - QString manual_gamemode() const; - bool is_manual_gamemode_selection_enabled() const; - QString timeofday() const; - QString manual_timeofday() const; - bool is_manual_timeofday_selection_enabled() const; - bool searchable_iniswap_enabled() const; - bool always_pre_enabled() const; - int chat_tick_interval() const; - bool emote_preview_enabled() const; - bool sticky_sfx_enabled() const; - int log_max_lines() const; - bool log_display_timestamp_enabled() const; - bool log_display_self_highlight_enabled() const; - bool log_display_empty_messages_enabled() const; - bool log_is_topdown_enabled() const; - bool log_format_use_newline_enabled() const; - bool log_display_music_switch_enabled() const; - bool log_is_recording_enabled() const; - - // audio - std::optional favorite_device_driver() const; - int master_volume() const; - bool suppress_background_audio() const; - int system_volume() const; - int effect_volume() const; - bool effect_ignore_suppression() const; - int music_volume() const; - bool music_ignore_suppression() const; - int video_volume() const; - bool video_ignore_suppression() const; - int blip_volume() const; - bool blip_ignore_suppression() const; - int blip_rate() const; - bool blank_blips_enabled() const; - - // io -public slots: - void save_file(); - - // setters -public slots: - void set_autosave(bool p_enabled); - void set_username(QString p_string); - void set_showname(QString p_string); - void set_showname_placeholder(QString p_string); - void clear_showname_placeholder(); - void set_character_ini(QString base_character, QString target_character); - void set_callwords(QString p_string); - void set_server_alerts(bool p_enabled); - void set_discord_presence(const bool p_enabled); - void set_discord_hide_server(const bool p_enabled); - void set_discord_hide_character(const bool p_enabled); - void set_theme(QString p_string); - void set_gamemode(QString p_string); - void set_manual_gamemode(QString p_string); - void set_manual_gamemode_selection_enabled(bool p_enabled); - void set_timeofday(QString p_string); - void set_manual_timeofday(QString p_string); - void set_manual_timeofday_selection_enabled(bool p_enabled); - void set_searchable_iniswap(bool); - void set_always_pre(bool p_enabled); - void set_chat_tick_interval(int p_number); - void set_emote_preview(bool p_enabled); - void set_sticky_sfx(bool p_enabled); - void set_log_max_lines(int p_number); - void set_log_display_timestamp(bool p_enabled); - void set_log_display_self_highlight(bool p_enabled); - void set_log_display_empty_messages(bool p_enabled); - void set_log_is_topdown(bool p_enabled); - void set_log_format_use_newline(bool p_enabled); - void set_log_display_music_switch(bool p_enabled); - void set_log_is_recording(bool p_enabled); - void set_suppress_background_audio(bool p_enabled); - - // audio - void set_favorite_device_driver(QString p_device_driver); - void set_master_volume(int p_number); - void set_system_volume(int p_number); - void set_effect_volume(int p_number); - void set_effect_ignore_suppression(bool p_enabled); - void set_music_volume(int p_number); - void set_music_ignore_suppression(bool p_enabled); - void set_video_volume(int p_number); - void set_video_ignore_suppression(bool p_enabled); - void set_blip_volume(int p_number); - void set_blip_ignore_suppression(bool p_enabled); - void set_blip_rate(int p_number); - void set_blank_blips(bool p_enabled); - - // signals -signals: - // meta - void autosave_changed(bool); - - // general - void username_changed(QString); - void callwords_changed(QString); - void server_alerts_changed(bool); - void discord_presence_changed(bool); - void discord_hide_server_changed(bool); - void discord_hide_character_changed(bool); - - // game - void showname_changed(QString); - void showname_placeholder_changed(QString); - void character_ini_changed(QString base_character); - void searchable_iniswap_changed(bool); - void always_pre_changed(bool); - void chat_tick_interval_changed(int); - void emote_preview_changed(bool); - void sticky_sfx_changed(bool); - - // theme - void theme_changed(QString); - void gamemode_changed(QString); - void manual_gamemode_changed(QString); - void manual_gamemode_selection_changed(bool); - void timeofday_changed(QString); - void manual_timeofday_changed(QString); - void manual_timeofday_selection_changed(bool); - - // log - void log_max_lines_changed(int); - void log_display_timestamp_changed(bool); - void log_display_self_highlight_changed(bool); - void log_display_empty_messages_changed(bool); - void log_is_topdown_changed(bool); - void log_format_use_newline_changed(bool); - void log_display_music_switch_changed(bool); - void log_is_recording_changed(bool); - - // audio - void favorite_device_changed(QString); - void master_volume_changed(int); - void suppress_background_audio_changed(bool); - void system_volume_changed(int); - void effect_volume_changed(int); - void effect_ignore_suppression_changed(bool); - void music_volume_changed(int); - void music_ignore_suppression_changed(bool); - void video_volume_changed(int); - void video_ignore_suppression_changed(bool); - void blip_volume_changed(int); - void blip_ignore_suppression_changed(bool); - void blip_rate_changed(int); - void blank_blips_changed(bool); -}; - -#endif // AOCONFIG_H +#ifndef AOCONFIG_H +#define AOCONFIG_H + +#include "datatypes.h" + +#include + +#include + +class AOConfig : public QObject +{ + Q_OBJECT + +public: + AOConfig(QObject *p_parent = nullptr); + ~AOConfig(); + + // generic getters + QString get_string(QString p_name, QString p_default = nullptr) const; + bool get_bool(QString p_name, bool p_default = false) const; + int get_number(QString p_name, int p_default = 0) const; + + // getters + bool autosave() const; + QString username() const; + QString showname() const; + QString showname_placeholder() const; + QString character_ini(QString base_character) const; + QString callwords() const; + bool server_alerts_enabled() const; + bool discord_presence() const; + bool discord_hide_server() const; + bool discord_hide_character() const; + QString theme() const; + QString gamemode() const; + QString manual_gamemode() const; + bool is_manual_gamemode_selection_enabled() const; + QString timeofday() const; + QString manual_timeofday() const; + bool is_manual_timeofday_selection_enabled() const; + bool searchable_iniswap_enabled() const; + bool always_pre_enabled() const; + int chat_tick_interval() const; + bool emote_preview_enabled() const; + bool sticky_sfx_enabled() const; + int log_max_lines() const; + bool log_display_timestamp_enabled() const; + bool log_display_self_highlight_enabled() const; + bool log_display_empty_messages_enabled() const; + bool log_is_topdown_enabled() const; + bool log_format_use_newline_enabled() const; + bool log_display_music_switch_enabled() const; + bool log_is_recording_enabled() const; + + // audio + std::optional favorite_device_driver() const; + int master_volume() const; + bool suppress_background_audio() const; + int system_volume() const; + int effect_volume() const; + bool effect_ignore_suppression() const; + int music_volume() const; + bool music_ignore_suppression() const; + int video_volume() const; + bool video_ignore_suppression() const; + int blip_volume() const; + bool blip_ignore_suppression() const; + int blip_rate() const; + bool blank_blips_enabled() const; + + // io +public slots: + void save_file(); + + // setters +public slots: + void set_autosave(bool p_enabled); + void set_username(QString p_string); + void set_showname(QString p_string); + void set_showname_placeholder(QString p_string); + void clear_showname_placeholder(); + void set_character_ini(QString base_character, QString target_character); + void set_callwords(QString p_string); + void set_server_alerts(bool p_enabled); + void set_discord_presence(const bool p_enabled); + void set_discord_hide_server(const bool p_enabled); + void set_discord_hide_character(const bool p_enabled); + void set_theme(QString p_string); + void set_gamemode(QString p_string); + void set_manual_gamemode(QString p_string); + void set_manual_gamemode_selection_enabled(bool p_enabled); + void set_timeofday(QString p_string); + void set_manual_timeofday(QString p_string); + void set_manual_timeofday_selection_enabled(bool p_enabled); + void set_searchable_iniswap(bool); + void set_always_pre(bool p_enabled); + void set_chat_tick_interval(int p_number); + void set_emote_preview(bool p_enabled); + void set_sticky_sfx(bool p_enabled); + void set_log_max_lines(int p_number); + void set_log_display_timestamp(bool p_enabled); + void set_log_display_self_highlight(bool p_enabled); + void set_log_display_empty_messages(bool p_enabled); + void set_log_is_topdown(bool p_enabled); + void set_log_format_use_newline(bool p_enabled); + void set_log_display_music_switch(bool p_enabled); + void set_log_is_recording(bool p_enabled); + void set_suppress_background_audio(bool p_enabled); + + // audio + void set_favorite_device_driver(QString p_device_driver); + void set_master_volume(int p_number); + void set_system_volume(int p_number); + void set_effect_volume(int p_number); + void set_effect_ignore_suppression(bool p_enabled); + void set_music_volume(int p_number); + void set_music_ignore_suppression(bool p_enabled); + void set_video_volume(int p_number); + void set_video_ignore_suppression(bool p_enabled); + void set_blip_volume(int p_number); + void set_blip_ignore_suppression(bool p_enabled); + void set_blip_rate(int p_number); + void set_blank_blips(bool p_enabled); + + // signals +signals: + // meta + void autosave_changed(bool); + + // general + void username_changed(QString); + void callwords_changed(QString); + void server_alerts_changed(bool); + void discord_presence_changed(bool); + void discord_hide_server_changed(bool); + void discord_hide_character_changed(bool); + + // game + void showname_changed(QString); + void showname_placeholder_changed(QString); + void character_ini_changed(QString base_character); + void searchable_iniswap_changed(bool); + void always_pre_changed(bool); + void chat_tick_interval_changed(int); + void emote_preview_changed(bool); + void sticky_sfx_changed(bool); + + // theme + void theme_changed(QString); + void gamemode_changed(QString); + void manual_gamemode_changed(QString); + void manual_gamemode_selection_changed(bool); + void timeofday_changed(QString); + void manual_timeofday_changed(QString); + void manual_timeofday_selection_changed(bool); + + // log + void log_max_lines_changed(int); + void log_display_timestamp_changed(bool); + void log_display_self_highlight_changed(bool); + void log_display_empty_messages_changed(bool); + void log_is_topdown_changed(bool); + void log_format_use_newline_changed(bool); + void log_display_music_switch_changed(bool); + void log_is_recording_changed(bool); + + // audio + void favorite_device_changed(QString); + void master_volume_changed(int); + void suppress_background_audio_changed(bool); + void system_volume_changed(int); + void effect_volume_changed(int); + void effect_ignore_suppression_changed(bool); + void music_volume_changed(int); + void music_ignore_suppression_changed(bool); + void video_volume_changed(int); + void video_ignore_suppression_changed(bool); + void blip_volume_changed(int); + void blip_ignore_suppression_changed(bool); + void blip_rate_changed(int); + void blank_blips_changed(bool); +}; + +#endif // AOCONFIG_H diff --git a/include/aoconfigpanel.h b/src/aoconfigpanel.h similarity index 96% rename from include/aoconfigpanel.h rename to src/aoconfigpanel.h index 9a13b482c..636e3b5b3 100644 --- a/include/aoconfigpanel.h +++ b/src/aoconfigpanel.h @@ -1,158 +1,158 @@ -#ifndef AOCONFIGPANEL_H -#define AOCONFIGPANEL_H - -#include "draudioengine.h" - -class AOApplication; -class AOConfig; - -#include - -class QCheckBox; -class QComboBox; -class QGroupBox; -class QLabel; -class QLineEdit; -class QPushButton; -class QRadioButton; -class QSlider; -class QSpinBox; - -class AOConfigPanel : public QWidget -{ - Q_OBJECT - -public: - AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent = nullptr); - -public slots: - void on_config_reload_theme_requested(); - -signals: - void reload_theme(); - void reload_character(); - void reload_audiotracks(); - -protected: - void showEvent(QShowEvent *event) override; - -private: - void refresh_theme_list(); - void refresh_gamemode_list(); - void refresh_timeofday_list(); - void update_audio_device_list(); - -private slots: - void on_reload_theme_clicked(); - void on_reload_character_clicked(); - void on_reload_audiotracks_clicked(); - void on_theme_changed(QString); - void on_gamemode_changed(QString); - void on_manual_gamemode_selection_changed(bool); - void on_manual_gamemode_changed(QString); - void on_manual_gamemode_index_changed(QString p_text); - void on_timeofday_changed(QString); - void on_manual_timeofday_selection_changed(bool); - void on_manual_timeofday_changed(QString); - void on_manual_timeofday_index_changed(QString p_text); - void on_showname_placeholder_changed(QString p_text); - void on_log_is_topdown_changed(bool p_enabled); - void on_device_current_index_changed(int p_index); - void on_audio_device_changed(DRAudioDevice p_device); - void on_favorite_audio_device_changed(DRAudioDevice p_device); - void on_audio_device_list_changed(QVector p_device_list); - void on_master_value_changed(int p_num); - void on_system_value_changed(int p_num); - void on_effect_value_changed(int p_num); - void on_music_value_changed(int p_num); - void on_video_value_changed(int p_num); - void on_blip_value_changed(int p_num); - -private: - // FIXME This dependency shouldn't have come to exist. - AOApplication *ao_app = nullptr; - // driver - AOConfig *m_config = nullptr; - DRAudioEngine *m_engine = nullptr; - - // behaviour - QPushButton *ui_save = nullptr; - QPushButton *ui_close = nullptr; - QCheckBox *ui_autosave = nullptr; - - // general - QLineEdit *ui_username = nullptr; - QLineEdit *ui_callwords = nullptr; - QCheckBox *ui_server_alerts = nullptr; - QGroupBox *ui_discord_presence = nullptr; - QCheckBox *ui_discord_hide_server = nullptr; - QCheckBox *ui_discord_hide_character = nullptr; - - // character - QLineEdit *ui_showname = nullptr; - QPushButton *ui_reload_character = nullptr; - QCheckBox *ui_searchable_iniswap = nullptr; - - // emotes - QCheckBox *ui_emote_preview = nullptr; - QCheckBox *ui_always_pre = nullptr; - QCheckBox *ui_sticky_sfx = nullptr; - - // dialog - QSpinBox *ui_chat_tick_interval = nullptr; - QSpinBox *ui_blip_rate = nullptr; - QCheckBox *ui_blank_blips = nullptr; - - // game - QComboBox *ui_theme = nullptr; - QPushButton *ui_reload_theme = nullptr; - QLineEdit *ui_gamemode = nullptr; - QComboBox *ui_manual_gamemode = nullptr; - QCheckBox *ui_manual_gamemode_selection = nullptr; - QLineEdit *ui_timeofday = nullptr; - QComboBox *ui_manual_timeofday = nullptr; - QCheckBox *ui_manual_timeofday_selection = nullptr; - - // chatlog - QSpinBox *ui_log_max_lines = nullptr; - QCheckBox *ui_log_display_timestamp = nullptr; - QCheckBox *ui_log_display_self_highlight = nullptr; - QCheckBox *ui_log_format_use_newline = nullptr; - QCheckBox *ui_log_display_empty_messages = nullptr; - QCheckBox *ui_log_display_music_switch = nullptr; - QRadioButton *ui_log_orientation_top_down = nullptr; - QRadioButton *ui_log_orientation_bottom_up = nullptr; - QCheckBox *ui_log_is_recording = nullptr; - - // audio - QComboBox *ui_device = nullptr; - QCheckBox *ui_favorite_device = nullptr; - QSlider *ui_master = nullptr; - QLabel *ui_master_value = nullptr; - QGroupBox *ui_suppress_background_audio = nullptr; - QSlider *ui_system = nullptr; - QLabel *ui_system_value = nullptr; - QSlider *ui_effect = nullptr; - QCheckBox *ui_effect_ignore_suppression = nullptr; - QLabel *ui_effect_value = nullptr; - QSlider *ui_music = nullptr; - QCheckBox *ui_music_ignore_suppression = nullptr; - QLabel *ui_music_value = nullptr; - QSlider *ui_video = nullptr; - QCheckBox *ui_video_ignore_suppression = nullptr; - QLabel *ui_video_value = nullptr; - QSlider *ui_blip = nullptr; - QCheckBox *ui_blip_ignore_suppression = nullptr; - QLabel *ui_blip_value = nullptr; - QPushButton *ui_reload_audiotracks = nullptr; - - // about - QLabel *ui_about = nullptr; - -private slots: - void username_editing_finished(); - void showname_editing_finished(); - void callwords_editing_finished(); -}; - -#endif // AOCONFIGPANEL_H +#ifndef AOCONFIGPANEL_H +#define AOCONFIGPANEL_H + +#include "draudioengine.h" + +class AOApplication; +class AOConfig; + +#include + +class QCheckBox; +class QComboBox; +class QGroupBox; +class QLabel; +class QLineEdit; +class QPushButton; +class QRadioButton; +class QSlider; +class QSpinBox; + +class AOConfigPanel : public QWidget +{ + Q_OBJECT + +public: + AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent = nullptr); + +public slots: + void on_config_reload_theme_requested(); + +signals: + void reload_theme(); + void reload_character(); + void reload_audiotracks(); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void refresh_theme_list(); + void refresh_gamemode_list(); + void refresh_timeofday_list(); + void update_audio_device_list(); + +private slots: + void on_reload_theme_clicked(); + void on_reload_character_clicked(); + void on_reload_audiotracks_clicked(); + void on_theme_changed(QString); + void on_gamemode_changed(QString); + void on_manual_gamemode_selection_changed(bool); + void on_manual_gamemode_changed(QString); + void on_manual_gamemode_index_changed(QString p_text); + void on_timeofday_changed(QString); + void on_manual_timeofday_selection_changed(bool); + void on_manual_timeofday_changed(QString); + void on_manual_timeofday_index_changed(QString p_text); + void on_showname_placeholder_changed(QString p_text); + void on_log_is_topdown_changed(bool p_enabled); + void on_device_current_index_changed(int p_index); + void on_audio_device_changed(DRAudioDevice p_device); + void on_favorite_audio_device_changed(DRAudioDevice p_device); + void on_audio_device_list_changed(QVector p_device_list); + void on_master_value_changed(int p_num); + void on_system_value_changed(int p_num); + void on_effect_value_changed(int p_num); + void on_music_value_changed(int p_num); + void on_video_value_changed(int p_num); + void on_blip_value_changed(int p_num); + +private: + // FIXME This dependency shouldn't have come to exist. + AOApplication *ao_app = nullptr; + // driver + AOConfig *m_config = nullptr; + DRAudioEngine *m_engine = nullptr; + + // behaviour + QPushButton *ui_save = nullptr; + QPushButton *ui_close = nullptr; + QCheckBox *ui_autosave = nullptr; + + // general + QLineEdit *ui_username = nullptr; + QLineEdit *ui_callwords = nullptr; + QCheckBox *ui_server_alerts = nullptr; + QGroupBox *ui_discord_presence = nullptr; + QCheckBox *ui_discord_hide_server = nullptr; + QCheckBox *ui_discord_hide_character = nullptr; + + // character + QLineEdit *ui_showname = nullptr; + QPushButton *ui_reload_character = nullptr; + QCheckBox *ui_searchable_iniswap = nullptr; + + // emotes + QCheckBox *ui_emote_preview = nullptr; + QCheckBox *ui_always_pre = nullptr; + QCheckBox *ui_sticky_sfx = nullptr; + + // dialog + QSpinBox *ui_chat_tick_interval = nullptr; + QSpinBox *ui_blip_rate = nullptr; + QCheckBox *ui_blank_blips = nullptr; + + // game + QComboBox *ui_theme = nullptr; + QPushButton *ui_reload_theme = nullptr; + QLineEdit *ui_gamemode = nullptr; + QComboBox *ui_manual_gamemode = nullptr; + QCheckBox *ui_manual_gamemode_selection = nullptr; + QLineEdit *ui_timeofday = nullptr; + QComboBox *ui_manual_timeofday = nullptr; + QCheckBox *ui_manual_timeofday_selection = nullptr; + + // chatlog + QSpinBox *ui_log_max_lines = nullptr; + QCheckBox *ui_log_display_timestamp = nullptr; + QCheckBox *ui_log_display_self_highlight = nullptr; + QCheckBox *ui_log_format_use_newline = nullptr; + QCheckBox *ui_log_display_empty_messages = nullptr; + QCheckBox *ui_log_display_music_switch = nullptr; + QRadioButton *ui_log_orientation_top_down = nullptr; + QRadioButton *ui_log_orientation_bottom_up = nullptr; + QCheckBox *ui_log_is_recording = nullptr; + + // audio + QComboBox *ui_device = nullptr; + QCheckBox *ui_favorite_device = nullptr; + QSlider *ui_master = nullptr; + QLabel *ui_master_value = nullptr; + QGroupBox *ui_suppress_background_audio = nullptr; + QSlider *ui_system = nullptr; + QLabel *ui_system_value = nullptr; + QSlider *ui_effect = nullptr; + QCheckBox *ui_effect_ignore_suppression = nullptr; + QLabel *ui_effect_value = nullptr; + QSlider *ui_music = nullptr; + QCheckBox *ui_music_ignore_suppression = nullptr; + QLabel *ui_music_value = nullptr; + QSlider *ui_video = nullptr; + QCheckBox *ui_video_ignore_suppression = nullptr; + QLabel *ui_video_value = nullptr; + QSlider *ui_blip = nullptr; + QCheckBox *ui_blip_ignore_suppression = nullptr; + QLabel *ui_blip_value = nullptr; + QPushButton *ui_reload_audiotracks = nullptr; + + // about + QLabel *ui_about = nullptr; + +private slots: + void username_editing_finished(); + void showname_editing_finished(); + void callwords_editing_finished(); +}; + +#endif // AOCONFIGPANEL_H diff --git a/include/aoemotebutton.h b/src/aoemotebutton.h similarity index 100% rename from include/aoemotebutton.h rename to src/aoemotebutton.h diff --git a/include/aoguiloader.h b/src/aoguiloader.h similarity index 95% rename from include/aoguiloader.h rename to src/aoguiloader.h index af6b6b567..42b42c617 100644 --- a/include/aoguiloader.h +++ b/src/aoguiloader.h @@ -1,20 +1,20 @@ -#ifndef AOGUILOADER_H -#define AOGUILOADER_H - -// qt -#include - -// we could make a smarter system but let's keep it simple ;) -#define AO_GUI_WIDGET(p_type, p_name) findChild(p_name) - -class AOGuiLoader : public QUiLoader -{ - Q_OBJECT - -public: - AOGuiLoader(QObject *p_parent = nullptr); - - QWidget *load_from_file(QString p_file_path, QWidget *p_parent = nullptr); -}; - -#endif +#ifndef AOGUILOADER_H +#define AOGUILOADER_H + +// qt +#include + +// we could make a smarter system but let's keep it simple ;) +#define AO_GUI_WIDGET(p_type, p_name) findChild(p_name) + +class AOGuiLoader : public QUiLoader +{ + Q_OBJECT + +public: + AOGuiLoader(QObject *p_parent = nullptr); + + QWidget *load_from_file(QString p_file_path, QWidget *p_parent = nullptr); +}; + +#endif diff --git a/include/aoimagedisplay.h b/src/aoimagedisplay.h similarity index 100% rename from include/aoimagedisplay.h rename to src/aoimagedisplay.h diff --git a/include/aolabel.h b/src/aolabel.h similarity index 78% rename from include/aolabel.h rename to src/aolabel.h index ea40f6ce6..b1f97c421 100644 --- a/include/aolabel.h +++ b/src/aolabel.h @@ -1,5 +1,5 @@ -#ifndef AOLABEL_HPP -#define AOLABEL_HPP +#ifndef AOLABEL_H +#define AOLABEL_H class AOApplication; @@ -16,4 +16,4 @@ class AOLabel : public QLabel AOApplication *ao_app = nullptr; }; -#endif // AOLABEL_HPP +#endif // AOLABEL_H diff --git a/include/aolineedit.h b/src/aolineedit.h similarity index 100% rename from include/aolineedit.h rename to src/aolineedit.h diff --git a/include/aomusicplayer.h b/src/aomusicplayer.h similarity index 100% rename from include/aomusicplayer.h rename to src/aomusicplayer.h diff --git a/include/aonotearea.h b/src/aonotearea.h similarity index 78% rename from include/aonotearea.h rename to src/aonotearea.h index de1971199..9a59d26b3 100644 --- a/include/aonotearea.h +++ b/src/aonotearea.h @@ -1,27 +1,27 @@ -#ifndef AONOTEAREA_HPP -#define AONOTEAREA_HPP - -#include "aoimagedisplay.h" - -class AOApplication; -class AOButton; - -class QVBoxLayout; - -class AONoteArea : public AOImageDisplay -{ - Q_OBJECT - -public: - AOButton *add_button = nullptr; - QVBoxLayout *m_layout = nullptr; - - AONoteArea(QWidget *p_parent, AOApplication *p_ao_app); - -private: - AOApplication *ao_app = nullptr; - - void set_layout(); -}; - -#endif // AONOTEAREA_HPP +#ifndef AONOTEAREA_H +#define AONOTEAREA_H + +#include "aoimagedisplay.h" + +class AOApplication; +class AOButton; + +class QVBoxLayout; + +class AONoteArea : public AOImageDisplay +{ + Q_OBJECT + +public: + AOButton *add_button = nullptr; + QVBoxLayout *m_layout = nullptr; + + AONoteArea(QWidget *p_parent, AOApplication *p_ao_app); + +private: + AOApplication *ao_app = nullptr; + + void set_layout(); +}; + +#endif // AONOTEAREA_H diff --git a/include/aonotepad.h b/src/aonotepad.h similarity index 93% rename from include/aonotepad.h rename to src/aonotepad.h index 0ec099f81..1f9b62172 100644 --- a/include/aonotepad.h +++ b/src/aonotepad.h @@ -1,19 +1,19 @@ -#ifndef AONOTEPAD_H -#define AONOTEPAD_H - -#include "drtextedit.h" - -class AOApplication; - -class AONotepad : public DRTextEdit -{ - Q_OBJECT - -public: - AONotepad(QWidget *p_parent, AOApplication *p_ao_app); - -private: - AOApplication *ao_app = nullptr; -}; - -#endif // AONOTEPAD_H +#ifndef AONOTEPAD_H +#define AONOTEPAD_H + +#include "drtextedit.h" + +class AOApplication; + +class AONotepad : public DRTextEdit +{ + Q_OBJECT + +public: + AONotepad(QWidget *p_parent, AOApplication *p_ao_app); + +private: + AOApplication *ao_app = nullptr; +}; + +#endif // AONOTEPAD_H diff --git a/include/aonotepicker.h b/src/aonotepicker.h similarity index 82% rename from include/aonotepicker.h rename to src/aonotepicker.h index 5e8324bcd..b27983b60 100644 --- a/include/aonotepicker.h +++ b/src/aonotepicker.h @@ -1,35 +1,35 @@ -#ifndef AONOTEPICKER_HPP -#define AONOTEPICKER_HPP - -class AOApplication; -class AOButton; - -#include - -class QHBoxLayout; -class QLineEdit; - -class AONotePicker : public QLabel -{ - Q_OBJECT - -public: - AONotePicker(QWidget *p_parent, AOApplication *p_ao_app); - - QString m_file; - - QLineEdit *ui_line = nullptr; - AOButton *ui_button = nullptr; - AOButton *ui_delete_button = nullptr; - AOButton *ui_hover = nullptr; - QHBoxLayout *ui_layout = nullptr; - - void set_active(bool p_active); - bool is_active(); -private: - AOApplication *ao_app = nullptr; - - bool m_active = false; -}; - -#endif // AONOTEPICKER_HPP +#ifndef AONOTEPICKER_H +#define AONOTEPICKER_H + +class AOApplication; +class AOButton; + +#include + +class QHBoxLayout; +class QLineEdit; + +class AONotePicker : public QLabel +{ + Q_OBJECT + +public: + AONotePicker(QWidget *p_parent, AOApplication *p_ao_app); + + QString m_file; + + QLineEdit *ui_line = nullptr; + AOButton *ui_button = nullptr; + AOButton *ui_delete_button = nullptr; + AOButton *ui_hover = nullptr; + QHBoxLayout *ui_layout = nullptr; + + void set_active(bool p_active); + bool is_active(); +private: + AOApplication *ao_app = nullptr; + + bool m_active = false; +}; + +#endif // AONOTEPICKER_H diff --git a/include/aoobject.h b/src/aoobject.h similarity index 93% rename from include/aoobject.h rename to src/aoobject.h index 9437549c5..7838b2a30 100644 --- a/include/aoobject.h +++ b/src/aoobject.h @@ -1,16 +1,16 @@ -#pragma once - -class AOApplication; - -#include - -class AOObject : public QObject -{ - Q_OBJECT - -public: - AOObject(AOApplication *p_ao_app, QObject *p_parent = nullptr); - -protected: - AOApplication *ao_app = nullptr; -}; +#pragma once + +class AOApplication; + +#include + +class AOObject : public QObject +{ + Q_OBJECT + +public: + AOObject(AOApplication *p_ao_app, QObject *p_parent = nullptr); + +protected: + AOApplication *ao_app = nullptr; +}; diff --git a/include/aopixmap.h b/src/aopixmap.h similarity index 93% rename from include/aopixmap.h rename to src/aopixmap.h index 67f0a4427..510abb91d 100644 --- a/include/aopixmap.h +++ b/src/aopixmap.h @@ -1,21 +1,21 @@ -#ifndef AOPIXMAP_H -#define AOPIXMAP_H - -#include - -class AOPixmap -{ -public: - AOPixmap(QPixmap p_pixmap = QPixmap()); - AOPixmap(QString p_file_path); - - void clear(); - - QPixmap scale(QSize p_size); - QPixmap scale_to_height(QSize p_size); - -private: - QPixmap m_pixmap; -}; - -#endif +#ifndef AOPIXMAP_H +#define AOPIXMAP_H + +#include + +class AOPixmap +{ +public: + AOPixmap(QPixmap p_pixmap = QPixmap()); + AOPixmap(QString p_file_path); + + void clear(); + + QPixmap scale(QSize p_size); + QPixmap scale_to_height(QSize p_size); + +private: + QPixmap m_pixmap; +}; + +#endif diff --git a/include/aosfxplayer.h b/src/aosfxplayer.h similarity index 100% rename from include/aosfxplayer.h rename to src/aosfxplayer.h diff --git a/include/aoshoutplayer.h b/src/aoshoutplayer.h similarity index 68% rename from include/aoshoutplayer.h rename to src/aoshoutplayer.h index cdb657156..e04c5f16c 100644 --- a/include/aoshoutplayer.h +++ b/src/aoshoutplayer.h @@ -1,16 +1,16 @@ -#ifndef AOSHOUTPLAYER_HPP -#define AOSHOUTPLAYER_HPP - -#include "aoobject.h" - -class AOShoutPlayer : public AOObject -{ - Q_OBJECT - -public: - AOShoutPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); - - void play(QString character, QString shout); -}; - -#endif // AOSHOUTPLAYER_HPP +#ifndef AOSHOUTPLAYER_H +#define AOSHOUTPLAYER_H + +#include "aoobject.h" + +class AOShoutPlayer : public AOObject +{ + Q_OBJECT + +public: + AOShoutPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); + + void play(QString character, QString shout); +}; + +#endif // AOSHOUTPLAYER_H diff --git a/include/aosystemplayer.h b/src/aosystemplayer.h similarity index 93% rename from include/aosystemplayer.h rename to src/aosystemplayer.h index ea0b18479..cda463a52 100644 --- a/include/aosystemplayer.h +++ b/src/aosystemplayer.h @@ -1,13 +1,13 @@ -#pragma once - -#include "aoobject.h" - -class AOSystemPlayer : public AOObject -{ - Q_OBJECT - -public: - AOSystemPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); - - void play(QString p_file); -}; +#pragma once + +#include "aoobject.h" + +class AOSystemPlayer : public AOObject +{ + Q_OBJECT + +public: + AOSystemPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); + + void play(QString p_file); +}; diff --git a/include/aotimer.h b/src/aotimer.h similarity index 100% rename from include/aotimer.h rename to src/aotimer.h diff --git a/include/commondefs.h b/src/commondefs.h similarity index 100% rename from include/commondefs.h rename to src/commondefs.h diff --git a/include/courtroom.h b/src/courtroom.h similarity index 100% rename from include/courtroom.h rename to src/courtroom.h diff --git a/include/datatypes.h b/src/datatypes.h similarity index 100% rename from include/datatypes.h rename to src/datatypes.h diff --git a/include/debug_functions.h b/src/debug_functions.h similarity index 100% rename from include/debug_functions.h rename to src/debug_functions.h diff --git a/include/draudio.h b/src/draudio.h similarity index 94% rename from include/draudio.h rename to src/draudio.h index 8941ba836..530330d7c 100644 --- a/include/draudio.h +++ b/src/draudio.h @@ -1,33 +1,33 @@ -#pragma once - -#include - -namespace DRAudio -{ -enum class Family -{ - FSystem, - FEffect, - FMusic, - FVideo, - FBlip, -}; - -enum Option -{ - OSuppressed = 0x1, - OIgnoreSuppression = 0x2, - - // engine - - /** - * If enabled, the engine will suppress all audio when the application is inactive. - */ - OEngineSuppressBackgroundAudio = 0x4, -}; -Q_DECLARE_FLAGS(Options, Option) - -QString get_bass_error(const int32_t p_error_code); -QString get_last_bass_error(); -} // namespace DRAudio -Q_DECLARE_METATYPE(DRAudio::Options) +#pragma once + +#include + +namespace DRAudio +{ +enum class Family +{ + FSystem, + FEffect, + FMusic, + FVideo, + FBlip, +}; + +enum Option +{ + OSuppressed = 0x1, + OIgnoreSuppression = 0x2, + + // engine + + /** + * If enabled, the engine will suppress all audio when the application is inactive. + */ + OEngineSuppressBackgroundAudio = 0x4, +}; +Q_DECLARE_FLAGS(Options, Option) + +QString get_bass_error(const int32_t p_error_code); +QString get_last_bass_error(); +} // namespace DRAudio +Q_DECLARE_METATYPE(DRAudio::Options) diff --git a/include/draudiodevice.h b/src/draudiodevice.h similarity index 94% rename from include/draudiodevice.h rename to src/draudiodevice.h index 215cc057d..ef4c4e890 100644 --- a/include/draudiodevice.h +++ b/src/draudiodevice.h @@ -1,45 +1,45 @@ -#pragma once - -#include -#include -#include - -#include - -#include - -class DRAudioDevice -{ -public: - static QVector get_device_list(); - - enum State : DWORD - { - SEnabled = BASS_DEVICE_ENABLED, - SDefault = BASS_DEVICE_DEFAULT, - SInit = BASS_DEVICE_INIT, - }; - Q_DECLARE_FLAGS(States, State) - - DRAudioDevice(); - ~DRAudioDevice(); - - DWORD get_id() const; - QString get_name() const; - QString get_driver() const; - States get_states() const; - bool is_state(State state) const; - bool is_enabled() const; - bool is_default() const; - bool is_init() const; - - bool operator==(const DRAudioDevice &other) const; - bool operator!=(const DRAudioDevice &other) const; - -private: - DWORD m_id; - QString m_name; - QString m_driver; - States m_states; -}; -Q_DECLARE_METATYPE(DRAudioDevice) +#pragma once + +#include +#include +#include + +#include + +#include + +class DRAudioDevice +{ +public: + static QVector get_device_list(); + + enum State : DWORD + { + SEnabled = BASS_DEVICE_ENABLED, + SDefault = BASS_DEVICE_DEFAULT, + SInit = BASS_DEVICE_INIT, + }; + Q_DECLARE_FLAGS(States, State) + + DRAudioDevice(); + ~DRAudioDevice(); + + DWORD get_id() const; + QString get_name() const; + QString get_driver() const; + States get_states() const; + bool is_state(State state) const; + bool is_enabled() const; + bool is_default() const; + bool is_init() const; + + bool operator==(const DRAudioDevice &other) const; + bool operator!=(const DRAudioDevice &other) const; + +private: + DWORD m_id; + QString m_name; + QString m_driver; + States m_states; +}; +Q_DECLARE_METATYPE(DRAudioDevice) diff --git a/include/draudioengine.h b/src/draudioengine.h similarity index 96% rename from include/draudioengine.h rename to src/draudioengine.h index 91d35666f..a930cc9be 100644 --- a/include/draudioengine.h +++ b/src/draudioengine.h @@ -1,41 +1,41 @@ -#pragma once - -#include "draudiostreamfamily.h" - -class DRAudioEngine : public QObject -{ - Q_OBJECT - -public: - static QVector get_device_list(); - static std::optional get_current_device(); - static QString get_favorite_device_driver(); - static std::optional get_favorite_device(); - static DRAudioStreamFamily::ptr get_family(DRAudio::Family p_family); - static QList get_family_list(); - static int32_t get_volume(); - static DRAudio::Options get_options(); - // option get - static bool is_option(DRAudio::Option p_option); - static bool is_suppressed(); - static bool is_suppress_background_audio(); - - DRAudioEngine(QObject *p_parent = nullptr); - ~DRAudioEngine(); - -public slots: - void set_favorite_device_driver(QString driver); - void set_volume(int32_t p_volume); - void set_options(DRAudio::Options p_options); - // option set - void set_option(DRAudio::Option p_option, bool p_enabled); - void set_suppressed(bool p_enabled); - void set_suppress_background_audio(bool p_enabled); - -signals: - void device_list_changed(QVector); - void current_device_changed(DRAudioDevice); - void favorite_device_changed(DRAudioDevice); - void volume_changed(int32_t); - void options_changed(DRAudio::Options); -}; +#pragma once + +#include "draudiostreamfamily.h" + +class DRAudioEngine : public QObject +{ + Q_OBJECT + +public: + static QVector get_device_list(); + static std::optional get_current_device(); + static QString get_favorite_device_driver(); + static std::optional get_favorite_device(); + static DRAudioStreamFamily::ptr get_family(DRAudio::Family p_family); + static QList get_family_list(); + static int32_t get_volume(); + static DRAudio::Options get_options(); + // option get + static bool is_option(DRAudio::Option p_option); + static bool is_suppressed(); + static bool is_suppress_background_audio(); + + DRAudioEngine(QObject *p_parent = nullptr); + ~DRAudioEngine(); + +public slots: + void set_favorite_device_driver(QString driver); + void set_volume(int32_t p_volume); + void set_options(DRAudio::Options p_options); + // option set + void set_option(DRAudio::Option p_option, bool p_enabled); + void set_suppressed(bool p_enabled); + void set_suppress_background_audio(bool p_enabled); + +signals: + void device_list_changed(QVector); + void current_device_changed(DRAudioDevice); + void favorite_device_changed(DRAudioDevice); + void volume_changed(int32_t); + void options_changed(DRAudio::Options); +}; diff --git a/include/draudioengine_p.h b/src/draudioengine_p.h similarity index 95% rename from include/draudioengine_p.h rename to src/draudioengine_p.h index e6129fe97..2420efbbd 100644 --- a/include/draudioengine_p.h +++ b/src/draudioengine_p.h @@ -1,44 +1,44 @@ -#pragma once - -#include -#include -#include - -#include "draudio.h" -#include "draudiostreamfamily.h" - -class QTimer; - -#include - -class DRAudioEnginePrivate : public QObject -{ - Q_OBJECT - -public: - using ptr = QPointer; - - QObjectList children; - QPointer engine; - - QPointer update_timer; - QVector device_list; - std::optional device; - QString favorite_device_driver; - std::optional favorite_device; - - int32_t volume = 0; - DRAudio::Options options; - QMap family_map; - - DRAudioEnginePrivate(); - ~DRAudioEnginePrivate(); - - void invoke_signal(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)); - -public slots: - void update_device_list(); - void update_current_device(); - void update_volume(); - void update_options(); -}; +#pragma once + +#include +#include +#include + +#include "draudio.h" +#include "draudiostreamfamily.h" + +class QTimer; + +#include + +class DRAudioEnginePrivate : public QObject +{ + Q_OBJECT + +public: + using ptr = QPointer; + + QObjectList children; + QPointer engine; + + QPointer update_timer; + QVector device_list; + std::optional device; + QString favorite_device_driver; + std::optional favorite_device; + + int32_t volume = 0; + DRAudio::Options options; + QMap family_map; + + DRAudioEnginePrivate(); + ~DRAudioEnginePrivate(); + + void invoke_signal(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)); + +public slots: + void update_device_list(); + void update_current_device(); + void update_volume(); + void update_options(); +}; diff --git a/include/draudioerror.h b/src/draudioerror.h similarity index 91% rename from include/draudioerror.h rename to src/draudioerror.h index 72be6ff27..b6b955643 100644 --- a/include/draudioerror.h +++ b/src/draudioerror.h @@ -1,15 +1,15 @@ -#pragma once - -#include - -class DRAudioError -{ -public: - DRAudioError(); - DRAudioError(QString p_error); - - QString what(); - -private: - QString m_error; -}; +#pragma once + +#include + +class DRAudioError +{ +public: + DRAudioError(); + DRAudioError(QString p_error); + + QString what(); + +private: + QString m_error; +}; diff --git a/include/draudiostream.h b/src/draudiostream.h similarity index 95% rename from include/draudiostream.h rename to src/draudiostream.h index d454ca6f8..2262800e0 100644 --- a/include/draudiostream.h +++ b/src/draudiostream.h @@ -1,90 +1,90 @@ -#pragma once - -#include "draudio.h" -#include "draudiodevice.h" -#include "draudioerror.h" - -#include - -#include -#include - -#include - -class DRAudioEngine; -class DRAudioEnginePrivate; -class DRAudioStreamFamily; - -class DRAudioStreamSync -{ -public: - HSYNC sync; - DWORD type; -}; - -class DRAudioStream : public QObject -{ - Q_OBJECT - - DRAudioStream(DRAudio::Family p_family); - -public: - using ptr = QSharedPointer; - - ~DRAudioStream(); - - DRAudio::Family get_family() const; - QString get_file_name() const; - bool is_repeatable() const; - bool is_playing() const; - -public slots: - std::optional set_file_name(QString m_file); - void set_volume(float p_volume); - void set_repeatable(bool); - void set_loop(quint64 start, quint64 end); - - void play(); - void stop(); - -signals: - void file_name_changed(QString p_file); - void finished(); - void looped(); - -public: - static void CALLBACK end_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata); - static void CALLBACK loop_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata); - -private: - friend class DRAudioStreamFamily; - - enum InitState - { - InitError, - InitNotDone, - InitFinished, - }; - - DRAudioEngine *m_engine = nullptr; - DRAudio::Family m_family; - QString m_file_name; - InitState m_init_state = InitNotDone; - HSTREAM m_hstream = 0; - float m_volume = 0.0f; - bool m_repeatable = false; - QWORD m_loop_start = 0; - QWORD m_loop_start_pos = 0; - QWORD m_loop_end = 0; - QWORD m_loop_end_pos = 0; - HSYNC m_loop_sync = 0; - - bool ensure_init(); - bool ensure_init() const; - void update_device(DRAudioDevice); - void init_loop(); - void seek_loop_start(); - -signals: - void device_error(QPrivateSignal); -}; +#pragma once + +#include "draudio.h" +#include "draudiodevice.h" +#include "draudioerror.h" + +#include + +#include +#include + +#include + +class DRAudioEngine; +class DRAudioEnginePrivate; +class DRAudioStreamFamily; + +class DRAudioStreamSync +{ +public: + HSYNC sync; + DWORD type; +}; + +class DRAudioStream : public QObject +{ + Q_OBJECT + + DRAudioStream(DRAudio::Family p_family); + +public: + using ptr = QSharedPointer; + + ~DRAudioStream(); + + DRAudio::Family get_family() const; + QString get_file_name() const; + bool is_repeatable() const; + bool is_playing() const; + +public slots: + std::optional set_file_name(QString m_file); + void set_volume(float p_volume); + void set_repeatable(bool); + void set_loop(quint64 start, quint64 end); + + void play(); + void stop(); + +signals: + void file_name_changed(QString p_file); + void finished(); + void looped(); + +public: + static void CALLBACK end_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata); + static void CALLBACK loop_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata); + +private: + friend class DRAudioStreamFamily; + + enum InitState + { + InitError, + InitNotDone, + InitFinished, + }; + + DRAudioEngine *m_engine = nullptr; + DRAudio::Family m_family; + QString m_file_name; + InitState m_init_state = InitNotDone; + HSTREAM m_hstream = 0; + float m_volume = 0.0f; + bool m_repeatable = false; + QWORD m_loop_start = 0; + QWORD m_loop_start_pos = 0; + QWORD m_loop_end = 0; + QWORD m_loop_end_pos = 0; + HSYNC m_loop_sync = 0; + + bool ensure_init(); + bool ensure_init() const; + void update_device(DRAudioDevice); + void init_loop(); + void seek_loop_start(); + +signals: + void device_error(QPrivateSignal); +}; diff --git a/include/draudiostreamfamily.h b/src/draudiostreamfamily.h similarity index 95% rename from include/draudiostreamfamily.h rename to src/draudiostreamfamily.h index 17f485c12..52118f3ac 100644 --- a/include/draudiostreamfamily.h +++ b/src/draudiostreamfamily.h @@ -1,69 +1,69 @@ -#pragma once - -#include "draudiostream.h" - -#include -#include -#include - -#include - -class DRAudioEngine; -class DRAudioEngineData; - -class DRAudioStreamFamily : public QObject -{ - Q_OBJECT - -public: - using ptr = QSharedPointer; - using stream_list = QVector; - - std::optional create_stream(QString p_file); - std::optional play_stream(QString p_file); - - // get - stream_list get_stream_list() const; - int32_t get_volume() const; - int32_t get_capacity() const; - DRAudio::Options get_options() const; - - // options get - bool is_suppressed() const; - bool is_ignore_suppression() const; - -public slots: - void set_volume(int32_t p_volume); - void set_capacity(int32_t p_capacity); - void set_options(DRAudio::Options p_options); - // options setter - void set_suppressed(bool p_enabled); - void set_ignore_suppression(bool p_enabled); - -signals: - void volume_changed(int32_t); - void capacity_changed(int32_t); - void options_changed(DRAudio::Options); - -private: - friend class DRAudioEngine; - friend class DRAudioEngineData; - friend class DRAudioEnginePrivate; - - DRAudio::Family m_family; - int32_t m_volume = 0; - int32_t m_capacity = 0; - DRAudio::Options m_options; - stream_list m_stream_list; - - DRAudioStreamFamily(DRAudio::Family p_family); - - float calculate_volume(); - - void update_capacity(); - void update_options(); - void update_volume(); - -private slots: - void on_stream_finished(); -}; +#pragma once + +#include "draudiostream.h" + +#include +#include +#include + +#include + +class DRAudioEngine; +class DRAudioEngineData; + +class DRAudioStreamFamily : public QObject +{ + Q_OBJECT + +public: + using ptr = QSharedPointer; + using stream_list = QVector; + + std::optional create_stream(QString p_file); + std::optional play_stream(QString p_file); + + // get + stream_list get_stream_list() const; + int32_t get_volume() const; + int32_t get_capacity() const; + DRAudio::Options get_options() const; + + // options get + bool is_suppressed() const; + bool is_ignore_suppression() const; + +public slots: + void set_volume(int32_t p_volume); + void set_capacity(int32_t p_capacity); + void set_options(DRAudio::Options p_options); + // options setter + void set_suppressed(bool p_enabled); + void set_ignore_suppression(bool p_enabled); + +signals: + void volume_changed(int32_t); + void capacity_changed(int32_t); + void options_changed(DRAudio::Options); + +private: + friend class DRAudioEngine; + friend class DRAudioEngineData; + friend class DRAudioEnginePrivate; + + DRAudio::Family m_family; + int32_t m_volume = 0; + int32_t m_capacity = 0; + DRAudio::Options m_options; + stream_list m_stream_list; + + DRAudioStreamFamily(DRAudio::Family p_family); + + float calculate_volume(); + + void update_capacity(); + void update_options(); + void update_volume(); + +private slots: + void on_stream_finished(); +}; diff --git a/include/draudiotrackmetadata.h b/src/draudiotrackmetadata.h similarity index 100% rename from include/draudiotrackmetadata.h rename to src/draudiotrackmetadata.h diff --git a/include/drcharactermovie.h b/src/drcharactermovie.h similarity index 100% rename from include/drcharactermovie.h rename to src/drcharactermovie.h diff --git a/include/drchatlog.h b/src/drchatlog.h similarity index 100% rename from include/drchatlog.h rename to src/drchatlog.h diff --git a/include/drdiscord.h b/src/drdiscord.h similarity index 100% rename from include/drdiscord.h rename to src/drdiscord.h diff --git a/include/dreffectmovie.h b/src/dreffectmovie.h similarity index 100% rename from include/dreffectmovie.h rename to src/dreffectmovie.h diff --git a/include/drmovie.h b/src/drmovie.h similarity index 100% rename from include/drmovie.h rename to src/drmovie.h diff --git a/include/drpacket.h b/src/drpacket.h similarity index 100% rename from include/drpacket.h rename to src/drpacket.h diff --git a/include/drpather.h b/src/drpather.h similarity index 100% rename from include/drpather.h rename to src/drpather.h diff --git a/include/drscenemovie.h b/src/drscenemovie.h similarity index 100% rename from include/drscenemovie.h rename to src/drscenemovie.h diff --git a/include/drserversocket.h b/src/drserversocket.h similarity index 100% rename from include/drserversocket.h rename to src/drserversocket.h diff --git a/include/drshoutmovie.h b/src/drshoutmovie.h similarity index 100% rename from include/drshoutmovie.h rename to src/drshoutmovie.h diff --git a/include/drsplashmovie.h b/src/drsplashmovie.h similarity index 100% rename from include/drsplashmovie.h rename to src/drsplashmovie.h diff --git a/include/drstickermovie.h b/src/drstickermovie.h similarity index 100% rename from include/drstickermovie.h rename to src/drstickermovie.h diff --git a/include/drtextedit.h b/src/drtextedit.h similarity index 100% rename from include/drtextedit.h rename to src/drtextedit.h diff --git a/include/drvideoscreen.h b/src/drvideoscreen.h similarity index 100% rename from include/drvideoscreen.h rename to src/drvideoscreen.h diff --git a/include/file_functions.h b/src/file_functions.h similarity index 100% rename from include/file_functions.h rename to src/file_functions.h diff --git a/include/hardware_functions.h b/src/hardware_functions.h similarity index 100% rename from include/hardware_functions.h rename to src/hardware_functions.h diff --git a/include/lobby.h b/src/lobby.h similarity index 100% rename from include/lobby.h rename to src/lobby.h diff --git a/include/logger.h b/src/logger.h similarity index 100% rename from include/logger.h rename to src/logger.h diff --git a/include/misc_functions.h b/src/misc_functions.h similarity index 100% rename from include/misc_functions.h rename to src/misc_functions.h diff --git a/include/theme.h b/src/theme.h similarity index 100% rename from include/theme.h rename to src/theme.h diff --git a/include/utils.h b/src/utils.h similarity index 100% rename from include/utils.h rename to src/utils.h diff --git a/include/version.h b/src/version.h similarity index 100% rename from include/version.h rename to src/version.h From 0983a86dc924ea52c06da1b447b48d8fc506d5dc Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 1 Apr 2022 08:01:01 +0200 Subject: [PATCH 636/842] Restructured 3rd-party folder and dependencies --- dronline-client.pro | 6 +++--- src/aoblipplayer.h | 2 +- src/draudio.cpp | 2 +- src/draudiodevice.cpp | 2 +- src/draudiodevice.h | 2 +- src/draudiostream.cpp | 4 ++-- src/draudiostream.h | 2 +- src/drdiscord.h | 2 +- src/version.cpp | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index e130b1f8a..1f0588e21 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -8,8 +8,8 @@ TARGET = dro-client RC_ICONS = icon.ico -INCLUDEPATH += $$PWD/include $$PWD/3rd $$PWD/3rd/include -DEPENDPATH += $$PWD/include +INCLUDEPATH += $$PWD/include $$PWD/3rd +DEPENDPATH += $$PWD/include $$PWD/3rd DEFINES += DRO_ACKMS CONFIG(debug, debug|release):DEFINES += DR_DEV @@ -145,7 +145,7 @@ SOURCES += \ # 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers # in the same way as BASS. Discord RPC uses CMake, which does not play nicely with # QMake, so this step must be manual. -LIBS += -L$$PWD/3rd/$${QMAKE_HOST.arch} -lbass -lbassopus -ldiscord-rpc +LIBS += -L$$PWD/3rd -lbass -lbassopus -ldiscord-rpc RESOURCES += \ res.qrc diff --git a/src/aoblipplayer.h b/src/aoblipplayer.h index 973196563..584983558 100644 --- a/src/aoblipplayer.h +++ b/src/aoblipplayer.h @@ -4,7 +4,7 @@ #include "draudioengine.h" // 3rd -#include +#include // std #include diff --git a/src/draudio.cpp b/src/draudio.cpp index bf968c599..44f95b4b3 100644 --- a/src/draudio.cpp +++ b/src/draudio.cpp @@ -1,6 +1,6 @@ #include "draudio.h" -#include +#include #include diff --git a/src/draudiodevice.cpp b/src/draudiodevice.cpp index ca3844d30..8a878e5fc 100644 --- a/src/draudiodevice.cpp +++ b/src/draudiodevice.cpp @@ -2,7 +2,7 @@ #include -#include +#include QVector DRAudioDevice::get_device_list() { diff --git a/src/draudiodevice.h b/src/draudiodevice.h index ef4c4e890..1e5ecb5ca 100644 --- a/src/draudiodevice.h +++ b/src/draudiodevice.h @@ -6,7 +6,7 @@ #include -#include +#include class DRAudioDevice { diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index a8f84c9ee..e98512694 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -4,8 +4,8 @@ #include #include -#include -#include +#include +#include #include "draudioengine.h" #include "draudiostreamfamily.h" diff --git a/src/draudiostream.h b/src/draudiostream.h index 2262800e0..8d72942a3 100644 --- a/src/draudiostream.h +++ b/src/draudiostream.h @@ -4,7 +4,7 @@ #include "draudiodevice.h" #include "draudioerror.h" -#include +#include #include #include diff --git a/src/drdiscord.h b/src/drdiscord.h index 29e591a1e..14dbf1cf4 100644 --- a/src/drdiscord.h +++ b/src/drdiscord.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/src/version.cpp b/src/version.cpp index a04e272fd..6183e8425 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -1,6 +1,6 @@ #include "version.h" -#include +#include #include #include From aa466fb6a5d44995f54485541b5c8e8a385ed7be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Fri, 1 Apr 2022 08:13:36 +0200 Subject: [PATCH 637/842] Updated workflow to reflect 3rd-party restructure --- .github/workflows/build-all.yml | 60 ++++++++++++++++----------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 7c41d984c..412866bfe 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -25,8 +25,8 @@ jobs: run: | echo "parentworkspace=D:\\a\\DRO-Client\\" >> $GITHUB_ENV mkdir "3rd" - mkdir "3rd/include" - mkdir "3rd/x86" + mkdir "3rd/bass" + mkdir "3rd/discord-rpc" - name: Fetch Discord external library shell: bash @@ -34,10 +34,10 @@ jobs: run: | curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o discord_rpc_win.zip unzip discord_rpc_win.zip - cp ./discord-rpc/win32-dynamic/include/discord_register.h ./DRO-Client/3rd/include - cp ./discord-rpc/win32-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include - cp ./discord-rpc/win32-dynamic/bin/discord-rpc.dll ./DRO-Client/3rd/x86 - cp ./discord-rpc/win32-dynamic/lib/discord-rpc.lib ./DRO-Client/3rd/x86 + cp ./discord-rpc/win32-dynamic/include/discord_register.h ./DRO-Client/3rd/discord-rpc + cp ./discord-rpc/win32-dynamic/include/discord_rpc.h ./DRO-Client/3rd/discord-rpc + cp ./discord-rpc/win32-dynamic/bin/discord-rpc.dll ./DRO-Client/3rd + cp ./discord-rpc/win32-dynamic/lib/discord-rpc.lib ./DRO-Client/3rd - name: Fetch Bass external library shell: bash @@ -45,9 +45,9 @@ jobs: run: | curl http://www.un4seen.com/files/bass24.zip -o bass.zip unzip bass.zip - cp ./c/bass.h ./DRO-Client/3rd/include - cp ./bass.dll ./DRO-Client/3rd/x86 - cp ./c/bass.lib ./DRO-Client/3rd/x86 + cp ./c/bass.h ./DRO-Client/3rd/bass + cp ./bass.dll ./DRO-Client/3rd + cp ./c/bass.lib ./DRO-Client/3rd - name: Fetch BassOpus external library shell: bash @@ -55,9 +55,9 @@ jobs: run: | curl http://www.un4seen.com/files/bassopus24.zip -o bassopus.zip unzip bassopus.zip - cp ./c/bassopus.h ./DRO-Client/3rd/include - cp ./bassopus.dll ./DRO-Client/3rd/x86 - cp ./c/bassopus.lib ./DRO-Client/3rd/x86 + cp ./c/bassopus.h ./DRO-Client/3rd/bass + cp ./bassopus.dll ./DRO-Client/3rd + cp ./c/bassopus.lib ./DRO-Client/3rd - name: Fetch QtApng external library shell: bash @@ -171,8 +171,8 @@ jobs: run: | echo "parentworkspace=/Users/runner/work/DRO-Client/" >> $GITHUB_ENV mkdir "3rd" - mkdir "3rd/include" - mkdir "3rd/x86_64" + mkdir "3rd/bass" + mkdir "3rd/discord-rpc" - name: Fetch Discord external library shell: bash @@ -180,9 +180,9 @@ jobs: run: | curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip -o discord_rpc_osx.zip unzip discord_rpc_osx.zip - cp ./discord-rpc/osx-dynamic/include/discord_register.h ./DRO-Client/3rd/include - cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include - cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./DRO-Client/3rd/x86_64 + cp ./discord-rpc/osx-dynamic/include/discord_register.h ./DRO-Client/3rd/discord-rpc + cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./DRO-Client/3rd/discord-rpc + cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./DRO-Client/3rd - name: Fetch Bass external library shell: bash @@ -190,8 +190,8 @@ jobs: run: | curl http://www.un4seen.com/files/bass24-osx.zip -o bass.zip unzip bass.zip - cp ./bass.h ./DRO-Client/3rd/include - cp ./libbass.dylib ./DRO-Client/3rd/x86_64 + cp ./bass.h ./DRO-Client/3rd/bass + cp ./libbass.dylib ./DRO-Client/3rd - name: Fetch BassOpus external library shell: bash @@ -199,8 +199,8 @@ jobs: run: | curl http://www.un4seen.com/files/bassopus24-osx.zip -o bassopus.zip unzip bassopus.zip - cp ./bassopus.h ./DRO-Client/3rd/include - cp ./libbassopus.dylib ./DRO-Client/3rd/x86_64 + cp ./bassopus.h ./DRO-Client/3rd/bass + cp ./libbassopus.dylib ./DRO-Client/3rd - name: Fetch QtApng external library shell: bash @@ -298,8 +298,8 @@ jobs: run: | echo "parentworkspace=/home/runner/work/DRO-Client/" >> $GITHUB_ENV mkdir "3rd" - mkdir "3rd/include" - mkdir "3rd/x86_64" + mkdir "3rd/bass" + mkdir "3rd/discord-rpc" - name: Fetch Discord external library shell: bash @@ -307,9 +307,9 @@ jobs: run: | curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip unzip discord_rpc_linux.zip - cp ./discord-rpc/linux-dynamic/include/discord_register.h ./DRO-Client/3rd/include/discord_register.h - cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include/discord_rpc.h - cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./DRO-Client/3rd/x86_64/libdiscord-rpc.so + cp ./discord-rpc/linux-dynamic/include/discord_register.h ./DRO-Client/3rd/discord-rpc/discord_register.h + cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./DRO-Client/3rd/discord-rpc/discord_rpc.h + cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./DRO-Client/3rd/libdiscord-rpc.so - name: Fetch Bass external library shell: bash @@ -317,8 +317,8 @@ jobs: run: | curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip unzip bass.zip - cp ./bass.h ./DRO-Client/3rd/include/bass.h - cp ./x64/libbass.so ./DRO-Client/3rd/x86_64/libbass.so + cp ./bass.h ./DRO-Client/3rd/bass/bass.h + cp ./x64/libbass.so ./DRO-Client/3rd/libbass.so - name: Fetch BassOpus external library shell: bash @@ -326,8 +326,8 @@ jobs: run: | curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip unzip bassopus.zip - cp ./bassopus.h ./DRO-Client/3rd/include/bassopus.h - cp ./x64/libbassopus.so ./DRO-Client/3rd/x86_64/libbassopus.so + cp ./bassopus.h ./DRO-Client/3rd/bass/bassopus.h + cp ./x64/libbassopus.so ./DRO-Client/3rd/libbassopus.so - name: Install Qt shell: bash From a650d9d70225be7e7ad7086255f1ffc80d45d6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Fri, 1 Apr 2022 08:15:55 +0200 Subject: [PATCH 638/842] Updated workflow to reflect 3rd-party restructure --- .github/workflows/build-all-merge-master.yml | 60 ++++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/build-all-merge-master.yml b/.github/workflows/build-all-merge-master.yml index 36c7085fc..263f4b824 100644 --- a/.github/workflows/build-all-merge-master.yml +++ b/.github/workflows/build-all-merge-master.yml @@ -38,8 +38,8 @@ jobs: run: | echo "parentworkspace=D:\\a\\DRO-Client\\" >> $GITHUB_ENV mkdir "3rd" - mkdir "3rd/include" - mkdir "3rd/x86" + mkdir "3rd/bass" + mkdir "3rd/discord-rpc" - name: Fetch Discord external library shell: bash @@ -47,10 +47,10 @@ jobs: run: | curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o discord_rpc_win.zip unzip discord_rpc_win.zip - cp ./discord-rpc/win32-dynamic/include/discord_register.h ./DRO-Client/3rd/include - cp ./discord-rpc/win32-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include - cp ./discord-rpc/win32-dynamic/bin/discord-rpc.dll ./DRO-Client/3rd/x86 - cp ./discord-rpc/win32-dynamic/lib/discord-rpc.lib ./DRO-Client/3rd/x86 + cp ./discord-rpc/win32-dynamic/include/discord_register.h ./DRO-Client/3rd/discord-rpc + cp ./discord-rpc/win32-dynamic/include/discord_rpc.h ./DRO-Client/3rd/discord-rpc + cp ./discord-rpc/win32-dynamic/bin/discord-rpc.dll ./DRO-Client/3rd + cp ./discord-rpc/win32-dynamic/lib/discord-rpc.lib ./DRO-Client/3rd - name: Fetch Bass external library shell: bash @@ -58,9 +58,9 @@ jobs: run: | curl http://www.un4seen.com/files/bass24.zip -o bass.zip unzip bass.zip - cp ./c/bass.h ./DRO-Client/3rd/include - cp ./bass.dll ./DRO-Client/3rd/x86 - cp ./c/bass.lib ./DRO-Client/3rd/x86 + cp ./c/bass.h ./DRO-Client/3rd/bass + cp ./bass.dll ./DRO-Client/3rd + cp ./c/bass.lib ./DRO-Client/3rd - name: Fetch BassOpus external library shell: bash @@ -68,9 +68,9 @@ jobs: run: | curl http://www.un4seen.com/files/bassopus24.zip -o bassopus.zip unzip bassopus.zip - cp ./c/bassopus.h ./DRO-Client/3rd/include - cp ./bassopus.dll ./DRO-Client/3rd/x86 - cp ./c/bassopus.lib ./DRO-Client/3rd/x86 + cp ./c/bassopus.h ./DRO-Client/3rd/discord-rpc + cp ./bassopus.dll ./DRO-Client/3rd + cp ./c/bassopus.lib ./DRO-Client/3rd - name: Fetch QtApng external library shell: bash @@ -194,8 +194,8 @@ jobs: run: | echo "parentworkspace=/Users/runner/work/DRO-Client/" >> $GITHUB_ENV mkdir "3rd" - mkdir "3rd/include" - mkdir "3rd/x86_64" + mkdir "3rd/bass" + mkdir "3rd/discord-rpc" - name: Fetch Discord external library shell: bash @@ -203,9 +203,9 @@ jobs: run: | curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip -o discord_rpc_osx.zip unzip discord_rpc_osx.zip - cp ./discord-rpc/osx-dynamic/include/discord_register.h ./DRO-Client/3rd/include - cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include - cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./DRO-Client/3rd/x86_64 + cp ./discord-rpc/osx-dynamic/include/discord_register.h ./DRO-Client/3rd/discord-rpc + cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./DRO-Client/3rd/discord-rpc + cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./DRO-Client/3rd - name: Fetch Bass external library shell: bash @@ -213,8 +213,8 @@ jobs: run: | curl http://www.un4seen.com/files/bass24-osx.zip -o bass.zip unzip bass.zip - cp ./bass.h ./DRO-Client/3rd/include - cp ./libbass.dylib ./DRO-Client/3rd/x86_64 + cp ./bass.h ./DRO-Client/3rd/bass + cp ./libbass.dylib ./DRO-Client/3rd - name: Fetch BassOpus external library shell: bash @@ -222,8 +222,8 @@ jobs: run: | curl http://www.un4seen.com/files/bassopus24-osx.zip -o bassopus.zip unzip bassopus.zip - cp ./bassopus.h ./DRO-Client/3rd/include - cp ./libbassopus.dylib ./DRO-Client/3rd/x86_64 + cp ./bassopus.h ./DRO-Client/3rd/bass + cp ./libbassopus.dylib ./DRO-Client/3rd - name: Fetch QtApng external library shell: bash @@ -331,8 +331,8 @@ jobs: run: | echo "parentworkspace=/home/runner/work/DRO-Client/" >> $GITHUB_ENV mkdir "3rd" - mkdir "3rd/include" - mkdir "3rd/x86_64" + mkdir "3rd/bass" + mkdir "3rd/discord-rpc" - name: Fetch Discord external library shell: bash @@ -340,9 +340,9 @@ jobs: run: | curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip unzip discord_rpc_linux.zip - cp ./discord-rpc/linux-dynamic/include/discord_register.h ./DRO-Client/3rd/include/discord_register.h - cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include/discord_rpc.h - cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./DRO-Client/3rd/x86_64/libdiscord-rpc.so + cp ./discord-rpc/linux-dynamic/include/discord_register.h ./DRO-Client/3rd/discord-rpc/discord_register.h + cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./DRO-Client/3rd/discord-rpc/discord_rpc.h + cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./DRO-Client/3rd/libdiscord-rpc.so - name: Fetch Bass external library shell: bash @@ -350,8 +350,8 @@ jobs: run: | curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip unzip bass.zip - cp ./bass.h ./DRO-Client/3rd/include/bass.h - cp ./x64/libbass.so ./DRO-Client/3rd/x86_64/libbass.so + cp ./bass.h ./DRO-Client/3rd/bass/bass.h + cp ./x64/libbass.so ./DRO-Client/3rd/libbass.so - name: Fetch BassOpus external library shell: bash @@ -359,8 +359,8 @@ jobs: run: | curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip unzip bassopus.zip - cp ./bassopus.h ./DRO-Client/3rd/include/bassopus.h - cp ./x64/libbassopus.so ./DRO-Client/3rd/x86_64/libbassopus.so + cp ./bassopus.h ./DRO-Client/3rd/bass/bassopus.h + cp ./x64/libbassopus.so ./DRO-Client/3rd/libbassopus.so - name: Install Qt shell: bash From 378922bc89877bc5b69b5a9ca4b88b7914d63fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Fri, 1 Apr 2022 08:18:40 +0200 Subject: [PATCH 639/842] Moved bassopus.h to the right folder --- .github/workflows/build-all-merge-master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-all-merge-master.yml b/.github/workflows/build-all-merge-master.yml index 263f4b824..73e506f44 100644 --- a/.github/workflows/build-all-merge-master.yml +++ b/.github/workflows/build-all-merge-master.yml @@ -68,7 +68,7 @@ jobs: run: | curl http://www.un4seen.com/files/bassopus24.zip -o bassopus.zip unzip bassopus.zip - cp ./c/bassopus.h ./DRO-Client/3rd/discord-rpc + cp ./c/bassopus.h ./DRO-Client/3rd/bass cp ./bassopus.dll ./DRO-Client/3rd cp ./c/bassopus.lib ./DRO-Client/3rd From a9fb62d4cd9a1f7f899e57d11020c3dcf5e3aab1 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 1 Apr 2022 20:04:46 -0400 Subject: [PATCH 640/842] Add trivial lookup checks to avoid unnecessary file IO penalties --- src/aoimagedisplay.cpp | 19 +++------ src/courtroom.cpp | 3 +- src/drcharactermovie.cpp | 91 +++++++++++++++++++++++----------------- src/drcharactermovie.h | 3 +- 4 files changed, 61 insertions(+), 55 deletions(-) diff --git a/src/aoimagedisplay.cpp b/src/aoimagedisplay.cpp index 61be43baf..ce4a40ffc 100644 --- a/src/aoimagedisplay.cpp +++ b/src/aoimagedisplay.cpp @@ -23,31 +23,22 @@ void AOImageDisplay::set_image(QString p_image) const QString l_path = ao_app->find_theme_asset_path(p_image); AOPixmap f_pixmap(l_path); this->setPixmap(f_pixmap.scale(size())); - - // Store final path if the path exists - if (file_exists(l_path)) - m_image = l_path; - else - m_image = ""; + m_image = l_path; } void AOImageDisplay::set_image_from_path(QString p_path) { - QString default_path = ao_app->find_theme_asset_path("chatmed.png"); - QString final_path; if (file_exists(p_path)) final_path = p_path; else + { + QString default_path = ao_app->find_theme_asset_path("chatmed.png"); final_path = default_path; + } AOPixmap f_pixmap(final_path); this->setPixmap(f_pixmap.scale(size())); - - // Store final path if the path exists - if (file_exists(final_path)) - m_image = final_path; - else - m_image = ""; + m_image = final_path; } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6ae799b9a..7cba22540 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -909,7 +909,6 @@ void Courtroom::handle_chatmessage_3() ui_vp_player_char->stop(); const QString f_char = m_chatmessage[CMChrName]; const QString f_emote = m_chatmessage[CMEmote]; - const bool l_hide_emote = (f_emote == "../../misc/blank"); QString path; if (!chatmessage_is_empty && ao_app->read_theme_ini_bool("enable_showname_image", COURTROOM_CONFIG_INI)) @@ -941,7 +940,7 @@ void Courtroom::handle_chatmessage_3() ui_vp_showname_image->hide(); } - ui_vp_player_char->setHidden(l_hide_emote); + ui_vp_player_char->show(); switch (f_anim_state) { case 2: diff --git a/src/drcharactermovie.cpp b/src/drcharactermovie.cpp index 6546759df..fa147180d 100644 --- a/src/drcharactermovie.cpp +++ b/src/drcharactermovie.cpp @@ -14,68 +14,83 @@ DRCharacterMovie::DRCharacterMovie(QWidget *parent) : DRMovie(parent), ao_app(dy DRCharacterMovie::~DRCharacterMovie() {} -void DRCharacterMovie::play(QString p_character, QString p_emote, QString p_prefix, bool p_play_once) +bool DRCharacterMovie::is_playing_empty(QString p_character, QString p_emote) { - QStringList l_filelist; - QStringList l_blacklist; - for (const QString &i_characterName : ao_app->get_char_include_tree(p_character)) - { - l_blacklist.append({ - ao_app->get_character_path(i_characterName, "char_icon.png"), - ao_app->get_character_path(i_characterName, "showname.png"), - ao_app->get_character_path(i_characterName, "emotions"), - }); + if (p_character == "") + return true; + if (p_emote.isEmpty()) + return true; + if (p_emote == "-" || p_emote == "0" || p_emote == "../../misc/blank") + return true; + return false; +} - if (!p_play_once) +void DRCharacterMovie::play(QString p_character, QString p_emote, QString p_prefix, bool p_play_once, bool p_hide_if_empty) +{ + QString l_file; + const bool playing_empty = is_playing_empty(p_character, p_emote); + if (!playing_empty) + { + QStringList l_filelist; + QStringList l_blacklist; + for (const QString &i_characterName : ao_app->get_char_include_tree(p_character)) { - l_filelist.append(ao_app->get_character_path(i_characterName, QString("%1%2").arg(p_prefix, p_emote))); - } - - l_filelist.append(ao_app->get_character_path(i_characterName, p_emote)); - } + l_blacklist.append({ + ao_app->get_character_path(i_characterName, "char_icon.png"), + ao_app->get_character_path(i_characterName, "showname.png"), + ao_app->get_character_path(i_characterName, "emotions"), + }); - QString l_file = ao_app->find_asset_path(l_filelist, animated_or_static_extensions()); - if (l_file.isEmpty() && !p_play_once) - { - l_file = ao_app->find_theme_asset_path("placeholder", animated_extensions()); - } + if (!p_play_once) + { + l_filelist.append(ao_app->get_character_path(i_characterName, QString("%1%2").arg(p_prefix, p_emote))); + } - for (const QString &i_blackened : qAsConst(l_blacklist)) - { - if (l_file == i_blackened) - { - l_file.clear(); - break; + l_filelist.append(ao_app->get_character_path(i_characterName, p_emote)); } - } - if (l_file.isEmpty()) - { - qWarning() << "error: character animation not found" - << "character:" << p_character << "emote:" << p_emote << "prefix:" << p_prefix; + l_file = ao_app->find_asset_path(l_filelist, animated_or_static_extensions()); + if (l_file.isEmpty() && !p_play_once) + { + l_file = ao_app->find_theme_asset_path("placeholder", animated_extensions()); + } + + for (const QString &i_blackened : qAsConst(l_blacklist)) + { + if (l_file == i_blackened) + { + l_file.clear(); + break; + } + } + + if (l_file.isEmpty()) + { + qWarning() << "error: character animation not found" + << "character:" << p_character << "emote:" << p_emote << "prefix:" << p_prefix; + } } set_file_name(l_file); set_play_once(p_play_once); - start(); - - if (p_character == "") + if (l_file.isEmpty() && p_hide_if_empty) { hide(); } + start(); } void DRCharacterMovie::play_pre(QString p_character, QString p_emote) { - play(p_character, p_emote, nullptr, true); + play(p_character, p_emote, nullptr, true, false); } void DRCharacterMovie::play_talk(QString p_character, QString p_emote) { - play(p_character, p_emote, "(b)", false); + play(p_character, p_emote, "(b)", false, true); } void DRCharacterMovie::play_idle(QString p_character, QString p_emote) { - play(p_character, p_emote, "(a)", false); + play(p_character, p_emote, "(a)", false, true); } diff --git a/src/drcharactermovie.h b/src/drcharactermovie.h index aeac0f092..5313d1c5a 100644 --- a/src/drcharactermovie.h +++ b/src/drcharactermovie.h @@ -12,7 +12,8 @@ class DRCharacterMovie : public DRMovie explicit DRCharacterMovie(QWidget *parent = nullptr); ~DRCharacterMovie(); - void play(QString character, QString emote, QString prefix, bool play_once); + bool is_playing_empty(QString p_character, QString p_emote); + void play(QString character, QString emote, QString prefix, bool play_once, bool p_hide_if_empty); void play_pre(QString character, QString emote); void play_talk(QString character, QString emote); void play_idle(QString character, QString emote); From 35c15956ab718f8a67399570975f7a4cc05ee9ae Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 1 Apr 2022 20:14:13 -0400 Subject: [PATCH 641/842] Remove unnecessary file_exists calls linked to find_theme_asset_path --- src/aobutton.cpp | 26 +++++++++++--------------- src/courtroom.cpp | 2 +- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/aobutton.cpp b/src/aobutton.cpp index 4c7da1674..07b20fb5f 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -25,25 +25,21 @@ void AOButton::set_image(QString p_image) { m_image_stem = p_image; m_image = ao_app->find_theme_asset_path(p_image); - // Get the path of the found image without the extension - const QString l_image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); - const QString l_hover_image = ao_app->find_theme_asset_path(l_image_name + "_hover.png"); - - if (file_exists(m_image)) + if (!file_exists(m_image)) { - if (file_exists(l_hover_image)) - this->setStyleSheet("QPushButton {border-image:url(\"" + m_image + - "\");}" - "QPushButton:hover {border-image:url(\"" + - l_hover_image + "\");}"); - else - this->setStyleSheet("border-image:url(\"" + m_image + "\")"); + m_image = ""; } - else + + // Get the path of the found image without the extension + const QString l_image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); + QString l_hover_image = ao_app->find_theme_asset_path(l_image_name + "_hover.png"); + if (l_hover_image.isEmpty()) { - m_image = ""; - this->setStyleSheet("border-image:url(\"" + m_image + "\")"); + l_hover_image = m_image; } + + this->setStyleSheet("QPushButton {border-image:url(\"" + m_image + "\");}" + "QPushButton:hover {border-image:url(\"" + l_hover_image + "\");}"); } void AOButton::refresh_image() diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 7cba22540..da1ead00e 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -845,7 +845,7 @@ void Courtroom::handle_chatmessage_2() // handles IC if (ao_config->log_display_self_highlight_enabled() && m_chatmessage[CMChrId].toInt() == m_chr_id) { const QString l_chatbox_self_name = "chatmed_self.png"; - if (file_exists(ao_app->find_theme_asset_path(l_chatbox_self_name))) + if (!ao_app->find_theme_asset_path(l_chatbox_self_name).isEmpty()) l_chatbox_name = l_chatbox_self_name; } From 61aa8cc457d19eba1452c349a5363981cdbd28cf Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 1 Apr 2022 20:24:15 -0400 Subject: [PATCH 642/842] Remove unnecessary button reloading --- src/courtroom.cpp | 1 - src/courtroom_widgets.cpp | 7 ------- 2 files changed, 8 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index da1ead00e..03cbceac6 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -676,7 +676,6 @@ void Courtroom::handle_acknowledged_ms() reset_shout_buttons(); reset_effect_buttons(); - reset_wtce_buttons(); clear_sfx_selection(); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 9c1aabbbe..de35efdd9 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -174,23 +174,16 @@ void Courtroom::create_widgets() ui_defense_bar = new AOImageDisplay(this, ao_app); ui_prosecution_bar = new AOImageDisplay(this, ao_app); - load_shouts(); // Readds from theme, deletes old shouts if needed and creates - // new ones - ui_shout_up = new AOButton(this, ao_app); ui_shout_up->setProperty("cycle_id", 1); ui_shout_down = new AOButton(this, ao_app); ui_shout_down->setProperty("cycle_id", 0); - load_effects(); - ui_effect_down = new AOButton(this, ao_app); ui_effect_down->setProperty("cycle_id", 2); ui_effect_up = new AOButton(this, ao_app); ui_effect_up->setProperty("cycle_id", 3); - load_wtce(); - ui_wtce_up = new AOButton(this, ao_app); ui_wtce_up->setProperty("cycle_id", 5); ui_wtce_down = new AOButton(this, ao_app); From a8a2932a9f81ee3cd5de29b8bbbf0d9f2df23ab5 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 1 Apr 2022 20:33:30 -0400 Subject: [PATCH 643/842] Implicitly assume all Qt versions are 5.12 or above --- README.md | 6 +++--- src/emotes.cpp | 7 +------ src/main.cpp | 3 --- src/theme.cpp | 9 --------- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 60e149838..4c9a5fe80 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ This is the official client used, it is a derivative of [Attorney-Online-Client-Remake](https://github.com/AttorneyOnline/AO2-Client). ## Qt -This project uses Qt 5.14.2, which is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.txt) with [certain licensing restrictions and exceptions](https://www.qt.io/qt-licensing-terms/). To comply with licensing requirements for static linking, object code is available if you would like to relink with an alternative version of Qt, and the source code for Qt may be found at https://github.com/qt/qtbase, http://code.qt.io/cgit/, or at https://qt.io. +This project uses Qt 5.15.2 (5.12.8 for Linux), which is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.txt) with [certain licensing restrictions and exceptions](https://www.qt.io/qt-licensing-terms/). To comply with licensing requirements for static linking, object code is available if you would like to relink with an alternative version of Qt, and the source code for Qt may be found at https://github.com/qt/qtbase, http://code.qt.io/cgit/, or at https://qt.io. -Copyright (C) 2016 The Qt Company Ltd. +Copyright (C) 2022 The Qt Company Ltd. ## BASS This project uses [BASS shared library](http://www.un4seen.com/). -Copyright (c) 1999-2016 Un4seen Developments Ltd. All rights reserved. +Copyright (c) 1999-2022 Un4seen Developments Ltd. All rights reserved. diff --git a/src/emotes.cpp b/src/emotes.cpp index b3660b7fa..615a89054 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -12,13 +12,8 @@ #include #include #include -#include - -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) -#include -#else #include -#endif +#include void Courtroom::construct_emotes() { diff --git a/src/main.cpp b/src/main.cpp index abf36084a..36db79dc5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,8 +9,6 @@ int main(int argc, char *argv[]) qInstallMessageHandler(logger::log); qInfo() << "Starting Danganronpa Online..."; - // High-DPI support is for Qt version >=5.6. -#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) bool l_dpi_scaling = true; for (int i = 0; i < argc; ++i) @@ -36,7 +34,6 @@ int main(int argc, char *argv[]) QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, false); } -#endif #ifdef Q_OS_MACOS { // MacOS diff --git a/src/theme.cpp b/src/theme.cpp index 1770dd6c0..0bb3b4df3 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -6,12 +6,7 @@ // qt #include #include - -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) -#include -#else #include -#endif // src #include "aoapplication.h" @@ -123,14 +118,10 @@ void center_widget_to_screen(QWidget *p_widget) if (!p_widget || p_widget->parentWidget()) return; -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - QRect screen_geometry = QApplication::desktop()->screenGeometry(); -#else QScreen *screen = QApplication::screenAt(p_widget->pos()); if (screen == nullptr) return; QRect screen_geometry = screen->geometry(); -#endif int x = (screen_geometry.width() - p_widget->width()) / 2; int y = (screen_geometry.height() - p_widget->height()) / 2; p_widget->move(x, y); From 8b8569863785269381f94c6f7dd480419b75577c Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 1 Apr 2022 20:34:52 -0400 Subject: [PATCH 644/842] Finish previous commit --- src/emotes.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/emotes.cpp b/src/emotes.cpp index 615a89054..89e543054 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -217,14 +217,10 @@ void Courtroom::show_emote_tooltip(int p_id, QPoint p_global_pos) ui_emote_preview_character->set_mirrored(ui_flip->isChecked()); ui_emote_preview_character->play_idle(l_emote.character, l_emote.dialog); -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - QRect l_screen_geometry = QApplication::desktop()->screenGeometry(); -#else QScreen *screen = QApplication::screenAt(p_global_pos); if (screen == nullptr) return; QRect l_screen_geometry = screen->geometry(); -#endif // position below cursor const int l_vertical_spacing = 8; From 8d0d23aefc8796ecfdc1299a2d3c320831d89f4d Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 1 Apr 2022 21:40:04 -0400 Subject: [PATCH 645/842] Optimize effect file IO access --- src/courtroom.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 03cbceac6..f793c1a47 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -736,8 +736,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) QString f_showname; qDebug() << "handle_chatmessage"; - // We actually DO wanna fail here if the showname is empty but the system is - // speaking. + // We actually DO wanna fail here if the showname is empty but the system is speaking. // Having an empty showname for system is actually what we expect. if (m_chatmessage[CMShowName].isEmpty() && !is_system_speaking) { @@ -889,7 +888,6 @@ void Courtroom::handle_chatmessage_3() qDebug() << "handle_chatmessage_3"; setup_chat(); - const QString f_side = m_chatmessage[CMPosition]; int f_anim_state = 0; // BLUE is from an enum in datatypes.h @@ -962,28 +960,28 @@ void Courtroom::handle_chatmessage_3() } int effect = m_chatmessage[CMEffectState].toInt(); - QStringList offset = ao_app->get_effect_offset(f_char, effect); - ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), ui_viewport->y() + offset.at(1).toInt()); - - QStringList overlay = ao_app->get_overlay(f_char, effect); - QString overlay_name = overlay.at(0); - QString overlay_sfx = overlay.at(1); if (effect > 0 && effect <= ui_effects.size() && effect_names.size() > 0) // check to prevent crashing { + QStringList offset = ao_app->get_effect_offset(f_char, effect); + ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), ui_viewport->y() + offset.at(1).toInt()); + QString s_eff = effect_names.at(effect - 1); QStringList f_eff = ao_app->get_effect(effect); - // QString s_eff = f_eff.at(0).trimmed(); bool once = f_eff.at(1).trimmed().toInt(); + QStringList overlay = ao_app->get_overlay(f_char, effect); + QString overlay_name = overlay.at(0); + QString overlay_sfx = overlay.at(1); + if (overlay_sfx == "") overlay_sfx = ao_app->get_sfx(s_eff); - // qDebug() << overlay_sfx << ao_app->get_sfx(s_eff); m_effects_player->play_effect(overlay_sfx); - ui_vp_effect->set_play_once(once); + if (overlay_name == "") overlay_name = s_eff; + ui_vp_effect->set_play_once(once); ui_vp_effect->play(overlay_name, f_char); } @@ -1282,7 +1280,6 @@ void Courtroom::setup_chat() QString f_gender = ao_app->get_gender(m_chatmessage[CMChrName]); - // m_blip_player->set_file(f_gender); m_blips_player->set_blips("sfx-blip" + f_gender + ".wav"); // means text is currently ticking From 3f9b343406df1722b195e3cc15822af8604aafd9 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 2 Apr 2022 10:40:08 +0200 Subject: [PATCH 646/842] Fixed small icon appearing out of nowhere when two widgets are crossing each others under a cursor --- src/aoemotebutton.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index 97310c47f..aec660a1b 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -89,8 +89,7 @@ void AOEmoteButton::paintEvent(QPaintEvent *event) } QPainter l_painter(this); - l_painter.drawImage(event->rect(), - m_texture.scaled(event->rect().size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + l_painter.drawImage(rect(), m_texture.scaled(size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); l_painter.end(); } From 3431f6abc324a78416ea6e0eed333ccd1af8a08c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 3 Apr 2022 15:58:50 +0200 Subject: [PATCH 647/842] Introduced HideCharacter to MS, misc optimizations --- src/aobutton.cpp | 10 +- src/aocharbutton.cpp | 2 +- src/aoimagedisplay.cpp | 27 +++-- src/aoimagedisplay.h | 3 +- src/charselect.cpp | 2 +- src/courtroom.cpp | 208 ++++++++++++++++++-------------------- src/courtroom.h | 5 +- src/courtroom_widgets.cpp | 30 +++--- src/datatypes.h | 9 ++ src/drcharactermovie.cpp | 90 +++++++---------- src/drcharactermovie.h | 3 +- src/file_functions.cpp | 13 ++- src/file_functions.h | 3 +- src/lobby.cpp | 4 +- src/main.cpp | 3 +- 15 files changed, 196 insertions(+), 216 deletions(-) diff --git a/src/aobutton.cpp b/src/aobutton.cpp index 07b20fb5f..fb1b443dd 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -25,10 +25,6 @@ void AOButton::set_image(QString p_image) { m_image_stem = p_image; m_image = ao_app->find_theme_asset_path(p_image); - if (!file_exists(m_image)) - { - m_image = ""; - } // Get the path of the found image without the extension const QString l_image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); @@ -38,8 +34,10 @@ void AOButton::set_image(QString p_image) l_hover_image = m_image; } - this->setStyleSheet("QPushButton {border-image:url(\"" + m_image + "\");}" - "QPushButton:hover {border-image:url(\"" + l_hover_image + "\");}"); + this->setStyleSheet("QPushButton {border-image:url(\"" + m_image + + "\");}" + "QPushButton:hover {border-image:url(\"" + + l_hover_image + "\");}"); } void AOButton::refresh_image() diff --git a/src/aocharbutton.cpp b/src/aocharbutton.cpp index d50c1bc16..9c6483af9 100644 --- a/src/aocharbutton.cpp +++ b/src/aocharbutton.cpp @@ -21,7 +21,7 @@ AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, ui_taken = new AOImageDisplay(this, ao_app); ui_taken->resize(60, 60); - ui_taken->set_image("char_taken.png"); + ui_taken->set_theme_image("char_taken.png"); ui_taken->setAttribute(Qt::WA_TransparentForMouseEvents); ui_taken->hide(); } diff --git a/src/aoimagedisplay.cpp b/src/aoimagedisplay.cpp index ce4a40ffc..b02985b6d 100644 --- a/src/aoimagedisplay.cpp +++ b/src/aoimagedisplay.cpp @@ -20,25 +20,22 @@ QString AOImageDisplay::get_image() void AOImageDisplay::set_image(QString p_image) { - const QString l_path = ao_app->find_theme_asset_path(p_image); - AOPixmap f_pixmap(l_path); - this->setPixmap(f_pixmap.scale(size())); - m_image = l_path; + m_image = p_image; + AOPixmap l_pixmap(p_image); + setPixmap(l_pixmap.scale(size())); } -void AOImageDisplay::set_image_from_path(QString p_path) +void AOImageDisplay::set_theme_image(QString p_image) { - QString final_path; + set_image(ao_app->find_theme_asset_path(p_image)); +} - if (file_exists(p_path)) - final_path = p_path; - else +void AOImageDisplay::set_chatbox_image(QString p_image) +{ + QString l_target_path = ao_app->find_asset_path(ao_app->get_base_path() + "misc/" + p_image + ".png"); + if (!file_exists(l_target_path)) { - QString default_path = ao_app->find_theme_asset_path("chatmed.png"); - final_path = default_path; + l_target_path = ao_app->find_theme_asset_path("chatmed.png"); } - - AOPixmap f_pixmap(final_path); - this->setPixmap(f_pixmap.scale(size())); - m_image = final_path; + set_image(l_target_path); } diff --git a/src/aoimagedisplay.h b/src/aoimagedisplay.h index 12c5e0fad..c890af1e7 100644 --- a/src/aoimagedisplay.h +++ b/src/aoimagedisplay.h @@ -12,7 +12,8 @@ class AOImageDisplay : public QLabel QString get_image(); void set_image(QString p_image); - void set_image_from_path(QString p_path); + void set_theme_image(QString p_image); + void set_chatbox_image(QString p_image); private: AOApplication *ao_app = nullptr; diff --git a/src/charselect.cpp b/src/charselect.cpp index f0a862047..4b7a6c877 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -111,7 +111,7 @@ void Courtroom::set_char_select() qWarning() << "warning: char_select not found or invalid within courtroom_design.ini"; ui_char_select_background->resize(f_charselect.width, f_charselect.height); - ui_char_select_background->set_image("charselect_background.png"); + ui_char_select_background->set_theme_image("charselect_background.png"); } void Courtroom::set_char_select_page() diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f793c1a47..2b9e9082e 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -555,26 +555,24 @@ void Courtroom::on_ic_message_return_pressed() if ((anim_state < 3 || text_state < 2) && m_shout_state == 0) return; - // qDebug() << "prev_emote = " << prev_emote << "current_emote = " << - // current_emote; - - // qDebug() << "same_emote = " << same_emote; - // MS# - // deskmod# - // pre-emote# - // character# - // emote# - // message# - // side# - // sfx-name# - // emote_modifier# - // char_id# - // sfx_delay# - // objection_modifier# - // evidence# - // placeholder# - // realization# - // text_color#% + // MS + // deskmod + // pre-emote + // character + // emote + // message + // side + // sfx-name + // emote_modifier + // char_id + // sfx_delay + // objection_modifier + // evidence + // placeholder + // realization + // text_color + // video_name + // show_emote QStringList packet_contents; @@ -603,29 +601,24 @@ void Courtroom::on_ic_message_return_pressed() packet_contents.append(l_sound_file.isEmpty() ? "0" : l_sound_file); // TODO remove empty string workaround for pre-DRO 1.0.0 - int l_emote_modifier = l_emote.modifier; - // needed or else legacy won't understand what we're saying - if (m_shout_state > 0) + int l_emote_mod = l_emote.modifier; + if (!ui_pre->isChecked()) { - if (l_emote_modifier == 5) - l_emote_modifier = 6; + if (l_emote_mod == PreZoomEmoteMod) + l_emote_mod = ZoomEmoteMod; else - l_emote_modifier = 2; - } - else if (ui_pre->isChecked()) - { - if (l_emote_modifier == 0) - l_emote_modifier = 1; + l_emote_mod = IdleEmoteMod; } - else + + if (m_shout_state != 0) { - if (l_emote_modifier == 1) - l_emote_modifier = 0; - else if (l_emote_modifier == 4) - l_emote_modifier = 5; + if (l_emote_mod == ZoomEmoteMod) + l_emote_mod = PreZoomEmoteMod; + else + l_emote_mod = PreEmoteMod; } - packet_contents.append(QString::number(l_emote_modifier)); + packet_contents.append(QString::number(l_emote_mod)); packet_contents.append(QString::number(m_chr_id)); if (l_emote.sound_file == current_sfx_file()) @@ -633,14 +626,7 @@ void Courtroom::on_ic_message_return_pressed() else packet_contents.append("0"); - QString f_obj_state; - - if (m_shout_state < 0) - f_obj_state = "0"; - else - f_obj_state = QString::number(m_shout_state); - - packet_contents.append(f_obj_state); + packet_contents.append(QString::number(m_shout_state)); // evidence packet_contents.append("0"); @@ -651,19 +637,20 @@ void Courtroom::on_ic_message_return_pressed() packet_contents.append(QString::number(m_effect_state)); QString f_text_color; - if (m_text_color < 0) f_text_color = "0"; else if (m_text_color > ui_text_color->count()) f_text_color = "0"; else f_text_color = QString::number(m_text_color); - packet_contents.append(f_text_color); if (ao_app->has_playable_video_feature()) packet_contents.append(!l_emote.video_file.isEmpty() ? l_emote.video_file : "0"); + // show emote + packet_contents.append("0"); + ao_app->send_server_packet(DRPacket("MS", packet_contents)); } @@ -689,6 +676,27 @@ void Courtroom::handle_chatmessage(QStringList p_contents) for (int i = 0; i < MESSAGE_SIZE; ++i) m_chatmessage[i] = p_contents[i]; + m_hide_character = m_chatmessage[CMShowCharacter].toInt(); + m_play_pre = false; + m_play_zoom = false; + const int l_emote_mod = m_chatmessage[CMEmoteModifier].toInt(); + switch (l_emote_mod) + { + case IdleEmoteMod: + default: + break; + case PreEmoteMod: + m_play_zoom = true; + break; + case ZoomEmoteMod: + m_play_zoom = true; + break; + case PreZoomEmoteMod: + m_play_pre = true; + m_play_zoom = true; + break; + } + int f_char_id = m_chatmessage[CMChrId].toInt(); if (f_char_id == SpectatorId) @@ -779,21 +787,13 @@ void Courtroom::video_finished() int objection_mod = m_chatmessage[CMShoutModifier].toInt(); QString f_char = m_chatmessage[CMChrName]; - // if an objection is used - if (objection_mod > 0) + // if an objection is to be used used + if (objection_mod > 0 && objection_mod <= ui_shouts.size()) { - int emote_mod = m_chatmessage[CMEmoteModifier].toInt(); - if (emote_mod == 0) - m_chatmessage[CMEmoteModifier] = 1; - - // handles cases 1-8 (5-8 are DRO only) - if (objection_mod >= 1 && objection_mod <= ui_shouts.size() && ui_shouts.size() > 0) // check to prevent crashing - { - ui_vp_objection->play_interjection(f_char, shout_names.at(objection_mod - 1)); - m_shouts_player->play(f_char, shout_names.at(objection_mod - 1)); - } - else - qDebug() << "W: Shout identifier unknown" << objection_mod; + m_play_pre = true; + const int l_target_obj_id = objection_mod - 1; + ui_vp_objection->play_interjection(f_char, shout_names.at(l_target_obj_id)); + m_shouts_player->play(f_char, shout_names.at(l_target_obj_id)); } else handle_chatmessage_2(); @@ -819,6 +819,11 @@ void Courtroom::handle_chatmessage_2() // handles IC QString f_showname; + ui_vp_message->clear(); + ui_vp_chatbox->hide(); + ui_vp_showname->hide(); + ui_vp_showname_image->hide(); + if (m_chatmessage[CMShowName].isEmpty()) { f_showname = ao_app->get_showname(real_name); @@ -827,13 +832,8 @@ void Courtroom::handle_chatmessage_2() // handles IC { f_showname = m_chatmessage[CMShowName]; } - ui_vp_showname->setText(f_showname); - ui_vp_message->clear(); - ui_vp_chatbox->hide(); - ui_vp_showname_image->hide(); - QString l_chatbox_name = ao_app->get_chat(m_chatmessage[CMChrName]); if (l_chatbox_name.isEmpty()) @@ -847,12 +847,12 @@ void Courtroom::handle_chatmessage_2() // handles IC l_chatbox_name = l_chatbox_self_name; } - ui_vp_chatbox->set_image(l_chatbox_name); + ui_vp_chatbox->set_theme_image(l_chatbox_name); } else { QString chatbox_path = ao_app->get_base_path() + "misc/" + l_chatbox_name + ".png"; - ui_vp_chatbox->set_image_from_path(chatbox_path); + ui_vp_chatbox->set_chatbox_image(chatbox_path); } if (m_msg_is_first_person == false) @@ -860,27 +860,15 @@ void Courtroom::handle_chatmessage_2() // handles IC update_background_scene(); } - int emote_mod = m_chatmessage[CMEmoteModifier].toInt(); - if (m_chatmessage[CMFlipState].toInt() == 1) ui_vp_player_char->set_mirrored(true); else ui_vp_player_char->set_mirrored(false); - switch (emote_mod) - { - case 1: - case 2: - case 6: + if (m_play_pre && !m_hide_character) play_preanim(); - break; - default: - qDebug() << "W: invalid emote mod: " << QString::number(emote_mod); - // intentional fallthru - case 0: - case 5: + else handle_chatmessage_3(); - } } void Courtroom::handle_chatmessage_3() @@ -907,41 +895,41 @@ void Courtroom::handle_chatmessage_3() const QString f_char = m_chatmessage[CMChrName]; const QString f_emote = m_chatmessage[CMEmote]; - QString path; - if (!chatmessage_is_empty && ao_app->read_theme_ini_bool("enable_showname_image", COURTROOM_CONFIG_INI)) + if (!chatmessage_is_empty) { - // Asset lookup order - // 1. In the theme folder (gamemode-timeofday/main/default), in the character - // folder, look for "showname" + extensions in `exts` in order - // 2. In the character folder, look for - // "showname" + extensions in `exts` in order + QString l_showname_image; + if (ao_app->read_theme_ini_bool("enable_showname_image", COURTROOM_CONFIG_INI)) + { + l_showname_image = ao_app->find_theme_asset_path("characters/" + f_char + "/showname", static_extensions()); + if (l_showname_image.isEmpty()) + l_showname_image = + ao_app->find_asset_path({ao_app->get_character_path(f_char, "showname")}, static_extensions()); + ui_vp_showname_image->set_image(l_showname_image); + ui_vp_showname_image->show(); + } - path = ao_app->find_theme_asset_path("characters/" + f_char + "/showname", {".png"}); - if (path.isEmpty()) - path = ao_app->find_asset_path({ao_app->get_character_path(f_char, "showname")}, {".png"}); + if (!l_showname_image.isEmpty()) + { + ui_vp_showname_image->set_image(l_showname_image); + ui_vp_showname_image->show(); + } + else + { + ui_vp_showname->show(); + } } // Path may be empty if // 1. Chat message was empty // 2. Enable showname images was false // 3. No valid showname image was found - if (!path.isEmpty()) - { - ui_vp_showname->hide(); - ui_vp_showname_image->set_image_from_path(path); - ui_vp_showname_image->show(); - } - else - { - ui_vp_showname->show(); - ui_vp_showname_image->hide(); - } - - ui_vp_player_char->show(); + ui_vp_player_char->hide(); + if (ui_vp_player_char->is_running()) + ui_vp_player_char->stop(); switch (f_anim_state) { case 2: - if (m_msg_is_first_person == false) + if (!m_hide_character && !m_msg_is_first_person) { ui_vp_player_char->play_talk(f_char, f_emote); } @@ -951,7 +939,7 @@ void Courtroom::handle_chatmessage_3() qDebug() << "W: invalid anim_state: " << f_anim_state; [[fallthrough]]; case 3: - if (m_msg_is_first_person == false) + if (!m_hide_character && !m_msg_is_first_person) { ui_vp_player_char->play_idle(f_char, f_emote); } @@ -1517,7 +1505,7 @@ void Courtroom::set_muted(bool p_muted, int p_cid) } ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); - ui_muted->set_image("muted.png"); + ui_muted->set_theme_image("muted.png"); is_client_muted = p_muted; ui_ic_chat_message->setEnabled(!p_muted); @@ -1620,12 +1608,12 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) if (p_bar == 1) { - ui_defense_bar->set_image("defensebar" + QString::number(p_state) + ".png"); + ui_defense_bar->set_theme_image("defensebar" + QString::number(p_state) + ".png"); defense_bar_state = p_state; } else if (p_bar == 2) { - ui_prosecution_bar->set_image("prosecutionbar" + QString::number(p_state) + ".png"); + ui_prosecution_bar->set_theme_image("prosecutionbar" + QString::number(p_state) + ".png"); prosecution_bar_state = p_state; } } diff --git a/src/courtroom.h b/src/courtroom.h index a20570e11..86ca04fac 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -264,8 +264,11 @@ class Courtroom : public QMainWindow // Generate a File Name based on the time you launched the client QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); - static const int MESSAGE_SIZE = 17; + static const int MESSAGE_SIZE = 18; QString m_chatmessage[MESSAGE_SIZE]; + bool m_hide_character = false; + bool m_play_pre = false; + bool m_play_zoom = false; bool chatmessage_is_empty = false; QString previous_ic_message; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index de35efdd9..7916db8fe 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -573,7 +573,7 @@ void Courtroom::set_widgets() ui_background->move(0, 0); ui_background->resize(m_default_size); - ui_background->set_image("courtroombackground.png"); + ui_background->set_theme_image("courtroombackground.png"); set_size_and_pos(ui_viewport, "viewport", COURTROOM_DESIGN_INI, ao_app); @@ -591,7 +591,7 @@ void Courtroom::set_widgets() ui_vp_desk->resize(ui_viewport->size()); set_size_and_pos(ui_vp_notepad_image, "notepad_image", COURTROOM_DESIGN_INI, ao_app); - ui_vp_notepad_image->set_image("notepad_image.png"); + ui_vp_notepad_image->set_theme_image("notepad_image.png"); ui_vp_notepad_image->hide(); set_size_and_pos(ui_vp_notepad, "notepad", COURTROOM_DESIGN_INI, ao_app); @@ -651,22 +651,22 @@ void Courtroom::set_widgets() set_size_and_pos(ui_vp_music_name, "music_name", COURTROOM_DESIGN_INI, ao_app); set_size_and_pos(ui_vp_music_display_a, "music_display_a", COURTROOM_DESIGN_INI, ao_app); - ui_vp_music_display_a->set_image("music_display_a.png"); + ui_vp_music_display_a->set_theme_image("music_display_a.png"); ui_vp_music_display_a->show(); set_size_and_pos(ui_vp_music_display_b, "music_display_b", COURTROOM_DESIGN_INI, ao_app); - ui_vp_music_display_b->set_image("music_display_b.png"); + ui_vp_music_display_b->set_theme_image("music_display_b.png"); ui_vp_music_display_b->show(); set_size_and_pos(ui_vp_clock, "clock", COURTROOM_DESIGN_INI, ao_app); if (m_current_clock == -1) ui_vp_clock->hide(); - ui_vp_chatbox->set_image("chatmed.png"); + ui_vp_chatbox->set_theme_image("chatmed.png"); ui_vp_chatbox->hide(); ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); - ui_muted->set_image("muted.png"); + ui_muted->set_theme_image("muted.png"); set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_FONTS_INI, ao_app); @@ -721,7 +721,7 @@ void Courtroom::set_widgets() l_emote_preview_size.height = 192; } ui_emote_preview->resize(l_emote_preview_size.width, l_emote_preview_size.height); - ui_emote_preview->set_image("emote_preview.png"); + ui_emote_preview->set_theme_image("emote_preview.png"); ui_emote_preview_character->resize(l_emote_preview_size.width, l_emote_preview_size.height); } @@ -735,10 +735,10 @@ void Courtroom::set_widgets() set_stylesheet(ui_pos_dropdown, "[POS DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); set_size_and_pos(ui_defense_bar, "defense_bar", COURTROOM_DESIGN_INI, ao_app); - ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + ".png"); + ui_defense_bar->set_theme_image("defensebar" + QString::number(defense_bar_state) + ".png"); set_size_and_pos(ui_prosecution_bar, "prosecution_bar", COURTROOM_DESIGN_INI, ao_app); - ui_prosecution_bar->set_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); + ui_prosecution_bar->set_theme_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); for (int i = 0; i < shout_names.size(); ++i) { @@ -895,7 +895,7 @@ void Courtroom::set_widgets() for (int i = 0; i < ui_checks.size(); ++i) // loop through checks { QString image = label_images[i].toLower() + ".png"; - ui_label_images[i]->set_image(image); + ui_label_images[i]->set_theme_image(image); if (!ui_label_images[i]->get_image().isEmpty()) ui_checks[i]->setText(""); @@ -907,7 +907,7 @@ void Courtroom::set_widgets() { int j = i + ui_checks.size(); QString image = label_images[j].toLower() + ".png"; - ui_label_images[j]->set_image(image); + ui_label_images[j]->set_theme_image(image); if (!ui_label_images[j]->get_image().isEmpty()) ui_labels[i]->setText(""); @@ -920,14 +920,14 @@ void Courtroom::set_widgets() for (int i = 0; i < ui_checks.size(); ++i) // same thing { ui_checks[i]->setText(label_images[i]); - ui_label_images[i]->set_image(""); + ui_label_images[i]->set_theme_image(""); } for (int i = 0; i < ui_labels.size(); ++i) // same thing { int j = i + ui_checks.size(); ui_labels[i]->setText(label_images[j]); - ui_label_images[j]->set_image(""); + ui_label_images[j]->set_theme_image(""); } } @@ -946,7 +946,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_text_color, "text_color", COURTROOM_DESIGN_INI, ao_app); set_stylesheet(ui_text_color, "[TEXT COLOR]", COURTROOM_STYLESHEETS_CSS, ao_app); - ui_char_button_selector->set_image("char_selector.png"); + ui_char_button_selector->set_theme_image("char_selector.png"); ui_char_button_selector->hide(); set_size_and_pos(ui_back_to_lobby, "back_to_lobby", COURTROOM_DESIGN_INI, ao_app); @@ -971,7 +971,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_note_scroll_area, "note_area", COURTROOM_DESIGN_INI, ao_app); ui_note_scroll_area->setWidget(ui_note_area); - ui_note_area->set_image("note_area.png"); + ui_note_area->set_theme_image("note_area.png"); ui_note_area->add_button->set_image("add_button.png"); ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); ui_note_area->setLayout(ui_note_area->m_layout); diff --git a/src/datatypes.h b/src/datatypes.h index 1fcb9f637..7a0e3c856 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -140,9 +140,18 @@ enum ChatMessage : int32_t CMEffectState, CMTextColor, CMVideoName, + CMShowCharacter, CMShowName, }; +enum EmoteMod +{ + IdleEmoteMod = 0, + PreEmoteMod = 1, + ZoomEmoteMod = 5, + PreZoomEmoteMod = 6, +}; + namespace DR { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) diff --git a/src/drcharactermovie.cpp b/src/drcharactermovie.cpp index fa147180d..4e20c2d54 100644 --- a/src/drcharactermovie.cpp +++ b/src/drcharactermovie.cpp @@ -14,83 +14,63 @@ DRCharacterMovie::DRCharacterMovie(QWidget *parent) : DRMovie(parent), ao_app(dy DRCharacterMovie::~DRCharacterMovie() {} -bool DRCharacterMovie::is_playing_empty(QString p_character, QString p_emote) +void DRCharacterMovie::play(QString p_character, QString p_emote, QString p_prefix, bool p_play_once) { - if (p_character == "") - return true; - if (p_emote.isEmpty()) - return true; - if (p_emote == "-" || p_emote == "0" || p_emote == "../../misc/blank") - return true; - return false; -} - -void DRCharacterMovie::play(QString p_character, QString p_emote, QString p_prefix, bool p_play_once, bool p_hide_if_empty) -{ - QString l_file; - const bool playing_empty = is_playing_empty(p_character, p_emote); - if (!playing_empty) + QStringList l_filelist; + QStringList l_blacklist; + for (const QString &i_characterName : ao_app->get_char_include_tree(p_character)) { - QStringList l_filelist; - QStringList l_blacklist; - for (const QString &i_characterName : ao_app->get_char_include_tree(p_character)) - { - l_blacklist.append({ - ao_app->get_character_path(i_characterName, "char_icon.png"), - ao_app->get_character_path(i_characterName, "showname.png"), - ao_app->get_character_path(i_characterName, "emotions"), - }); - - if (!p_play_once) - { - l_filelist.append(ao_app->get_character_path(i_characterName, QString("%1%2").arg(p_prefix, p_emote))); - } + l_blacklist.append({ + ao_app->get_character_path(i_characterName, "char_icon.png"), + ao_app->get_character_path(i_characterName, "showname.png"), + ao_app->get_character_path(i_characterName, "emotions"), + }); - l_filelist.append(ao_app->get_character_path(i_characterName, p_emote)); + if (!p_play_once) + { + l_filelist.append(ao_app->get_character_path(i_characterName, QString("%1%2").arg(p_prefix, p_emote))); } - l_file = ao_app->find_asset_path(l_filelist, animated_or_static_extensions()); - if (l_file.isEmpty() && !p_play_once) - { - l_file = ao_app->find_theme_asset_path("placeholder", animated_extensions()); - } + l_filelist.append(ao_app->get_character_path(i_characterName, p_emote)); + } - for (const QString &i_blackened : qAsConst(l_blacklist)) - { - if (l_file == i_blackened) - { - l_file.clear(); - break; - } - } + QString l_file = ao_app->find_asset_path(l_filelist, animated_or_static_extensions()); + if (l_file.isEmpty() && !p_play_once) + { + l_file = ao_app->find_theme_asset_path("placeholder", animated_extensions()); + } - if (l_file.isEmpty()) - { - qWarning() << "error: character animation not found" - << "character:" << p_character << "emote:" << p_emote << "prefix:" << p_prefix; - } + for (const QString &i_blackened : qAsConst(l_blacklist)) + { + if (l_file == i_blackened) + { + l_file.clear(); + break; + } } - set_file_name(l_file); - set_play_once(p_play_once); - if (l_file.isEmpty() && p_hide_if_empty) + if (l_file.isEmpty()) { - hide(); + qWarning() << "error: character animation not found" + << "character:" << p_character << "emote:" << p_emote << "prefix:" << p_prefix; } + + set_file_name(l_file); + set_play_once(p_play_once); start(); } void DRCharacterMovie::play_pre(QString p_character, QString p_emote) { - play(p_character, p_emote, nullptr, true, false); + play(p_character, p_emote, nullptr, true); } void DRCharacterMovie::play_talk(QString p_character, QString p_emote) { - play(p_character, p_emote, "(b)", false, true); + play(p_character, p_emote, "(b)", false); } void DRCharacterMovie::play_idle(QString p_character, QString p_emote) { - play(p_character, p_emote, "(a)", false, true); + play(p_character, p_emote, "(a)", false); } diff --git a/src/drcharactermovie.h b/src/drcharactermovie.h index 5313d1c5a..aeac0f092 100644 --- a/src/drcharactermovie.h +++ b/src/drcharactermovie.h @@ -12,8 +12,7 @@ class DRCharacterMovie : public DRMovie explicit DRCharacterMovie(QWidget *parent = nullptr); ~DRCharacterMovie(); - bool is_playing_empty(QString p_character, QString p_emote); - void play(QString character, QString emote, QString prefix, bool play_once, bool p_hide_if_empty); + void play(QString character, QString emote, QString prefix, bool play_once); void play_pre(QString character, QString emote); void play_talk(QString character, QString emote); void play_idle(QString character, QString emote); diff --git a/src/file_functions.cpp b/src/file_functions.cpp index 3244a1a24..09e218508 100644 --- a/src/file_functions.cpp +++ b/src/file_functions.cpp @@ -3,14 +3,19 @@ #include #include -QStringList animated_or_static_extensions() +QStringList animated_extensions() { - return QStringList{".webp", ".apng", ".gif", ".png"}; + return QStringList{".webp", ".apng", ".gif"}; } -QStringList animated_extensions() +QStringList static_extensions() { - return QStringList{".webp", ".apng", ".gif"}; + return QStringList{".png"}; +} + +QStringList animated_or_static_extensions() +{ + return animated_extensions() + static_extensions(); } QStringList audio_extensions(bool no_suffix) diff --git a/src/file_functions.h b/src/file_functions.h index 4c057f302..c06aa4744 100644 --- a/src/file_functions.h +++ b/src/file_functions.h @@ -4,8 +4,9 @@ class QString; class QStringList; -QStringList animated_or_static_extensions(); QStringList animated_extensions(); +QStringList static_extensions(); +QStringList animated_or_static_extensions(); QStringList audio_extensions(bool no_suffix = false); bool file_exists(QString file_path); diff --git a/src/lobby.cpp b/src/lobby.cpp index ff57191db..c1cc6e43e 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -114,7 +114,7 @@ void Lobby::set_widgets() center_widget_to_screen(this); set_size_and_pos(ui_background, "lobby", LOBBY_DESIGN_INI, ao_app); - ui_background->set_image("lobbybackground.png"); + ui_background->set_theme_image("lobbybackground.png"); set_size_and_pos(ui_public_servers, "public_servers", LOBBY_DESIGN_INI, ao_app); ui_public_servers->set_image("publicservers_selected.png"); @@ -165,7 +165,7 @@ void Lobby::set_widgets() "selection-background-color: rgba(0, 0, 0, 0);"); ui_loading_background->resize(this->width(), this->height()); - ui_loading_background->set_image("loadingbackground.png"); + ui_loading_background->set_theme_image("loadingbackground.png"); set_size_and_pos(ui_loading_text, "loading_label", LOBBY_DESIGN_INI, ao_app); ui_loading_text->setFont(QFont("Arial", 20, QFont::Bold)); diff --git a/src/main.cpp b/src/main.cpp index 36db79dc5..c15fdd231 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,10 +7,9 @@ int main(int argc, char *argv[]) { qInstallMessageHandler(logger::log); - qInfo() << "Starting Danganronpa Online..."; - bool l_dpi_scaling = true; + bool l_dpi_scaling = true; for (int i = 0; i < argc; ++i) { const QString l_arg(argv[i]); From 2a593c9be9fca14b3988f1637b76445ec031fe46 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 3 Apr 2022 16:09:28 +0200 Subject: [PATCH 648/842] Fix pre emote mod not playing pre --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2b9e9082e..4ab9cf038 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -686,7 +686,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) default: break; case PreEmoteMod: - m_play_zoom = true; + m_play_pre = true; break; case ZoomEmoteMod: m_play_zoom = true; From 60f4019abee74012991038e072d00cd3a7ddc53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sun, 3 Apr 2022 16:12:17 +0200 Subject: [PATCH 649/842] Fixed missing depends for build-all-merge-master --- .github/workflows/build-all-merge-master.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build-all-merge-master.yml b/.github/workflows/build-all-merge-master.yml index 73e506f44..299335755 100644 --- a/.github/workflows/build-all-merge-master.yml +++ b/.github/workflows/build-all-merge-master.yml @@ -369,6 +369,10 @@ jobs: sudo apt-get install build-essential sudo apt-get install qt5-default qttools5-dev sudo apt-get install libqt5designer5 + sudo apt-get install libqt5multimedia5 + sudo apt-get install libqt5multimedia5-plugins + sudo apt-get install libqt5multimediawidgets5 + sudo apt-get install qtmultimedia5-dev sudo apt-get install git - name: Build QtApng external library From f8342f785e0a8875247236b7d39b6314d45db6d5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 3 Apr 2022 17:13:04 +0200 Subject: [PATCH 650/842] Renamed ui_hidden to ui_hide_character; added ui_hide_character value to MS packet --- src/courtroom.cpp | 6 +++--- src/courtroom.h | 2 +- src/courtroom_widgets.cpp | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 4ab9cf038..3a3bb312f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -586,7 +586,7 @@ void Courtroom::on_ic_message_return_pressed() packet_contents.append(get_character_ini()); - if (ui_hidden->isChecked()) + if (ui_hide_character->isChecked()) packet_contents.append("../../misc/blank"); else packet_contents.append(l_emote.dialog); @@ -648,8 +648,8 @@ void Courtroom::on_ic_message_return_pressed() if (ao_app->has_playable_video_feature()) packet_contents.append(!l_emote.video_file.isEmpty() ? l_emote.video_file : "0"); - // show emote - packet_contents.append("0"); + // hide character + packet_contents.append(QString::number(ui_hide_character->isChecked())); ao_app->send_server_packet(DRPacket("MS", packet_contents)); } diff --git a/src/courtroom.h b/src/courtroom.h index 86ca04fac..74b6ff3f8 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -453,7 +453,7 @@ class Courtroom : public QMainWindow QCheckBox *ui_pre = nullptr; QCheckBox *ui_flip = nullptr; - QCheckBox *ui_hidden = nullptr; + QCheckBox *ui_hide_character = nullptr; QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 7916db8fe..b6467db75 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -207,13 +207,13 @@ void Courtroom::create_widgets() ui_flip = new QCheckBox(this); ui_flip->setText("Flip"); ui_flip->hide(); - ui_hidden = new QCheckBox(this); - ui_hidden->setText("Hidden"); + ui_hide_character = new QCheckBox(this); + ui_hide_character->setText("Hidden"); // filling vectors with existing label/checkbox pointers ui_checks.push_back(ui_pre); ui_checks.push_back(ui_flip); - ui_checks.push_back(ui_hidden); + ui_checks.push_back(ui_hide_character); ui_defense_plus = new AOButton(this, ao_app); ui_defense_minus = new AOButton(this, ao_app); @@ -328,7 +328,7 @@ void Courtroom::connect_widgets() connect(ui_pre, SIGNAL(clicked()), this, SLOT(on_pre_clicked())); connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); - connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); + connect(ui_hide_character, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); connect(ui_sfx_list, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(_p_sfxCurrentItemChanged(QListWidgetItem *, QListWidgetItem *))); @@ -406,7 +406,7 @@ void Courtroom::reset_widget_names() // Each ui_label_images[i] {"pre", ui_pre}, {"flip", ui_flip}, - {"hidden", ui_hidden}, + {"hidden", ui_hide_character}, {"defense_plus", ui_defense_plus}, {"defense_minus", ui_defense_minus}, {"prosecution_plus", ui_prosecution_plus}, @@ -883,7 +883,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_flip, "flip", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_hidden, "hidden", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_hide_character, "hidden", COURTROOM_DESIGN_INI, ao_app); for (int i = 0; i < ui_label_images.size(); ++i) { From 10cd6e370a430e18b30caaa21aeb58cf25c6d420 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 3 Apr 2022 18:48:52 +0200 Subject: [PATCH 651/842] Fixed final emote change not obeying "hide_character" rule --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 3a3bb312f..b4dc3d5d3 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1458,7 +1458,7 @@ void Courtroom::post_chat() anim_state = 3; stop_chat_timer(); - if (m_msg_is_first_person == false) + if (!m_hide_character && !m_msg_is_first_person) { ui_vp_player_char->play_idle(m_chatmessage[CMChrName], m_chatmessage[CMEmote]); } From 23d88a8252d6b6a672c597e205514855bbe26b22 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 3 Apr 2022 19:01:25 +0200 Subject: [PATCH 652/842] Small optimization --- src/aoimagedisplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aoimagedisplay.cpp b/src/aoimagedisplay.cpp index b02985b6d..b744276f2 100644 --- a/src/aoimagedisplay.cpp +++ b/src/aoimagedisplay.cpp @@ -33,7 +33,7 @@ void AOImageDisplay::set_theme_image(QString p_image) void AOImageDisplay::set_chatbox_image(QString p_image) { QString l_target_path = ao_app->find_asset_path(ao_app->get_base_path() + "misc/" + p_image + ".png"); - if (!file_exists(l_target_path)) + if (!l_target_path.isEmpty()) { l_target_path = ao_app->find_theme_asset_path("chatmed.png"); } From 25eaf9cefd219e361ed9a8600171efb561a18817 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 4 Apr 2022 10:58:49 -0400 Subject: [PATCH 653/842] Remove upper bound for clock --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6ae799b9a..71b53687a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -351,7 +351,7 @@ void Courtroom::handle_music_anim() void Courtroom::handle_clock(QString time) { m_current_clock = time.toInt(); - if (m_current_clock < 0 || m_current_clock > 23) + if (m_current_clock < 0) m_current_clock = -1; qInfo() << QString("Clock time changed to %1").arg(m_current_clock); From aa9a1fa9c9f8d2a93f8e45a79e52d49157ab23fa Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 7 Apr 2022 12:22:07 +0200 Subject: [PATCH 654/842] Added showname to MS packet --- src/courtroom.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index b4dc3d5d3..1baced374 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -651,6 +651,8 @@ void Courtroom::on_ic_message_return_pressed() // hide character packet_contents.append(QString::number(ui_hide_character->isChecked())); + packet_contents.append(ao_config->showname()); + ao_app->send_server_packet(DRPacket("MS", packet_contents)); } From 2fa9454e59e60825b42ad4f7cef3787b40a31bc8 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 7 Apr 2022 18:27:12 +0200 Subject: [PATCH 655/842] Re-ordered MS fields --- src/courtroom.cpp | 7 +++---- src/datatypes.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 1baced374..de16a352f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -645,14 +645,13 @@ void Courtroom::on_ic_message_return_pressed() f_text_color = QString::number(m_text_color); packet_contents.append(f_text_color); - if (ao_app->has_playable_video_feature()) - packet_contents.append(!l_emote.video_file.isEmpty() ? l_emote.video_file : "0"); + packet_contents.append(ao_config->showname()); + + packet_contents.append(!l_emote.video_file.isEmpty() ? l_emote.video_file : "0"); // hide character packet_contents.append(QString::number(ui_hide_character->isChecked())); - packet_contents.append(ao_config->showname()); - ao_app->send_server_packet(DRPacket("MS", packet_contents)); } diff --git a/src/datatypes.h b/src/datatypes.h index 7a0e3c856..285dcb6e6 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -139,9 +139,9 @@ enum ChatMessage : int32_t CMFlipState, CMEffectState, CMTextColor, + CMShowName, CMVideoName, CMShowCharacter, - CMShowName, }; enum EmoteMod From e1d6df3ebdfce53a7642c9f0b7a5bf9a82c1ca95 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 8 Apr 2022 12:13:51 +0200 Subject: [PATCH 656/842] Assume v110 server compatible --- dronline-client.pro | 3 --- src/aoapplication.cpp | 29 ++---------------------- src/aoapplication.h | 16 ++----------- src/courtroom.cpp | 50 ++++++++++++----------------------------- src/debug_functions.cpp | 16 ++++++------- src/debug_functions.h | 2 +- src/lobby.cpp | 6 +++++ src/master_socket.cpp | 30 ------------------------- src/server_socket.cpp | 18 ++------------- 9 files changed, 35 insertions(+), 135 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index 1f0588e21..185e8f27f 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -11,9 +11,6 @@ RC_ICONS = icon.ico INCLUDEPATH += $$PWD/include $$PWD/3rd DEPENDPATH += $$PWD/include $$PWD/3rd -DEFINES += DRO_ACKMS -CONFIG(debug, debug|release):DEFINES += DR_DEV - HEADERS += \ src/aoapplication.h \ src/aoblipplayer.h \ diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 7f063c701..8496f26bc 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -154,34 +154,9 @@ DRDiscord *AOApplication::get_discord() const return dr_discord; } -bool AOApplication::has_message_acknowledgement_feature() const +bool AOApplication::is_server_compatible() const { - return feature_ackMS; -} - -bool AOApplication::has_character_declaration_feature() const -{ - return feature_chrini; -} - -bool AOApplication::has_showname_declaration_feature() const -{ - return feature_showname; -} - -bool AOApplication::has_chat_speed_feature() const -{ - return feature_chat_speed; -} - -bool AOApplication::has_character_availability_request_feature() const -{ - return feature_charscheck; -} - -bool AOApplication::has_playable_video_feature() const -{ - return feature_playable_video; + return feature_version_compatible; } void AOApplication::handle_theme_modification() diff --git a/src/aoapplication.h b/src/aoapplication.h index d1dceae48..ffc7827d6 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -48,12 +48,7 @@ class AOApplication : public QApplication DRDiscord *get_discord() const; - bool has_message_acknowledgement_feature() const; - bool has_character_declaration_feature() const; - bool has_showname_declaration_feature() const; - bool has_chat_speed_feature() const; - bool has_character_availability_request_feature() const; - bool has_playable_video_feature() const; + bool is_server_compatible() const; /////////////////////////////////////////// @@ -228,14 +223,7 @@ public slots: bool is_courtroom_loaded = false; ///////////////server metadata//////////////// -#ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - bool feature_ackMS = false; -#endif - bool feature_showname = false; - bool feature_chrini = false; - bool feature_chat_speed = false; - bool feature_charscheck = false; - bool feature_playable_video = false; + bool feature_version_compatible = false; ///////////////loading info/////////////////// // player number, it's hardly used but might be needed for some old servers diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 968dddd39..62e4c02e4 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -179,11 +179,9 @@ void Courtroom::enter_courtroom(int p_cid) ao_app->get_discord()->set_character_name(l_final_showname); ao_config->set_showname_placeholder(l_final_showname); - if (ao_app->has_character_declaration_feature()) - { - QStringList l_content{l_chr_name, l_final_showname}; - ao_app->send_server_packet(DRPacket("chrini", l_content)); - } + // send the character declaration + QStringList l_content{l_chr_name, l_final_showname}; + ao_app->send_server_packet(DRPacket("chrini", l_content)); } const bool l_changed_chr = l_chr_name != l_prev_chr_name; if (l_changed_chr) @@ -514,14 +512,7 @@ void Courtroom::send_showname_packet(QString p_showname) is_first_showname_sent = true; - if (ao_app->has_showname_declaration_feature()) - { - ao_app->send_server_packet(DRPacket("SN", {p_showname})); - } - else - { - send_ooc_packet(QString("/showname %1").arg(p_showname)); - } + ao_app->send_server_packet(DRPacket("SN", {p_showname})); } void Courtroom::on_showname_changed(QString p_showname) @@ -645,12 +636,17 @@ void Courtroom::on_ic_message_return_pressed() f_text_color = QString::number(m_text_color); packet_contents.append(f_text_color); - packet_contents.append(ao_config->showname()); + if (ao_app->is_server_compatible()) + { + // showname + packet_contents.append(ao_config->showname()); - packet_contents.append(!l_emote.video_file.isEmpty() ? l_emote.video_file : "0"); + // video name + packet_contents.append(!l_emote.video_file.isEmpty() ? l_emote.video_file : "0"); - // hide character - packet_contents.append(QString::number(ui_hide_character->isChecked())); + // hide character + packet_contents.append(QString::number(ui_hide_character->isChecked())); + } ao_app->send_server_packet(DRPacket("MS", packet_contents)); } @@ -721,23 +717,6 @@ void Courtroom::handle_chatmessage(QStringList p_contents) // reset our ui state if client just spoke if (m_chr_id == f_char_id && is_system_speaking == false) { -#ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - // If the server does not have the feature of acknowledging our MS - // messages, assume in this if that the message is proof the server - // acknowledged our message. It is not quite the same, as it is - // possible the server crafted a message with the same char_id - // as the client, but the client did not send that message, but it is - // the best we can do. - if (!ao_app->has_message_acknowledgement_feature()) - { - handle_acknowledged_ms(); - } - - // If the server does have the feature of acknowledging our MS messages, - // it will have sent an ackMS packet prior to the MS one, so chat would - // have been cleared and thus we need not do anything else. -#endif - // update first person mode status m_msg_is_first_person = ao_app->get_first_person_enabled(); } @@ -2114,8 +2093,7 @@ void Courtroom::on_change_character_clicked() ui_spectator->show(); - if (ao_app->has_character_availability_request_feature()) - ao_app->send_server_packet(DRPacket("CharsCheck")); + ao_app->send_server_packet(DRPacket("CharsCheck")); } void Courtroom::load_theme() diff --git a/src/debug_functions.cpp b/src/debug_functions.cpp index 993af094d..dd14f3a36 100644 --- a/src/debug_functions.cpp +++ b/src/debug_functions.cpp @@ -6,20 +6,20 @@ void drMessageBox(QString p_message, bool p_error) { QMessageBox l_box; - l_box.setWindowTitle(p_error ? "Error" : "Notice"); - l_box.setIcon(p_error ? QMessageBox::Critical : QMessageBox::Information); + l_box.setWindowTitle(p_error ? "Warning" : "Notice"); + l_box.setIcon(p_error ? QMessageBox::Warning : QMessageBox::Information); l_box.setText(p_message); l_box.exec(); } -void call_error(QString p_message) -{ - drMessageBox(p_message, true); - qWarning() << "ERROR" << p_message; -} - void call_notice(QString p_message) { drMessageBox(p_message, false); qInfo() << p_message; } + +void call_warning(QString p_message) +{ + drMessageBox(p_message, true); + qWarning() << "error:" << p_message; +} diff --git a/src/debug_functions.h b/src/debug_functions.h index 35717a101..84bfb3707 100644 --- a/src/debug_functions.h +++ b/src/debug_functions.h @@ -3,7 +3,7 @@ class QString; -void call_error(QString message); void call_notice(QString message); +void call_warning(QString message); #endif // DEBUG_FUNCTIONS_H diff --git a/src/lobby.cpp b/src/lobby.cpp index c1cc6e43e..9fcdf3741 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -301,6 +301,12 @@ void Lobby::on_connect_pressed() void Lobby::on_connect_released() { + if (!ao_app->is_server_compatible()) + { + call_warning("You are connecting to an incompatible DRO server.
    " + "The client may not work properly, if at all."); + } + ui_connect->set_image("connect.png"); ao_app->send_server_packet(DRPacket("askchaa")); } diff --git a/src/master_socket.cpp b/src/master_socket.cpp index 3cf2b42a2..08958b1c5 100644 --- a/src/master_socket.cpp +++ b/src/master_socket.cpp @@ -114,35 +114,5 @@ void AOApplication::_p_handle_master_packet(DRPacket p_packet) { send_master_packet(DRPacket("ID", {"DRO", get_version_string()})); send_master_packet(DRPacket("HI", {get_hdid()})); - - if (l_content.size() < 1) - return; - - QStringList version_contents = l_content.at(0).split("."); - - if (version_contents.size() < 3) - return; - - int f_release = version_contents.at(0).toInt(); - int f_major = version_contents.at(1).toInt(); - int f_minor = version_contents.at(2).toInt(); - - if (get_release_version() > f_release) - return; - else if (get_release_version() == f_release) - { - if (get_major_version() > f_major) - return; - else if (get_major_version() == f_major) - { - if (get_minor_version() >= f_minor) - return; - } - } - - call_notice("Outdated version! Your version: " + get_version_string() + - "\nPlease go to aceattorneyonline.com to update."); - destruct_courtroom(); - destruct_lobby(); } } diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 6ff12b0a9..24652c2bc 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -55,14 +55,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) if (l_content.size() == 0) return; -#ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - feature_ackMS = false; -#endif - feature_showname = false; - feature_chrini = false; - feature_chat_speed = false; - feature_charscheck = false; - feature_playable_video = false; + feature_version_compatible = false; send_server_packet(DRPacket("HI", {get_hdid()})); } @@ -86,14 +79,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) } else if (l_header == "FL") { -#ifdef DRO_ACKMS // TODO WARNING remove entire block on 1.0.0 release - feature_ackMS = l_content.contains("ackMS", Qt::CaseInsensitive); -#endif - feature_showname = l_content.contains("showname", Qt::CaseInsensitive); - feature_chrini = l_content.contains("chrini", Qt::CaseInsensitive); - feature_chat_speed = l_content.contains("chat_speed", Qt::CaseInsensitive); - feature_charscheck = l_content.contains("charscheck", Qt::CaseInsensitive); - feature_playable_video = l_content.contains("playable_video", Qt::CaseInsensitive); + feature_version_compatible = l_content.contains("v110", Qt::CaseInsensitive); } else if (l_header == "PN") { From 56a2ff1e898e13894cfa383b4b70c809c20c95b6 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 8 Apr 2022 17:54:51 +0200 Subject: [PATCH 657/842] Added notification filtering for warnings --- res/ui/config_panel.ui | 50 ++++++++++++++++++++++++++++++----------- src/aoconfig.cpp | 40 +++++++++++++++++++++++++++++++++ src/aoconfig.h | 3 +++ src/aoconfigpanel.cpp | 6 +++++ src/aoconfigpanel.h | 3 +++ src/debug_functions.cpp | 30 ++++++++++++++++++------- 6 files changed, 111 insertions(+), 21 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 8aaa93856..cd69c381c 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -7,7 +7,7 @@ 0 0 487 - 439 + 447 @@ -32,7 +32,7 @@ - 1 + 0 @@ -118,17 +118,41 @@ - - - - 0 - 0 - - - - Server alerts - - + + + + + + 0 + 0 + + + + Server alerts + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reset Notifications + + + + diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 1180389ab..83e84223b 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -7,6 +7,7 @@ // qt #include #include +#include #include #include #include @@ -46,6 +47,7 @@ private slots: // data bool autosave; + QStringList notification_filter; QString username; QString callwords; bool server_alerts; @@ -118,6 +120,20 @@ AOConfigPrivate::~AOConfigPrivate() void AOConfigPrivate::read_file() { autosave = cfg.value("autosave", true).toBool(); + + { // notifications + notification_filter.clear(); + cfg.beginGroup("notifications"); + for (const QString &i_key : cfg.childKeys()) + { + const QString l_message = cfg.value(i_key).toString(); + if (l_message.isEmpty()) + continue; + notification_filter.append(l_message); + } + cfg.endGroup(); + } + username = cfg.value("username").toString(); showname = cfg.value("showname").toString(); callwords = cfg.value("callwords").toString(); @@ -200,6 +216,15 @@ void AOConfigPrivate::read_file() void AOConfigPrivate::save_file() { cfg.setValue("autosave", autosave); + + { // notifications + cfg.remove("notifications"); + cfg.beginGroup("notifications"); + for (int i = 0; i < notification_filter.length(); ++i) + cfg.setValue(QString::number(i), notification_filter[i]); + cfg.endGroup(); + } + cfg.setValue("username", username); cfg.setValue("showname", showname); cfg.setValue("callwords", callwords); @@ -328,6 +353,11 @@ bool AOConfig::autosave() const return d->autosave; } +bool AOConfig::display_notification(QString p_message) const +{ + return !d->notification_filter.contains(p_message, Qt::CaseInsensitive); +} + QString AOConfig::username() const { return d->username; @@ -593,6 +623,16 @@ void AOConfig::set_autosave(bool p_enabled) d->invoke_signal("autosave_changed", Q_ARG(bool, p_enabled)); } +void AOConfig::clear_notification_filter() +{ + d->notification_filter.clear(); +} + +void AOConfig::filter_notification(QString p_message) +{ + d->notification_filter.append(p_message); +} + void AOConfig::set_username(QString p_value) { const QString l_simplified_value = p_value.simplified(); diff --git a/src/aoconfig.h b/src/aoconfig.h index 97457ed9b..ca05a1df2 100644 --- a/src/aoconfig.h +++ b/src/aoconfig.h @@ -22,6 +22,7 @@ class AOConfig : public QObject // getters bool autosave() const; + bool display_notification(QString message) const; QString username() const; QString showname() const; QString showname_placeholder() const; @@ -75,6 +76,8 @@ public slots: // setters public slots: void set_autosave(bool p_enabled); + void clear_notification_filter(); + void filter_notification(QString message); void set_username(QString p_string); void set_showname(QString p_string); void set_showname_placeholder(QString p_string); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index aee0144da..84065d510 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -41,6 +41,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_close = AO_GUI_WIDGET(QPushButton, "close"); ui_autosave = AO_GUI_WIDGET(QCheckBox, "autosave"); + // notifications + ui_clear_notifications = AO_GUI_WIDGET(QPushButton, "clear_notifications"); + // general ui_username = AO_GUI_WIDGET(QLineEdit, "username"); ui_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); @@ -113,6 +116,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // meta connect(m_config, SIGNAL(autosave_changed(bool)), ui_autosave, SLOT(setChecked(bool))); + // notifications + connect(ui_clear_notifications, SIGNAL(clicked()), m_config, SLOT(clear_notification_filter())); + // general connect(m_config, SIGNAL(username_changed(QString)), ui_username, SLOT(setText(QString))); connect(m_config, SIGNAL(callwords_changed(QString)), ui_callwords, SLOT(setText(QString))); diff --git a/src/aoconfigpanel.h b/src/aoconfigpanel.h index 636e3b5b3..a193530e7 100644 --- a/src/aoconfigpanel.h +++ b/src/aoconfigpanel.h @@ -80,6 +80,9 @@ private slots: QPushButton *ui_close = nullptr; QCheckBox *ui_autosave = nullptr; + // notifications + QPushButton *ui_clear_notifications = nullptr; + // general QLineEdit *ui_username = nullptr; QLineEdit *ui_callwords = nullptr; diff --git a/src/debug_functions.cpp b/src/debug_functions.cpp index dd14f3a36..f44640bcf 100644 --- a/src/debug_functions.cpp +++ b/src/debug_functions.cpp @@ -1,25 +1,39 @@ #include "debug_functions.h" +#include #include #include -void drMessageBox(QString p_message, bool p_error) +#include "aoconfig.h" + +void drMessageBox(QString p_message, bool p_is_warning) { - QMessageBox l_box; - l_box.setWindowTitle(p_error ? "Warning" : "Notice"); - l_box.setIcon(p_error ? QMessageBox::Warning : QMessageBox::Information); - l_box.setText(p_message); - l_box.exec(); + AOConfig config; + if (p_is_warning && !config.display_notification(p_message)) + return; + + QMessageBox message; + message.setWindowTitle(p_is_warning ? "Warning" : "Notice"); + message.setIcon(p_is_warning ? QMessageBox::Warning : QMessageBox::Information); + message.setText(p_message); + QCheckBox *show_again = new QCheckBox(&message); + show_again->setText(QObject::tr("&Show this message again")); + show_again->setChecked(true); + message.setCheckBox(show_again); + message.exec(); + + if (!show_again->isChecked()) + config.filter_notification(p_message); } void call_notice(QString p_message) { - drMessageBox(p_message, false); qInfo() << p_message; + drMessageBox(p_message, false); } void call_warning(QString p_message) { - drMessageBox(p_message, true); qWarning() << "error:" << p_message; + drMessageBox(p_message, true); } From 03a936a095c711189ebc3ebc0f014f3c218e504f Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 8 Apr 2022 13:43:17 -0400 Subject: [PATCH 658/842] Restore hiding for misc blank (remove for 1.2.0) --- src/courtroom.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 62e4c02e4..77377cd0b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -673,7 +673,9 @@ void Courtroom::handle_chatmessage(QStringList p_contents) for (int i = 0; i < MESSAGE_SIZE; ++i) m_chatmessage[i] = p_contents[i]; - m_hide_character = m_chatmessage[CMShowCharacter].toInt(); + // TODO: Remove the second part of the OR by 1.2.0 + m_hide_character = (m_chatmessage[CMShowCharacter].toInt() + || m_chatmessage[CMEmote] == "../../misc/blank"); m_play_pre = false; m_play_zoom = false; const int l_emote_mod = m_chatmessage[CMEmoteModifier].toInt(); From 699d43f3ef7b271bf7475b51fa49d9485e59f79d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 9 Apr 2022 14:15:39 +0200 Subject: [PATCH 659/842] Reverted blank emote changes as asked --- src/courtroom.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 77377cd0b..62e4c02e4 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -673,9 +673,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) for (int i = 0; i < MESSAGE_SIZE; ++i) m_chatmessage[i] = p_contents[i]; - // TODO: Remove the second part of the OR by 1.2.0 - m_hide_character = (m_chatmessage[CMShowCharacter].toInt() - || m_chatmessage[CMEmote] == "../../misc/blank"); + m_hide_character = m_chatmessage[CMShowCharacter].toInt(); m_play_pre = false; m_play_zoom = false; const int l_emote_mod = m_chatmessage[CMEmoteModifier].toInt(); From 8bcbdb1f677c5d78949fdcb2d22a88b999de3883 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 9 Apr 2022 11:36:42 -0400 Subject: [PATCH 660/842] Enforce proper start and end ranges in certain at and mid calls --- src/server_socket.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 24652c2bc..c7f82d9c0 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -141,7 +141,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) return; QVector l_chr_list = m_courtroom->get_character_list(); - for (int i = 0; i < l_chr_list.length(); ++i) + for (int i = 0; i < qMin(l_chr_list.length(), l_content.length()); ++i) l_chr_list[i].taken = l_content.at(i) == "-1"; m_courtroom->set_character_list(l_chr_list); } @@ -192,8 +192,8 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) if (!l_found_music) continue; - l_area_list = l_content.mid(0, i - 1); - l_music_list = l_content.mid(i - 1); + l_area_list = l_content.mid(0, qMax(0, i - 1)); + l_music_list = l_content.mid(qMax(0, i - 1)); break; } m_courtroom->set_area_list(l_area_list); @@ -320,7 +320,6 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) } else if (l_header == "CL") { - qDebug() << l_content; m_courtroom->handle_clock(l_content.at(1)); } else if (l_header == "GM") From 3a675709f2bdfa5c83885acfabbb97ce66616b0a Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 9 Apr 2022 13:49:42 -0400 Subject: [PATCH 661/842] Apply recommended fixes --- src/server_socket.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index c7f82d9c0..fdbd51e18 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -141,7 +141,15 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) return; QVector l_chr_list = m_courtroom->get_character_list(); - for (int i = 0; i < qMin(l_chr_list.length(), l_content.length()); ++i) + if (l_content.length() != l_chr_list.length()) + { + qWarning() << "Server sent a character list of length " << l_content.length() + << "which is different from the expected length " << l_chr_list.length() + << "so ignoring it."; + return; + } + + for (int i = 0; i < l_chr_list.length(); ++i) l_chr_list[i].taken = l_content.at(i) == "-1"; m_courtroom->set_character_list(l_chr_list); } @@ -192,8 +200,8 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) if (!l_found_music) continue; - l_area_list = l_content.mid(0, qMax(0, i - 1)); - l_music_list = l_content.mid(qMax(0, i - 1)); + l_area_list = l_content.mid(0, i - 1); + l_music_list = l_content.mid(i - 1); break; } m_courtroom->set_area_list(l_area_list); From d64e006f96c95b8b7a16f52d285d9839b4eada87 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 9 Apr 2022 13:53:39 -0400 Subject: [PATCH 662/842] Choose more descriptive name for CMShowCharacter --- src/courtroom.cpp | 2 +- src/datatypes.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 62e4c02e4..42bcce9ce 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -673,7 +673,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) for (int i = 0; i < MESSAGE_SIZE; ++i) m_chatmessage[i] = p_contents[i]; - m_hide_character = m_chatmessage[CMShowCharacter].toInt(); + m_hide_character = m_chatmessage[CMHideCharacter].toInt(); m_play_pre = false; m_play_zoom = false; const int l_emote_mod = m_chatmessage[CMEmoteModifier].toInt(); diff --git a/src/datatypes.h b/src/datatypes.h index 285dcb6e6..c86f63c22 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -141,7 +141,7 @@ enum ChatMessage : int32_t CMTextColor, CMShowName, CMVideoName, - CMShowCharacter, + CMHideCharacter, }; enum EmoteMod From 5c11a3dabdcab94b85ad4459dd5eeaa81ca20863 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 9 Apr 2022 21:17:22 +0200 Subject: [PATCH 663/842] Information notifications may now be suppressed --- src/debug_functions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debug_functions.cpp b/src/debug_functions.cpp index f44640bcf..c4f7acc1b 100644 --- a/src/debug_functions.cpp +++ b/src/debug_functions.cpp @@ -9,7 +9,7 @@ void drMessageBox(QString p_message, bool p_is_warning) { AOConfig config; - if (p_is_warning && !config.display_notification(p_message)) + if (!config.display_notification(p_message)) return; QMessageBox message; From be205132f5b479dc0efea67c90ace7b30d6f2ad9 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 9 Apr 2022 19:09:58 -0400 Subject: [PATCH 664/842] Require incoming CL packets have at least one argument --- src/server_socket.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index fdbd51e18..53d908e09 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -328,7 +328,8 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) } else if (l_header == "CL") { - m_courtroom->handle_clock(l_content.at(1)); + if (is_courtroom_constructed && l_content.size() > 0) + m_courtroom->handle_clock(l_content.at(1)); } else if (l_header == "GM") { From c8b5a3396bd207d4c5471d572d8b1ce1ffda1637 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 10 Apr 2022 15:26:02 +0200 Subject: [PATCH 665/842] Reworked server advertiser, server info, ... * Removed server and favorite server from AOApplication * Introduced DRMasterClient to handle server advertiser business * Lobby is now in charge of favorite servers --- dronline-client.pro | 3 +- res/ui/config_panel.ui | 23 +++ src/aoapplication.cpp | 46 +---- src/aoapplication.h | 37 +--- src/aoconfig.cpp | 16 ++ src/aoconfig.h | 3 + src/aoconfigpanel.cpp | 9 + src/aoconfigpanel.h | 2 + src/commondefs.cpp | 3 + src/commondefs.h | 3 + src/courtroom.cpp | 1 - src/datatypes.cpp | 31 +++ src/datatypes.h | 32 ++-- src/drchatlog.cpp | 99 +++++----- src/drchatlog.h | 8 +- src/drmasterclient.cpp | 106 +++++++++++ src/drmasterclient.h | 51 +++++ src/drserversocket.cpp | 6 +- src/drserversocket.h | 4 +- src/lobby.cpp | 364 ++++++++++++++++++++++++------------ src/lobby.h | 55 ++++-- src/master_socket.cpp | 118 ------------ src/path_functions.cpp | 5 + src/server_socket.cpp | 9 +- src/text_file_functions.cpp | 55 ------ 25 files changed, 622 insertions(+), 467 deletions(-) create mode 100644 src/drmasterclient.cpp create mode 100644 src/drmasterclient.h delete mode 100644 src/master_socket.cpp diff --git a/dronline-client.pro b/dronline-client.pro index 185e8f27f..7449ff0be 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -49,6 +49,7 @@ HEADERS += \ src/drchatlog.h \ src/drdiscord.h \ src/dreffectmovie.h \ + src/drmasterclient.h \ src/drmovie.h \ src/drpacket.h \ src/drpather.h \ @@ -110,6 +111,7 @@ SOURCES += \ src/drcharactermovie.cpp \ src/drchatlog.cpp \ src/dreffectmovie.cpp \ + src/drmasterclient.cpp \ src/drmovie.cpp \ src/drpacket.cpp \ src/drpather.cpp \ @@ -127,7 +129,6 @@ SOURCES += \ src/lobby.cpp \ src/logger.cpp \ src/main.cpp \ - src/master_socket.cpp \ src/misc_functions.cpp \ src/path_functions.cpp \ src/server_socket.cpp \ diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index cd69c381c..6f812fe3a 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -117,6 +117,29 @@ + + + + Server Advertiser + + + + + + Address: + + + + + + + https://serveraddress.addr/ + + + + + + diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 8496f26bc..946bd4c0b 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -5,6 +5,7 @@ #include "courtroom.h" #include "debug_functions.h" #include "drdiscord.h" +#include "drmasterclient.h" #include "drpacket.h" #include "drserversocket.h" #include "lobby.h" @@ -15,17 +16,11 @@ #include #include -const QString AOApplication::MASTER_NAME = "Master"; -const QString AOApplication::MASTER_HOST = "master.aceattorneyonline.com"; -const int AOApplication::MASTER_PORT = 27016; -const int AOApplication::MASTER_RECONNECT_DELAY = 5000; - AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) { ao_config = new AOConfig(this); ao_config_panel = new AOConfigPanel(this); dr_discord = new DRDiscord(this); - m_master_socket = new DRServerSocket(this); m_server_socket = new DRServerSocket(this); connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(handle_theme_modification())); @@ -47,23 +42,15 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(ao_config, SIGNAL(discord_hide_server_changed(bool)), dr_discord, SLOT(set_hide_server(bool))); connect(ao_config, SIGNAL(discord_hide_character_changed(bool)), dr_discord, SLOT(set_hide_character(bool))); - connect(m_master_socket, SIGNAL(connected_to_server()), this, SLOT(_p_send_master_handshake())); - connect(m_master_socket, SIGNAL(socket_error(QString)), this, SLOT(_p_handle_master_error(QString))); - connect(m_master_socket, SIGNAL(packet_received(DRPacket)), this, SLOT(_p_handle_master_packet(DRPacket))); - connect(m_server_socket, SIGNAL(disconnected_from_server()), this, SLOT(_p_handle_server_disconnection())); connect(m_server_socket, SIGNAL(packet_received(DRPacket)), this, SLOT(_p_handle_server_packet(DRPacket))); - -#ifndef QT_DEBUG - connect_to_master(); -#endif } AOApplication::~AOApplication() { + qInfo() << "Closing Danganronpa Online..."; destruct_lobby(); destruct_courtroom(); - qInfo() << "Closing Danganronpa Online..."; } int AOApplication::get_client_id() const @@ -175,16 +162,6 @@ void AOApplication::handle_audiotracks_reloading() emit reload_audiotracks(); } -void AOApplication::set_favorite_list() -{ - m_favorite_server_list = read_serverlist_txt(); -} - -QVector &AOApplication::get_favorite_list() -{ - return m_favorite_server_list; -} - QString AOApplication::get_current_char() { if (!is_courtroom_constructed) @@ -228,25 +205,6 @@ bool AOApplication::get_first_person_enabled() return ao_config->get_bool("first_person", false); } -void AOApplication::add_favorite_server(int p_server) -{ - if (p_server < 0 || p_server >= m_server_list.size()) - return; - - server_type fav_server = m_server_list.at(p_server); - - QString str_port = QString::number(fav_server.port); - - QString server_line = fav_server.ip + ":" + str_port + ":" + fav_server.name; - - write_to_serverlist_txt(server_line); -} - -QVector &AOApplication::get_server_list() -{ - return m_server_list; -} - void AOApplication::load_fonts() { QFontDatabase l_database; diff --git a/src/aoapplication.h b/src/aoapplication.h index ffc7827d6..0e359468c 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -8,6 +8,7 @@ class AOConfig; class AOConfigPanel; class Courtroom; class DRDiscord; +class DRMasterClient; class DRServerSocket; class Lobby; @@ -21,21 +22,13 @@ class AOApplication : public QApplication Q_OBJECT public: - static const QString MASTER_NAME; - static const QString MASTER_HOST; - static const int MASTER_PORT; - static const int MASTER_RECONNECT_DELAY; - AOApplication(int &argc, char **argv); ~AOApplication(); int get_client_id() const; void set_client_id(int id); - void connect_to_master(); - void send_master_packet(DRPacket packet); - void request_server_list(); - void connect_to_server(server_type server); + void connect_to_server(DRServerInfo server); void send_server_packet(DRPacket packet); Lobby *get_lobby() const; @@ -52,17 +45,12 @@ class AOApplication : public QApplication /////////////////////////////////////////// - void set_favorite_list(); - QVector &get_favorite_list(); - void add_favorite_server(int p_server); - - QVector &get_server_list(); - // Returns the character the player has currently selected QString get_current_char(); // implementation in path_functions.cpp QString get_base_path(); + QString get_base_file_path(QString file); QString get_character_folder_path(QString character); QString get_character_path(QString p_character, QString p_file); // QString get_demothings_path(); @@ -102,18 +90,12 @@ class AOApplication : public QApplication // TODO document what this does QStringList get_sfx_list(); - // Appends the argument string to serverlist.txt - void write_to_serverlist_txt(QString p_line); - // Writes to note file void write_note(QString p_text, QString filename); // appends to note file void append_note(QString p_line, QString filename); - // Returns the contents of serverlist.txt - QVector read_serverlist_txt(); - QString read_ini(QString p_identifier, QString p_path); QString read_theme_ini(QString p_identifier, QString p_file); @@ -163,18 +145,12 @@ class AOApplication : public QApplication // Returns effect on cc_config according to index QStringList get_effect(int index); - // Returns wtce on cc_config according to index - QStringList get_wtce(int index); - // Returns the side of the p_char character from that characters ini file QString get_char_side(QString p_char); // Returns the showname from the ini of p_char QString get_showname(QString p_char); - // Returns showname from showname.ini - QString read_showname(QString p_char); - // Returns the value of chat from the specific p_char's ini file QString get_chat(QString p_char); @@ -212,7 +188,6 @@ public slots: AOConfigPanel *ao_config_panel = nullptr; DRDiscord *dr_discord = nullptr; - DRServerSocket *m_master_socket = nullptr; DRServerSocket *m_server_socket = nullptr; Lobby *m_lobby = nullptr; @@ -238,13 +213,7 @@ public slots: int m_music_count = 0; int m_loaded_music = 0; - QVector m_server_list; - QVector m_favorite_server_list; - private slots: - void _p_send_master_handshake(); - void _p_handle_master_error(QString); - void _p_handle_master_packet(DRPacket); void _p_handle_server_disconnection(); void _p_handle_server_packet(DRPacket); void on_courtroom_closing(); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 83e84223b..823ff7b7e 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -50,6 +50,7 @@ private slots: QStringList notification_filter; QString username; QString callwords; + QString server_advertiser; bool server_alerts; bool discord_presence = false; bool discord_hide_server = false; @@ -137,6 +138,7 @@ void AOConfigPrivate::read_file() username = cfg.value("username").toString(); showname = cfg.value("showname").toString(); callwords = cfg.value("callwords").toString(); + server_advertiser = cfg.value("server_advertiser", "https://servers.aceattorneyonline.com").toString(); server_alerts = cfg.value("server_alerts", true).toBool(); discord_presence = cfg.value("discord_presence", true).toBool(); @@ -228,6 +230,7 @@ void AOConfigPrivate::save_file() cfg.setValue("username", username); cfg.setValue("showname", showname); cfg.setValue("callwords", callwords); + cfg.setValue("server_advertiser", server_advertiser); cfg.setValue("server_alerts", server_alerts); cfg.setValue("discord_presence", discord_presence); @@ -385,6 +388,11 @@ QString AOConfig::callwords() const return d->callwords; } +QString AOConfig::server_advertiser() const +{ + return d->server_advertiser; +} + bool AOConfig::server_alerts_enabled() const { return d->server_alerts; @@ -688,6 +696,14 @@ void AOConfig::set_callwords(QString p_string) d->invoke_signal("callwords_changed", Q_ARG(QString, p_string)); } +void AOConfig::set_server_advertiser(QString p_address) +{ + if (d->server_advertiser == p_address) + return; + d->server_advertiser = p_address; + d->invoke_signal("server_advertiser_changed", Q_ARG(QString, p_address)); +} + void AOConfig::set_server_alerts(bool p_enabled) { if (d->server_alerts == p_enabled) diff --git a/src/aoconfig.h b/src/aoconfig.h index ca05a1df2..841f94d90 100644 --- a/src/aoconfig.h +++ b/src/aoconfig.h @@ -28,6 +28,7 @@ class AOConfig : public QObject QString showname_placeholder() const; QString character_ini(QString base_character) const; QString callwords() const; + QString server_advertiser() const; bool server_alerts_enabled() const; bool discord_presence() const; bool discord_hide_server() const; @@ -84,6 +85,7 @@ public slots: void clear_showname_placeholder(); void set_character_ini(QString base_character, QString target_character); void set_callwords(QString p_string); + void set_server_advertiser(QString address); void set_server_alerts(bool p_enabled); void set_discord_presence(const bool p_enabled); void set_discord_hide_server(const bool p_enabled); @@ -133,6 +135,7 @@ public slots: // general void username_changed(QString); void callwords_changed(QString); + void server_advertiser_changed(QString); void server_alerts_changed(bool); void discord_presence_changed(bool); void discord_hide_server_changed(bool); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 84065d510..7cd998bc4 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -47,6 +47,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // general ui_username = AO_GUI_WIDGET(QLineEdit, "username"); ui_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); + ui_advertiser = AO_GUI_WIDGET(QLineEdit, "advertiser"); ui_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); ui_discord_presence = AO_GUI_WIDGET(QGroupBox, "discord_presence"); ui_discord_hide_server = AO_GUI_WIDGET(QCheckBox, "discord_hide_server"); @@ -122,6 +123,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // general connect(m_config, SIGNAL(username_changed(QString)), ui_username, SLOT(setText(QString))); connect(m_config, SIGNAL(callwords_changed(QString)), ui_callwords, SLOT(setText(QString))); + connect(m_config, SIGNAL(server_advertiser_changed(QString)), ui_advertiser, SLOT(setText(QString))); connect(m_config, SIGNAL(server_alerts_changed(bool)), ui_server_alerts, SLOT(setChecked(bool))); connect(m_config, SIGNAL(discord_presence_changed(bool)), ui_discord_presence, SLOT(setChecked(bool))); connect(m_config, SIGNAL(discord_hide_server_changed(bool)), ui_discord_hide_server, SLOT(setChecked(bool))); @@ -192,6 +194,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // general connect(ui_username, SIGNAL(editingFinished()), this, SLOT(username_editing_finished())); connect(ui_callwords, SIGNAL(editingFinished()), this, SLOT(callwords_editing_finished())); + connect(ui_advertiser, SIGNAL(editingFinished()), this, SLOT(advertiser_editing_finished())); connect(ui_server_alerts, SIGNAL(toggled(bool)), m_config, SLOT(set_server_alerts(bool))); connect(ui_discord_presence, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_presence(bool))); connect(ui_discord_hide_server, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_server(const bool))); @@ -254,6 +257,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // general ui_username->setText(m_config->username()); ui_callwords->setText(m_config->callwords()); + ui_advertiser->setText(m_config->server_advertiser()); ui_server_alerts->setChecked(m_config->server_alerts_enabled()); // game @@ -594,6 +598,11 @@ void AOConfigPanel::showname_editing_finished() m_config->set_showname(ui_showname->text()); } +void AOConfigPanel::advertiser_editing_finished() +{ + m_config->set_server_advertiser(ui_advertiser->text()); +} + void AOConfigPanel::callwords_editing_finished() { m_config->set_callwords(ui_callwords->text()); diff --git a/src/aoconfigpanel.h b/src/aoconfigpanel.h index a193530e7..8598bdcfd 100644 --- a/src/aoconfigpanel.h +++ b/src/aoconfigpanel.h @@ -86,6 +86,7 @@ private slots: // general QLineEdit *ui_username = nullptr; QLineEdit *ui_callwords = nullptr; + QLineEdit *ui_advertiser = nullptr; QCheckBox *ui_server_alerts = nullptr; QGroupBox *ui_discord_presence = nullptr; QCheckBox *ui_discord_hide_server = nullptr; @@ -155,6 +156,7 @@ private slots: private slots: void username_editing_finished(); void showname_editing_finished(); + void advertiser_editing_finished(); void callwords_editing_finished(); }; diff --git a/src/commondefs.cpp b/src/commondefs.cpp index 16b2ca463..73bc6b3a6 100644 --- a/src/commondefs.cpp +++ b/src/commondefs.cpp @@ -5,6 +5,9 @@ const QString BACKGROUND_DEFAULT_NAME = "gs4"; const QString BASE_CONFIG_INI = "/base/config.ini"; +const QString BASE_SERVER_BROWSER_INI = "server_browser.ini"; +const QString BASE_FAVORITE_SERVERS_INI = "favorite_servers.ini"; +const QString BASE_SERVERLIST_TXT = "serverlist.txt"; const QString CHARACTER_CHAR_INI = "char.ini"; const QString CHARACTER_SOUNDS_INI = "sounds.ini"; diff --git a/src/commondefs.h b/src/commondefs.h index 6c2f60780..e3cea60d8 100644 --- a/src/commondefs.h +++ b/src/commondefs.h @@ -5,6 +5,9 @@ class QString; extern const QString BACKGROUND_DEFAULT_NAME; extern const QString BASE_CONFIG_INI; +extern const QString BASE_SERVER_BROWSER_INI; +extern const QString BASE_FAVORITE_SERVERS_INI; +extern const QString BASE_SERVERLIST_TXT; extern const QString CHARACTER_CHAR_INI; extern const QString CHARACTER_SOUNDS_INI; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 62e4c02e4..39fbb8038 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2126,7 +2126,6 @@ void Courtroom::on_back_to_lobby_clicked() hide(); ao_app->construct_lobby(); - ao_app->get_lobby()->list_servers(); ao_app->get_lobby()->set_choose_a_server(); ao_app->destruct_courtroom(); } diff --git a/src/datatypes.cpp b/src/datatypes.cpp index 9e87853ea..1c665bccf 100644 --- a/src/datatypes.cpp +++ b/src/datatypes.cpp @@ -17,3 +17,34 @@ QMap DR::get_default_color_map() return default_color_map; } + +QString DRServerInfo::to_info() const +{ + QString r_info = "Unnamed Server"; + + if (!name.isEmpty()) + { + r_info = name; + } + else if (!address.isEmpty()) + { + r_info = to_address(); + } + + return r_info; +} + +QString DRServerInfo::to_address() const +{ + return address + ":" + QString::number(port); +} + +bool DRServerInfo::operator==(const DRServerInfo &o) const +{ + return address == o.address && port == o.port; +} + +bool DRServerInfo::operator!=(const DRServerInfo &other) const +{ + return !operator==(other); +} diff --git a/src/datatypes.h b/src/datatypes.h index 285dcb6e6..a32d6c7fc 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -3,6 +3,7 @@ #include #include +#include class DREmote { @@ -82,31 +83,22 @@ struct DRSfx bool is_found; }; -struct server_type +class DRServerInfo { +public: QString name; - QString desc; - QString ip; + QString description; + QString address; int port; - bool is_favorite = false; + bool favorite = false; - QString to_info() const - { - QString r_info; - - if (!name.isEmpty()) - { - r_info = name; - } - else if (!ip.isEmpty()) - { - r_info = to_address(); - } - - return r_info; - } - QString to_address() const { return ip + ":" + QString::number(port); } + QString to_info() const; + QString to_address() const; + + bool operator==(const DRServerInfo &other) const; + bool operator!=(const DRServerInfo &other) const; }; +using DRServerInfoList = QVector; struct char_type { diff --git a/src/drchatlog.cpp b/src/drchatlog.cpp index 61cb71b85..fe46afef0 100644 --- a/src/drchatlog.cpp +++ b/src/drchatlog.cpp @@ -15,12 +15,17 @@ DRChatLog::DRChatLog(QWidget *parent) : QTextBrowser(parent), dr_config(new AOCo void DRChatLog::append_chatmessage(QString p_name, QString p_text) { - queue_message(p_name.trimmed().isEmpty() ? "Anonymous" : p_name, p_text); + queue_message(p_name.trimmed().isEmpty() ? "Anonymous" : p_name, p_text, false); } -void DRChatLog::append_error(QString p_text) +void DRChatLog::append_information(QString p_text) { - queue_message(nullptr, p_text); + queue_message(nullptr, p_text, false); +} + +void DRChatLog::append_html(QString p_text) +{ + queue_message(nullptr, p_text, true); } void DRChatLog::reset_message_format() @@ -28,12 +33,13 @@ void DRChatLog::reset_message_format() _p_reset_log(); } -void DRChatLog::queue_message(QString p_name, QString p_text) +void DRChatLog::queue_message(QString p_name, QString p_text, bool p_is_html) { Message l_message; l_message.timestamp = QDateTime::currentDateTime(); l_message.name = p_name; l_message.text = p_text; + l_message.is_html = p_is_html; m_message_queue.append(std::move(l_message)); Q_EMIT message_queued(); } @@ -73,53 +79,58 @@ void DRChatLog::_p_write_message_queue() l_cursor.insertText(": ", l_normal_format); } - const QString l_text = l_message.text; - - class TextPiece + if (l_message.is_html) { - public: - QString text; - bool is_href = false; - - TextPiece() - {} - TextPiece(QString p_text, bool p_is_href = false) : text(p_text), is_href(p_is_href) - {} - }; - QVector l_piece_list; - - const QRegularExpression l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); - QRegularExpressionMatchIterator l_iterator = l_regex.globalMatch(l_text); - { // capture all text pieces - int l_index = 0; - - while (l_iterator.hasNext()) - { - QRegularExpressionMatch l_match = l_iterator.next(); - - const int l_captureIndex = l_match.capturedStart(); - if (l_index < l_captureIndex) - l_piece_list.append(l_text.mid(l_index, l_captureIndex - l_index)); - l_piece_list.append(TextPiece(l_match.captured(), true)); - l_index = l_match.capturedEnd(); - } - - l_piece_list.append(l_text.mid(l_index)); + insertHtml(l_message.text); } - - for (const TextPiece &i_piece : qAsConst(l_piece_list)) + else { - if (i_piece.text.isEmpty()) - continue; + const QString l_text = l_message.text; - QTextCharFormat l_piece_format = l_normal_format; - if (i_piece.is_href) + class TextPiece { - l_piece_format = l_href_format; - l_piece_format.setAnchorHref(i_piece.text); + public: + QString text; + bool is_href = false; + + TextPiece() {} + TextPiece(QString p_text, bool p_is_href = false) : text(p_text), is_href(p_is_href) {} + }; + QVector l_piece_list; + + const QRegularExpression l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); + QRegularExpressionMatchIterator l_iterator = l_regex.globalMatch(l_text); + { // capture all text pieces + int l_index = 0; + + while (l_iterator.hasNext()) + { + QRegularExpressionMatch l_match = l_iterator.next(); + + const int l_captureIndex = l_match.capturedStart(); + if (l_index < l_captureIndex) + l_piece_list.append(l_text.mid(l_index, l_captureIndex - l_index)); + l_piece_list.append(TextPiece(l_match.captured(), true)); + l_index = l_match.capturedEnd(); + } + + l_piece_list.append(l_text.mid(l_index)); } - l_cursor.insertText(i_piece.is_href ? QUrl(i_piece.text).toString() : i_piece.text, l_piece_format); + for (const TextPiece &i_piece : qAsConst(l_piece_list)) + { + if (i_piece.text.isEmpty()) + continue; + + QTextCharFormat l_piece_format = l_normal_format; + if (i_piece.is_href) + { + l_piece_format = l_href_format; + l_piece_format.setAnchorHref(i_piece.text); + } + + l_cursor.insertText(i_piece.is_href ? QUrl(i_piece.text).toString() : i_piece.text, l_piece_format); + } } } diff --git a/src/drchatlog.h b/src/drchatlog.h index 41758cdb7..82ce535f5 100644 --- a/src/drchatlog.h +++ b/src/drchatlog.h @@ -14,8 +14,9 @@ class DRChatLog : public QTextBrowser public: DRChatLog(QWidget *parent = nullptr); - void append_chatmessage(QString name, QString text); - void append_error(QString text); + void append_chatmessage(QString name, QString message); + void append_information(QString message); + void append_html(QString html); void reset_message_format(); signals: @@ -30,11 +31,12 @@ class DRChatLog : public QTextBrowser QDateTime timestamp; QString name; QString text; + bool is_html = false; }; QList m_message_list; QQueue m_message_queue; - void queue_message(QString name, QString text); + void queue_message(QString name, QString text, bool is_html); private slots: void _p_write_message_queue(); diff --git a/src/drmasterclient.cpp b/src/drmasterclient.cpp new file mode 100644 index 000000000..06bf590d9 --- /dev/null +++ b/src/drmasterclient.cpp @@ -0,0 +1,106 @@ +#include "drmasterclient.h" + +// qt +#include +#include +#include +#include +#include +#include + +DRMasterClient::DRMasterClient(QObject *parent) : QObject(parent), m_network(new QNetworkAccessManager(this)) +{} + +DRMasterClient::~DRMasterClient() +{} + +QString DRMasterClient::address() const +{ + return m_address; +} + +void DRMasterClient::set_address(QString p_address) +{ + if (m_address == p_address) + return; + m_address = p_address; + emit address_changed(); +} + +QString DRMasterClient::motd() const +{ + return m_motd; +} + +void DRMasterClient::request_motd() +{ + send_get_request("/motd", &DRMasterClient::process_motd); +} + +DRServerInfoList DRMasterClient::server_list() const +{ + return m_server_list; +} + +void DRMasterClient::request_server_list() +{ + send_get_request("/servers", &DRMasterClient::process_server_list); +} + +void DRMasterClient::send_get_request(QString request, Delegate delegate) +{ + if (m_address.isEmpty()) + { + qWarning() << "error: address is undefined"; + return; + } + + QNetworkRequest l_request(m_address + request); + QNetworkReply *l_reply = m_network->get(l_request); + l_reply->setParent(this); + m_pending_delegates.insert(l_reply, delegate); + connect(l_reply, SIGNAL(finished()), this, SLOT(process_request())); +} + +void DRMasterClient::process_request() +{ + QNetworkReply *l_reply = dynamic_cast(sender()); + if (!l_reply) + { + qCritical() << "error: sender is not expected object" << sender(); + return; + } + + QVariant l_data = l_reply->readAll(); + const QJsonDocument l_doc = QJsonDocument::fromJson(l_data.toByteArray()); + if (!l_doc.isNull()) + l_data = l_doc.toVariant(); + + const Delegate l_delegate = m_pending_delegates.take(l_reply); + (this->*l_delegate)(l_data); + l_reply->deleteLater(); +} + +void DRMasterClient::process_motd(QVariant p_data) +{ + m_motd = p_data.toString(); + emit motd_changed(); +} + +void DRMasterClient::process_server_list(QVariant p_data) +{ + DRServerInfoList l_server_list; + const QJsonArray l_json_array = p_data.toJsonArray(); + for (const QJsonValue &i_value : l_json_array) + { + const QJsonObject i_object = i_value.toObject(); + DRServerInfo l_server; + l_server.name = i_object.value("name").toString(); + l_server.description = i_object.value("description").toString(); + l_server.address = i_object.value("ip").toString(); + l_server.port = i_object.value("port").toInt(); + l_server_list.append(std::move(l_server)); + } + m_server_list = std::move(l_server_list); + emit server_list_changed(); +} diff --git a/src/drmasterclient.h b/src/drmasterclient.h new file mode 100644 index 000000000..0a355d665 --- /dev/null +++ b/src/drmasterclient.h @@ -0,0 +1,51 @@ +#pragma once + +// std +#include + +// qt +#include +#include +#include + +class QNetworkAccessManager; + +// src +#include "datatypes.h" + +class DRMasterClient : public QObject +{ + Q_OBJECT + +public: + DRMasterClient(QObject *parent = nullptr); + ~DRMasterClient(); + + QString address() const; + QString motd() const; + DRServerInfoList server_list() const; + +public slots: + void set_address(QString address); + void request_motd(); + void request_server_list(); + +signals: + void address_changed(); + void motd_changed(); + void server_list_changed(); + +private: + QNetworkAccessManager *m_network = nullptr; + QString m_address; + QString m_motd; + DRServerInfoList m_server_list; + using Delegate = void (DRMasterClient::*)(QVariant); + QHash m_pending_delegates; + +private slots: + void send_get_request(QString request, Delegate delegate); + void process_request(); + void process_motd(QVariant); + void process_server_list(QVariant); +}; diff --git a/src/drserversocket.cpp b/src/drserversocket.cpp index 3acf5622e..dcaac1465 100644 --- a/src/drserversocket.cpp +++ b/src/drserversocket.cpp @@ -7,7 +7,7 @@ const int DRServerSocket::RECONNECT_DELAY = 5000; namespace { -QString drFormatServerInfo(const server_type &server) +QString drFormatServerInfo(const DRServerInfo &server) { const QString l_server_info = server.to_info(); return !l_server_info.isEmpty() ? QString("<%1>").arg(l_server_info) : nullptr; @@ -35,14 +35,14 @@ bool DRServerSocket::is_connected() const return m_socket->state() == QTcpSocket::ConnectedState; } -void DRServerSocket::connect_to_server(server_type p_server, bool p_is_reconnectable) +void DRServerSocket::connect_to_server(DRServerInfo p_server, bool p_is_reconnectable) { disconnect_from_server(); m_server = p_server; is_reconnectable = p_is_reconnectable; if (is_reconnectable) m_reconnect_timer->start(); - m_socket->connectToHost(p_server.ip, p_server.port); + m_socket->connectToHost(p_server.address, p_server.port); } void DRServerSocket::disconnect_from_server() diff --git a/src/drserversocket.h b/src/drserversocket.h index b9b51bb9d..79ce73088 100644 --- a/src/drserversocket.h +++ b/src/drserversocket.h @@ -17,7 +17,7 @@ class DRServerSocket : public QObject DRServerSocket(QObject *parent = nullptr); bool is_connected() const; - void connect_to_server(server_type server, bool is_reconnectable); + void connect_to_server(DRServerInfo server, bool is_reconnectable); void disconnect_from_server(); public slots: @@ -33,7 +33,7 @@ public slots: private: static const int RECONNECT_DELAY; - server_type m_server; + DRServerInfo m_server; QTcpSocket *m_socket = nullptr; bool m_is_connected = false; QTimer *m_reconnect_timer = nullptr; diff --git a/src/lobby.cpp b/src/lobby.cpp index 9fcdf3741..f1407d023 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -7,6 +7,7 @@ #include "commondefs.h" #include "debug_functions.h" #include "drchatlog.h" +#include "drmasterclient.h" #include "drpacket.h" #include "drpather.h" #include "drtextedit.h" @@ -14,24 +15,29 @@ #include "version.h" #include +#include #include +#include #include #include #include #include +#include +#include Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; ao_config = new AOConfig(this); + m_master_client = new DRMasterClient(this); this->setWindowTitle("Danganronpa Online"); ui_background = new AOImageDisplay(this, ao_app); - ui_public_servers = new AOButton(this, ao_app); - ui_favorites = new AOButton(this, ao_app); + ui_hide_public_servers = new AOButton(this, ao_app); + ui_hide_favorite_servers = new AOButton(this, ao_app); ui_refresh = new AOButton(this, ao_app); - ui_add_to_fav = new AOButton(this, ao_app); + ui_toggle_favorite = new AOButton(this, ao_app); ui_connect = new AOButton(this, ao_app); ui_version = new DRTextEdit(this); ui_version->setFrameStyle(QFrame::NoFrame); @@ -52,9 +58,6 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_chatbox = new DRChatLog(this); ui_chatbox->setOpenExternalLinks(true); ui_chatbox->setReadOnly(true); - ui_chatname = new QLineEdit(this); - ui_chatname->setPlaceholderText("Name"); - ui_chatmessage = new QLineEdit(this); ui_loading_background = new AOImageDisplay(this, ao_app); ui_loading_text = new DRTextEdit(ui_loading_background); ui_progress_bar = new QProgressBar(ui_loading_background); @@ -63,25 +66,37 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_progress_bar->setStyleSheet("QProgressBar{ color: white; }"); ui_cancel = new AOButton(ui_loading_background, ao_app); - connect(ui_public_servers, SIGNAL(clicked()), this, SLOT(on_public_servers_clicked())); - connect(ui_favorites, SIGNAL(clicked()), this, SLOT(on_favorites_clicked())); + connect(ao_config, SIGNAL(server_advertiser_changed(QString)), m_master_client, SLOT(set_address(QString))); + connect(m_master_client, SIGNAL(address_changed()), this, SLOT(request_advertiser_update())); + connect(m_master_client, SIGNAL(motd_changed()), this, SLOT(update_motd())); + connect(m_master_client, SIGNAL(server_list_changed()), this, SLOT(update_server_list())); + + connect(ui_hide_public_servers, SIGNAL(clicked()), this, SLOT(toggle_hide_public_servers())); + connect(ui_hide_favorite_servers, SIGNAL(clicked()), this, SLOT(toggle_hide_favorite_servers())); connect(ui_refresh, SIGNAL(pressed()), this, SLOT(on_refresh_pressed())); connect(ui_refresh, SIGNAL(released()), this, SLOT(on_refresh_released())); - connect(ui_add_to_fav, SIGNAL(pressed()), this, SLOT(on_add_to_fav_pressed())); - connect(ui_add_to_fav, SIGNAL(released()), this, SLOT(on_add_to_fav_released())); + connect(ui_toggle_favorite, SIGNAL(pressed()), this, SLOT(on_add_to_fav_pressed())); + connect(ui_toggle_favorite, SIGNAL(released()), this, SLOT(on_add_to_fav_released())); connect(ui_connect, SIGNAL(pressed()), this, SLOT(on_connect_pressed())); connect(ui_connect, SIGNAL(released()), this, SLOT(on_connect_released())); connect(ui_about, SIGNAL(clicked()), this, SLOT(on_about_clicked())); - connect(ui_server_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_server_list_clicked(QModelIndex))); - connect(ui_chatmessage, SIGNAL(returnPressed()), this, SLOT(on_chatfield_return_pressed())); + connect(ui_server_list, SIGNAL(currentRowChanged(int)), this, SLOT(connect_to_server(int))); connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); set_widgets(); + load_settings(); + load_favorite_server_list(); + m_master_client->set_address(ao_config->server_advertiser()); +} + +Lobby::~Lobby() +{ + save_settings(); } -bool Lobby::is_public_server() const +DRServerInfoList Lobby::get_combined_server_list() { - return is_public_server_selected; + return m_combined_server_list; } // sets images, position and size @@ -116,17 +131,17 @@ void Lobby::set_widgets() set_size_and_pos(ui_background, "lobby", LOBBY_DESIGN_INI, ao_app); ui_background->set_theme_image("lobbybackground.png"); - set_size_and_pos(ui_public_servers, "public_servers", LOBBY_DESIGN_INI, ao_app); - ui_public_servers->set_image("publicservers_selected.png"); + set_size_and_pos(ui_hide_public_servers, "public_servers", LOBBY_DESIGN_INI, ao_app); + ui_hide_public_servers->set_image("publicservers.png"); - set_size_and_pos(ui_favorites, "favorites", LOBBY_DESIGN_INI, ao_app); - ui_favorites->set_image("favorites.png"); + set_size_and_pos(ui_hide_favorite_servers, "favorites", LOBBY_DESIGN_INI, ao_app); + ui_hide_favorite_servers->set_image("favorites.png"); set_size_and_pos(ui_refresh, "refresh", LOBBY_DESIGN_INI, ao_app); ui_refresh->set_image("refresh.png"); - set_size_and_pos(ui_add_to_fav, "add_to_fav", LOBBY_DESIGN_INI, ao_app); - ui_add_to_fav->set_image("addtofav.png"); + set_size_and_pos(ui_toggle_favorite, "add_to_fav", LOBBY_DESIGN_INI, ao_app); + ui_toggle_favorite->set_image("addtofav.png"); set_size_and_pos(ui_connect, "connect", LOBBY_DESIGN_INI, ao_app); ui_connect->set_image("connect.png"); @@ -154,16 +169,6 @@ void Lobby::set_widgets() ui_chatbox->setReadOnly(true); ui_chatbox->setStyleSheet("QTextBrowser{background-color: rgba(0, 0, 0, 0);}"); - set_size_and_pos(ui_chatname, "chatname", LOBBY_DESIGN_INI, ao_app); - set_text_alignment(ui_chatname, "chatname", LOBBY_FONTS_INI, ao_app); - ui_chatname->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "selection-background-color: rgba(0, 0, 0, 0);"); - - set_size_and_pos(ui_chatmessage, "chatmessage", LOBBY_DESIGN_INI, ao_app); - set_text_alignment(ui_chatmessage, "chatmessage", LOBBY_FONTS_INI, ao_app); - ui_chatmessage->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "selection-background-color: rgba(0, 0, 0, 0);"); - ui_loading_background->resize(this->width(), this->height()); ui_loading_background->set_theme_image("loadingbackground.png"); @@ -192,8 +197,6 @@ void Lobby::set_fonts() set_drtextedit_font(ui_player_count, "player_count", LOBBY_FONTS_INI, ao_app); set_font(ui_description, "description", LOBBY_FONTS_INI, ao_app); set_font(ui_chatbox, "chatbox", LOBBY_FONTS_INI, ao_app); - set_font(ui_chatname, "chatname", LOBBY_FONTS_INI, ao_app); - set_font(ui_chatmessage, "chatmessage", LOBBY_FONTS_INI, ao_app); set_drtextedit_font(ui_loading_text, "loading_text", LOBBY_FONTS_INI, ao_app); set_font(ui_server_list, "server_list", LOBBY_FONTS_INI, ao_app); } @@ -211,8 +214,6 @@ void Lobby::set_stylesheets() set_stylesheet(ui_player_count, "[PLAYER COUNT]"); set_stylesheet(ui_description, "[DESCRIPTION]"); set_stylesheet(ui_chatbox, "[CHAT BOX]"); - set_stylesheet(ui_chatname, "[CHAT NAME]"); - set_stylesheet(ui_chatmessage, "[CHAT MESSAGE]"); set_stylesheet(ui_loading_text, "[LOADING TEXT]"); set_stylesheet(ui_server_list, "[SERVER LIST]"); } @@ -234,9 +235,9 @@ void Lobby::set_loading_text(QString p_text) ui_loading_text->append(p_text); } -server_type Lobby::get_selected_server() +DRServerInfo Lobby::get_selected_server() { - return m_last_server; + return m_current_server; } void Lobby::set_loading_value(int p_value) @@ -244,155 +245,274 @@ void Lobby::set_loading_value(int p_value) ui_progress_bar->setValue(p_value); } -void Lobby::on_public_servers_clicked() +void Lobby::load_settings() { - ui_public_servers->set_image("publicservers_selected.png"); - ui_favorites->set_image("favorites.png"); + QSettings l_ini(ao_app->get_base_file_path(BASE_SERVER_BROWSER_INI), QSettings::IniFormat); + l_ini.setIniCodec("UTF-8"); - list_servers(); - - is_public_server_selected = true; + l_ini.beginGroup("filters"); + hide_public_servers(l_ini.value("hide_public", false).toBool()); + hide_favorite_servers(l_ini.value("hide_favorites", false).toBool()); + l_ini.endGroup(); } -void Lobby::on_favorites_clicked() +void Lobby::save_settings() { - ui_favorites->set_image("favorites_selected.png"); - ui_public_servers->set_image("publicservers.png"); - - ao_app->set_favorite_list(); - // ao_app->favorite_list = read_serverlist_txt(); + QSettings l_ini(ao_app->get_base_file_path(BASE_SERVER_BROWSER_INI), QSettings::IniFormat); + l_ini.setIniCodec("UTF-8"); + + l_ini.beginGroup("filters"); + l_ini.setValue("hide_public", m_hide_public_servers); + l_ini.setValue("hide_favorites", m_hide_favorite_servers); + l_ini.endGroup(); + l_ini.sync(); +} - list_favorites(); +void Lobby::load_favorite_server_list() +{ + const QString l_file_path = ao_app->find_asset_path(ao_app->get_base_file_path(BASE_FAVORITE_SERVERS_INI)); + if (l_file_path.isEmpty()) + { + load_legacy_favorite_server_list(); + return; + } - is_public_server_selected = false; + DRServerInfoList l_server_list; + QSettings l_ini(l_file_path, QSettings::IniFormat); + l_ini.setIniCodec("UTF-8"); + l_server_list.clear(); + for (const QString &i_group : l_ini.childGroups()) + { + l_ini.beginGroup(i_group); + DRServerInfo l_server; + l_server.name = l_ini.value("name").toString(); + l_server.address = l_ini.value("address").toString(); + l_server.port = l_ini.value("port").toInt(); + l_server.favorite = true; + l_server_list.append(std::move(l_server)); + l_ini.endGroup(); + } + set_favorite_server_list(l_server_list); } -void Lobby::on_refresh_pressed() +void Lobby::load_legacy_favorite_server_list() { - ui_refresh->set_image("refresh_pressed.png"); + DRServerInfoList l_server_list; + QFile l_file(ao_app->get_base_file_path(BASE_SERVERLIST_TXT)); + if (l_file.open(QIODevice::ReadOnly)) + { + QTextStream in(&l_file); + while (!in.atEnd()) + { + const QStringList l_contents = in.readLine().split(":"); + if (l_contents.length() < 3) + continue; + DRServerInfo f_server; + f_server.address = l_contents.at(0); + f_server.port = l_contents.at(1).toInt(); + f_server.name = l_contents.at(2); + f_server.favorite = true; + l_server_list.append(std::move(f_server)); + } + } + set_favorite_server_list(l_server_list); } -void Lobby::on_refresh_released() +void Lobby::save_favorite_server_list() { - ui_refresh->set_image("refresh.png"); - ao_app->request_server_list(); + QSettings l_ini(ao_app->get_base_file_path(BASE_FAVORITE_SERVERS_INI), QSettings::IniFormat); + l_ini.setIniCodec("UTF-8"); + + l_ini.clear(); + for (int i = 0; i < m_favorite_server_list.length(); ++i) + { + const DRServerInfo &i_server = m_favorite_server_list.at(i); + l_ini.beginGroup(QString::number(i)); + l_ini.setValue("name", i_server.name); + l_ini.setValue("address", i_server.address); + l_ini.setValue("port", i_server.port); + l_ini.endGroup(); + } + l_ini.sync(); } -void Lobby::on_add_to_fav_pressed() +void Lobby::request_advertiser_update() { - ui_add_to_fav->set_image("addtofav_pressed.png"); + m_master_client->request_motd(); + m_master_client->request_server_list(); } -void Lobby::on_add_to_fav_released() +void Lobby::update_motd() { - ui_add_to_fav->set_image("addtofav.png"); + ui_chatbox->append_html(m_master_client->motd()); +} - // you cant add favorites from favorites m8 - if (!is_public_server_selected) - return; +void Lobby::update_server_list() +{ + m_server_list = m_master_client->server_list(); + update_combined_server_list(); + emit server_list_changed(); +} - ao_app->add_favorite_server(ui_server_list->currentRow()); +void Lobby::set_favorite_server_list(DRServerInfoList p_server_list) +{ + m_favorite_server_list = p_server_list; + update_combined_server_list(); + emit favorite_server_list_changed(); } -void Lobby::on_connect_pressed() +void Lobby::update_combined_server_list() { - ui_connect->set_image("connect_pressed.png"); + m_combined_server_list = m_favorite_server_list + m_server_list; + update_server_listing(); } -void Lobby::on_connect_released() +void Lobby::update_server_listing() { - if (!ao_app->is_server_compatible()) + ui_server_list->clear(); + const QBrush l_favorite_color = ao_app->get_color("favorite_server_color", LOBBY_DESIGN_INI); + for (int i = 0; i < m_combined_server_list.length(); ++i) { - call_warning("You are connecting to an incompatible DRO server.
    " - "The client may not work properly, if at all."); + const DRServerInfo &l_server = m_combined_server_list[i]; + QListWidgetItem *l_server_item = new QListWidgetItem; + ui_server_list->addItem(l_server_item); + l_server_item->setText(l_server.name); + l_server_item->setData(Qt::UserRole, i); + if (l_server.favorite) + { + l_server_item->setBackground(l_favorite_color); + } } - - ui_connect->set_image("connect.png"); - ao_app->send_server_packet(DRPacket("askchaa")); + filter_server_listing(); } -void Lobby::on_about_clicked() +void Lobby::filter_server_listing() { - QMessageBox::about(this, tr("About"), get_about_message()); + for (int i = 0; i < ui_server_list->count(); ++i) + { + QListWidgetItem *l_server_item = ui_server_list->item(i); + l_server_item->setHidden(m_combined_server_list.at(i).favorite ? m_hide_favorite_servers : m_hide_public_servers); + } + select_current_server(); } -void Lobby::on_server_list_clicked(QModelIndex p_model) +void Lobby::select_current_server() { - int n_server = p_model.row(); - - if (n_server < 0) - return; - - if (is_public_server_selected) + for (int i = 0; i < ui_server_list->count(); ++i) { - QVector f_server_list = ao_app->get_server_list(); - - if (n_server >= f_server_list.size()) - return; - - m_last_server = f_server_list.at(p_model.row()); - } - else - { - if (n_server >= ao_app->get_favorite_list().size()) - return; - - m_last_server = ao_app->get_favorite_list().at(p_model.row()); - m_last_server.is_favorite = true; + QListWidgetItem *l_item = ui_server_list->item(i); + if (l_item->text() == m_current_server.name) + { + ui_server_list->scrollToItem(l_item); + ui_server_list->setCurrentItem(l_item); + ui_server_list->setFocus(); + break; + } } +} - ui_player_count->setText("Connecting..."); - ui_description->setHtml("Connecting to " + m_last_server.name + "..."); +void Lobby::hide_public_servers(bool p_on) +{ + if (m_hide_public_servers == p_on) + return; + m_hide_public_servers = p_on; + ui_hide_public_servers->set_image(m_hide_public_servers ? "publicservers_selected.png" : "publicservers.png"); + filter_server_listing(); +} - ao_app->connect_to_server(m_last_server); +void Lobby::toggle_hide_public_servers() +{ + hide_public_servers(!m_hide_public_servers); } -void Lobby::on_chatfield_return_pressed() +void Lobby::hide_favorite_servers(bool p_on) { - // no you can't send empty messages - if (ui_chatname->text() == "" || ui_chatmessage->text() == "") + if (m_hide_favorite_servers == p_on) return; + m_hide_favorite_servers = p_on; + ui_hide_favorite_servers->set_image(m_hide_favorite_servers ? "favorites_selected.png" : "favorites.png"); + filter_server_listing(); +} - QString f_header = "CT"; - QStringList f_contents{ui_chatname->text(), ui_chatmessage->text()}; - - ao_app->send_master_packet(DRPacket(f_header, f_contents)); +void Lobby::toggle_hide_favorite_servers() +{ + hide_favorite_servers(!m_hide_favorite_servers); +} - ui_chatmessage->clear(); +void Lobby::on_refresh_pressed() +{ + ui_refresh->set_image("refresh_pressed.png"); } -void Lobby::list_servers() +void Lobby::on_refresh_released() { - is_public_server_selected = true; - ui_favorites->set_image("favorites.png"); - ui_public_servers->set_image("publicservers_selected.png"); + ui_refresh->set_image("refresh.png"); + m_master_client->request_server_list(); +} - ui_server_list->clear(); +void Lobby::on_add_to_fav_pressed() +{ + ui_toggle_favorite->set_image("addtofav_pressed.png"); +} - for (const server_type &i_server : ao_app->get_server_list()) +void Lobby::on_add_to_fav_released() +{ + ui_toggle_favorite->set_image("addtofav.png"); + DRServerInfoList l_new_list = m_favorite_server_list; + if (m_current_server.favorite) + { + l_new_list.removeAll(m_current_server); + } + else if (!m_favorite_server_list.contains(m_current_server)) { - ui_server_list->addItem(i_server.name); + m_current_server.favorite = true; + + const QString l_new_name = + QInputDialog::getText(this, windowTitle(), "Name", QLineEdit::Normal, m_current_server.name); + if (!l_new_name.isEmpty()) + m_current_server.name = l_new_name; + + l_new_list.append(m_current_server); } + set_favorite_server_list(l_new_list); + save_favorite_server_list(); } -void Lobby::list_favorites() +void Lobby::on_connect_pressed() { - ui_server_list->clear(); + ui_connect->set_image("connect_pressed.png"); +} - for (const server_type &i_server : ao_app->get_favorite_list()) +void Lobby::on_connect_released() +{ + if (!ao_app->is_server_compatible()) { - ui_server_list->addItem(i_server.name); + call_warning("You are connecting to an incompatible DRO server.
    " + "The client may not work properly, if at all."); } + + ui_connect->set_image("connect.png"); + ao_app->send_server_packet(DRPacket("askchaa")); } -void Lobby::append_chatmessage(QString f_name, QString f_message) +void Lobby::on_about_clicked() { - ui_chatbox->append_chatmessage(f_name, f_message); + QMessageBox::about(this, tr("About"), get_about_message()); } -void Lobby::append_error(QString f_message) +void Lobby::connect_to_server(int p_row) { - ui_chatbox->append_error(f_message); + if (p_row == -1) + return; + + const DRServerInfo l_prev_server = std::move(m_current_server); + m_current_server = m_combined_server_list.at(p_row); + if (l_prev_server != m_current_server) + { + ui_player_count->setText("Connecting..."); + ui_description->setHtml("Connecting to " + m_current_server.name + "..."); + ao_app->connect_to_server(m_current_server); + } } void Lobby::set_choose_a_server() @@ -407,7 +527,7 @@ void Lobby::set_player_count(int players_online, int max_players) ui_player_count->setText(f_string); ui_player_count->setAlignment(Qt::AlignHCenter); - QString l_text = m_last_server.desc.toHtmlEscaped(); + QString l_text = m_current_server.description.toHtmlEscaped(); const QRegExp l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); if (l_text.contains(l_regex)) l_text.replace(l_regex, "\\1"); diff --git a/src/lobby.h b/src/lobby.h index 977fe72a7..911a06b30 100644 --- a/src/lobby.h +++ b/src/lobby.h @@ -8,6 +8,7 @@ class AOButton; class AOConfig; class AOImageDisplay; class DRChatLog; +class DRMasterClient; class DRTextEdit; #include @@ -24,14 +25,11 @@ class Lobby : public QMainWindow public: Lobby(AOApplication *p_ao_app); + ~Lobby(); - bool is_public_server() const; + DRServerInfoList get_combined_server_list(); void set_widgets(); - void list_servers(); - void list_favorites(); - void append_chatmessage(QString f_name, QString f_message); - void append_error(QString f_message); void set_choose_a_server(); void set_player_count(int players_online, int max_players); void set_loading_text(QString p_text); @@ -40,22 +38,33 @@ class Lobby : public QMainWindow void set_fonts(); void show_loading_overlay(); void hide_loading_overlay(); - server_type get_selected_server(); + DRServerInfo get_selected_server(); void set_loading_value(int p_value); +signals: + void server_list_changed(); + void favorite_server_list_changed(); + void combined_server_list_changed(); + private: AOApplication *ao_app = nullptr; AOConfig *ao_config = nullptr; - server_type m_last_server; + DRMasterClient *m_master_client = nullptr; + DRServerInfoList m_server_list; + DRServerInfoList m_favorite_server_list; + DRServerInfoList m_combined_server_list; + DRServerInfo m_current_server; bool is_public_server_selected = true; // ui AOImageDisplay *ui_background = nullptr; - AOButton *ui_public_servers = nullptr; - AOButton *ui_favorites = nullptr; + AOButton *ui_hide_public_servers = nullptr; + bool m_hide_public_servers = false; + AOButton *ui_hide_favorite_servers = nullptr; + bool m_hide_favorite_servers = false; AOButton *ui_refresh = nullptr; - AOButton *ui_add_to_fav = nullptr; + AOButton *ui_toggle_favorite = nullptr; AOButton *ui_connect = nullptr; DRTextEdit *ui_version = nullptr; AOButton *ui_about = nullptr; @@ -63,16 +72,31 @@ class Lobby : public QMainWindow DRTextEdit *ui_player_count = nullptr; QTextBrowser *ui_description = nullptr; DRChatLog *ui_chatbox = nullptr; - QLineEdit *ui_chatname = nullptr; - QLineEdit *ui_chatmessage = nullptr; AOImageDisplay *ui_loading_background = nullptr; DRTextEdit *ui_loading_text = nullptr; QProgressBar *ui_progress_bar = nullptr; AOButton *ui_cancel = nullptr; + void load_settings(); + void save_settings(); + + void load_favorite_server_list(); + void load_legacy_favorite_server_list(); + void save_favorite_server_list(); + private slots: - void on_public_servers_clicked(); - void on_favorites_clicked(); + void request_advertiser_update(); + void update_motd(); + void update_server_list(); + void set_favorite_server_list(DRServerInfoList server_list); + void update_combined_server_list(); + void hide_public_servers(bool on); + void toggle_hide_public_servers(); + void hide_favorite_servers(bool on); + void toggle_hide_favorite_servers(); + void update_server_listing(); + void filter_server_listing(); + void select_current_server(); void on_refresh_pressed(); void on_refresh_released(); @@ -81,8 +105,7 @@ private slots: void on_connect_pressed(); void on_connect_released(); void on_about_clicked(); - void on_server_list_clicked(QModelIndex p_model); - void on_chatfield_return_pressed(); + void connect_to_server(int row); }; #endif // LOBBY_H diff --git a/src/master_socket.cpp b/src/master_socket.cpp deleted file mode 100644 index 08958b1c5..000000000 --- a/src/master_socket.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "aoapplication.h" - -#include "debug_functions.h" -#include "drpacket.h" -#include "drserversocket.h" -#include "hardware_functions.h" -#include "lobby.h" -#include "version.h" - -#include - -void AOApplication::connect_to_master() -{ - server_type l_server; - l_server.name = MASTER_NAME; - l_server.ip = MASTER_HOST; - l_server.port = MASTER_PORT; - m_master_socket->connect_to_server(l_server, true); -} - -void AOApplication::send_master_packet(DRPacket p_packet) -{ - if (!m_master_socket->is_connected()) - { - qDebug() << "Failed to send packet: not connected to master"; - return; - } - - qDebug().noquote() << "M/S:" << p_packet.to_string(); - m_master_socket->send_packet(p_packet); -} - -void AOApplication::request_server_list() -{ - if (!m_master_socket->is_connected()) - { - connect_to_master(); - return; - } - m_master_socket->send_packet(DRPacket("ALL")); -} - -void AOApplication::_p_send_master_handshake() -{ - send_master_packet(DRPacket("ALL")); -} - -void AOApplication::_p_handle_master_error(QString p_error) -{ - if (!is_lobby_constructed) - return; - m_lobby->append_error(p_error); -} - -void AOApplication::_p_handle_master_packet(DRPacket p_packet) -{ - const QString l_header = p_packet.get_header(); - const QStringList l_content = p_packet.get_content(); - - if (l_header != "CHECK") - qDebug().noquote() << "M/R:" << p_packet.to_string(); - - if (l_header == "ALL") - { - m_server_list.clear(); - - for (const QString &i_string : qAsConst(l_content)) - { - server_type f_server; - QStringList sub_contents = i_string.split("&"); - - if (sub_contents.size() < 4) - { - qDebug() << "W: malformed packet"; - continue; - } - - f_server.name = sub_contents.at(0); - f_server.desc = sub_contents.at(1); - f_server.ip = sub_contents.at(2); - f_server.port = sub_contents.at(3).toInt(); - - m_server_list.append(f_server); - } - - if (is_lobby_constructed) - { - m_lobby->list_servers(); - } - } - else if (l_header == "CT") - { - QString f_name, f_message; - - if (l_content.size() == 1) - { - f_name = ""; - f_message = l_content.at(0); - } - else if (l_content.size() >= 2) - { - f_name = l_content.at(0); - f_message = l_content.at(1); - } - else - return; - - if (is_lobby_constructed) - { - m_lobby->append_chatmessage(f_name, f_message); - } - } - else if (l_header == "AO2CHECK") - { - send_master_packet(DRPacket("ID", {"DRO", get_version_string()})); - send_master_packet(DRPacket("HI", {get_hdid()})); - } -} diff --git a/src/path_functions.cpp b/src/path_functions.cpp index ea7d20d1b..4fe91a029 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -25,6 +25,11 @@ QString AOApplication::get_base_path() return DRPather::get_application_path() + "/base/"; } +QString AOApplication::get_base_file_path(QString p_file) +{ + return get_base_path() + p_file; +} + QString AOApplication::get_character_folder_path(QString p_chr) { QString r_path = get_base_path() + "characters/" + p_chr; diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 24652c2bc..c1a6f4316 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -13,7 +13,7 @@ #include -void AOApplication::connect_to_server(server_type p_server) +void AOApplication::connect_to_server(DRServerInfo p_server) { m_server_socket->connect_to_server(p_server, false); } @@ -108,11 +108,12 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) is_courtroom_loaded = false; - server_type l_current_server = m_lobby->get_selected_server(); - if (l_current_server.is_favorite) + DRServerInfo l_current_server = m_lobby->get_selected_server(); + if (l_current_server.favorite) { const QString l_current_server_address = l_current_server.to_address(); - for (const server_type &i_server : qAsConst(m_server_list)) + const DRServerInfoList l_server_list = m_lobby->get_combined_server_list(); + for (const DRServerInfo &i_server : qAsConst(l_server_list)) { if (l_current_server_address != i_server.to_address()) continue; diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index cb249237b..ed16f5532 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -60,61 +60,6 @@ void AOApplication::append_note(QString p_line, QString p_file) } } -void AOApplication::write_to_serverlist_txt(QString p_line) -{ - QFile serverlist_txt; - QString serverlist_txt_path = get_base_path() + "serverlist.txt"; - - serverlist_txt.setFileName(serverlist_txt_path); - - if (!serverlist_txt.open(QIODevice::WriteOnly | QIODevice::Append)) - { - return; - } - - QTextStream out(&serverlist_txt); - - out << "\r\n" << p_line; - - serverlist_txt.close(); -} - -QVector AOApplication::read_serverlist_txt() -{ - QVector f_server_list; - - QFile serverlist_txt; - QString serverlist_txt_path = get_base_path() + "serverlist.txt"; - - serverlist_txt.setFileName(serverlist_txt_path); - - if (!serverlist_txt.open(QIODevice::ReadOnly)) - { - return f_server_list; - } - - QTextStream in(&serverlist_txt); - - while (!in.atEnd()) - { - QString line = in.readLine(); - server_type f_server; - QStringList line_contents = line.split(":"); - - if (line_contents.size() < 3) - continue; - - f_server.ip = line_contents.at(0); - f_server.port = line_contents.at(1).toInt(); - f_server.name = line_contents.at(2); - f_server.desc = ""; - - f_server_list.append(f_server); - } - - return f_server_list; -} - /** * @brief Reads p_path and returns the value associated with key * p_identifier. If the file or key do not exist, return empty. From deb0ca5923646c2103fbaf79ae84366ea1c6a6de Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 10 Apr 2022 15:39:05 +0200 Subject: [PATCH 666/842] Added favorite server icon --- src/lobby.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index f1407d023..80798a5f8 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -17,10 +17,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -370,16 +372,16 @@ void Lobby::update_combined_server_list() void Lobby::update_server_listing() { ui_server_list->clear(); + const QIcon l_favorite_icon = QPixmap(ao_app->find_theme_asset_path("favorite_server.png")); const QBrush l_favorite_color = ao_app->get_color("favorite_server_color", LOBBY_DESIGN_INI); - for (int i = 0; i < m_combined_server_list.length(); ++i) + for (const DRServerInfo &l_server : qAsConst(m_combined_server_list)) { - const DRServerInfo &l_server = m_combined_server_list[i]; QListWidgetItem *l_server_item = new QListWidgetItem; ui_server_list->addItem(l_server_item); l_server_item->setText(l_server.name); - l_server_item->setData(Qt::UserRole, i); if (l_server.favorite) { + l_server_item->setIcon(l_favorite_icon); l_server_item->setBackground(l_favorite_color); } } From 162df02ee05d0991c08d05ab753f11b4ee853a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sun, 10 Apr 2022 18:18:40 +0200 Subject: [PATCH 667/842] Resolve OpenSSL dependency in workflow --- .github/workflows/build-all.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 412866bfe..70f1bb375 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -28,6 +28,13 @@ jobs: mkdir "3rd/bass" mkdir "3rd/discord-rpc" + - name: Fetch OpenSSL (user) + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://cdn.discordapp.com/attachments/457180869229019159/962746488616792145/openssl-1.1.1n.zip -o openssl.zip + unzip openssl.zip + - name: Fetch Discord external library shell: bash working-directory: ${{env.parentworkspace}} @@ -135,6 +142,8 @@ jobs: shell: bash run: | windeployqt.exe --quick --compiler-runtime . + cp ../libssl-1_1.dll . + cp ../libcryptor-1_1.dll . cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . cp ../bass.dll . cp ../bassopus.dll . From fb8233c1e732aeaa21a48051ea1af094b734f512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sun, 10 Apr 2022 18:20:48 +0200 Subject: [PATCH 668/842] Resolve OpenSSL dependency in workflow #2 --- .github/workflows/build-all-merge-master.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/build-all-merge-master.yml b/.github/workflows/build-all-merge-master.yml index 299335755..c913c5b3e 100644 --- a/.github/workflows/build-all-merge-master.yml +++ b/.github/workflows/build-all-merge-master.yml @@ -41,6 +41,13 @@ jobs: mkdir "3rd/bass" mkdir "3rd/discord-rpc" + - name: Fetch OpenSSL (user) + shell: bash + working-directory: ${{env.parentworkspace}} + run: | + curl -L https://cdn.discordapp.com/attachments/457180869229019159/962746488616792145/openssl-1.1.1n.zip -o openssl.zip + unzip openssl.zip + - name: Fetch Discord external library shell: bash working-directory: ${{env.parentworkspace}} @@ -148,6 +155,8 @@ jobs: shell: bash run: | windeployqt.exe --quick --compiler-runtime . + cp ../libssl-1_1.dll . + cp ../libcryptor-1_1.dll . cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . cp ../bass.dll . cp ../bassopus.dll . From adc7bfed17e39f2b5efdf4b97dfad161acb823ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sun, 10 Apr 2022 18:27:00 +0200 Subject: [PATCH 669/842] Fix link --- .github/workflows/build-all.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 70f1bb375..9a8cdd6d1 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -32,7 +32,7 @@ jobs: shell: bash working-directory: ${{env.parentworkspace}} run: | - curl -L https://cdn.discordapp.com/attachments/457180869229019159/962746488616792145/openssl-1.1.1n.zip -o openssl.zip + curl -L https://cdn.discordapp.com/attachments/457180869229019159/962750500632150168/openssl-1.1.1n.zip -o openssl.zip unzip openssl.zip - name: Fetch Discord external library From ea30da076d3fa97b4aa2b53bb84da4a4a3ab247b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sun, 10 Apr 2022 18:27:23 +0200 Subject: [PATCH 670/842] Fix link --- .github/workflows/build-all-merge-master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-all-merge-master.yml b/.github/workflows/build-all-merge-master.yml index c913c5b3e..5e6004e69 100644 --- a/.github/workflows/build-all-merge-master.yml +++ b/.github/workflows/build-all-merge-master.yml @@ -45,7 +45,7 @@ jobs: shell: bash working-directory: ${{env.parentworkspace}} run: | - curl -L https://cdn.discordapp.com/attachments/457180869229019159/962746488616792145/openssl-1.1.1n.zip -o openssl.zip + curl -L https://cdn.discordapp.com/attachments/457180869229019159/962750500632150168/openssl-1.1.1n.zip -o openssl.zip unzip openssl.zip - name: Fetch Discord external library From 1ada6284ec8ccdb66b81905c4b8b83793f036acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sun, 10 Apr 2022 18:38:12 +0200 Subject: [PATCH 671/842] Fixed name typo --- .github/workflows/build-all-merge-master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-all-merge-master.yml b/.github/workflows/build-all-merge-master.yml index 5e6004e69..618e14b32 100644 --- a/.github/workflows/build-all-merge-master.yml +++ b/.github/workflows/build-all-merge-master.yml @@ -156,7 +156,7 @@ jobs: run: | windeployqt.exe --quick --compiler-runtime . cp ../libssl-1_1.dll . - cp ../libcryptor-1_1.dll . + cp ../libcrypto-1_1.dll . cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . cp ../bass.dll . cp ../bassopus.dll . From e6e335bb14ef410e7adb23dea6ea2dff365d0dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sun, 10 Apr 2022 18:38:27 +0200 Subject: [PATCH 672/842] Fixed name typo --- .github/workflows/build-all.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 9a8cdd6d1..410109847 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -143,7 +143,7 @@ jobs: run: | windeployqt.exe --quick --compiler-runtime . cp ../libssl-1_1.dll . - cp ../libcryptor-1_1.dll . + cp ../libcrypto-1_1.dll . cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . cp ../bass.dll . cp ../bassopus.dll . From abcd3fee1c2955e16e4936cd2c2528264759b683 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 10 Apr 2022 23:13:19 +0200 Subject: [PATCH 673/842] Added client_version packet support and versioning checks --- src/aoapplication.cpp | 9 +++++++-- src/aoapplication.h | 7 +++++-- src/courtroom.cpp | 2 +- src/datatypes.cpp | 27 +++++++++++++++++++++++++++ src/datatypes.h | 25 +++++++++++++++++++++++++ src/lobby.cpp | 25 +++++++++++++++++++++++-- src/server_socket.cpp | 28 ++++++++++++++++++++++------ src/version.cpp | 13 ++++++++++--- src/version.h | 3 +++ 9 files changed, 123 insertions(+), 16 deletions(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 946bd4c0b..d51157c09 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -141,9 +141,14 @@ DRDiscord *AOApplication::get_discord() const return dr_discord; } -bool AOApplication::is_server_compatible() const +VersionNumber AOApplication::get_server_client_version() const { - return feature_version_compatible; + return m_server_client_version; +} + +VersionStatus AOApplication::get_server_client_version_status() const +{ + return m_server_client_version_status; } void AOApplication::handle_theme_modification() diff --git a/src/aoapplication.h b/src/aoapplication.h index 0e359468c..33e900cc8 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -41,7 +41,8 @@ class AOApplication : public QApplication DRDiscord *get_discord() const; - bool is_server_compatible() const; + VersionNumber get_server_client_version() const; + VersionStatus get_server_client_version_status() const; /////////////////////////////////////////// @@ -198,7 +199,9 @@ public slots: bool is_courtroom_loaded = false; ///////////////server metadata//////////////// - bool feature_version_compatible = false; + + VersionNumber m_server_client_version; + VersionStatus m_server_client_version_status = VersionStatus::NotCompatible; ///////////////loading info/////////////////// // player number, it's hardly used but might be needed for some old servers diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 39fbb8038..9bb79acd0 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -636,7 +636,7 @@ void Courtroom::on_ic_message_return_pressed() f_text_color = QString::number(m_text_color); packet_contents.append(f_text_color); - if (ao_app->is_server_compatible()) + if (ao_app->get_server_client_version_status() == VersionStatus::Ok) { // showname packet_contents.append(ao_config->showname()); diff --git a/src/datatypes.cpp b/src/datatypes.cpp index 1c665bccf..42f638689 100644 --- a/src/datatypes.cpp +++ b/src/datatypes.cpp @@ -48,3 +48,30 @@ bool DRServerInfo::operator!=(const DRServerInfo &other) const { return !operator==(other); } + +VersionNumber::VersionNumber() +{} + +VersionNumber::VersionNumber(int p_release, int p_major, int p_minor) + : release(p_release), major(p_major), minor(p_minor) +{} + +QString VersionNumber::to_string() const +{ + return QString("%1.%2.%3").arg(release).arg(major).arg(minor); +} + +bool VersionNumber::operator==(const VersionNumber &other) const +{ + return release == other.release && major == other.major; +} + +bool VersionNumber::operator<(const VersionNumber &other) const +{ + return release < other.release || (release == other.release && major < other.major); +} + +bool VersionNumber::operator>(const VersionNumber &other) const +{ + return release > other.release || (release == other.release && major > other.major); +} diff --git a/src/datatypes.h b/src/datatypes.h index a32d6c7fc..4a9d9c675 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -100,6 +100,31 @@ class DRServerInfo }; using DRServerInfoList = QVector; +class VersionNumber +{ +public: + int release = 0; + int major = 0; + int minor = 0; + + VersionNumber(); + VersionNumber(int release, int major, int minor); + + QString to_string() const; + + bool operator==(const VersionNumber &other) const; + bool operator>(const VersionNumber &other) const; + bool operator<(const VersionNumber &other) const; +}; + +enum class VersionStatus +{ + Ok, + NotCompatible, + ClientOutdated, + ServerOutdated, +}; + struct char_type { QString name; diff --git a/src/lobby.cpp b/src/lobby.cpp index 80798a5f8..53dfac3be 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -5,6 +5,7 @@ #include "aoconfig.h" #include "aoimagedisplay.h" #include "commondefs.h" +#include "datatypes.h" #include "debug_functions.h" #include "drchatlog.h" #include "drmasterclient.h" @@ -487,9 +488,29 @@ void Lobby::on_connect_pressed() void Lobby::on_connect_released() { - if (!ao_app->is_server_compatible()) + const VersionStatus l_status = ao_app->get_server_client_version_status(); + if (l_status != VersionStatus::Ok) { - call_warning("You are connecting to an incompatible DRO server.
    " + QString l_reason; + switch (l_status) + { + case VersionStatus::NotCompatible: + l_reason = "The server did not report any client version."; + break; + case VersionStatus::ServerOutdated: + l_reason = QString("The server is outdated.
    (Server version: %1, expected version: %2)") + .arg(ao_app->get_server_client_version().to_string(), get_version_number().to_string()); + break; + case VersionStatus::ClientOutdated: + l_reason = QString("Your client is outdated.
    (Client version: %1, expected version: %2)") + .arg(get_version_number().to_string(), ao_app->get_server_client_version().to_string()); + break; + default: + break; + } + + call_warning("You are connecting to an incompatible DRO server.

    Reason: " + l_reason + + "

    " "The client may not work properly, if at all."); } diff --git a/src/server_socket.cpp b/src/server_socket.cpp index c1a6f4316..ee340ab33 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -1,5 +1,7 @@ #include "aoapplication.h" +#include + #include "aoconfig.h" #include "courtroom.h" #include "debug_functions.h" @@ -11,8 +13,6 @@ #include "lobby.h" #include "version.h" -#include - void AOApplication::connect_to_server(DRServerInfo p_server) { m_server_socket->connect_to_server(p_server, false); @@ -55,8 +55,8 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) if (l_content.size() == 0) return; - feature_version_compatible = false; - + m_server_client_version = VersionNumber(); + m_server_client_version_status = VersionStatus::NotCompatible; send_server_packet(DRPacket("HI", {get_hdid()})); } else if (l_header == "ID") @@ -77,9 +77,25 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) if (is_courtroom_constructed) m_courtroom->append_server_chatmessage(l_content.at(0), l_content.at(1)); } - else if (l_header == "FL") + else if (l_header == "client_version") { - feature_version_compatible = l_content.contains("v110", Qt::CaseInsensitive); + if (l_content.size() < 3) + return; + + m_server_client_version = VersionNumber(l_content.at(0).toInt(), l_content.at(1).toInt(), l_content.at(2).toInt()); + const VersionNumber l_client_version = get_version_number(); + if (l_client_version == m_server_client_version) + { + m_server_client_version_status = VersionStatus::Ok; + } + else if (l_client_version < m_server_client_version) + { + m_server_client_version_status = VersionStatus::ClientOutdated; + } + else if (l_client_version > m_server_client_version) + { + m_server_client_version_status = VersionStatus::ServerOutdated; + } } else if (l_header == "PN") { diff --git a/src/version.cpp b/src/version.cpp index 6183e8425..e6dd54ac4 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -1,11 +1,13 @@ #include "version.h" -#include - #include #include #include +#include + +#include "datatypes.h" + int get_release_version() { return 1; @@ -21,6 +23,11 @@ int get_minor_version() return 0; } +VersionNumber get_version_number() +{ + return VersionNumber(get_release_version(), get_major_version(), get_minor_version()); +} + QString get_post_version() { return "b1"; @@ -28,7 +35,7 @@ QString get_post_version() QString get_version_string() { - QString l_version = QString("%1.%2.%3").arg(get_release_version()).arg(get_major_version()).arg(get_minor_version()); + QString l_version = get_version_number().to_string(); const QString l_post = get_post_version(); if (!l_post.isEmpty()) diff --git a/src/version.h b/src/version.h index 430247c38..9e80e59b3 100644 --- a/src/version.h +++ b/src/version.h @@ -2,9 +2,12 @@ class QString; +class VersionNumber; + int get_release_version(); int get_major_version(); int get_minor_version(); +VersionNumber get_version_number(); QString get_post_version(); QString get_version_string(); QString get_about_message(); From f3d210f298153989550d51b0b568136742e1c01a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 10 Apr 2022 23:23:56 +0200 Subject: [PATCH 674/842] Suppress invalid version prompt when you don't have a server selected --- src/aoapplication.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aoapplication.h b/src/aoapplication.h index 33e900cc8..8d0ee6d22 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -201,7 +201,7 @@ public slots: ///////////////server metadata//////////////// VersionNumber m_server_client_version; - VersionStatus m_server_client_version_status = VersionStatus::NotCompatible; + VersionStatus m_server_client_version_status = VersionStatus::Ok; ///////////////loading info/////////////////// // player number, it's hardly used but might be needed for some old servers From df8417529de6ffdea6616e9f58ffc68552e002e9 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 10 Apr 2022 23:42:35 +0200 Subject: [PATCH 675/842] Added config button to server browser and allow server browser to reload its theme --- src/lobby.cpp | 22 ++++++++++++++++++++-- src/lobby.h | 6 +++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 53dfac3be..e93e5680d 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -48,6 +48,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_version->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_version->setReadOnly(true); ui_about = new AOButton(this, ao_app); + ui_config = new AOButton(this, ao_app); ui_server_list = new QListWidget(this); ui_player_count = new DRTextEdit(this); ui_player_count->setFrameStyle(QFrame::NoFrame); @@ -69,6 +70,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_progress_bar->setStyleSheet("QProgressBar{ color: white; }"); ui_cancel = new AOButton(ui_loading_background, ao_app); + connect(ao_app, SIGNAL(reload_theme()), this, SLOT(update_widgets())); connect(ao_config, SIGNAL(server_advertiser_changed(QString)), m_master_client, SLOT(set_address(QString))); connect(m_master_client, SIGNAL(address_changed()), this, SLOT(request_advertiser_update())); connect(m_master_client, SIGNAL(motd_changed()), this, SLOT(update_motd())); @@ -83,10 +85,12 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() connect(ui_connect, SIGNAL(pressed()), this, SLOT(on_connect_pressed())); connect(ui_connect, SIGNAL(released()), this, SLOT(on_connect_released())); connect(ui_about, SIGNAL(clicked()), this, SLOT(on_about_clicked())); + connect(ui_config, SIGNAL(pressed()), this, SLOT(on_config_pressed())); + connect(ui_config, SIGNAL(released()), this, SLOT(on_config_released())); connect(ui_server_list, SIGNAL(currentRowChanged(int)), this, SLOT(connect_to_server(int))); connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); - set_widgets(); + update_widgets(); load_settings(); load_favorite_server_list(); m_master_client->set_address(ao_config->server_advertiser()); @@ -103,7 +107,7 @@ DRServerInfoList Lobby::get_combined_server_list() } // sets images, position and size -void Lobby::set_widgets() +void Lobby::update_widgets() { pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", LOBBY_DESIGN_INI); @@ -155,6 +159,9 @@ void Lobby::set_widgets() set_size_and_pos(ui_about, "about", LOBBY_DESIGN_INI, ao_app); ui_about->set_image("about.png"); + set_size_and_pos(ui_config, "config", LOBBY_DESIGN_INI, ao_app); + ui_config->set_image("lobby_config.png"); + set_size_and_pos(ui_server_list, "server_list", LOBBY_DESIGN_INI, ao_app); ui_server_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "font: bold;"); @@ -518,6 +525,17 @@ void Lobby::on_connect_released() ao_app->send_server_packet(DRPacket("askchaa")); } +void Lobby::on_config_pressed() +{ + ui_config->set_image("lobby_config_pressed.png"); +} + +void Lobby::on_config_released() +{ + ui_config->set_image("lobby_config.png"); + ao_app->toggle_config_panel(); +} + void Lobby::on_about_clicked() { QMessageBox::about(this, tr("About"), get_about_message()); diff --git a/src/lobby.h b/src/lobby.h index 911a06b30..b0ee6ed79 100644 --- a/src/lobby.h +++ b/src/lobby.h @@ -29,7 +29,6 @@ class Lobby : public QMainWindow DRServerInfoList get_combined_server_list(); - void set_widgets(); void set_choose_a_server(); void set_player_count(int players_online, int max_players); void set_loading_text(QString p_text); @@ -68,6 +67,7 @@ class Lobby : public QMainWindow AOButton *ui_connect = nullptr; DRTextEdit *ui_version = nullptr; AOButton *ui_about = nullptr; + AOButton *ui_config = nullptr; QListWidget *ui_server_list = nullptr; DRTextEdit *ui_player_count = nullptr; QTextBrowser *ui_description = nullptr; @@ -85,6 +85,8 @@ class Lobby : public QMainWindow void save_favorite_server_list(); private slots: + void update_widgets(); + void request_advertiser_update(); void update_motd(); void update_server_list(); @@ -104,6 +106,8 @@ private slots: void on_add_to_fav_released(); void on_connect_pressed(); void on_connect_released(); + void on_config_pressed(); + void on_config_released(); void on_about_clicked(); void connect_to_server(int row); }; From 26368406d2a1a617b7b09d35266f1db55149a804 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 10 Apr 2022 23:51:14 +0200 Subject: [PATCH 676/842] Fixed public/favorite filter resetting to default state after reload --- src/lobby.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index e93e5680d..a0f2cc51b 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -139,10 +139,10 @@ void Lobby::update_widgets() ui_background->set_theme_image("lobbybackground.png"); set_size_and_pos(ui_hide_public_servers, "public_servers", LOBBY_DESIGN_INI, ao_app); - ui_hide_public_servers->set_image("publicservers.png"); + ui_hide_public_servers->set_image(m_hide_public_servers ? "publicservers_selected.png" : "publicservers.png"); set_size_and_pos(ui_hide_favorite_servers, "favorites", LOBBY_DESIGN_INI, ao_app); - ui_hide_favorite_servers->set_image("favorites.png"); + ui_hide_favorite_servers->set_image(m_hide_favorite_servers ? "favorites_selected.png" : "favorites.png"); set_size_and_pos(ui_refresh, "refresh", LOBBY_DESIGN_INI, ao_app); ui_refresh->set_image("refresh.png"); From a6b4b39b467191a6973a34bbe92f8c77b913cdb2 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 10 Apr 2022 23:59:35 +0200 Subject: [PATCH 677/842] Renamed config to config_panel and textures as well --- src/lobby.cpp | 14 +++++++------- src/lobby.h | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index a0f2cc51b..0d4737135 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -48,7 +48,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_version->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_version->setReadOnly(true); ui_about = new AOButton(this, ao_app); - ui_config = new AOButton(this, ao_app); + ui_config_panel = new AOButton(this, ao_app); ui_server_list = new QListWidget(this); ui_player_count = new DRTextEdit(this); ui_player_count->setFrameStyle(QFrame::NoFrame); @@ -85,8 +85,8 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() connect(ui_connect, SIGNAL(pressed()), this, SLOT(on_connect_pressed())); connect(ui_connect, SIGNAL(released()), this, SLOT(on_connect_released())); connect(ui_about, SIGNAL(clicked()), this, SLOT(on_about_clicked())); - connect(ui_config, SIGNAL(pressed()), this, SLOT(on_config_pressed())); - connect(ui_config, SIGNAL(released()), this, SLOT(on_config_released())); + connect(ui_config_panel, SIGNAL(pressed()), this, SLOT(on_config_pressed())); + connect(ui_config_panel, SIGNAL(released()), this, SLOT(on_config_released())); connect(ui_server_list, SIGNAL(currentRowChanged(int)), this, SLOT(connect_to_server(int))); connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); @@ -159,8 +159,8 @@ void Lobby::update_widgets() set_size_and_pos(ui_about, "about", LOBBY_DESIGN_INI, ao_app); ui_about->set_image("about.png"); - set_size_and_pos(ui_config, "config", LOBBY_DESIGN_INI, ao_app); - ui_config->set_image("lobby_config.png"); + set_size_and_pos(ui_config_panel, "config_panel", LOBBY_DESIGN_INI, ao_app); + ui_config_panel->set_image("lobby_config.png"); set_size_and_pos(ui_server_list, "server_list", LOBBY_DESIGN_INI, ao_app); ui_server_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);" @@ -527,12 +527,12 @@ void Lobby::on_connect_released() void Lobby::on_config_pressed() { - ui_config->set_image("lobby_config_pressed.png"); + ui_config_panel->set_image("lobby_config_pressed.png"); } void Lobby::on_config_released() { - ui_config->set_image("lobby_config.png"); + ui_config_panel->set_image("lobby_config.png"); ao_app->toggle_config_panel(); } diff --git a/src/lobby.h b/src/lobby.h index b0ee6ed79..9cdc585ee 100644 --- a/src/lobby.h +++ b/src/lobby.h @@ -67,7 +67,7 @@ class Lobby : public QMainWindow AOButton *ui_connect = nullptr; DRTextEdit *ui_version = nullptr; AOButton *ui_about = nullptr; - AOButton *ui_config = nullptr; + AOButton *ui_config_panel = nullptr; QListWidget *ui_server_list = nullptr; DRTextEdit *ui_player_count = nullptr; QTextBrowser *ui_description = nullptr; From eeb948d1b01314d9610f95bf132a1ff54c2383ed Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 11 Apr 2022 00:02:50 +0200 Subject: [PATCH 678/842] Renamed textures for config_panel --- src/lobby.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 0d4737135..58a220f47 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -160,7 +160,7 @@ void Lobby::update_widgets() ui_about->set_image("about.png"); set_size_and_pos(ui_config_panel, "config_panel", LOBBY_DESIGN_INI, ao_app); - ui_config_panel->set_image("lobby_config.png"); + ui_config_panel->set_image("lobby_config_panel.png"); set_size_and_pos(ui_server_list, "server_list", LOBBY_DESIGN_INI, ao_app); ui_server_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);" @@ -527,12 +527,12 @@ void Lobby::on_connect_released() void Lobby::on_config_pressed() { - ui_config_panel->set_image("lobby_config_pressed.png"); + ui_config_panel->set_image("lobby_config_panel_pressed.png"); } void Lobby::on_config_released() { - ui_config_panel->set_image("lobby_config.png"); + ui_config_panel->set_image("lobby_config_panel.png"); ao_app->toggle_config_panel(); } From 1a0841b389c5cb501ff822a7b8e93a82628a2d3b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 11 Apr 2022 00:17:32 +0200 Subject: [PATCH 679/842] Allow both bold and outline text --- src/theme.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/theme.cpp b/src/theme.cpp index 0bb3b4df3..fc5afd8a8 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -76,6 +76,10 @@ void set_font(QWidget *p_widget, QString p_identifier, QString ini_file, AOAppli int bold = ao_app->get_font_property(p_identifier + "_bold", ini_file); QString is_bold = (bold == 1 ? "bold" : ""); + QFont font = p_widget->font(); + font.setBold(bold); + p_widget->setFont(font); + QString style_sheet_string = class_name + " { " + "background-color: rgba(0, 0, 0, 0);\n" + "color: " + l_font_color.name(QColor::HexArgb) + ";\n" + "font: " + is_bold + ";" + " }"; p_widget->setStyleSheet(style_sheet_string); From 873ed36c538586a73c1f7865f17a5f6d89875d3b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 11 Apr 2022 00:56:20 +0200 Subject: [PATCH 680/842] Removed about button from server browser --- src/lobby.cpp | 10 ---------- src/lobby.h | 2 -- 2 files changed, 12 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 58a220f47..51af37f69 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -47,7 +47,6 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ui_version->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_version->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_version->setReadOnly(true); - ui_about = new AOButton(this, ao_app); ui_config_panel = new AOButton(this, ao_app); ui_server_list = new QListWidget(this); ui_player_count = new DRTextEdit(this); @@ -84,7 +83,6 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() connect(ui_toggle_favorite, SIGNAL(released()), this, SLOT(on_add_to_fav_released())); connect(ui_connect, SIGNAL(pressed()), this, SLOT(on_connect_pressed())); connect(ui_connect, SIGNAL(released()), this, SLOT(on_connect_released())); - connect(ui_about, SIGNAL(clicked()), this, SLOT(on_about_clicked())); connect(ui_config_panel, SIGNAL(pressed()), this, SLOT(on_config_pressed())); connect(ui_config_panel, SIGNAL(released()), this, SLOT(on_config_released())); connect(ui_server_list, SIGNAL(currentRowChanged(int)), this, SLOT(connect_to_server(int))); @@ -156,9 +154,6 @@ void Lobby::update_widgets() set_size_and_pos(ui_version, "version", LOBBY_DESIGN_INI, ao_app); ui_version->setText("Version: " + get_version_string()); - set_size_and_pos(ui_about, "about", LOBBY_DESIGN_INI, ao_app); - ui_about->set_image("about.png"); - set_size_and_pos(ui_config_panel, "config_panel", LOBBY_DESIGN_INI, ao_app); ui_config_panel->set_image("lobby_config_panel.png"); @@ -536,11 +531,6 @@ void Lobby::on_config_released() ao_app->toggle_config_panel(); } -void Lobby::on_about_clicked() -{ - QMessageBox::about(this, tr("About"), get_about_message()); -} - void Lobby::connect_to_server(int p_row) { if (p_row == -1) diff --git a/src/lobby.h b/src/lobby.h index 9cdc585ee..fdc521654 100644 --- a/src/lobby.h +++ b/src/lobby.h @@ -66,7 +66,6 @@ class Lobby : public QMainWindow AOButton *ui_toggle_favorite = nullptr; AOButton *ui_connect = nullptr; DRTextEdit *ui_version = nullptr; - AOButton *ui_about = nullptr; AOButton *ui_config_panel = nullptr; QListWidget *ui_server_list = nullptr; DRTextEdit *ui_player_count = nullptr; @@ -108,7 +107,6 @@ private slots: void on_connect_released(); void on_config_pressed(); void on_config_released(); - void on_about_clicked(); void connect_to_server(int row); }; From 7224c20107d5b7e77c9567266d6cf858781ee76c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 11 Apr 2022 01:11:28 +0200 Subject: [PATCH 681/842] Changed default to transparent black --- src/text_file_functions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index ed16f5532..e477a0785 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -175,7 +175,7 @@ std::optional AOApplication::maybe_color(QString p_identifier, QString p QColor AOApplication::get_color(QString p_identifier, QString p_file) { - return maybe_color(p_identifier, p_file).value_or(QColor(255, 255, 255)); + return maybe_color(p_identifier, p_file).value_or(QColor(0, 0, 0, 127)); } QString AOApplication::get_font_name(QString p_identifier, QString p_file) From e7d3ce8ff4920b6060b6edadf7fe81bf50a79e75 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 11 Apr 2022 01:16:57 +0200 Subject: [PATCH 682/842] Changed default color to half transparent grey --- src/lobby.cpp | 3 ++- src/text_file_functions.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 51af37f69..2b4ab3967 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -92,6 +92,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() load_settings(); load_favorite_server_list(); m_master_client->set_address(ao_config->server_advertiser()); + set_choose_a_server(); } Lobby::~Lobby() @@ -194,7 +195,7 @@ void Lobby::update_widgets() set_fonts(); set_stylesheets(); - set_choose_a_server(); + update_server_listing(); } void Lobby::set_fonts() diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index e477a0785..3d2a3f5eb 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -175,7 +175,7 @@ std::optional AOApplication::maybe_color(QString p_identifier, QString p QColor AOApplication::get_color(QString p_identifier, QString p_file) { - return maybe_color(p_identifier, p_file).value_or(QColor(0, 0, 0, 127)); + return maybe_color(p_identifier, p_file).value_or(QColor(127, 127, 127, 127)); } QString AOApplication::get_font_name(QString p_identifier, QString p_file) From fee1c1e8cc9e8be7dcf3ec749a796a8b82e64cce Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 11 Apr 2022 01:53:07 +0200 Subject: [PATCH 683/842] Force the config_panel button to be shown if hidden/invalid size --- src/lobby.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lobby.cpp b/src/lobby.cpp index 2b4ab3967..5a4d0b795 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -157,6 +157,13 @@ void Lobby::update_widgets() set_size_and_pos(ui_config_panel, "config_panel", LOBBY_DESIGN_INI, ao_app); ui_config_panel->set_image("lobby_config_panel.png"); + ui_config_panel->setText("Config"); + if (ui_config_panel->isHidden() || ui_config_panel->size().isEmpty()) + { + ui_config_panel->resize(64, 64); + ui_config_panel->move(0, 0); + ui_config_panel->show(); + } set_size_and_pos(ui_server_list, "server_list", LOBBY_DESIGN_INI, ao_app); ui_server_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);" From 7edf28c0c33593da90d88bfd8e3132495ce157ca Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 11 Apr 2022 01:58:58 +0200 Subject: [PATCH 684/842] Added switch between image or placeholder text for AOImage --- src/aobutton.cpp | 15 +++++++++++---- src/aobutton.h | 2 ++ src/lobby.cpp | 3 +-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/aobutton.cpp b/src/aobutton.cpp index fb1b443dd..137775918 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -34,10 +34,17 @@ void AOButton::set_image(QString p_image) l_hover_image = m_image; } - this->setStyleSheet("QPushButton {border-image:url(\"" + m_image + - "\");}" - "QPushButton:hover {border-image:url(\"" + - l_hover_image + "\");}"); + setStyleSheet("QPushButton {border-image:url(\"" + m_image + + "\");}" + "QPushButton:hover {border-image:url(\"" + + l_hover_image + "\");}"); + setText(m_image.isEmpty() ? m_text : nullptr); +} + +void AOButton::set_image_and_text(QString p_image, QString p_text) +{ + m_text = p_text; + set_image(p_image); } void AOButton::refresh_image() diff --git a/src/aobutton.h b/src/aobutton.h index ba4565c4d..6b6443512 100644 --- a/src/aobutton.h +++ b/src/aobutton.h @@ -15,12 +15,14 @@ class AOButton : public QPushButton QString get_image(); bool has_image(); void set_image(QString p_image); + void set_image_and_text(QString p_image, QString p_text); void refresh_image(); private: AOApplication *ao_app = nullptr; QString m_image; QString m_image_stem; + QString m_text; }; #endif // AOBUTTON_H diff --git a/src/lobby.cpp b/src/lobby.cpp index 5a4d0b795..c011568d0 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -156,8 +156,7 @@ void Lobby::update_widgets() ui_version->setText("Version: " + get_version_string()); set_size_and_pos(ui_config_panel, "config_panel", LOBBY_DESIGN_INI, ao_app); - ui_config_panel->set_image("lobby_config_panel.png"); - ui_config_panel->setText("Config"); + ui_config_panel->set_image_and_text("lobby_config_panel.png", "Config"); if (ui_config_panel->isHidden() || ui_config_panel->size().isEmpty()) { ui_config_panel->resize(64, 64); From c5f476b8a2fb30dd7513fbb49d9272b1087a7bb5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 15 Apr 2022 14:07:51 +0200 Subject: [PATCH 685/842] Added media tester --- dronline-client.pro | 2 ++ res.qrc | 1 + res/tests/mp4_media_test.mp4 | Bin 0 -> 3219 bytes src/drmediatester.cpp | 31 +++++++++++++++++++++++++++++++ src/drmediatester.h | 21 +++++++++++++++++++++ src/main.cpp | 6 ++++-- 6 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 res/tests/mp4_media_test.mp4 create mode 100644 src/drmediatester.cpp create mode 100644 src/drmediatester.h diff --git a/dronline-client.pro b/dronline-client.pro index 7449ff0be..9f7842adc 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -50,6 +50,7 @@ HEADERS += \ src/drdiscord.h \ src/dreffectmovie.h \ src/drmasterclient.h \ + src/drmediatester.h \ src/drmovie.h \ src/drpacket.h \ src/drpather.h \ @@ -112,6 +113,7 @@ SOURCES += \ src/drchatlog.cpp \ src/dreffectmovie.cpp \ src/drmasterclient.cpp \ + src/drmediatester.cpp \ src/drmovie.cpp \ src/drpacket.cpp \ src/drpather.cpp \ diff --git a/res.qrc b/res.qrc index f0ebec7b1..bc2bedb90 100644 --- a/res.qrc +++ b/res.qrc @@ -4,5 +4,6 @@ res/ui/config_panel.ui res/git/git_branch.txt res/git/git_hash.txt + res/tests/mp4_media_test.mp4 diff --git a/res/tests/mp4_media_test.mp4 b/res/tests/mp4_media_test.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..ce21536697fdb3b8afed0acc51117c0918d1207e GIT binary patch literal 3219 zcmcgtd2AF_7=ODxC~bjOs1&L35x^_$va>CvEg5XBXjP0vBBBtN*_qky(wUv@%=A(S zl|%4~N&roXS3Cd}W165SB8bGGA(5aU7)2$PNQ}1;f}!j0n>`q|)%b_;WqaeF)t$u1f)(`(Bm}%VAW30Y>Oq9Jeg$jUQZUQZ*Oes&zEsETcYt57w|b|g-v zsklxoZ;h@*CFr=WWUsx$&)D^$+xI!1RS`uqQ!!X)R8iAB(fODgEmBO;RI>4WrAK37 zFylIPC#bvZDN0gTp_nS!&>($rhpF)-?|H_uLmK!eEZa&S2-&QmLj$GZj+qoRV%aTV z2{eXSB*m_8c_M+CfT+zLg^fyl!^&DgU+wp<{GBtljI&GUcuuUo(Zlj0-yi6&Ja^^{ zd6!$Zv#-Qcl8YG2IhB-KwBIzm0O>{n6EQ0PHN0a;@miGg&qXF=2>@XMy zSs755qSt^n0BJet2n#G*Hb7}0)`)qG72-?_ZvsNVeLx@Z7H|mog|VVipc04!4*^GT zG8Y52z#V{zwLbn2y-VxH@Xk+C=j)*bJF9jHv*(R&9b^7f?_TNZU5Z_nz)1S0ayY@g z=l;HrclI(CXT$ZeWpu>cY3E_CobO}rmQ){?Sjj$4+y09l_TtJpJ-j5Vhws9pBc%d4 zr8~Trl@O&;F=|yyaT+P7x3_mHdMOuld?TGH^K{`ea$6oGd8LodA7J_5JhV00ZCSxs zE_obuwI5}ntZ9%Rf#&oCrRC(9mgl+T`7Zegm%P9wFLcR^T=HUf%DOUGCmRn?ZT}atu2$g$T%TFMBA^3U4{QSt00X#2*u$dB0la2KtAUSj zm2eitR|7W#c$cR=>G}NKk66jr4Thy@u z|KE;+u&Jty<+bRFgufE+^8CC+;@&T2pW1cuSj(FmE06MHlYjiFJ5XE0tGH~KDi@~L zb2Ct1FWka~88d{?^y$#F_+T;ZmbtTUsH)*j^P6F+s4^7IM!ZK;!!{52{nb?gzfcch z#J1xN)24NGb@}KY$Izsh&oEo3Q5s*w)-~7|aXV_ntOhPiAxRbmj(-)gnk#BZGvqc= zXz(}qxfGMM9!nMd-PH~LZb7K#x++H0ZfV&=u*5p98Ah8F!6cZL3Ea}6GHOW+{5)nlIpnA=*;Gd~ zW~-(q!3gq@)?rFLLD|sbl8w2HEyAN6jUfaKO^Iy6rlhMDxGpu?8nNSIfVZhVP>VvE z3*rGpY)e*Ss@!3VHGaxtO0*}YYDM5|%E3&%yG6f(IU!p2Ztp(yN8VRc;%C&o53 z+F{;K#fg4jE!a4vaj4WS*49H{*=k&@;n6rY9R~~#q?X!oo^(2_7u|i0x^~sX#NZDf ze?712g;CzGK0UGdQaSNW!Oe$@$FYj~U3;g5c6jsO{bVsK-_P3iJuuOE;qb^gzlo0@ z_p&+o=bv8Zs~3C%f?mrD-Ycj7)WW8|nWekGZDd6|CY9taez+p}g=Tb9->n78N7BRu zY2V7o$29es-OV4a-}8v0$aaHzolAXzU%#VxH1Qi}IXkn`!SB#eHR^!k*P)6`IVDY- zE^^fOCkE>p*{GJRxTQ^-0*-#gWrLenF?PH+<66_S>9(Y5`Nm26PK=xy+4Fw;ve}_Il6a^2XauU_H%FmLacTokrs)tc*GE4nH- zx9HH@sTwn(YH>;>a$?Kw)B4si3s17`iTxXv9X|W?%A + +#include "debug_functions.h" + +DRMediaTester::DRMediaTester(QObject *parent) : QObject(parent) +{ + m_player.setMuted(true); + connect(&m_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, + SLOT(p_check_status(QMediaPlayer::MediaStatus))); + m_player.setMedia(QUrl("qrc:/res/tests/mp4_media_test.mp4")); +} + +DRMediaTester::~DRMediaTester() +{} + +void DRMediaTester::p_check_status(QMediaPlayer::MediaStatus p_status) +{ + switch (p_status) + { + case QMediaPlayer::InvalidMedia: + call_warning("Your operating system appears to not currently support video playback of MP4 files. The video " + "playback will not work properly.

    In order for the feature to function properly, you " + "will need to install the appropriate codecs."); + break; + + default: + break; + } +} diff --git a/src/drmediatester.h b/src/drmediatester.h new file mode 100644 index 000000000..8d16d6de3 --- /dev/null +++ b/src/drmediatester.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class DRMediaTester : public QObject +{ + Q_OBJECT + +public: + DRMediaTester(QObject *parent = nullptr); + ~DRMediaTester(); + +private slots: + +private: + QMediaPlayer m_player; + +private slots: + void p_check_status(QMediaPlayer::MediaStatus p_status); +}; diff --git a/src/main.cpp b/src/main.cpp index c15fdd231..624ecaf5a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,10 @@ +#include + #include "aoapplication.h" +#include "drmediatester.h" #include "lobby.h" #include "logger.h" -#include - int main(int argc, char *argv[]) { qInstallMessageHandler(logger::log); @@ -41,6 +42,7 @@ int main(int argc, char *argv[]) #endif AOApplication app(argc, argv); + DRMediaTester tester; app.load_fonts(); app.construct_lobby(); From 3d0630121528ba261fe6273419e3c0e9b694128e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 15 Apr 2022 18:27:32 +0200 Subject: [PATCH 686/842] Added chat message placeholder instructions --- src/courtroom_widgets.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index b6467db75..621a6f41e 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -146,12 +146,14 @@ void Courtroom::create_widgets() ui_ic_chat_message = new QLineEdit(this); ui_ic_chat_message->setFrame(false); + ui_ic_chat_message->setPlaceholderText(tr("Type and send your message to the game chat here.")); ui_muted = new AOImageDisplay(ui_ic_chat_message, ao_app); ui_muted->hide(); ui_ooc_chat_message = new QLineEdit(this); ui_ooc_chat_message->setFrame(false); + ui_ooc_chat_message->setPlaceholderText(tr("Type and send your message to the public chat here.")); ui_ooc_chat_name = new QLineEdit(this); ui_ooc_chat_name->setFrame(false); From 627025c12566ede04afd86a4bfe1bc8727d9665b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 16 Apr 2022 12:34:29 +0200 Subject: [PATCH 687/842] Check control pointer before operations --- src/drvideoscreen.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index 83868fd98..279e2267c 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -190,6 +190,11 @@ void DRVideoWidget::update_audio_output() const QString l_new_device_name = m_device.get_name(); QMediaService *l_service = m_player->service(); QAudioOutputSelectorControl *l_control = l_service->requestControl(); + if (!l_control) + { + qWarning() << "error: missing audio output control, device unchanged"; + return; + } if (l_control->outputDescription(l_control->activeOutput()) != l_new_device_name) { From 92d4a478f1e862644a8c25b4d891aa4af462a6ed Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 16 Apr 2022 13:53:49 +0200 Subject: [PATCH 688/842] Always consider SpectatorId as a changed character --- src/courtroom_character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index 924444166..ff73889a3 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -22,7 +22,7 @@ int Courtroom::get_character_id() void Courtroom::set_character_id(const int p_chr_id) { - if (m_chr_id == p_chr_id) + if (p_chr_id != SpectatorId && m_chr_id == p_chr_id) return; m_chr_id = p_chr_id; load_character(); From 69e6fabc002b314bc3cf66b5804e03571054746c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 16 Apr 2022 15:22:54 +0200 Subject: [PATCH 689/842] Fix division by zero --- src/charselect.cpp | 3 +-- src/emotes.cpp | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/charselect.cpp b/src/charselect.cpp index 4b7a6c877..737a1ba20 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -67,8 +67,7 @@ void Courtroom::reconstruct_char_select() char_columns = ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + 1; char_rows = ((ui_char_buttons->height() - button_height) / (y_spacing + button_height)) + 1; - m_page_max_chr_count = char_columns * char_rows; - + m_page_max_chr_count = qMax(1, char_columns * char_rows); for (int n = 0; n < m_page_max_chr_count; ++n) { int x_pos = (button_width + x_spacing) * x_mod_count; diff --git a/src/emotes.cpp b/src/emotes.cpp index 89e543054..6b43d03aa 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -62,8 +62,7 @@ void Courtroom::construct_emote_page_layout() emote_columns = ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; emote_rows = ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; - m_page_max_emote_count = emote_columns * emote_rows; - + m_page_max_emote_count = qMax(1, emote_columns * emote_rows); for (int n = 0; n < m_page_max_emote_count; ++n) { int x_pos = (button_width + x_spacing) * x_mod_count; From 0b40bfd99f8582fdf938af2943ec3ac2dd940bb8 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 16 Apr 2022 15:46:49 +0200 Subject: [PATCH 690/842] Always hide emote buttons --- src/emotes.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/emotes.cpp b/src/emotes.cpp index 6b43d03aa..2343acf1e 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -104,14 +104,14 @@ void Courtroom::refresh_emote_page(const bool p_scroll_to_current_emote) ui_emote_left->hide(); ui_emote_right->hide(); - if (is_spectating()) - return; - const int l_emote_count = m_emote_list.length(); for (AOEmoteButton *i_button : qAsConst(ui_emote_list)) i_button->hide(); hide_emote_tooltip(m_emote_preview_id); + if (is_spectating()) + return; + const int l_page_count = qFloor(l_emote_count / m_page_max_emote_count) + bool(l_emote_count % m_page_max_emote_count); From 2cf5acb432a0e538b4673045aab88e8157e82439 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 16 Apr 2022 15:48:14 +0200 Subject: [PATCH 691/842] Reorder variable --- src/emotes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emotes.cpp b/src/emotes.cpp index 2343acf1e..76ae83e49 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -104,7 +104,6 @@ void Courtroom::refresh_emote_page(const bool p_scroll_to_current_emote) ui_emote_left->hide(); ui_emote_right->hide(); - const int l_emote_count = m_emote_list.length(); for (AOEmoteButton *i_button : qAsConst(ui_emote_list)) i_button->hide(); hide_emote_tooltip(m_emote_preview_id); @@ -112,6 +111,7 @@ void Courtroom::refresh_emote_page(const bool p_scroll_to_current_emote) if (is_spectating()) return; + const int l_emote_count = m_emote_list.length(); const int l_page_count = qFloor(l_emote_count / m_page_max_emote_count) + bool(l_emote_count % m_page_max_emote_count); From a0faf17d8a8c575ab5a9449e864f78ddf4dc0a43 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 16 Apr 2022 10:00:46 -0400 Subject: [PATCH 692/842] Change placeholder to say in-character and out-of-character --- src/courtroom_widgets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 621a6f41e..1e9553ec2 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -146,14 +146,14 @@ void Courtroom::create_widgets() ui_ic_chat_message = new QLineEdit(this); ui_ic_chat_message->setFrame(false); - ui_ic_chat_message->setPlaceholderText(tr("Type and send your message to the game chat here.")); + ui_ic_chat_message->setPlaceholderText(tr("Say something in-character.")); ui_muted = new AOImageDisplay(ui_ic_chat_message, ao_app); ui_muted->hide(); ui_ooc_chat_message = new QLineEdit(this); ui_ooc_chat_message->setFrame(false); - ui_ooc_chat_message->setPlaceholderText(tr("Type and send your message to the public chat here.")); + ui_ooc_chat_message->setPlaceholderText(tr("Say something out-of-character.")); ui_ooc_chat_name = new QLineEdit(this); ui_ooc_chat_name->setFrame(false); From 2289aa08204d44e112f5456e2edc6e5668558d2d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 16 Apr 2022 16:02:12 +0200 Subject: [PATCH 693/842] Added website link --- src/drmediatester.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/drmediatester.cpp b/src/drmediatester.cpp index ffb75e424..2a479cd57 100644 --- a/src/drmediatester.cpp +++ b/src/drmediatester.cpp @@ -22,7 +22,8 @@ void DRMediaTester::p_check_status(QMediaPlayer::MediaStatus p_status) case QMediaPlayer::InvalidMedia: call_warning("Your operating system appears to not currently support video playback of MP4 files. The video " "playback will not work properly.

    In order for the feature to function properly, you " - "will need to install the appropriate codecs."); + "will need to install the appropriate codecs.

    For more information visit " + "https://www.danganronpaonline.com"); break; default: From 5ef99269edb6bf10abb93f11713adb2668b25d88 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 19 Apr 2022 01:02:52 +0200 Subject: [PATCH 694/842] Added client Id to MS packet --- res/ui/config_panel.ui | 34 ++++++++++++++++++++++------------ src/aoconfig.cpp | 16 ++++++++++++++++ src/aoconfig.h | 3 +++ src/aoconfigpanel.cpp | 4 ++++ src/aoconfigpanel.h | 1 + src/courtroom.cpp | 21 ++++++++++++++++----- src/courtroom.h | 4 ++-- src/courtroom_widgets.cpp | 1 + src/datatypes.h | 32 ++++++++++++-------------------- 9 files changed, 77 insertions(+), 39 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 6f812fe3a..2a6977683 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -32,7 +32,7 @@ - 0 + 4 @@ -1115,13 +1115,16 @@ - + + + A client identifier allows you to identify users with identical names. + - Display self-identification highlight + Display client identifier - + @@ -1134,32 +1137,39 @@ - - + + 0 0 + + <html><head/><body><p>Extra newlines are added between the name and message.</p></body></html> + - Display music switch + Format use newline - - + + 0 0 - - <html><head/><body><p>Extra newlines are added between the name and message.</p></body></html> + + Display music switch + + + + - Format use newline + Display self-identification highlight diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 823ff7b7e..0cc93fabd 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -72,6 +72,7 @@ private slots: bool sticky_sfx; int log_max_lines; bool log_display_timestamp; + bool log_display_client_id; bool log_display_self_highlight; bool log_display_empty_messages; bool log_is_topdown; @@ -161,6 +162,7 @@ void AOConfigPrivate::read_file() log_max_lines = cfg.value("chatlog_limit", 100).toInt(); log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); log_display_timestamp = cfg.value("chatlog_display_timestamp", true).toBool(); + log_display_client_id = cfg.value("chatlog_display_client_id", false).toBool(); log_display_self_highlight = cfg.value("chatlog_display_self_highlight", true).toBool(); log_display_empty_messages = cfg.value("chatlog_display_empty_messages", false).toBool(); log_format_use_newline = cfg.value("chatlog_newline", false).toBool(); @@ -249,6 +251,7 @@ void AOConfigPrivate::save_file() cfg.setValue("sticky_sfx", sticky_sfx); cfg.setValue("chatlog_limit", log_max_lines); cfg.setValue("chatlog_display_timestamp", log_display_timestamp); + cfg.setValue("chatlog_display_client_id", log_display_client_id); cfg.setValue("chatlog_display_self_highlight", log_display_self_highlight); cfg.setValue("chatlog_newline", log_format_use_newline); cfg.setValue("chatlog_display_empty_messages", log_display_empty_messages); @@ -519,6 +522,11 @@ bool AOConfig::log_display_timestamp_enabled() const return d->log_display_timestamp; } +bool AOConfig::log_display_client_id_enabled() const +{ + return d->log_display_client_id; +} + bool AOConfig::log_display_self_highlight_enabled() const { return d->log_display_self_highlight; @@ -851,6 +859,14 @@ void AOConfig::set_log_display_timestamp(bool p_enabled) d->invoke_signal("log_display_timestamp_changed", Q_ARG(bool, p_enabled)); } +void AOConfig::set_log_display_client_id(bool p_enabled) +{ + if (d->log_display_client_id == p_enabled) + return; + d->log_display_client_id = p_enabled; + d->invoke_signal("log_display_client_id_changed", Q_ARG(bool, p_enabled)); +} + void AOConfig::set_log_display_self_highlight(bool p_enabled) { if (d->log_display_self_highlight == p_enabled) diff --git a/src/aoconfig.h b/src/aoconfig.h index 841f94d90..302127058 100644 --- a/src/aoconfig.h +++ b/src/aoconfig.h @@ -47,6 +47,7 @@ class AOConfig : public QObject bool sticky_sfx_enabled() const; int log_max_lines() const; bool log_display_timestamp_enabled() const; + bool log_display_client_id_enabled() const; bool log_display_self_highlight_enabled() const; bool log_display_empty_messages_enabled() const; bool log_is_topdown_enabled() const; @@ -104,6 +105,7 @@ public slots: void set_sticky_sfx(bool p_enabled); void set_log_max_lines(int p_number); void set_log_display_timestamp(bool p_enabled); + void set_log_display_client_id(bool p_enabled); void set_log_display_self_highlight(bool p_enabled); void set_log_display_empty_messages(bool p_enabled); void set_log_is_topdown(bool p_enabled); @@ -163,6 +165,7 @@ public slots: // log void log_max_lines_changed(int); void log_display_timestamp_changed(bool); + void log_display_client_id_changed(bool); void log_display_self_highlight_changed(bool); void log_display_empty_messages_changed(bool); void log_is_topdown_changed(bool); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 7cd998bc4..56eb0c50d 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -73,6 +73,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // IC Chatlog ui_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); ui_log_display_timestamp = AO_GUI_WIDGET(QCheckBox, "log_display_timestamp"); + ui_log_display_client_id = AO_GUI_WIDGET(QCheckBox, "log_display_client_id"); ui_log_display_self_highlight = AO_GUI_WIDGET(QCheckBox, "log_display_self_highlight"); ui_log_format_use_newline = AO_GUI_WIDGET(QCheckBox, "log_format_use_newline"); ui_log_display_empty_messages = AO_GUI_WIDGET(QCheckBox, "log_display_empty_messages"); @@ -151,6 +152,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // log connect(m_config, SIGNAL(log_max_lines_changed(int)), ui_log_max_lines, SLOT(setValue(int))); connect(m_config, SIGNAL(log_display_timestamp_changed(bool)), ui_log_display_timestamp, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_client_id_changed(bool)), ui_log_display_client_id, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_display_self_highlight_changed(bool)), ui_log_display_self_highlight, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_format_use_newline_changed(bool)), ui_log_format_use_newline, SLOT(setChecked(bool))); @@ -223,6 +225,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // out, log connect(ui_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); connect(ui_log_display_timestamp, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_timestamp(bool))); + connect(ui_log_display_client_id, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_client_id(bool))); connect(ui_log_display_self_highlight, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_self_highlight(bool))); connect(ui_log_format_use_newline, SIGNAL(toggled(bool)), m_config, SLOT(set_log_format_use_newline(bool))); connect(ui_log_display_empty_messages, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_empty_messages(bool))); @@ -287,6 +290,7 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) } ui_log_display_timestamp->setChecked(m_config->log_display_timestamp_enabled()); + ui_log_display_client_id->setChecked(m_config->log_display_client_id_enabled()); ui_log_display_self_highlight->setChecked(m_config->log_display_self_highlight_enabled()); ui_log_format_use_newline->setChecked(m_config->log_format_use_newline_enabled()); ui_log_display_empty_messages->setChecked(m_config->log_display_empty_messages_enabled()); diff --git a/src/aoconfigpanel.h b/src/aoconfigpanel.h index 8598bdcfd..5c1cd76bb 100644 --- a/src/aoconfigpanel.h +++ b/src/aoconfigpanel.h @@ -120,6 +120,7 @@ private slots: // chatlog QSpinBox *ui_log_max_lines = nullptr; QCheckBox *ui_log_display_timestamp = nullptr; + QCheckBox *ui_log_display_client_id = nullptr; QCheckBox *ui_log_display_self_highlight = nullptr; QCheckBox *ui_log_format_use_newline = nullptr; QCheckBox *ui_log_display_empty_messages = nullptr; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 906b56af6..2a73cd885 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -29,6 +29,7 @@ #include "file_functions.h" #include "hardware_functions.h" #include "lobby.h" +#include "src/datatypes.h" #include #include @@ -752,7 +753,10 @@ void Courtroom::handle_chatmessage(QStringList p_contents) if (is_system_speaking) append_system_text(f_showname, l_message); else - append_ic_text(f_showname, l_message, false, false, f_char_id == m_chr_id); + { + const int l_client_id = m_chatmessage[CMClientId].toInt(); + append_ic_text(f_showname, l_message, false, false, l_client_id, f_char_id == m_chr_id); + } if (ao_config->log_is_recording_enabled() && (!chatmessage_is_empty || !is_system_speaking)) { @@ -1090,6 +1094,11 @@ void Courtroom::update_ic_log(bool p_reset_log) l_separator = ": "; else l_separator = " "; + + const int l_client_id = l_record.get_client_id(); + if (l_client_id != NoClientId && ao_config->log_display_client_id_enabled()) + l_cursor.insertText(QString::number(l_record.get_client_id()) + " | ", l_target_name_format); + l_cursor.insertText(l_record.get_name() + l_separator, l_target_name_format); l_cursor.insertText(l_record.get_message(), l_message_format); } @@ -1152,7 +1161,8 @@ void Courtroom::on_ic_chatlog_scroll_bottomup_clicked() l_scrollbar->setValue(l_scrollbar->minimum()); } -void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, bool p_self) +void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, int p_client_id, + bool p_self) { if (p_name.trimmed().isEmpty()) p_name = "Anonymous"; @@ -1163,7 +1173,8 @@ void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bo DRChatRecord new_record(p_name, p_line); new_record.set_music(p_music); new_record.set_system(p_system); - new_record.set_self(p_self); + new_record.set_client_id(p_client_id); + new_record.set_self(ao_app->get_client_id() == p_client_id); m_ic_record_queue.append(new_record); update_ic_log(false); } @@ -1179,7 +1190,7 @@ void Courtroom::append_system_text(QString p_showname, QString p_line) if (chatmessage_is_empty) return; - append_ic_text(p_showname, p_line, true, false, false); + append_ic_text(p_showname, p_line, true, false, NoClientId, false); } void Courtroom::play_preanim() @@ -1553,7 +1564,7 @@ void Courtroom::handle_song(QStringList p_contents) str_char = f_showname; } - append_ic_text(str_char, "has played a song: " + f_song, false, true, l_chr_id == m_chr_id); + append_ic_text(str_char, "has played a song: " + f_song, false, true, NoClientId, l_chr_id == m_chr_id); if (ao_config->log_is_recording_enabled()) save_textlog(str_char + " has played a song: " + f_song); m_music_player->play(f_song); diff --git a/src/courtroom.h b/src/courtroom.h index 74b6ff3f8..cd083f117 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -173,7 +173,7 @@ class Courtroom : public QMainWindow // selected // or the user isn't already scrolled to the top void update_ic_log(bool p_reset_log); - void append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, bool p_self); + void append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, int p_client_id, bool p_self); void append_system_text(QString p_showname, QString p_line); @@ -264,7 +264,7 @@ class Courtroom : public QMainWindow // Generate a File Name based on the time you launched the client QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); - static const int MESSAGE_SIZE = 18; + static const int MESSAGE_SIZE = 19; QString m_chatmessage[MESSAGE_SIZE]; bool m_hide_character = false; bool m_play_pre = false; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 1e9553ec2..e2ca27139 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -308,6 +308,7 @@ void Courtroom::connect_widgets() connect(this, SIGNAL(loaded_theme()), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_display_timestamp_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_display_client_id_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_display_self_highlight_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_format_use_newline_changed(bool)), this, SLOT(on_chat_config_changed())); connect(ao_config, SIGNAL(log_display_empty_messages_changed(bool)), this, SLOT(on_chat_config_changed())); diff --git a/src/datatypes.h b/src/datatypes.h index d27442780..d2a77d821 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -27,44 +27,35 @@ class DRAreaBackground QMap background_tod_map; }; +enum ClientId +{ + NoClientId = -1, +}; + class DRChatRecord { public: - using list = QVector; - DRChatRecord(QString p_name, QString p_message) : name(p_name), message(p_message) {} QDateTime get_timestamp() const { return timestamp; } QString get_name() const { return name; } QString get_message() const { return message; } + int get_client_id() const { return client_id; } bool is_self() const { return self; } bool is_system() const { return system; } bool is_music() const { return music; } // set - void set_self(const bool p_enabled) - { - if (self == p_enabled) - return; - self = p_enabled; - } - void set_system(bool p_enabled) - { - if (system == p_enabled) - return; - system = p_enabled; - } - void set_music(bool p_enabled) - { - if (music == p_enabled) - return; - music = p_enabled; - } + void set_client_id(const int p_client_id) { client_id = p_client_id; } + void set_self(const bool p_enabled) { self = p_enabled; } + void set_system(bool p_enabled) { system = p_enabled; } + void set_music(bool p_enabled) { music = p_enabled; } private: QDateTime timestamp = QDateTime::currentDateTime(); QString name; QString message; + int client_id = NoClientId; bool self = false; bool system = false; bool music = false; @@ -159,6 +150,7 @@ enum ChatMessage : int32_t CMShowName, CMVideoName, CMHideCharacter, + CMClientId, }; enum EmoteMod From 05b196a0184d3ac33e53ce4bdbb1a11a9505e990 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 22 Apr 2022 22:43:15 +0200 Subject: [PATCH 695/842] Deprecated ackMS --- src/courtroom.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2a73cd885..5c441e69e 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -674,6 +674,10 @@ void Courtroom::handle_chatmessage(QStringList p_contents) for (int i = 0; i < MESSAGE_SIZE; ++i) m_chatmessage[i] = p_contents[i]; + const int l_player_id = m_chatmessage[CMClientId].toInt(); + if (l_player_id == ao_app->get_client_id()) + handle_acknowledged_ms(); + m_hide_character = m_chatmessage[CMHideCharacter].toInt(); m_play_pre = false; m_play_zoom = false; From b73ad3ea2d8ae930c03c153f217f46c14afb881a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 23 Apr 2022 01:57:19 +0200 Subject: [PATCH 696/842] Readded ackMS, added checks for client ID being valid --- src/aoapplication.cpp | 5 +++++ src/aoapplication.h | 1 + src/courtroom.cpp | 12 +++++++++--- src/server_socket.cpp | 5 ++--- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index d51157c09..83927abe4 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -151,6 +151,11 @@ VersionStatus AOApplication::get_server_client_version_status() const return m_server_client_version_status; } +bool AOApplication::is_server_client_version_compatible() const +{ + return m_server_client_version_status == VersionStatus::Ok; +} + void AOApplication::handle_theme_modification() { load_fonts(); diff --git a/src/aoapplication.h b/src/aoapplication.h index 8d0ee6d22..0264eeaac 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -43,6 +43,7 @@ class AOApplication : public QApplication VersionNumber get_server_client_version() const; VersionStatus get_server_client_version_status() const; + bool is_server_client_version_compatible() const; /////////////////////////////////////////// diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 5c441e69e..1049e1043 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -674,9 +674,15 @@ void Courtroom::handle_chatmessage(QStringList p_contents) for (int i = 0; i < MESSAGE_SIZE; ++i) m_chatmessage[i] = p_contents[i]; - const int l_player_id = m_chatmessage[CMClientId].toInt(); - if (l_player_id == ao_app->get_client_id()) - handle_acknowledged_ms(); + if (ao_app->is_server_client_version_compatible()) + { + bool l_ok; + const int l_client_id = m_chatmessage[CMClientId].toInt(&l_ok); + if (l_ok && l_client_id == ao_app->get_client_id()) + { + handle_acknowledged_ms(); + } + } m_hide_character = m_chatmessage[CMHideCharacter].toInt(); m_play_pre = false; diff --git a/src/server_socket.cpp b/src/server_socket.cpp index a863dd34e..7f7845933 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -161,8 +161,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) if (l_content.length() != l_chr_list.length()) { qWarning() << "Server sent a character list of length " << l_content.length() - << "which is different from the expected length " << l_chr_list.length() - << "so ignoring it."; + << "which is different from the expected length " << l_chr_list.length() << "so ignoring it."; return; } @@ -294,7 +293,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) } else if (l_header == "ackMS") { - if (is_courtroom_constructed && is_courtroom_loaded) + if (is_courtroom_constructed && is_courtroom_loaded && !is_server_client_version_compatible()) m_courtroom->handle_acknowledged_ms(); } else if (l_header == "MC") From e25914bccb491a4ff5eb8259d0c5fb211fdc27f5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 24 Apr 2022 00:25:05 +0200 Subject: [PATCH 697/842] Reworked music behavior Resolve #297 --- src/courtroom.cpp | 12 ++++++++++-- src/courtroom.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 1049e1043..30e67d4c2 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1525,11 +1525,19 @@ void Courtroom::set_ban(int p_cid) void Courtroom::handle_song(QStringList p_contents) { - if (p_contents.size() < 2) + const bool l_server_compatible = ao_app->is_server_client_version_compatible(); + if (p_contents.size() < (l_server_compatible ? 3 : 2)) return; QString f_song = p_contents.at(0); - int l_chr_id = p_contents.at(1).toInt(); + const int l_chr_id = p_contents.at(1).toInt(); + if (l_server_compatible) + { + const bool l_restart = p_contents.at(2).toInt(); + if (m_current_song == f_song && !l_restart) + return; + } + m_current_song = f_song; for (auto &ext : audio_extensions()) { diff --git a/src/courtroom.h b/src/courtroom.h index cd083f117..3f2c63e48 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -229,6 +229,7 @@ class Courtroom : public QMainWindow QVector m_chr_list; QStringList m_area_list; QStringList m_music_list; + QString m_current_song; QSignalMapper *char_button_mapper = nullptr; From 3168bb097d36a6634e16d3432703a996b9e88d87 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 24 Apr 2022 01:28:27 +0200 Subject: [PATCH 698/842] [FIX] Always hide narrator showname --- src/courtroom.cpp | 31 ++++++++----------------------- src/courtroom.h | 1 + 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 1049e1043..e878d33b9 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -732,19 +732,17 @@ void Courtroom::handle_chatmessage(QStringList p_contents) m_msg_is_first_person = ao_app->get_first_person_enabled(); } - QString f_showname; qDebug() << "handle_chatmessage"; // We actually DO wanna fail here if the showname is empty but the system is speaking. // Having an empty showname for system is actually what we expect. - if (m_chatmessage[CMShowName].isEmpty() && !is_system_speaking) + + QString f_showname = m_chatmessage[CMShowName]; + if (f_showname.isEmpty() && !is_system_speaking) { f_showname = ao_app->get_showname(m_chr_list.at(f_char_id).name); } - else - { - f_showname = m_chatmessage[CMShowName]; - } + m_speaker_showname = f_showname; ui_vp_chat_arrow->hide(); m_effects_player->stop_all(); @@ -761,16 +759,16 @@ void Courtroom::handle_chatmessage(QStringList p_contents) ui_vp_effect->stop(); if (is_system_speaking) - append_system_text(f_showname, l_message); + append_system_text(m_speaker_showname, l_message); else { const int l_client_id = m_chatmessage[CMClientId].toInt(); - append_ic_text(f_showname, l_message, false, false, l_client_id, f_char_id == m_chr_id); + append_ic_text(m_speaker_showname, l_message, false, false, l_client_id, f_char_id == m_chr_id); } if (ao_config->log_is_recording_enabled() && (!chatmessage_is_empty || !is_system_speaking)) { - save_textlog(f_showname + ": " + l_message); + save_textlog(m_speaker_showname + ": " + l_message); } ui_video->play_character_video(m_chatmessage[CMChrName], m_chatmessage[CMVideoName]); @@ -809,24 +807,11 @@ void Courtroom::handle_chatmessage_2() // handles IC load_theme(); } - QString real_name = m_chr_list.at(m_chatmessage[CMChrId].toInt()).name; - - QString f_showname; - ui_vp_message->clear(); ui_vp_chatbox->hide(); ui_vp_showname->hide(); ui_vp_showname_image->hide(); - - if (m_chatmessage[CMShowName].isEmpty()) - { - f_showname = ao_app->get_showname(real_name); - } - else - { - f_showname = m_chatmessage[CMShowName]; - } - ui_vp_showname->setText(f_showname); + ui_vp_showname->setText(m_speaker_showname); QString l_chatbox_name = ao_app->get_chat(m_chatmessage[CMChrName]); diff --git a/src/courtroom.h b/src/courtroom.h index cd083f117..28b982675 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -266,6 +266,7 @@ class Courtroom : public QMainWindow static const int MESSAGE_SIZE = 19; QString m_chatmessage[MESSAGE_SIZE]; + QString m_speaker_showname; bool m_hide_character = false; bool m_play_pre = false; bool m_play_zoom = false; From b49ad53ee6e72c24ba60bdd246445b8fad328063 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 24 Apr 2022 02:18:41 +0200 Subject: [PATCH 699/842] Improved speaker chr id handling --- src/courtroom.cpp | 22 +++++++--------------- src/courtroom.h | 18 +++++++++--------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index e878d33b9..f36a414ec 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -705,18 +705,10 @@ void Courtroom::handle_chatmessage(QStringList p_contents) break; } - int f_char_id = m_chatmessage[CMChrId].toInt(); + m_speaker_chr_id = m_chatmessage[CMChrId].toInt(); + is_system_speaking = (m_speaker_chr_id == SpectatorId); - if (f_char_id == SpectatorId) - { - is_system_speaking = true; - m_chatmessage[CMChrId] = "0"; - f_char_id = 0; - } - else - is_system_speaking = false; - - if (f_char_id < 0 || f_char_id >= m_chr_list.size()) + if (m_speaker_chr_id != SpectatorId && (m_speaker_chr_id < 0 || m_speaker_chr_id >= m_chr_list.length())) return; const QString l_message = QString(m_chatmessage[CMMessage]) @@ -726,7 +718,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) m_msg_is_first_person = false; // reset our ui state if client just spoke - if (m_chr_id == f_char_id && is_system_speaking == false) + if (m_chr_id == m_speaker_chr_id && is_system_speaking == false) { // update first person mode status m_msg_is_first_person = ao_app->get_first_person_enabled(); @@ -740,7 +732,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) QString f_showname = m_chatmessage[CMShowName]; if (f_showname.isEmpty() && !is_system_speaking) { - f_showname = ao_app->get_showname(m_chr_list.at(f_char_id).name); + f_showname = ao_app->get_showname(m_chr_list.at(m_speaker_chr_id).name); } m_speaker_showname = f_showname; @@ -763,7 +755,7 @@ void Courtroom::handle_chatmessage(QStringList p_contents) else { const int l_client_id = m_chatmessage[CMClientId].toInt(); - append_ic_text(m_speaker_showname, l_message, false, false, l_client_id, f_char_id == m_chr_id); + append_ic_text(m_speaker_showname, l_message, false, false, l_client_id, m_speaker_chr_id == m_chr_id); } if (ao_config->log_is_recording_enabled() && (!chatmessage_is_empty || !is_system_speaking)) @@ -819,7 +811,7 @@ void Courtroom::handle_chatmessage_2() // handles IC { l_chatbox_name = "chatmed.png"; - if (ao_config->log_display_self_highlight_enabled() && m_chatmessage[CMChrId].toInt() == m_chr_id) + if (ao_config->log_display_self_highlight_enabled() && m_speaker_chr_id == m_chr_id) { const QString l_chatbox_self_name = "chatmed_self.png"; if (!ao_app->find_theme_asset_path(l_chatbox_self_name).isEmpty()) diff --git a/src/courtroom.h b/src/courtroom.h index 28b982675..4cd0c6ec9 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -168,18 +168,17 @@ class Courtroom : public QMainWindow IcLogTextFormat m_ic_log_format; void load_ic_text_format(); - // adds text to the IC chatlog. p_name first as bold then p_text then a newlin - // this function keeps the chatlog scrolled to the top unless there's text - // selected - // or the user isn't already scrolled to the top + // adds text to the IC chatlog. p_name first as bold then p_text then a + // newlin this function keeps the chatlog scrolled to the top unless there's + // text selected or the user isn't already scrolled to the top void update_ic_log(bool p_reset_log); void append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, int p_client_id, bool p_self); void append_system_text(QString p_showname, QString p_line); - // prints who played the song to IC chat and plays said song(if found on local - // filesystem) takes in a list where the first element is the song name and - // the second is the char id of who played it + // prints who played the song to IC chat and plays said song(if found on + // local filesystem) takes in a list where the first element is the song + // name and the second is the char id of who played it void handle_song(QStringList p_contents); // animates music text @@ -257,8 +256,8 @@ class Courtroom : public QMainWindow // delay before sfx plays QTimer *m_sound_timer = nullptr; - // keeps track of how long realization is visible(it's just a white square and - // should be visible less than a second) + // keeps track of how long realization is visible(it's just a white square + // and should be visible less than a second) QTimer *m_flash_timer = nullptr; // Generate a File Name based on the time you launched the client @@ -266,6 +265,7 @@ class Courtroom : public QMainWindow static const int MESSAGE_SIZE = 19; QString m_chatmessage[MESSAGE_SIZE]; + int m_speaker_chr_id = SpectatorId; QString m_speaker_showname; bool m_hide_character = false; bool m_play_pre = false; From 1cf2b7f1c9ae2343eee7d7ec1f51f2af716a0ef0 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 29 Apr 2022 18:03:08 +0200 Subject: [PATCH 700/842] Added AL and ML packet --- src/aoapplication.h | 2 ++ src/courtroom.cpp | 1 - src/server_socket.cpp | 42 +++++++++++++++++++++++++++++++++--------- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/aoapplication.h b/src/aoapplication.h index 0264eeaac..ff3c88a09 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -216,6 +216,8 @@ public slots: int m_loaded_evidence = 0; int m_music_count = 0; int m_loaded_music = 0; + bool m_loaded_music_list = false; + bool m_loaded_area_list = false; private slots: void _p_handle_server_disconnection(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 338d03d41..7e0337d18 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -591,7 +591,6 @@ void Courtroom::on_ic_message_return_pressed() // sfx file const QString l_sound_file = current_sfx_file(); packet_contents.append(l_sound_file.isEmpty() ? "0" : l_sound_file); - // TODO remove empty string workaround for pre-DRO 1.0.0 int l_emote_mod = l_emote.modifier; if (!ui_pre->isChecked()) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 7f7845933..369340737 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -119,6 +119,8 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) m_loaded_characters = 0; m_loaded_evidence = 0; m_loaded_music = 0; + m_loaded_music_list = false; + m_loaded_area_list = false; construct_courtroom(); @@ -191,7 +193,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) send_server_packet(DRPacket("RM")); } - else if (l_header == "SM" || l_header == "FM") + else if (l_header == "SM") // TODO remove block for 1.2.0+ { if (!is_courtroom_constructed) return; @@ -223,17 +225,39 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) m_courtroom->set_area_list(l_area_list); m_courtroom->set_music_list(l_music_list); - if (l_header == "SM") + m_loaded_music = m_music_count; + m_lobby->set_loading_text("Loading music:\n" + QString::number(m_loaded_music) + "/" + + QString::number(m_music_count)); + int total_loading_size = m_character_count + m_evidence_count + m_music_count; + int loading_value = + ((m_loaded_characters + m_loaded_evidence + m_loaded_music) / static_cast(total_loading_size)) * 100; + m_lobby->set_loading_value(loading_value); + send_server_packet(DRPacket("RD")); + } + else if (l_header == "AL") + { + if (!is_courtroom_constructed) + return; + m_courtroom->set_area_list(l_content); + + if (!m_loaded_area_list && is_lobby_constructed) + { + m_lobby->set_loading_text("Loading areas..."); + } + m_loaded_area_list = true; + } + else if (l_header == "ML") + { + if (!is_courtroom_constructed) + return; + m_courtroom->set_music_list(l_content); + + if (!m_loaded_area_list && is_lobby_constructed) { - m_loaded_music = m_music_count; - m_lobby->set_loading_text("Loading music:\n" + QString::number(m_loaded_music) + "/" + - QString::number(m_music_count)); - int total_loading_size = m_character_count + m_evidence_count + m_music_count; - int loading_value = - ((m_loaded_characters + m_loaded_evidence + m_loaded_music) / static_cast(total_loading_size)) * 100; - m_lobby->set_loading_value(loading_value); + m_lobby->set_loading_text("Loading music..."); send_server_packet(DRPacket("RD")); } + m_loaded_music_list = true; } else if (l_header == "DONE") { From 5fff560aeb64ea13dec11f3b083a997b521a14ff Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 29 Apr 2022 20:57:51 +0200 Subject: [PATCH 701/842] Use all variables --- src/courtroom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 7e0337d18..f9ebe204d 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1157,10 +1157,10 @@ void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bo p_line = p_line.trimmed(); DRChatRecord new_record(p_name, p_line); - new_record.set_music(p_music); new_record.set_system(p_system); new_record.set_client_id(p_client_id); - new_record.set_self(ao_app->get_client_id() == p_client_id); + new_record.set_self(p_self); + new_record.set_music(p_music); m_ic_record_queue.append(new_record); update_ic_log(false); } From 560f654f309fd8ff471cc2f208679e507c4e93e1 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 30 Apr 2022 14:22:32 +0200 Subject: [PATCH 702/842] Changed packet header checks. --- src/server_socket.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 369340737..f8bf61eb7 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -234,7 +234,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) m_lobby->set_loading_value(loading_value); send_server_packet(DRPacket("RD")); } - else if (l_header == "AL") + else if (l_header == "FA") { if (!is_courtroom_constructed) return; @@ -246,7 +246,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) } m_loaded_area_list = true; } - else if (l_header == "ML") + else if (l_header == "FM") { if (!is_courtroom_constructed) return; From 1980d0a23e9216c9f98a3473c57101a36a33d602 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 2 May 2022 12:10:29 +0200 Subject: [PATCH 703/842] Fix change music packet * The MC packet now correctly send the file name of a song rather than a title --- src/courtroom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f9ebe204d..f07e9947f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1819,8 +1819,8 @@ void Courtroom::on_music_list_double_clicked(QModelIndex p_model) { if (is_client_muted) return; - const QString p_song_name = ui_music_list->item(p_model.row())->text(); - ao_app->send_server_packet(DRPacket("MC", {p_song_name, QString::number(m_chr_id)})); + const QString l_song_name = ui_music_list->item(p_model.row())->data(Qt::UserRole).toString(); + ao_app->send_server_packet(DRPacket("MC", {l_song_name, QString::number(m_chr_id)})); ui_ic_chat_message->setFocus(); } From 928a95bd751616be7678f664965399a6165b7864 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 3 May 2022 01:08:47 +0200 Subject: [PATCH 704/842] Overhauled position file seeking Resolve #302 --- dronline-client.pro | 2 + src/aoapplication.h | 5 ++- src/courtroom.cpp | 68 ++++++++++------------------- src/courtroom.h | 5 ++- src/drposition.cpp | 97 ++++++++++++++++++++++++++++++++++++++++++ src/drposition.h | 46 ++++++++++++++++++++ src/drscenemovie.cpp | 12 +++--- src/drscenemovie.h | 2 +- src/path_functions.cpp | 20 ++++----- 9 files changed, 190 insertions(+), 67 deletions(-) create mode 100644 src/drposition.cpp create mode 100644 src/drposition.h diff --git a/dronline-client.pro b/dronline-client.pro index 9f7842adc..cb7b0b9cd 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -54,6 +54,7 @@ HEADERS += \ src/drmovie.h \ src/drpacket.h \ src/drpather.h \ + src/drposition.h \ src/drscenemovie.h \ src/drserversocket.h \ src/drshoutmovie.h \ @@ -117,6 +118,7 @@ SOURCES += \ src/drmovie.cpp \ src/drpacket.cpp \ src/drpather.cpp \ + src/drposition.cpp \ src/drscenemovie.cpp \ src/drserversocket.cpp \ src/drshoutmovie.cpp \ diff --git a/src/aoapplication.h b/src/aoapplication.h index ff3c88a09..54f91b37b 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -59,9 +59,10 @@ class AOApplication : public QApplication QString get_sounds_path(QString p_file); QString get_music_folder_path(); QString get_music_path(QString p_song); - QString format_background_path(QString p_identifier); + + QString get_background_path(QString p_background_name); QStringList get_available_background_identifier_list(); - QString get_current_background_path(); + QString get_current_background(); bool is_safe_path(QString p_file); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f9ebe204d..8c75c726a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -21,6 +21,7 @@ #include "drdiscord.h" #include "dreffectmovie.h" #include "drpacket.h" +#include "drposition.h" #include "drscenemovie.h" #include "drshoutmovie.h" #include "drsplashmovie.h" @@ -53,6 +54,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ao_app = p_ao_app; ao_config = new AOConfig(this); + m_position_reader = new DRPositionReader(this); + connect(ao_app, SIGNAL(reload_theme()), this, SLOT(load_theme())); connect(ao_app, SIGNAL(reload_character()), this, SLOT(load_character())); connect(ao_app, SIGNAL(reload_audiotracks()), this, SLOT(load_audiotracks())); @@ -248,60 +251,33 @@ void Courtroom::set_window_title(QString p_title) void Courtroom::update_background_scene() { - // witness is default if pos is invalid - const QString L_DEFAULT_BACK = "witnessempty"; - const QString L_DEFAULT_FRONT = "stand"; - const QString f_desk_mod = m_chatmessage[CMDeskModifier]; - const QString f_side = m_chatmessage[CMPosition]; - QString f_background = L_DEFAULT_BACK; - QString f_desk_image = L_DEFAULT_FRONT; + const QString l_new_background_name = ao_app->get_current_background(); - if (f_side == "def") - { - f_background = "defenseempty"; - f_desk_image = "defensedesk"; - } - else if (f_side == "pro") - { - f_background = "prosecutorempty"; - f_desk_image = "prosecutiondesk"; - } - else if (f_side == "jud") + // see TOD background list for why this method is called here + if (m_current_background_name.isEmpty() || m_current_background_name != l_new_background_name) { - f_background = "judgestand"; - f_desk_image = "judgedesk"; - } - else if (f_side == "hld") - { - f_background = "helperstand"; - f_desk_image = "helperdesk"; - } - else if (f_side == "hlp") - { - f_background = "prohelperstand"; - f_desk_image = "prohelperdesk"; + m_current_background_name = l_new_background_name; + const QString l_positions_ini = + ao_app->find_asset_path(ao_app->get_background_path(m_current_background_name) + "/" + "positions.ini"); + m_position_reader->load_file(l_positions_ini); } - if (f_desk_mod == "0") - { - ui_vp_desk->hide(); - } - else + const QString l_position_id = m_chatmessage[CMPosition]; + DRPosition l_position = m_position_reader->get_position(l_position_id); + + ui_vp_background->show(); + ui_vp_background->set_background_image(m_current_background_name, l_position.get_back()); + if (!ui_vp_background->is_valid()) { - ui_vp_desk->show(); - ui_vp_desk->set_image(f_desk_image); - if (!ui_vp_desk->is_valid()) - { - qWarning() << "warning: background missing file (" << m_background.background << f_side << f_desk_image << ")"; - ui_vp_desk->set_image(L_DEFAULT_FRONT); - } + ui_vp_background->hide(); } - ui_vp_background->set_image(f_background); - if (!ui_vp_background->is_valid()) + ui_vp_desk->show(); + ui_vp_desk->set_background_image(m_current_background_name, l_position.get_front()); + const QString l_desk_mode = m_chatmessage[CMDeskModifier]; + if (!ui_vp_desk->is_valid() || l_desk_mode == "0") { - qWarning() << "warning: background missing file (" << m_background.background << f_side << f_background << ")"; - ui_vp_background->set_image(L_DEFAULT_BACK); + ui_vp_desk->hide(); } } diff --git a/src/courtroom.h b/src/courtroom.h index 3ccb8ae90..7e0218a19 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -21,14 +21,15 @@ class AOShoutPlayer; class AOSystemPlayer; class AOTimer; class DRCharacterMovie; -class DRVideoWidget; class DRChatLog; class DREffectMovie; +class DRPositionReader; class DRSceneMovie; class DRShoutMovie; class DRSplashMovie; class DRStickerMovie; class DRTextEdit; +class DRVideoWidget; #include #include @@ -332,6 +333,8 @@ class Courtroom : public QMainWindow int m_current_clock = -1; DRAreaBackground m_background; + QString m_current_background_name; + DRPositionReader *m_position_reader = nullptr; AOImageDisplay *ui_background = nullptr; diff --git a/src/drposition.cpp b/src/drposition.cpp new file mode 100644 index 000000000..61751ea6c --- /dev/null +++ b/src/drposition.cpp @@ -0,0 +1,97 @@ +#include "drposition.h" + +#include +#include + +const QMap DRPositionReader::READONLY_POSITIONS{ + { + "wit", + DRPosition("witnessempty", "stand"), + }, + { + "def", + DRPosition("defenseempty", "defensedesk"), + }, + { + "pro", + DRPosition("prosecutorempty", "prosecutiondesk"), + }, + { + "jud", + DRPosition("judgestand", "judgedesk"), + }, + { + "hld", + DRPosition("helperstand", "helperdesk"), + }, + { + "hlp", + DRPosition("prohelperstand", "prohelperdesk"), + }, +}; + +DRPosition::DRPosition() +{} + +DRPosition::DRPosition(QString p_back, QString p_front) : m_back(p_back), m_front(p_front) +{} + +DRPosition::~DRPosition() +{} + +QString DRPosition::get_back() +{ + return m_back; +} + +QString DRPosition::get_front() +{ + return m_front; +} + +void DRPosition::set_back(QString p_back) +{ + m_back = p_back; +} + +void DRPosition::set_front(QString p_front) +{ + m_front = p_front; +} + +DRPositionReader::DRPositionReader(QObject *parent) : QObject(parent) +{} + +DRPositionReader::~DRPositionReader() +{} + +DRPosition DRPositionReader::get_position(QString p_id) +{ + p_id = p_id.toLower(); + return m_positions.contains(p_id) ? m_positions.value(p_id) : READONLY_POSITIONS.value(p_id); +} + +void DRPositionReader::load_file(QString p_filename) +{ + QSettings l_settings(p_filename, QSettings::IniFormat); + l_settings.setIniCodec("UTF-8"); + if (l_settings.status() != QSettings::NoError) + { + qWarning() << "[Position Reader]" + << "warning:" + << "could not load positions.ini file"; + return; + } + + m_positions.clear(); + const QStringList l_group_list = l_settings.childGroups(); + for (const QString &i_group : l_group_list) + { + const QString l_lower_group = i_group.toLower(); + l_settings.beginGroup(i_group); + const QString l_back = l_settings.value("back").toString(); + const QString l_front = l_settings.value("front").toString(); + m_positions.insert(l_lower_group, DRPosition(l_back, l_front)); + l_settings.endGroup(); + } +} diff --git a/src/drposition.h b/src/drposition.h new file mode 100644 index 000000000..54a05febb --- /dev/null +++ b/src/drposition.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +class DRPosition +{ +public: + DRPosition(); + + DRPosition(QString p_back, QString p_front); + + ~DRPosition(); + + QString get_back(); + + QString get_front(); + + void set_back(QString p_back); + + void set_front(QString p_front); + +private: + QString m_back; + + QString m_front; +}; + +class DRPositionReader : public QObject +{ + Q_OBJECT + +public: + DRPositionReader(QObject *parent = nullptr); + + ~DRPositionReader(); + + DRPosition get_position(QString p_id); + + void load_file(QString p_filename); + +private: + static const QMap READONLY_POSITIONS; + + QMap m_positions; +}; diff --git a/src/drscenemovie.cpp b/src/drscenemovie.cpp index bf0ceefbf..e9dae2df7 100644 --- a/src/drscenemovie.cpp +++ b/src/drscenemovie.cpp @@ -12,14 +12,14 @@ DRSceneMovie::DRSceneMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_ca DRSceneMovie::~DRSceneMovie() {} -void DRSceneMovie::set_image(QString p_image) +void DRSceneMovie::set_background_image(QString p_background_name, QString p_image) { - const QString l_file_path = - ao_app->find_asset_path(ao_app->get_current_background_path() + "/" + p_image, animated_or_static_extensions()); - - if (file_name() == l_file_path) + const QString l_filename = file_name(); + const QString l_target_filename = + ao_app->find_asset_path(ao_app->get_background_path(p_background_name) + "/" + p_image); + if (l_filename == l_target_filename) return; - set_file_name(l_file_path); + set_file_name(l_target_filename); start(); } diff --git a/src/drscenemovie.h b/src/drscenemovie.h index 0742d5d51..b79f44183 100644 --- a/src/drscenemovie.h +++ b/src/drscenemovie.h @@ -12,7 +12,7 @@ class DRSceneMovie : public DRMovie explicit DRSceneMovie(QWidget *parent = nullptr); ~DRSceneMovie(); - void set_image(QString p_image); + void set_background_image(QString p_background_name, QString p_image); private: AOApplication *ao_app; diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 4fe91a029..98b4d7488 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -6,6 +6,7 @@ #include "drpather.h" #include "file_functions.h" +#include #include #include #include @@ -60,7 +61,7 @@ QString AOApplication::get_music_path(QString p_song) return get_case_sensitive_path(l_path); } -QString AOApplication::format_background_path(QString p_identifier) +QString AOApplication::get_background_path(QString p_identifier) { return get_base_path() + "background/" + p_identifier; } @@ -94,26 +95,23 @@ QStringList AOApplication::get_available_background_identifier_list() return l_bg_list; } -#include - -QString AOApplication::get_current_background_path() +QString AOApplication::get_current_background() { - QString l_bg_path; + QString l_background_name; if (is_courtroom_constructed) { - QStringList l_bg_list = get_available_background_identifier_list(); - for (QString &i_bg : l_bg_list) + const QStringList l_background_name_list = get_available_background_identifier_list(); + for (const QString &i_background_name : l_background_name_list) { - i_bg = get_case_sensitive_path(format_background_path(i_bg)); - if (!dir_exists(i_bg)) + if (!dir_exists(get_case_sensitive_path(get_background_path(i_background_name)))) continue; - l_bg_path = i_bg; + l_background_name = i_background_name; break; } } - return l_bg_path; + return l_background_name; } /** From df9ef5e33914a01ea519a8bc3c5782b5f63dd12f Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 3 May 2022 01:11:47 +0200 Subject: [PATCH 705/842] Added expected extension --- src/drposition.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/drposition.cpp b/src/drposition.cpp index 61751ea6c..0bfc951ff 100644 --- a/src/drposition.cpp +++ b/src/drposition.cpp @@ -6,27 +6,27 @@ const QMap DRPositionReader::READONLY_POSITIONS{ { "wit", - DRPosition("witnessempty", "stand"), + DRPosition("witnessempty.png", "stand.png"), }, { "def", - DRPosition("defenseempty", "defensedesk"), + DRPosition("defenseempty.png", "defensedesk.png"), }, { "pro", - DRPosition("prosecutorempty", "prosecutiondesk"), + DRPosition("prosecutorempty.png", "prosecutiondesk.png"), }, { "jud", - DRPosition("judgestand", "judgedesk"), + DRPosition("judgestand.png", "judgedesk.png"), }, { "hld", - DRPosition("helperstand", "helperdesk"), + DRPosition("helperstand.png", "helperdesk.png"), }, { "hlp", - DRPosition("prohelperstand", "prohelperdesk"), + DRPosition("prohelperstand.png", "prohelperdesk.png"), }, }; From a1906a85d8712d93b3395ed7a118568e6fcf773f Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 3 May 2022 02:49:43 +0200 Subject: [PATCH 706/842] Removed default background look-up --- src/commondefs.cpp | 2 -- src/commondefs.h | 2 -- src/path_functions.cpp | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/commondefs.cpp b/src/commondefs.cpp index 73bc6b3a6..8a655353c 100644 --- a/src/commondefs.cpp +++ b/src/commondefs.cpp @@ -2,8 +2,6 @@ #include -const QString BACKGROUND_DEFAULT_NAME = "gs4"; - const QString BASE_CONFIG_INI = "/base/config.ini"; const QString BASE_SERVER_BROWSER_INI = "server_browser.ini"; const QString BASE_FAVORITE_SERVERS_INI = "favorite_servers.ini"; diff --git a/src/commondefs.h b/src/commondefs.h index e3cea60d8..0c63a9868 100644 --- a/src/commondefs.h +++ b/src/commondefs.h @@ -2,8 +2,6 @@ class QString; -extern const QString BACKGROUND_DEFAULT_NAME; - extern const QString BASE_CONFIG_INI; extern const QString BASE_SERVER_BROWSER_INI; extern const QString BASE_FAVORITE_SERVERS_INI; diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 98b4d7488..b00f782c2 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -88,8 +88,6 @@ QStringList AOApplication::get_available_background_identifier_list() if (!l_area_bg.background.isEmpty()) l_bg_list.append(l_area_bg.background); - - l_bg_list.append(BACKGROUND_DEFAULT_NAME); } return l_bg_list; From 80f8368897f1e050f1d4c75206b1fb41cf282bce Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 3 May 2022 15:59:51 +0200 Subject: [PATCH 707/842] Added hardcoded initial background position --- src/courtroom.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 04981f3af..6719c5bec 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -55,6 +55,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ao_config = new AOConfig(this); m_position_reader = new DRPositionReader(this); + m_chatmessage[CMPosition] = "wit"; connect(ao_app, SIGNAL(reload_theme()), this, SLOT(load_theme())); connect(ao_app, SIGNAL(reload_character()), this, SLOT(load_character())); From 6139f18902c85060a855b4b20b8cc02c27d083ba Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 4 May 2022 01:45:26 +0200 Subject: [PATCH 708/842] Changed the filter server buttons * When a filter is toggled on, it will now only show the intended category rather than the opposite. --- src/lobby.cpp | 53 +++++++++++++++++++++------------------------------ src/lobby.h | 21 +++++++++++--------- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index c011568d0..e58632b39 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -37,8 +37,8 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() this->setWindowTitle("Danganronpa Online"); ui_background = new AOImageDisplay(this, ao_app); - ui_hide_public_servers = new AOButton(this, ao_app); - ui_hide_favorite_servers = new AOButton(this, ao_app); + ui_public_server_filter = new AOButton(this, ao_app); + ui_favorite_server_filter = new AOButton(this, ao_app); ui_refresh = new AOButton(this, ao_app); ui_toggle_favorite = new AOButton(this, ao_app); ui_connect = new AOButton(this, ao_app); @@ -75,8 +75,8 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() connect(m_master_client, SIGNAL(motd_changed()), this, SLOT(update_motd())); connect(m_master_client, SIGNAL(server_list_changed()), this, SLOT(update_server_list())); - connect(ui_hide_public_servers, SIGNAL(clicked()), this, SLOT(toggle_hide_public_servers())); - connect(ui_hide_favorite_servers, SIGNAL(clicked()), this, SLOT(toggle_hide_favorite_servers())); + connect(ui_public_server_filter, SIGNAL(clicked()), this, SLOT(toggle_public_server_filter())); + connect(ui_favorite_server_filter, SIGNAL(clicked()), this, SLOT(toggle_favorite_server_filter())); connect(ui_refresh, SIGNAL(pressed()), this, SLOT(on_refresh_pressed())); connect(ui_refresh, SIGNAL(released()), this, SLOT(on_refresh_released())); connect(ui_toggle_favorite, SIGNAL(pressed()), this, SLOT(on_add_to_fav_pressed())); @@ -137,11 +137,12 @@ void Lobby::update_widgets() set_size_and_pos(ui_background, "lobby", LOBBY_DESIGN_INI, ao_app); ui_background->set_theme_image("lobbybackground.png"); - set_size_and_pos(ui_hide_public_servers, "public_servers", LOBBY_DESIGN_INI, ao_app); - ui_hide_public_servers->set_image(m_hide_public_servers ? "publicservers_selected.png" : "publicservers.png"); + set_size_and_pos(ui_public_server_filter, "public_servers", LOBBY_DESIGN_INI, ao_app); + ui_public_server_filter->set_image(m_server_filter == PublicOnly ? "publicservers_selected.png" + : "publicservers.png"); - set_size_and_pos(ui_hide_favorite_servers, "favorites", LOBBY_DESIGN_INI, ao_app); - ui_hide_favorite_servers->set_image(m_hide_favorite_servers ? "favorites_selected.png" : "favorites.png"); + set_size_and_pos(ui_favorite_server_filter, "favorites", LOBBY_DESIGN_INI, ao_app); + ui_favorite_server_filter->set_image(m_server_filter == FavoriteOnly ? "favorites_selected.png" : "favorites.png"); set_size_and_pos(ui_refresh, "refresh", LOBBY_DESIGN_INI, ao_app); ui_refresh->set_image("refresh.png"); @@ -263,8 +264,7 @@ void Lobby::load_settings() l_ini.setIniCodec("UTF-8"); l_ini.beginGroup("filters"); - hide_public_servers(l_ini.value("hide_public", false).toBool()); - hide_favorite_servers(l_ini.value("hide_favorites", false).toBool()); + m_server_filter = ServerFilter(l_ini.value("server_filter", NoFilter).toInt()); l_ini.endGroup(); } @@ -274,8 +274,7 @@ void Lobby::save_settings() l_ini.setIniCodec("UTF-8"); l_ini.beginGroup("filters"); - l_ini.setValue("hide_public", m_hide_public_servers); - l_ini.setValue("hide_favorites", m_hide_favorite_servers); + l_ini.setValue("server_filter", int(m_server_filter)); l_ini.endGroup(); l_ini.sync(); } @@ -403,7 +402,7 @@ void Lobby::filter_server_listing() for (int i = 0; i < ui_server_list->count(); ++i) { QListWidgetItem *l_server_item = ui_server_list->item(i); - l_server_item->setHidden(m_combined_server_list.at(i).favorite ? m_hide_favorite_servers : m_hide_public_servers); + l_server_item->setHidden(m_server_filter == (m_combined_server_list.at(i).favorite ? PublicOnly : FavoriteOnly)); } select_current_server(); } @@ -423,34 +422,26 @@ void Lobby::select_current_server() } } -void Lobby::hide_public_servers(bool p_on) +void Lobby::toggle_public_server_filter() { - if (m_hide_public_servers == p_on) - return; - m_hide_public_servers = p_on; - ui_hide_public_servers->set_image(m_hide_public_servers ? "publicservers_selected.png" : "publicservers.png"); - filter_server_listing(); + m_server_filter = m_server_filter == PublicOnly ? NoFilter : PublicOnly; + update_server_filter_buttons(); } -void Lobby::toggle_hide_public_servers() +void Lobby::toggle_favorite_server_filter() { - hide_public_servers(!m_hide_public_servers); + m_server_filter = m_server_filter == FavoriteOnly ? NoFilter : FavoriteOnly; + update_server_filter_buttons(); } -void Lobby::hide_favorite_servers(bool p_on) +void Lobby::update_server_filter_buttons() { - if (m_hide_favorite_servers == p_on) - return; - m_hide_favorite_servers = p_on; - ui_hide_favorite_servers->set_image(m_hide_favorite_servers ? "favorites_selected.png" : "favorites.png"); + ui_public_server_filter->set_image(m_server_filter == PublicOnly ? "publicservers_selected.png" + : "publicservers.png"); + ui_favorite_server_filter->set_image(m_server_filter == FavoriteOnly ? "favorites_selected.png" : "favorites.png"); filter_server_listing(); } -void Lobby::toggle_hide_favorite_servers() -{ - hide_favorite_servers(!m_hide_favorite_servers); -} - void Lobby::on_refresh_pressed() { ui_refresh->set_image("refresh_pressed.png"); diff --git a/src/lobby.h b/src/lobby.h index fdc521654..32da304fe 100644 --- a/src/lobby.h +++ b/src/lobby.h @@ -54,14 +54,18 @@ class Lobby : public QMainWindow DRServerInfoList m_favorite_server_list; DRServerInfoList m_combined_server_list; DRServerInfo m_current_server; - bool is_public_server_selected = true; // ui AOImageDisplay *ui_background = nullptr; - AOButton *ui_hide_public_servers = nullptr; - bool m_hide_public_servers = false; - AOButton *ui_hide_favorite_servers = nullptr; - bool m_hide_favorite_servers = false; + AOButton *ui_public_server_filter = nullptr; + AOButton *ui_favorite_server_filter = nullptr; + enum ServerFilter + { + NoFilter, + PublicOnly, + FavoriteOnly, + }; + ServerFilter m_server_filter = NoFilter; AOButton *ui_refresh = nullptr; AOButton *ui_toggle_favorite = nullptr; AOButton *ui_connect = nullptr; @@ -91,10 +95,9 @@ private slots: void update_server_list(); void set_favorite_server_list(DRServerInfoList server_list); void update_combined_server_list(); - void hide_public_servers(bool on); - void toggle_hide_public_servers(); - void hide_favorite_servers(bool on); - void toggle_hide_favorite_servers(); + void toggle_public_server_filter(); + void toggle_favorite_server_filter(); + void update_server_filter_buttons(); void update_server_listing(); void filter_server_listing(); void select_current_server(); From 99a4cd0b5a22b741d7f691b01b8af0871f87291a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 4 May 2022 18:43:42 +0200 Subject: [PATCH 709/842] Changed ini-swap dropdown to have elide enabled --- src/courtroom_character.cpp | 7 ++++--- src/courtroom_widgets.cpp | 3 --- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index ff73889a3..742ef522e 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -130,14 +130,15 @@ void Courtroom::on_iniswap_dropdown_changed(int p_index) void Courtroom::set_iniswap_dropdown_searchable(bool p_enabled) { ui_iniswap_dropdown->setEditable(p_enabled); + QAbstractItemView *l_view = ui_iniswap_dropdown->view(); if (p_enabled) { QCompleter *l_completer = ui_iniswap_dropdown->completer(); l_completer->setCompletionMode(QCompleter::PopupCompletion); l_completer->setFilterMode(Qt::MatchContains); - QAbstractItemView *l_popup = l_completer->popup(); - l_popup->setTextElideMode(Qt::TextElideMode::ElideNone); - l_popup->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + l_view = l_completer->popup(); } + l_view->setTextElideMode(Qt::TextElideMode::ElideMiddle); + l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index e2ca27139..ad6742581 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -111,9 +111,6 @@ void Courtroom::create_widgets() ui_iniswap_dropdown = new QComboBox(this); ui_iniswap_dropdown->setInsertPolicy(QComboBox::NoInsert); - QAbstractItemView *l_view = ui_iniswap_dropdown->view(); - l_view->setTextElideMode(Qt::TextElideMode::ElideNone); - l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); From 730d1319749878a924a78b3e27f605418ed31e42 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 4 May 2022 19:09:23 +0200 Subject: [PATCH 710/842] Restored horizontal scrollbar to ini-swap dropdown --- src/courtroom_character.cpp | 9 +++++---- src/courtroom_widgets.cpp | 5 +++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index 742ef522e..ee00a7282 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -130,15 +131,15 @@ void Courtroom::on_iniswap_dropdown_changed(int p_index) void Courtroom::set_iniswap_dropdown_searchable(bool p_enabled) { ui_iniswap_dropdown->setEditable(p_enabled); - QAbstractItemView *l_view = ui_iniswap_dropdown->view(); if (p_enabled) { QCompleter *l_completer = ui_iniswap_dropdown->completer(); l_completer->setCompletionMode(QCompleter::PopupCompletion); l_completer->setFilterMode(Qt::MatchContains); - l_view = l_completer->popup(); + QListView *l_view = new QListView(ui_iniswap_dropdown); + l_view->setTextElideMode(Qt::TextElideMode::ElideNone); + l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + l_completer->setPopup(l_view); } - l_view->setTextElideMode(Qt::TextElideMode::ElideMiddle); - l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index ad6742581..1df3325a1 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -111,6 +112,10 @@ void Courtroom::create_widgets() ui_iniswap_dropdown = new QComboBox(this); ui_iniswap_dropdown->setInsertPolicy(QComboBox::NoInsert); + QListView *l_view = new QListView(ui_iniswap_dropdown); + l_view->setTextElideMode(Qt::TextElideMode::ElideNone); + l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + ui_iniswap_dropdown->setView(l_view); ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); From ebdec7717742fb686938814516d1a89d32c6b5be Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 4 May 2022 19:18:59 +0200 Subject: [PATCH 711/842] Final fix for ini-swap dropdown scrollbar --- src/courtroom_character.cpp | 10 +++++++--- src/courtroom_widgets.cpp | 5 ----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index ee00a7282..f1df91d4b 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -131,15 +131,19 @@ void Courtroom::on_iniswap_dropdown_changed(int p_index) void Courtroom::set_iniswap_dropdown_searchable(bool p_enabled) { ui_iniswap_dropdown->setEditable(p_enabled); + QListView *l_view = new QListView(ui_iniswap_dropdown); if (p_enabled) { QCompleter *l_completer = ui_iniswap_dropdown->completer(); l_completer->setCompletionMode(QCompleter::PopupCompletion); l_completer->setFilterMode(Qt::MatchContains); - QListView *l_view = new QListView(ui_iniswap_dropdown); - l_view->setTextElideMode(Qt::TextElideMode::ElideNone); - l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); l_completer->setPopup(l_view); } + else + { + ui_iniswap_dropdown->setView(l_view); + } + l_view->setTextElideMode(Qt::TextElideMode::ElideNone); + l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 1df3325a1..ad6742581 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -112,10 +111,6 @@ void Courtroom::create_widgets() ui_iniswap_dropdown = new QComboBox(this); ui_iniswap_dropdown->setInsertPolicy(QComboBox::NoInsert); - QListView *l_view = new QListView(ui_iniswap_dropdown); - l_view->setTextElideMode(Qt::TextElideMode::ElideNone); - l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); - ui_iniswap_dropdown->setView(l_view); ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); From ffe0dc921ce7b63a9c767b2a40bcf3a2aa618e94 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 4 May 2022 20:19:21 +0200 Subject: [PATCH 712/842] Fix initial ini-swap dropdown not being updated --- src/courtroom_widgets.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index ad6742581..6bef07cbe 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -111,6 +111,7 @@ void Courtroom::create_widgets() ui_iniswap_dropdown = new QComboBox(this); ui_iniswap_dropdown->setInsertPolicy(QComboBox::NoInsert); + set_iniswap_dropdown_searchable(ao_config->searchable_iniswap_enabled()); ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); From ca62297d4e1fc898c3ded0eefbab2dcdc39c19e1 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 4 May 2022 20:57:34 +0200 Subject: [PATCH 713/842] Ini-swap fix if enabled at start --- src/courtroom_character.cpp | 12 ++++-------- src/courtroom_widgets.cpp | 7 ++++++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index f1df91d4b..bfbc22584 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -93,8 +93,8 @@ void Courtroom::update_iniswap_list() drSetItemIcon(ui_iniswap_dropdown, i, i_name, ao_app); } update_default_iniswap_item(); - select_base_character_iniswap(); + set_iniswap_dropdown_searchable(ao_config->searchable_iniswap_enabled()); } void Courtroom::update_default_iniswap_item() @@ -131,19 +131,15 @@ void Courtroom::on_iniswap_dropdown_changed(int p_index) void Courtroom::set_iniswap_dropdown_searchable(bool p_enabled) { ui_iniswap_dropdown->setEditable(p_enabled); - QListView *l_view = new QListView(ui_iniswap_dropdown); if (p_enabled) { QCompleter *l_completer = ui_iniswap_dropdown->completer(); l_completer->setCompletionMode(QCompleter::PopupCompletion); l_completer->setFilterMode(Qt::MatchContains); + QListView *l_view = new QListView(ui_iniswap_dropdown); l_completer->setPopup(l_view); + l_view->setTextElideMode(Qt::TextElideMode::ElideNone); + l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); } - else - { - ui_iniswap_dropdown->setView(l_view); - } - l_view->setTextElideMode(Qt::TextElideMode::ElideNone); - l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 6bef07cbe..fd93776cd 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -111,7 +111,12 @@ void Courtroom::create_widgets() ui_iniswap_dropdown = new QComboBox(this); ui_iniswap_dropdown->setInsertPolicy(QComboBox::NoInsert); - set_iniswap_dropdown_searchable(ao_config->searchable_iniswap_enabled()); + { + QListView *l_view = new QListView(ui_iniswap_dropdown); + ui_iniswap_dropdown->setView(l_view); + l_view->setTextElideMode(Qt::TextElideMode::ElideNone); + l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + } ui_ic_chatlog = new DRTextEdit(this); ui_ic_chatlog->setReadOnly(true); From 563dbda82771b79d9d930a437f770841ec90b54c Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 4 May 2022 17:49:36 -0400 Subject: [PATCH 714/842] Bump up post-version to b2 --- src/version.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.cpp b/src/version.cpp index e6dd54ac4..bf1732599 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -30,7 +30,7 @@ VersionNumber get_version_number() QString get_post_version() { - return "b1"; + return "b2"; } QString get_version_string() From ab59ed15cfb5c2f967f91a56b8c33de628d3b8f5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 5 May 2022 00:52:43 +0200 Subject: [PATCH 715/842] Fixed audio device defaulting * Fixed audio device defaulting to "No Sound" if no favorite device driver was set. --- src/draudioengine_p.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/draudioengine_p.cpp b/src/draudioengine_p.cpp index 2ebae8e82..5c8679621 100644 --- a/src/draudioengine_p.cpp +++ b/src/draudioengine_p.cpp @@ -37,10 +37,10 @@ void DRAudioEnginePrivate::update_current_device() { update_device_list(); - DRAudioDevice l_new_device; + DRAudioDevice l_target_device; for (const DRAudioDevice &i_device : qAsConst(device_list)) { - if (i_device.get_driver() == favorite_device_driver) + if (!favorite_device_driver.isEmpty() && i_device.get_driver() == favorite_device_driver) { if (!favorite_device.has_value() || favorite_device.value() != i_device) { @@ -50,21 +50,21 @@ void DRAudioEnginePrivate::update_current_device() if (i_device.is_enabled()) { - l_new_device = i_device; + l_target_device = i_device; break; } } if (i_device.is_default()) { - l_new_device = i_device; + l_target_device = i_device; } } - if (device.has_value() && device.value() == l_new_device) + if (device.has_value() && device.value() == l_target_device) return; const std::optional l_prev_device = device; - device = l_new_device; + device = l_target_device; if (l_prev_device.has_value() && l_prev_device->get_id() == device->get_id()) return; From 6be89212fdce47a2eb48f6bde6806514daae83d5 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 4 May 2022 21:58:15 -0400 Subject: [PATCH 716/842] Fix server given showname not being displayed when playing music --- src/courtroom.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6719c5bec..63fb7b637 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1509,16 +1509,16 @@ void Courtroom::handle_song(QStringList p_contents) } else { - // This 2th argument corresponds to the showname to use when displaying the + // This last argument corresponds to the showname to use when displaying the // music change message in IC // Backwards compatibility is explicitly kept for older versions of // tsuserver that do not send such an argument by assuming an empty showname // If there is an empty showname, the client will use instead the default // showname of the character. QString f_showname; - if (p_contents.size() == 3) + if (p_contents.size() == (l_server_compatible ? 4 : 3)) { - f_showname = p_contents.at(2); + f_showname = p_contents.at(p_contents.size() - 1); } else { From a9be06278962103613306eeaca15b12a77cb539e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 5 May 2022 13:34:28 +0200 Subject: [PATCH 717/842] Guarantee MC packet argument order --- src/aomusicplayer.cpp | 4 +- src/aomusicplayer.h | 2 +- src/courtroom.cpp | 91 ++++++++++++++++----------------------- src/courtroom.h | 3 +- src/courtroom_widgets.cpp | 4 +- 5 files changed, 45 insertions(+), 59 deletions(-) diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index 166333b00..6e78d6bd9 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -15,8 +15,8 @@ void AOMusicPlayer::play(QString p_song) { stop(); - m_file_name = p_song; - auto l_maybe_stream = m_family->create_stream(ao_app->get_music_path(p_song)); + m_filename = p_song; + auto l_maybe_stream = m_family->create_stream(p_song); if (l_maybe_stream) { auto l_stream = l_maybe_stream.value(); diff --git a/src/aomusicplayer.h b/src/aomusicplayer.h index af9b1c910..3b24719d4 100644 --- a/src/aomusicplayer.h +++ b/src/aomusicplayer.h @@ -16,5 +16,5 @@ public slots: private: DRAudioStreamFamily::ptr m_family; - QString m_file_name; + QString m_filename; }; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 63fb7b637..e45c03c56 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -303,7 +303,13 @@ void Courtroom::set_tick_rate(const int p_tick_rate) m_server_tick_rate = p_tick_rate; } -void Courtroom::handle_music_anim() +void Courtroom::set_music_text(QString p_text) +{ + ui_vp_music_name->setText(p_text); + update_music_text_anim(); +} + +void Courtroom::update_music_text_anim() { pos_size_type res_a = ao_app->get_element_dimensions("music_name", COURTROOM_DESIGN_INI); pos_size_type res_b = ao_app->get_element_dimensions("music_area", COURTROOM_DESIGN_INI); @@ -1479,74 +1485,53 @@ void Courtroom::set_ban(int p_cid) void Courtroom::handle_song(QStringList p_contents) { const bool l_server_compatible = ao_app->is_server_client_version_compatible(); - if (p_contents.size() < (l_server_compatible ? 3 : 2)) + if (p_contents.size() < (l_server_compatible ? 4 : 3)) return; - QString f_song = p_contents.at(0); - const int l_chr_id = p_contents.at(1).toInt(); - if (l_server_compatible) - { - const bool l_restart = p_contents.at(2).toInt(); - if (m_current_song == f_song && !l_restart) - return; - } - m_current_song = f_song; - - for (auto &ext : audio_extensions()) + QString l_song = p_contents.at(0); + for (auto &i_extension : audio_extensions()) { - QString r_song = f_song + ext; - QString song_path = ao_app->get_music_path(r_song); - if (file_exists(song_path)) + const QString l_fetched_song = l_song + i_extension; + const QString l_path = ao_app->get_music_path(l_fetched_song); + if (file_exists(l_path)) { - f_song = r_song; + l_song = l_fetched_song; break; } } - if (l_chr_id < 0 || l_chr_id >= m_chr_list.size()) + const int l_chr_id = p_contents.at(1).toInt(); + + QString l_showname = p_contents.at(2); + if (l_showname.isEmpty()) { - m_music_player->play(f_song); + l_showname = ao_app->get_showname(m_chr_list.at(l_chr_id).name); } - else + + if (l_server_compatible) { - // This last argument corresponds to the showname to use when displaying the - // music change message in IC - // Backwards compatibility is explicitly kept for older versions of - // tsuserver that do not send such an argument by assuming an empty showname - // If there is an empty showname, the client will use instead the default - // showname of the character. - QString f_showname; - if (p_contents.size() == (l_server_compatible ? 4 : 3)) - { - f_showname = p_contents.at(p_contents.size() - 1); - } - else - { - f_showname = ""; - } + const bool l_restart = p_contents.at(3).toInt(); + if (m_current_song == l_song && !l_restart) + return; + } + m_current_song = l_song; - QString str_char; - if (f_showname.isEmpty()) - { - str_char = ao_app->get_showname(m_chr_list.at(l_chr_id).name); - } - else - { - str_char = f_showname; - } + const QString l_song_filename = ao_app->find_asset_path(ao_app->get_music_path(l_song), audio_extensions()); + m_music_player->play(l_song_filename); + + DRAudiotrackMetadata l_song_meta(l_song); + if (l_chr_id >= 0 || l_chr_id < m_chr_list.length()) + { + append_ic_text(l_showname, "has played a song: " + l_song_meta.title(), false, true, NoClientId, + l_chr_id == m_chr_id); - append_ic_text(str_char, "has played a song: " + f_song, false, true, NoClientId, l_chr_id == m_chr_id); if (ao_config->log_is_recording_enabled()) - save_textlog(str_char + " has played a song: " + f_song); - m_music_player->play(f_song); + { + save_textlog(l_showname + " has played a song: " + l_song_meta.file_name()); + } } - int pos = f_song.lastIndexOf(QChar('.')); - QString r_song = f_song.left(pos); - - ui_vp_music_name->setText(r_song); - - handle_music_anim(); + set_music_text(l_song_meta.title()); } void Courtroom::handle_wtce(QString p_wtce) diff --git a/src/courtroom.h b/src/courtroom.h index 7e0218a19..27e0bd80b 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -183,7 +183,8 @@ class Courtroom : public QMainWindow void handle_song(QStringList p_contents); // animates music text - void handle_music_anim(); + void set_music_text(QString p_text); + void update_music_text_anim(); // handle server-side clock animation and display void handle_clock(QString time); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index fd93776cd..ac91fe5e8 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -75,12 +75,12 @@ void Courtroom::create_widgets() ui_vp_music_display_b = new AOImageDisplay(this, ao_app); ui_vp_music_area = new QWidget(ui_vp_music_display_a); ui_vp_music_name = new DRTextEdit(ui_vp_music_area); - ui_vp_music_name->setText("DANGANRONPA ONLINE"); ui_vp_music_name->setFrameStyle(QFrame::NoFrame); ui_vp_music_name->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_vp_music_name->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui_vp_music_name->setReadOnly(true); music_anim = new QPropertyAnimation(ui_vp_music_name, "geometry", this); + set_music_text("DANGANRONPA ONLINE"); ui_vp_clock = new DRStickerMovie(this); ui_vp_clock->set_play_once(true); @@ -968,7 +968,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_spectator, "spectator", COURTROOM_DESIGN_INI, ao_app); - handle_music_anim(); + update_music_text_anim(); set_size_and_pos(ui_set_notes, "set_notes_button", COURTROOM_DESIGN_INI, ao_app); ui_set_notes->set_image("set_notes.png"); From c23e59bcecd82fa3ad54cccbba09c0c8bf53697c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 5 May 2022 14:18:54 +0200 Subject: [PATCH 718/842] Fix loop breaking (expecting song name, not path) --- src/aomusicplayer.cpp | 2 +- src/courtroom.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index 6e78d6bd9..b595033db 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -16,7 +16,7 @@ void AOMusicPlayer::play(QString p_song) stop(); m_filename = p_song; - auto l_maybe_stream = m_family->create_stream(p_song); + auto l_maybe_stream = m_family->create_stream(ao_app->get_music_path(p_song)); if (l_maybe_stream) { auto l_stream = l_maybe_stream.value(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index e45c03c56..b896db240 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1516,8 +1516,7 @@ void Courtroom::handle_song(QStringList p_contents) } m_current_song = l_song; - const QString l_song_filename = ao_app->find_asset_path(ao_app->get_music_path(l_song), audio_extensions()); - m_music_player->play(l_song_filename); + m_music_player->play(l_song); DRAudiotrackMetadata l_song_meta(l_song); if (l_chr_id >= 0 || l_chr_id < m_chr_list.length()) From 7dd4e5b44140a721142a380a04d2102afa11cde6 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 5 May 2022 14:31:43 +0200 Subject: [PATCH 719/842] Avoid displaying a message from Spectator --- src/courtroom.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index b896db240..54db74d5a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1503,10 +1503,6 @@ void Courtroom::handle_song(QStringList p_contents) const int l_chr_id = p_contents.at(1).toInt(); QString l_showname = p_contents.at(2); - if (l_showname.isEmpty()) - { - l_showname = ao_app->get_showname(m_chr_list.at(l_chr_id).name); - } if (l_server_compatible) { @@ -1521,6 +1517,11 @@ void Courtroom::handle_song(QStringList p_contents) DRAudiotrackMetadata l_song_meta(l_song); if (l_chr_id >= 0 || l_chr_id < m_chr_list.length()) { + if (l_showname.isEmpty()) + { + l_showname = ao_app->get_showname(m_chr_list.at(l_chr_id).name); + } + append_ic_text(l_showname, "has played a song: " + l_song_meta.title(), false, true, NoClientId, l_chr_id == m_chr_id); From 143cb184f8eee3e743fe27d74b18dc2771bc6bb1 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 5 May 2022 14:33:43 +0200 Subject: [PATCH 720/842] Jesus, I need a coding pause. --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 54db74d5a..037da550b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1515,7 +1515,7 @@ void Courtroom::handle_song(QStringList p_contents) m_music_player->play(l_song); DRAudiotrackMetadata l_song_meta(l_song); - if (l_chr_id >= 0 || l_chr_id < m_chr_list.length()) + if (l_chr_id >= 0 && l_chr_id < m_chr_list.length()) { if (l_showname.isEmpty()) { From 85851f409f9b265d7f3a6ce9337f657bf7f46f43 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 5 May 2022 17:45:20 +0200 Subject: [PATCH 721/842] Added `*_anim_play_once` setting to theme config ini Resolve #307 --- src/courtroom_widgets.cpp | 12 ++++++++---- src/theme.cpp | 11 +++++++++++ src/theme.h | 2 ++ src/version.cpp | 2 +- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index ac91fe5e8..5cc19df78 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -83,7 +83,6 @@ void Courtroom::create_widgets() set_music_text("DANGANRONPA ONLINE"); ui_vp_clock = new DRStickerMovie(this); - ui_vp_clock->set_play_once(true); ui_vp_chatbox = new AOImageDisplay(this, ao_app); ui_vp_showname = new DRTextEdit(ui_vp_chatbox); @@ -107,7 +106,6 @@ void Courtroom::create_widgets() ui_vp_objection->set_hide_on_done(true); ui_vp_chat_arrow = new DRStickerMovie(this); - ui_vp_chat_arrow->set_play_once(false); ui_iniswap_dropdown = new QComboBox(this); ui_iniswap_dropdown->setInsertPolicy(QComboBox::NoInsert); @@ -615,7 +613,10 @@ void Courtroom::set_widgets() set_size_and_pos(ui_vp_chat_arrow, "chat_arrow", COURTROOM_DESIGN_INI, ao_app); if (!ao_app->find_theme_asset_path("chat_arrow", animated_or_static_extensions()).isEmpty()) + { ui_vp_chat_arrow->play("chat_arrow"); + } + set_sticker_play_once(ui_vp_chat_arrow, "chat_arrow", COURTROOM_CONFIG_INI, ao_app); ui_vp_chat_arrow->hide(); ui_vp_effect->move(ui_viewport->x(), ui_viewport->y()); @@ -667,6 +668,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_vp_clock, "clock", COURTROOM_DESIGN_INI, ao_app); if (m_current_clock == -1) ui_vp_clock->hide(); + set_sticker_play_once(ui_vp_clock, "clock", COURTROOM_CONFIG_INI, ao_app); ui_vp_chatbox->set_theme_image("chatmed.png"); ui_vp_chatbox->hide(); @@ -1399,8 +1401,10 @@ void Courtroom::set_free_blocks() { for (int i = 0; i < ui_free_blocks.size(); i++) { - DRStickerMovie *free_block = ui_free_blocks[i]; - free_block->play(free_block_names[i]); + DRStickerMovie *l_block = ui_free_blocks[i]; + const QString l_block_name = free_block_names[i]; + l_block->play(l_block_name); + set_sticker_play_once(l_block, l_block_name, COURTROOM_CONFIG_INI, ao_app); } } diff --git a/src/theme.cpp b/src/theme.cpp index fc5afd8a8..5e73477af 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -11,6 +11,7 @@ // src #include "aoapplication.h" #include "datatypes.h" +#include "drstickermovie.h" #include "drtextedit.h" void set_size_and_pos(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) @@ -130,3 +131,13 @@ void center_widget_to_screen(QWidget *p_widget) int y = (screen_geometry.height() - p_widget->height()) / 2; p_widget->move(x, y); } + +void set_sticker_play_once(DRStickerMovie *p_sticker, QString p_identifier, QString p_ini_file, AOApplication *ao_app) +{ + const bool l_play_once = ao_app->read_theme_ini_bool(p_identifier + "_anim_play_once", p_ini_file); + p_sticker->set_play_once(l_play_once); + if (l_play_once && !p_sticker->is_running()) + { + p_sticker->start(); + } +} diff --git a/src/theme.h b/src/theme.h index fe0f6bc2a..95a0770ea 100644 --- a/src/theme.h +++ b/src/theme.h @@ -2,6 +2,7 @@ // src class AOApplication; +class DRStickerMovie; class DRTextEdit; // qt @@ -15,3 +16,4 @@ void set_font(QWidget *widget, QString identifier, QString ini_file, AOApplicati void set_drtextedit_font(DRTextEdit *widget, QString identifier, QString ini_file, AOApplication *ao_app); bool set_stylesheet(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); void center_widget_to_screen(QWidget *widget); +void set_sticker_play_once(DRStickerMovie *sticker, QString identifier, QString ini_file, AOApplication *ao_app); diff --git a/src/version.cpp b/src/version.cpp index bf1732599..1e2cf69a6 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -30,7 +30,7 @@ VersionNumber get_version_number() QString get_post_version() { - return "b2"; + return "b3"; } QString get_version_string() From 268d817065324f2f272ea6b149b30aeacfd7a639 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 5 May 2022 17:58:41 +0200 Subject: [PATCH 722/842] Renamed field * Field shortened to `*_play_once` from `_anim_play_once` --- src/theme.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/theme.cpp b/src/theme.cpp index 5e73477af..c9231c9d2 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -134,7 +134,7 @@ void center_widget_to_screen(QWidget *p_widget) void set_sticker_play_once(DRStickerMovie *p_sticker, QString p_identifier, QString p_ini_file, AOApplication *ao_app) { - const bool l_play_once = ao_app->read_theme_ini_bool(p_identifier + "_anim_play_once", p_ini_file); + const bool l_play_once = ao_app->read_theme_ini_bool(p_identifier + "_play_once", p_ini_file); p_sticker->set_play_once(l_play_once); if (l_play_once && !p_sticker->is_running()) { From eea1818f3ff6b5a12de617aef8ce6d4a6ad4bfc2 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 5 May 2022 22:25:59 +0200 Subject: [PATCH 723/842] Extra checks for emote modifiers --- src/courtroom.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 037da550b..637aa237c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -576,7 +576,15 @@ void Courtroom::on_ic_message_return_pressed() packet_contents.append(l_sound_file.isEmpty() ? "0" : l_sound_file); int l_emote_mod = l_emote.modifier; - if (!ui_pre->isChecked()) + + if (ui_pre->isChecked()) + { + if (l_emote_mod == ZoomEmoteMod) + l_emote_mod = PreZoomEmoteMod; + else + l_emote_mod = PreEmoteMod; + } + else { if (l_emote_mod == PreZoomEmoteMod) l_emote_mod = ZoomEmoteMod; From 780deae3dbe50da39d6f990fa962719534e53f46 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 6 May 2022 00:52:20 +0200 Subject: [PATCH 724/842] Added legacy background support --- src/courtroom.cpp | 75 ++++++++++++++++++++++++++++++++++++++++++++++- src/courtroom.h | 6 +++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 637aa237c..b74d0b161 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -254,13 +254,28 @@ void Courtroom::update_background_scene() { const QString l_new_background_name = ao_app->get_current_background(); + m_is_legacy_background = false; // see TOD background list for why this method is called here if (m_current_background_name.isEmpty() || m_current_background_name != l_new_background_name) { m_current_background_name = l_new_background_name; + const QString l_positions_ini = ao_app->find_asset_path(ao_app->get_background_path(m_current_background_name) + "/" + "positions.ini"); - m_position_reader->load_file(l_positions_ini); + if (!l_positions_ini.isEmpty()) + { + m_position_reader->load_file(l_positions_ini); + } + else + { + m_is_legacy_background = true; + } + } + + if (m_is_legacy_background) + { + update_legacy_background_scene(); + return; } const QString l_position_id = m_chatmessage[CMPosition]; @@ -282,6 +297,64 @@ void Courtroom::update_background_scene() } } +void Courtroom::update_legacy_background_scene() +{ + QString l_back_image = "witnessempty"; + QString l_front_image = "stand"; + + const QString l_position_id = m_chatmessage[CMPosition]; + if (l_position_id == "def") + { + l_back_image = "defenseempty"; + l_front_image = "defensedesk"; + } + else if (l_position_id == "pro") + { + l_back_image = "prosecutorempty"; + l_front_image = "prosecutiondesk"; + } + else if (l_position_id == "jud") + { + l_back_image = "judgestand"; + l_front_image = "judgedesk"; + } + else if (l_position_id == "hld") + { + l_back_image = "helperstand"; + l_front_image = "helperdesk"; + } + else if (l_position_id == "hlp") + { + l_back_image = "prohelperstand"; + l_front_image = "prohelperdesk"; + } + else + { + l_back_image = "witnessempty"; + l_front_image = "stand"; + } + + l_back_image = ao_app->find_asset_path(ao_app->get_background_path(m_current_background_name) + "/" + l_back_image, + animated_or_static_extensions()); + l_front_image = ao_app->find_asset_path(ao_app->get_background_path(m_current_background_name) + "/" + l_front_image, + animated_or_static_extensions()); + + ui_vp_background->show(); + ui_vp_background->set_file_name(l_back_image); + ui_vp_background->start(); + + if (m_chatmessage[CMDeskModifier] == "0") + { + ui_vp_desk->hide(); + } + else + { + ui_vp_desk->show(); + ui_vp_desk->set_file_name(l_front_image); + ui_vp_desk->start(); + } +} + DRAreaBackground Courtroom::get_background() { return m_background; diff --git a/src/courtroom.h b/src/courtroom.h index 27e0bd80b..bf72b02aa 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -90,9 +90,12 @@ class Courtroom : public QMainWindow // called when a DONE#% from the server was received void done_received(); - // sets desk and bg based on pos in chatmessage + // updates background and front based on the position given from the chatmessage void update_background_scene(); + // updates background and front based on legacy background + void update_legacy_background_scene(); + // sets text color based on text color in chatmessage void set_text_color(); @@ -335,6 +338,7 @@ class Courtroom : public QMainWindow DRAreaBackground m_background; QString m_current_background_name; + bool m_is_legacy_background = false; DRPositionReader *m_position_reader = nullptr; AOImageDisplay *ui_background = nullptr; From c9e19124bd98a8aa208a48033ad0196b04f6a9b7 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 6 May 2022 14:23:26 +0200 Subject: [PATCH 725/842] Added version to window title to ease tech-support --- src/lobby.cpp | 2 +- src/server_socket.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index e58632b39..d5eb8ec99 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -34,7 +34,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() ao_config = new AOConfig(this); m_master_client = new DRMasterClient(this); - this->setWindowTitle("Danganronpa Online"); + setWindowTitle("Danganronpa Online (" + get_version_string() + ")"); ui_background = new AOImageDisplay(this, ao_app); ui_public_server_filter = new AOButton(this, ao_app); diff --git a/src/server_socket.cpp b/src/server_socket.cpp index f8bf61eb7..61bd45203 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -140,7 +140,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) } } - QString l_window_title = "Danganronpa Online"; + QString l_window_title = "Danganronpa Online (" + get_version_string() + ")"; if (!l_current_server.name.isEmpty()) l_window_title = l_window_title + ": " + l_current_server.to_info(); m_courtroom->set_window_title(l_window_title); From 2f4c6a688389e05901cb9603750d7b988ff31f77 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 6 May 2022 19:58:21 +0200 Subject: [PATCH 726/842] Added dynamic scaling to animated images --- src/drcharactermovie.cpp | 2 +- src/drmovie.cpp | 58 +++++++++++++++++++++++++++++++++------- src/drmovie.h | 12 +++++++-- src/drscenemovie.cpp | 2 +- 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/drcharactermovie.cpp b/src/drcharactermovie.cpp index 4e20c2d54..bb516499e 100644 --- a/src/drcharactermovie.cpp +++ b/src/drcharactermovie.cpp @@ -8,7 +8,7 @@ DRCharacterMovie::DRCharacterMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_cast(qApp)) { Q_ASSERT(ao_app); - set_scale_to_height(true); + set_scale_mode(DRMovie::HeightScaling); } DRCharacterMovie::~DRCharacterMovie() diff --git a/src/drmovie.cpp b/src/drmovie.cpp index f0968e860..5c93cf5c7 100644 --- a/src/drmovie.cpp +++ b/src/drmovie.cpp @@ -87,9 +87,9 @@ void DRMovie::set_mirrored(bool p_on) * * By default, scale_to_height is off. */ -void DRMovie::set_scale_to_height(bool p_on) +void DRMovie::set_scale_mode(DRMovie::ScalingMode p_mode) { - m_scale_to_height = p_on; + m_scaling_mode = p_mode; } /** @@ -142,17 +142,55 @@ void DRMovie::resizeEvent(QResizeEvent *event) void DRMovie::paint_frame() { - QPixmap l_frame = m_current_pixmap; - - const bool l_is_larger = l_frame.width() > width() || l_frame.height() > height(); - const Qt::TransformationMode l_transform = l_is_larger ? Qt::SmoothTransformation : Qt::FastTransformation; - if (m_scale_to_height) + ScalingMode l_mode = m_scaling_mode; + if (l_mode == DynamicScaling) { - l_frame = l_frame.scaledToHeight(height(), l_transform); + const qreal l_width_factor = (qreal)m_current_pixmap.width() / qMax(width(), 1); + const qreal l_height_factor = (qreal)m_current_pixmap.height() / qMax(height(), 1); + + if (l_width_factor < l_height_factor) + { + l_mode = WidthScaling; + } + else if (l_height_factor < l_width_factor) + { + l_mode = HeightScaling; + } + else + { + l_mode = NoScaling; + } } - else + + QPixmap l_frame = m_current_pixmap; + Qt::TransformationMode l_transformation = Qt::FastTransformation; + switch (l_mode) { - l_frame = l_frame.scaled(size(), Qt::IgnoreAspectRatio, l_transform); + case NoScaling: + [[fallthrough]]; + default: + l_frame = l_frame.scaled(size(), Qt::IgnoreAspectRatio, l_transformation); + if (m_current_pixmap.width() > width() || m_current_pixmap.height() > height()) + { + l_transformation = Qt::SmoothTransformation; + } + break; + + case WidthScaling: + l_frame = l_frame.scaledToWidth(width(), l_transformation); + if (m_current_pixmap.width() > width()) + { + l_transformation = Qt::SmoothTransformation; + } + break; + + case HeightScaling: + l_frame = l_frame.scaledToHeight(height(), l_transformation); + if (m_current_pixmap.height() > height()) + { + l_transformation = Qt::SmoothTransformation; + } + break; } setPixmap(l_frame); diff --git a/src/drmovie.h b/src/drmovie.h index 87abff789..43fcf7d8c 100644 --- a/src/drmovie.h +++ b/src/drmovie.h @@ -9,6 +9,14 @@ class DRMovie : public QLabel Q_OBJECT public: + enum ScalingMode + { + NoScaling, + WidthScaling, + HeightScaling, + DynamicScaling, + }; + explicit DRMovie(QWidget *parent = nullptr); ~DRMovie(); @@ -17,7 +25,7 @@ class DRMovie : public QLabel void set_play_once(bool); void set_mirrored(bool); - void set_scale_to_height(bool); + void set_scale_mode(ScalingMode); void set_hide_on_done(bool); bool is_running(); @@ -34,7 +42,7 @@ class DRMovie : public QLabel private: bool m_play_once = false; bool m_mirrored = false; - bool m_scale_to_height = false; + ScalingMode m_scaling_mode = NoScaling; bool m_hide_when_done = false; QString m_file_name; diff --git a/src/drscenemovie.cpp b/src/drscenemovie.cpp index e9dae2df7..141113b1e 100644 --- a/src/drscenemovie.cpp +++ b/src/drscenemovie.cpp @@ -6,7 +6,7 @@ DRSceneMovie::DRSceneMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_cast(qApp)) { Q_ASSERT(ao_app); - set_scale_to_height(true); + set_scale_mode(DRMovie::DynamicScaling); } DRSceneMovie::~DRSceneMovie() From e97f802575236e553ff47215bf70540b1572cbe0 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 6 May 2022 20:16:44 +0200 Subject: [PATCH 727/842] Always calculate the factor based on the lowest value --- src/drmovie.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/drmovie.cpp b/src/drmovie.cpp index 5c93cf5c7..6638c391b 100644 --- a/src/drmovie.cpp +++ b/src/drmovie.cpp @@ -145,8 +145,10 @@ void DRMovie::paint_frame() ScalingMode l_mode = m_scaling_mode; if (l_mode == DynamicScaling) { - const qreal l_width_factor = (qreal)m_current_pixmap.width() / qMax(width(), 1); - const qreal l_height_factor = (qreal)m_current_pixmap.height() / qMax(height(), 1); + const qreal l_width_factor = + (qreal)qMax(m_current_pixmap.width(), width()) / qMax(qMin(m_current_pixmap.width(), width()), 1); + const qreal l_height_factor = + (qreal)qMax(m_current_pixmap.height(), height()) / qMax(qMin(m_current_pixmap.height(), height()), 1); if (l_width_factor < l_height_factor) { From 02ee1a909e4f0399cd181ca072784a085d94cd91 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 8 May 2022 00:09:19 +0200 Subject: [PATCH 728/842] Reworked scaling --- src/drmovie.cpp | 67 ++++++++++++++++++++++++++----------------------- src/drmovie.h | 5 ++-- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/drmovie.cpp b/src/drmovie.cpp index 6638c391b..0a3cfbbb7 100644 --- a/src/drmovie.cpp +++ b/src/drmovie.cpp @@ -2,6 +2,7 @@ #include #include +#include DRMovie::DRMovie(QWidget *parent) : QLabel(parent) { @@ -142,60 +143,62 @@ void DRMovie::resizeEvent(QResizeEvent *event) void DRMovie::paint_frame() { - ScalingMode l_mode = m_scaling_mode; - if (l_mode == DynamicScaling) + ScalingMode l_target_mode = m_scaling_mode; + QSize l_target_size = size(); + if (l_target_mode == DynamicScaling) { - const qreal l_width_factor = - (qreal)qMax(m_current_pixmap.width(), width()) / qMax(qMin(m_current_pixmap.width(), width()), 1); - const qreal l_height_factor = - (qreal)qMax(m_current_pixmap.height(), height()) / qMax(qMin(m_current_pixmap.height(), height()), 1); - - if (l_width_factor < l_height_factor) + const qreal l_width_factor = (qreal)qMax(m_current_pixmap.width(), 1) / qMax(width(), 1); + const qreal l_height_factor = (qreal)qMax(m_current_pixmap.height(), 1) / qMax(height(), 1); + + const QSize l_by_width_size{ + int((qreal)m_current_pixmap.width() / l_width_factor), + int((qreal)m_current_pixmap.height() / l_width_factor), + }; + const QSize l_by_height_size{ + int((qreal)m_current_pixmap.width() / l_height_factor), + int((qreal)m_current_pixmap.height() / l_height_factor), + }; + + if (l_by_width_size.height() >= height()) { - l_mode = WidthScaling; + l_target_mode = WidthScaling; + l_target_size = l_by_width_size; } - else if (l_height_factor < l_width_factor) + else if (l_by_height_size.width() >= width()) { - l_mode = HeightScaling; + l_target_mode = HeightScaling; + l_target_size = l_by_height_size; } else { - l_mode = NoScaling; + l_target_mode = StretchScaling; } } - QPixmap l_frame = m_current_pixmap; - Qt::TransformationMode l_transformation = Qt::FastTransformation; - switch (l_mode) + QPixmap l_scaled_image = m_current_pixmap; + Qt::TransformationMode l_transformation = Qt::SmoothTransformation; + if (m_current_pixmap.width() < width() || m_current_pixmap.height() < height()) + { + l_transformation = Qt::FastTransformation; + } + switch (l_target_mode) { - case NoScaling: + case StretchScaling: [[fallthrough]]; default: - l_frame = l_frame.scaled(size(), Qt::IgnoreAspectRatio, l_transformation); - if (m_current_pixmap.width() > width() || m_current_pixmap.height() > height()) - { - l_transformation = Qt::SmoothTransformation; - } + l_scaled_image = l_scaled_image.scaled(size(), Qt::IgnoreAspectRatio, l_transformation); break; case WidthScaling: - l_frame = l_frame.scaledToWidth(width(), l_transformation); - if (m_current_pixmap.width() > width()) - { - l_transformation = Qt::SmoothTransformation; - } + l_scaled_image = l_scaled_image.scaledToWidth(width(), l_transformation); break; case HeightScaling: - l_frame = l_frame.scaledToHeight(height(), l_transformation); - if (m_current_pixmap.height() > height()) - { - l_transformation = Qt::SmoothTransformation; - } + l_scaled_image = l_scaled_image.scaledToHeight(height(), l_transformation); break; } - setPixmap(l_frame); + setPixmap(l_scaled_image); } void DRMovie::update_frame(int p_frame_number) diff --git a/src/drmovie.h b/src/drmovie.h index 43fcf7d8c..f08672ee1 100644 --- a/src/drmovie.h +++ b/src/drmovie.h @@ -11,11 +11,12 @@ class DRMovie : public QLabel public: enum ScalingMode { - NoScaling, + StretchScaling, WidthScaling, HeightScaling, DynamicScaling, }; + Q_ENUM(ScalingMode) explicit DRMovie(QWidget *parent = nullptr); ~DRMovie(); @@ -42,7 +43,7 @@ class DRMovie : public QLabel private: bool m_play_once = false; bool m_mirrored = false; - ScalingMode m_scaling_mode = NoScaling; + ScalingMode m_scaling_mode = StretchScaling; bool m_hide_when_done = false; QString m_file_name; From 4fe4ee1dceaa7f889820f4fbb2625874ec67527a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 8 May 2022 00:33:34 +0200 Subject: [PATCH 729/842] Removed needless header --- src/drmovie.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/drmovie.cpp b/src/drmovie.cpp index 0a3cfbbb7..5cac603df 100644 --- a/src/drmovie.cpp +++ b/src/drmovie.cpp @@ -2,7 +2,6 @@ #include #include -#include DRMovie::DRMovie(QWidget *parent) : QLabel(parent) { From 443d9391f6ef9881e41f1049c7f56e560f882a6e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 8 May 2022 01:09:51 +0200 Subject: [PATCH 730/842] Fixed legacy background being erroneously considered non-legacy --- src/courtroom.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index b74d0b161..d0e3f70e6 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -252,14 +252,13 @@ void Courtroom::set_window_title(QString p_title) void Courtroom::update_background_scene() { - const QString l_new_background_name = ao_app->get_current_background(); + const QString l_prev_background_name = m_current_background_name; + m_current_background_name = ao_app->get_current_background(); - m_is_legacy_background = false; // see TOD background list for why this method is called here - if (m_current_background_name.isEmpty() || m_current_background_name != l_new_background_name) + if (l_prev_background_name.isEmpty() || l_prev_background_name != m_current_background_name) { - m_current_background_name = l_new_background_name; - + m_is_legacy_background = false; const QString l_positions_ini = ao_app->find_asset_path(ao_app->get_background_path(m_current_background_name) + "/" + "positions.ini"); if (!l_positions_ini.isEmpty()) @@ -341,7 +340,12 @@ void Courtroom::update_legacy_background_scene() ui_vp_background->show(); ui_vp_background->set_file_name(l_back_image); + ui_vp_background->set_play_once(false); ui_vp_background->start(); + if (!ui_vp_background->is_valid()) + { + ui_vp_background->hide(); + } if (m_chatmessage[CMDeskModifier] == "0") { @@ -351,7 +355,12 @@ void Courtroom::update_legacy_background_scene() { ui_vp_desk->show(); ui_vp_desk->set_file_name(l_front_image); + ui_vp_desk->set_play_once(false); ui_vp_desk->start(); + if (!ui_vp_desk->is_valid()) + { + ui_vp_desk->hide(); + } } } From d7505ecd9446a4f1695c66d2f4f3a365b311d373 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 8 May 2022 19:43:45 +0200 Subject: [PATCH 731/842] No sound audio device was set a driver, ... * A default device is now attributed the driver "NOSOUND" * Removed needless creation of a string list * Various code formatting fixes --- src/aoconfigpanel.cpp | 6 +++++- src/draudiodevice.cpp | 2 +- src/drshoutmovie.cpp | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 56eb0c50d..79193fa4e 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -21,7 +21,9 @@ #include AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) - : QWidget(p_parent), m_config(new AOConfig(this)), m_engine(new DRAudioEngine(this)) + : QWidget(p_parent), + m_config(new AOConfig(this)), + m_engine(new DRAudioEngine(this)) { ao_app = p_ao_app; @@ -537,11 +539,13 @@ void AOConfigPanel::on_device_current_index_changed(int p_index) const QString target_device_driver = ui_device->itemData(p_index).toString(); for (DRAudioDevice &i_device : m_engine->get_device_list()) + { if (target_device_driver == i_device.get_driver()) { m_config->set_favorite_device_driver(i_device.get_driver()); break; } + } } void AOConfigPanel::on_audio_device_changed(DRAudioDevice p_device) diff --git a/src/draudiodevice.cpp b/src/draudiodevice.cpp index 8a878e5fc..ab3cbbaad 100644 --- a/src/draudiodevice.cpp +++ b/src/draudiodevice.cpp @@ -40,7 +40,7 @@ QString DRAudioDevice::get_name() const QString DRAudioDevice::get_driver() const { - return m_driver; + return m_driver.isEmpty() ? "NOSOUND" : m_driver; } DRAudioDevice::States DRAudioDevice::get_states() const diff --git a/src/drshoutmovie.cpp b/src/drshoutmovie.cpp index 08705f8db..23b46f313 100644 --- a/src/drshoutmovie.cpp +++ b/src/drshoutmovie.cpp @@ -23,7 +23,7 @@ void DRShoutMovie::play_interjection(QString p_character, QString p_shout) } QString l_file_name = - ao_app->find_asset_path({ao_app->get_character_path(p_character, p_shout)}, animated_extensions()); + ao_app->find_asset_path(ao_app->get_character_path(p_character, p_shout), animated_extensions()); if (l_file_name.isEmpty()) { From 85d4bdb1d4b0512cacca4a4e1a368ef374076025 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 8 May 2022 23:15:23 +0200 Subject: [PATCH 732/842] Added suffix for shouts * Shouts now look for the shout identifier and an additional suffix appended to the identifier as a filename --- src/drshoutmovie.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/drshoutmovie.cpp b/src/drshoutmovie.cpp index 23b46f313..43879642f 100644 --- a/src/drshoutmovie.cpp +++ b/src/drshoutmovie.cpp @@ -17,13 +17,9 @@ DRShoutMovie::~DRShoutMovie() void DRShoutMovie::play_interjection(QString p_character, QString p_shout) { - if (p_shout.toLower() == "custom") - { - p_shout.append("_bubble"); - } - - QString l_file_name = - ao_app->find_asset_path(ao_app->get_character_path(p_character, p_shout), animated_extensions()); + QString l_file_name = ao_app->find_asset_path( + {ao_app->get_character_path(p_character, p_shout), ao_app->get_character_path(p_character, p_shout + "_bubble")}, + animated_extensions()); if (l_file_name.isEmpty()) { From 888cf4c6f2dcb065f8c3be74d2bbd0863f25f4b6 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 11 May 2022 20:39:23 -0400 Subject: [PATCH 733/842] Update Ubuntu readme --- .github/workflows/build-all-merge-master.yml | 2 +- .github/workflows/build-all.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-all-merge-master.yml b/.github/workflows/build-all-merge-master.yml index 618e14b32..520cfdec6 100644 --- a/.github/workflows/build-all-merge-master.yml +++ b/.github/workflows/build-all-merge-master.yml @@ -437,7 +437,7 @@ jobs: echo "LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client" > "dro-client.sh" echo "Installation instructions 1. On your terminal, run - sudo apt install qt5-default + sudo apt-get install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools 2. Change directory to this folder, and run chmod +x dro-client.sh chmod +x dro-client diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 410109847..a6357379a 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -404,7 +404,7 @@ jobs: echo "LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client" > "dro-client.sh" echo "Installation instructions 1. On your terminal, run - sudo apt install qt5-default + sudo apt-get install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools 2. Change directory to this folder, and run chmod +x dro-client.sh chmod +x dro-client From 3f77b136c2e40499c0ed605ffe5e57aa64f58d34 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 13 May 2022 01:57:52 +0200 Subject: [PATCH 734/842] Editable ini-swap now uses the proper icon --- src/courtroom.cpp | 2 -- src/courtroom.h | 2 +- src/courtroom_character.cpp | 61 ++++++++++++++++++++----------------- src/courtroom_widgets.cpp | 4 +-- 4 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d0e3f70e6..56f6eea07 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -116,8 +116,6 @@ void Courtroom::setup_courtroom() set_widgets(); ui_char_select_background->setVisible(l_chr_select_visible); - update_iniswap_list(); - m_shout_state = 0; m_shout_current = 0; check_shouts(); diff --git a/src/courtroom.h b/src/courtroom.h index bf72b02aa..1cbe32593 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -585,7 +585,7 @@ private slots: void on_emote_dropdown_changed(int p_index); void on_iniswap_dropdown_changed(int p_index); - void set_iniswap_dropdown_searchable(bool); + void update_iniswap_dropdown_searchable(); void on_pos_dropdown_changed(int p_index); void on_cycle_clicked(); diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index bfbc22584..51e4e3825 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -70,31 +70,36 @@ void drSetItemIcon(QComboBox *p_widget, const int p_index, const QString &p_chr_ void Courtroom::update_iniswap_list() { - QSignalBlocker b_ini_list(ui_iniswap_dropdown); - ui_iniswap_dropdown->clear(); + ui_iniswap_dropdown->setEditable(false); - QStringList l_name_list{"Default"}; - const QString l_path = ao_app->get_base_path() + "/characters"; - const QFileInfoList l_info_list = QDir(l_path).entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); - for (const QFileInfo &i_info : l_info_list) { - const QString l_name = i_info.fileName(); - if (!file_exists(ao_app->get_character_path(l_name, CHARACTER_CHAR_INI))) - continue; - l_name_list.append(l_name); + QSignalBlocker b_ini_list(ui_iniswap_dropdown); + ui_iniswap_dropdown->clear(); + + QStringList l_name_list{"Default"}; + const QString l_path = ao_app->get_base_path() + "/characters"; + const QFileInfoList l_info_list = QDir(l_path).entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); + for (const QFileInfo &i_info : l_info_list) + { + const QString l_name = i_info.fileName(); + if (!file_exists(ao_app->get_character_path(l_name, CHARACTER_CHAR_INI))) + continue; + l_name_list.append(l_name); + } + + for (int i = 0; i < l_name_list.length(); ++i) + { + const QString &i_name = l_name_list.at(i); + ui_iniswap_dropdown->addItem(i_name); + if (i == 0) + continue; + drSetItemIcon(ui_iniswap_dropdown, i, i_name, ao_app); + } + update_default_iniswap_item(); + select_base_character_iniswap(); } - for (int i = 0; i < l_name_list.length(); ++i) - { - const QString &i_name = l_name_list.at(i); - ui_iniswap_dropdown->addItem(i_name); - if (i == 0) - continue; - drSetItemIcon(ui_iniswap_dropdown, i, i_name, ao_app); - } - update_default_iniswap_item(); - select_base_character_iniswap(); - set_iniswap_dropdown_searchable(ao_config->searchable_iniswap_enabled()); + update_iniswap_dropdown_searchable(); } void Courtroom::update_default_iniswap_item() @@ -128,18 +133,18 @@ void Courtroom::on_iniswap_dropdown_changed(int p_index) p_index == 0 ? get_character() : ui_iniswap_dropdown->itemText(p_index)); } -void Courtroom::set_iniswap_dropdown_searchable(bool p_enabled) +void Courtroom::update_iniswap_dropdown_searchable() { - ui_iniswap_dropdown->setEditable(p_enabled); - if (p_enabled) + const bool l_enabled = ao_config->searchable_iniswap_enabled(); + ui_iniswap_dropdown->setEditable(l_enabled); + if (l_enabled) { QCompleter *l_completer = ui_iniswap_dropdown->completer(); l_completer->setCompletionMode(QCompleter::PopupCompletion); l_completer->setFilterMode(Qt::MatchContains); - QListView *l_view = new QListView(ui_iniswap_dropdown); - l_completer->setPopup(l_view); - l_view->setTextElideMode(Qt::TextElideMode::ElideNone); - l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + QAbstractItemView *l_list = l_completer->popup(); + l_list->setTextElideMode(Qt::TextElideMode::ElideNone); + l_list->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); } set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 5cc19df78..5434ac205 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -261,7 +261,7 @@ void Courtroom::connect_widgets() connect(m_flash_timer, SIGNAL(timeout()), this, SLOT(realization_done())); - connect(ao_config, SIGNAL(searchable_iniswap_changed(bool)), this, SLOT(set_iniswap_dropdown_searchable(bool))); + connect(ao_config, SIGNAL(searchable_iniswap_changed(bool)), this, SLOT(update_iniswap_dropdown_searchable())); connect(ao_config, SIGNAL(emote_preview_changed(bool)), this, SLOT(on_emote_preview_toggled(bool))); connect(ui_emote_left, SIGNAL(clicked()), this, SLOT(on_emote_left_clicked())); connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); @@ -737,7 +737,7 @@ void Courtroom::set_widgets() set_stylesheet(ui_emote_dropdown, "[EMOTE DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); set_size_and_pos(ui_iniswap_dropdown, "iniswap_dropdown", COURTROOM_DESIGN_INI, ao_app); - set_iniswap_dropdown_searchable(ao_config->searchable_iniswap_enabled()); + update_iniswap_dropdown_searchable(); set_size_and_pos(ui_pos_dropdown, "pos_dropdown", COURTROOM_DESIGN_INI, ao_app); set_stylesheet(ui_pos_dropdown, "[POS DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); From 696079d69c32ae2d3e0371a9cbfc2ad4b0331dab Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 13 May 2022 21:55:47 +0200 Subject: [PATCH 735/842] Fixed chatbox not appearing if an incorrect name was provided --- src/aoimagedisplay.cpp | 26 +++++++++++++++++++++----- src/aoimagedisplay.h | 2 +- src/courtroom.cpp | 23 +++-------------------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/aoimagedisplay.cpp b/src/aoimagedisplay.cpp index b744276f2..9c5f16e1e 100644 --- a/src/aoimagedisplay.cpp +++ b/src/aoimagedisplay.cpp @@ -1,5 +1,7 @@ #include "aoimagedisplay.h" +#include + #include "aoapplication.h" #include "aopixmap.h" #include "file_functions.h" @@ -30,12 +32,26 @@ void AOImageDisplay::set_theme_image(QString p_image) set_image(ao_app->find_theme_asset_path(p_image)); } -void AOImageDisplay::set_chatbox_image(QString p_image) +void AOImageDisplay::set_chatbox_image(QString p_chatbox_name, bool p_is_self) { - QString l_target_path = ao_app->find_asset_path(ao_app->get_base_path() + "misc/" + p_image + ".png"); - if (!l_target_path.isEmpty()) + QString l_target_file = ao_app->find_asset_path(ao_app->get_base_path() + "misc/" + p_chatbox_name + ".png"); + if (l_target_file.isEmpty()) + { + l_target_file = ao_app->find_theme_asset_path("chatmed.png"); + + if (p_is_self) + { + const QString l_self_file = ao_app->find_theme_asset_path("chatmed_self.png"); + if (!l_self_file.isEmpty()) + { + l_target_file = l_self_file; + } + } + } + + if (l_target_file.isEmpty()) { - l_target_path = ao_app->find_theme_asset_path("chatmed.png"); + qWarning() << "warning: could not retrieve any chatbox image, will display blank"; } - set_image(l_target_path); + set_image(l_target_file); } diff --git a/src/aoimagedisplay.h b/src/aoimagedisplay.h index c890af1e7..abd991acf 100644 --- a/src/aoimagedisplay.h +++ b/src/aoimagedisplay.h @@ -13,7 +13,7 @@ class AOImageDisplay : public QLabel QString get_image(); void set_image(QString p_image); void set_theme_image(QString p_image); - void set_chatbox_image(QString p_image); + void set_chatbox_image(QString p_chatbox_name, bool p_is_self); private: AOApplication *ao_app = nullptr; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 56f6eea07..0fe87e92c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -875,26 +875,9 @@ void Courtroom::handle_chatmessage_2() // handles IC ui_vp_showname_image->hide(); ui_vp_showname->setText(m_speaker_showname); - QString l_chatbox_name = ao_app->get_chat(m_chatmessage[CMChrName]); - - if (l_chatbox_name.isEmpty()) - { - l_chatbox_name = "chatmed.png"; - - if (ao_config->log_display_self_highlight_enabled() && m_speaker_chr_id == m_chr_id) - { - const QString l_chatbox_self_name = "chatmed_self.png"; - if (!ao_app->find_theme_asset_path(l_chatbox_self_name).isEmpty()) - l_chatbox_name = l_chatbox_self_name; - } - - ui_vp_chatbox->set_theme_image(l_chatbox_name); - } - else - { - QString chatbox_path = ao_app->get_base_path() + "misc/" + l_chatbox_name + ".png"; - ui_vp_chatbox->set_chatbox_image(chatbox_path); - } + const QString l_chatbox_name = ao_app->get_chat(m_chatmessage[CMChrName]); + const bool l_is_self = (ao_config->log_display_self_highlight_enabled() && m_speaker_chr_id == m_chr_id); + ui_vp_chatbox->set_chatbox_image(l_chatbox_name, l_is_self); if (m_msg_is_first_person == false) { From 3dd847f3d2e4db2ca4af18914fba7df7c0f0d8e9 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 14 May 2022 01:21:34 +0200 Subject: [PATCH 736/842] Fixed sound not playing, added soundd * Fixed sounds not playing if the character is hidden * Added SoundD for char.ini to provide an exact time rather than an approximation in 60ms --- src/courtroom.cpp | 19 ++++++++++++------- src/text_file_functions.cpp | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 0fe87e92c..f73739c46 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -889,10 +889,20 @@ void Courtroom::handle_chatmessage_2() // handles IC else ui_vp_player_char->set_mirrored(false); - if (m_play_pre && !m_hide_character) - play_preanim(); + if (m_play_pre) + { + int sfx_delay = m_chatmessage[CMSoundDelay].toInt(); + m_sound_timer->start(sfx_delay); + + if (!m_hide_character) + { + play_preanim(); + } + } else + { handle_chatmessage_3(); + } } void Courtroom::handle_chatmessage_3() @@ -1235,11 +1245,6 @@ void Courtroom::append_system_text(QString p_showname, QString p_line) void Courtroom::play_preanim() { - // all time values in char.inis are multiplied by a constant(time_mod) to get - // the actual time - int sfx_delay = m_chatmessage[CMSoundDelay].toInt() * 60; - m_sound_timer->start(sfx_delay); - // set state anim_state = 1; ui_vp_player_char->show(); diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 3d2a3f5eb..798d0b795 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -625,9 +625,19 @@ QVector AOApplication::get_emote_list(QString p_chr) l_emote.sound_file = l_chrini.value(i_key).toString(); l_chrini.endGroup(); - l_chrini.beginGroup(l_fetcher.lookup_group("soundt")); - l_emote.sound_delay = qMax(l_chrini.value(i_key).toInt(), 0); + l_chrini.beginGroup(l_fetcher.lookup_group("soundd")); + if (l_chrini.contains(i_key)) + { + l_emote.sound_delay = l_chrini.value(i_key).toInt(); + } + else + { + l_chrini.endGroup(); + l_chrini.beginGroup(l_fetcher.lookup_group("soundt")); + l_emote.sound_delay = l_chrini.value(i_key).toInt() * 60; + } l_chrini.endGroup(); + l_emote.sound_delay = qMax(0, l_emote.sound_delay); l_chrini.beginGroup(l_fetcher.lookup_group("videos")); l_emote.video_file = l_chrini.value(i_key).toString(); From fe37c5458fee35640f2e19ff16c0d2de87f460ff Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 14 May 2022 19:55:30 +0200 Subject: [PATCH 737/842] Fixed exceptions, back to lobby duplication * In debug mode, the client no longer raise exceptions if the font name is empty. * The back to lobby may now only be clicked once (should fix the lobby duplication bug due to missing UI elements) --- src/courtroom.cpp | 6 ++++-- src/courtroom.h | 1 + src/theme.cpp | 26 +++++++++++++++----------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f73739c46..b218c739e 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2165,11 +2165,13 @@ void Courtroom::load_audiotracks() void Courtroom::on_back_to_lobby_clicked() { + if (m_back_to_lobby_clicked) + return; + m_back_to_lobby_clicked = true; + // hide so we don't get the 'disconnected from server' prompt hide(); - ao_app->construct_lobby(); - ao_app->get_lobby()->set_choose_a_server(); ao_app->destruct_courtroom(); } diff --git a/src/courtroom.h b/src/courtroom.h index 1cbe32593..4cde32df1 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -493,6 +493,7 @@ class Courtroom : public QMainWindow QVector ui_char_button_list; AOButton *ui_back_to_lobby = nullptr; + bool m_back_to_lobby_clicked = false; AOButton *ui_chr_select_left = nullptr; AOButton *ui_chr_select_right = nullptr; diff --git a/src/theme.cpp b/src/theme.cpp index c9231c9d2..00cbfba36 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -20,7 +20,7 @@ void set_size_and_pos(QWidget *p_widget, QString p_identifier, QString p_ini_fil if (design_ini_result.width < 0 || design_ini_result.height < 0) { - qDebug() << "W: could not find \"" << p_identifier << "\" in " << p_ini_file; + qDebug() << "W: could not find" << p_identifier << "in " << p_ini_file; // Don't hide, as some widgets don't have a built-in way of reappearing again. p_widget->move(0, 0); p_widget->resize(0, 0); @@ -59,8 +59,7 @@ void set_font(QWidget *p_widget, QString p_identifier, QString ini_file, AOAppli { QString class_name = p_widget->metaObject()->className(); - int f_weight = ao_app->get_font_property(p_identifier, ini_file); - + QFont l_font; // Font priority // 1. "font_" + p_identifier // 2. "font_default" @@ -71,18 +70,23 @@ void set_font(QWidget *p_widget, QString p_identifier, QString ini_file, AOAppli { font_name = ao_app->get_font_name("font_default", ini_file); } - p_widget->setFont(QFont(font_name, f_weight)); + if (!font_name.isEmpty()) + { + l_font.setFamily(font_name); + } - const QColor l_font_color = ao_app->get_color(p_identifier + "_color", ini_file); - int bold = ao_app->get_font_property(p_identifier + "_bold", ini_file); - QString is_bold = (bold == 1 ? "bold" : ""); + int f_weight = ao_app->get_font_property(p_identifier, ini_file); + l_font.setPointSize(f_weight); - QFont font = p_widget->font(); - font.setBold(bold); - p_widget->setFont(font); + bool is_bold = ao_app->get_font_property(p_identifier + "_bold", ini_file) == 1; + l_font.setBold(is_bold); + p_widget->setFont(l_font); + + const QColor l_font_color = ao_app->get_color(p_identifier + "_color", ini_file); QString style_sheet_string = class_name + " { " + "background-color: rgba(0, 0, 0, 0);\n" + - "color: " + l_font_color.name(QColor::HexArgb) + ";\n" + "font: " + is_bold + ";" + " }"; + "color: " + l_font_color.name(QColor::HexArgb) + ";\n" + (is_bold ? "font: bold;" : "") + + "}"; p_widget->setStyleSheet(style_sheet_string); } From 1fe939b59cad9baebdc8a9482c172aa21bfc7cce Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 15 May 2022 17:16:25 +0200 Subject: [PATCH 738/842] Changed DPI scaling to always be disabled --- src/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 624ecaf5a..3c5c9fa2f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,14 +10,14 @@ int main(int argc, char *argv[]) qInstallMessageHandler(logger::log); qInfo() << "Starting Danganronpa Online..."; - bool l_dpi_scaling = true; + bool l_dpi_scaling = false; for (int i = 0; i < argc; ++i) { const QString l_arg(argv[i]); - if (l_arg == "-noscaling") + if (l_arg == "-dpiscaling") { - l_dpi_scaling = false; + l_dpi_scaling = true; } } From 9099a8e95d0a0ed9cf3bfb5b569fb81cff31a3b6 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 16 May 2022 23:28:06 +0200 Subject: [PATCH 739/842] Fix no fallback for pre-anim + invis character --- src/courtroom.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index b218c739e..09d42714d 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -897,12 +897,11 @@ void Courtroom::handle_chatmessage_2() // handles IC if (!m_hide_character) { play_preanim(); + return; } } - else - { - handle_chatmessage_3(); - } + + handle_chatmessage_3(); } void Courtroom::handle_chatmessage_3() From f9a138bd2176ca29985f8aacef9db50364965284 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 31 May 2022 07:43:06 -0400 Subject: [PATCH 740/842] Implement outgoing FS packet --- src/courtroom_character.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index 51e4e3825..0991915e8 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -3,6 +3,7 @@ #include "aoapplication.h" #include "aoconfig.h" #include "commondefs.h" +#include "drpacket.h" #include "file_functions.h" #include "theme.h" @@ -124,7 +125,14 @@ void Courtroom::refresh_character_content_url() if (m_character_content_url == l_new_content_url) return; m_character_content_url = l_new_content_url; - send_ooc_packet("/files_set " + m_character_content_url); + if (ao_app->is_server_client_version_compatible()) + { + ao_app->send_server_packet(DRPacket("FS", {m_character_content_url})); + } + else + { + send_ooc_packet("/files_set " + m_character_content_url); + } } void Courtroom::on_iniswap_dropdown_changed(int p_index) From b4b4b1a9aad993130590d4f07627cc8c003bd4e7 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 5 Jun 2022 20:34:21 +0200 Subject: [PATCH 741/842] Added context menu for music list --- src/courtroom.cpp | 40 ++++++++++++++++++++++++++++++++++++--- src/courtroom.h | 12 ++++++++++++ src/courtroom_widgets.cpp | 14 +++++++++++++- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 09d42714d..2624765fe 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -1854,13 +1855,39 @@ void Courtroom::on_music_list_clicked() void Courtroom::on_music_list_double_clicked(QModelIndex p_model) { - if (is_client_muted) - return; const QString l_song_name = ui_music_list->item(p_model.row())->data(Qt::UserRole).toString(); - ao_app->send_server_packet(DRPacket("MC", {l_song_name, QString::number(m_chr_id)})); + send_mc_packet(l_song_name); + ui_ic_chat_message->setFocus(); +} + +void Courtroom::on_music_list_context_menu_requested(QPoint p_point) +{ + const QPoint l_global_pos = ui_music_list->viewport()->mapToGlobal(p_point); + ui_music_menu->popup(l_global_pos); +} + +void Courtroom::on_music_menu_play_triggered() +{ + QListWidgetItem *l_item = ui_music_list->currentItem(); + if (l_item) + { + const QString l_song = l_item->data(Qt::UserRole).toString(); + send_mc_packet(l_song); + } ui_ic_chat_message->setFocus(); } +void Courtroom::on_music_menu_insert_ooc_triggered() +{ + QListWidgetItem *l_item = ui_music_list->currentItem(); + if (l_item) + { + const QString l_song = l_item->data(Qt::UserRole).toString(); + ui_ooc_chat_message->insert(l_song); + } + ui_ooc_chat_message->setFocus(); +} + void Courtroom::on_music_search_edited(QString p_filter) { filter_list_widget(ui_music_list, p_filter); @@ -1871,6 +1898,13 @@ void Courtroom::on_music_search_edited() on_music_search_edited(ui_music_search->text()); } +void Courtroom::send_mc_packet(QString p_song) +{ + if (is_client_muted) + return; + ao_app->send_server_packet(DRPacket("MC", {p_song, QString::number(m_chr_id)})); +} + /** * @brief Set the sprites of the shout buttons, and mark the currently * selected shout as such. diff --git a/src/courtroom.h b/src/courtroom.h index 4cde32df1..42a31dac4 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -38,11 +38,13 @@ class DRVideoWidget; #include #include +class QAction; class QCheckBox; class QComboBox; class QLineEdit; class QListWidget; class QListWidgetItem; +class QMenu; class QPropertyAnimation; class QScrollArea; class QSignalMapper; @@ -387,6 +389,9 @@ class Courtroom : public QMainWindow QLineEdit *ui_area_search = nullptr; QListWidget *ui_music_list = nullptr; QLineEdit *ui_music_search = nullptr; + QMenu *ui_music_menu = nullptr; + QAction *ui_music_menu_play = nullptr; + QAction *ui_music_menu_insert_ooc = nullptr; QListWidget *ui_sfx_list = nullptr; QVector m_sfx_list; @@ -571,8 +576,12 @@ private slots: void on_music_list_clicked(); void on_music_list_double_clicked(QModelIndex p_model); + void on_music_list_context_menu_requested(QPoint p_point); + void on_music_menu_play_triggered(); + void on_music_menu_insert_ooc_triggered(); void on_music_search_edited(QString); void on_music_search_edited(); + void send_mc_packet(QString p_song); void select_emote(int p_id); @@ -659,6 +668,7 @@ private slots: // character // =========================================================================== + public: using CharacterId = int; enum : CharacterId @@ -677,6 +687,7 @@ public slots: CharacterId m_chr_id = SpectatorId; // sfx + public: std::optional current_sfx(); QString current_sfx_file(); @@ -717,6 +728,7 @@ public slots: bool is_audio_muted = false; // QWidget interface + protected: void changeEvent(QEvent *) override; void closeEvent(QCloseEvent *event) override; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 5434ac205..1eddd594a 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -27,11 +27,13 @@ #include "file_functions.h" #include "theme.h" +#include #include #include #include #include #include +#include #include #include #include @@ -132,9 +134,13 @@ void Courtroom::create_widgets() ui_area_search->setPlaceholderText("Area filter"); ui_music_list = new QListWidget(this); + ui_music_list->setContextMenuPolicy(Qt::CustomContextMenu); ui_music_search = new QLineEdit(this); ui_music_search->setFrame(false); ui_music_search->setPlaceholderText("Music filter"); + ui_music_menu = new QMenu(this); + ui_music_menu_play = ui_music_menu->addAction(tr("Play")); + ui_music_menu_insert_ooc = ui_music_menu->addAction(tr("Insert to OOC")); ui_sfx_list = new QListWidget(this); ui_sfx_search = new QLineEdit(this); @@ -284,8 +290,14 @@ void Courtroom::connect_widgets() connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_music_list_clicked())); - connect(ui_area_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_area_list_clicked())); connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_music_list_double_clicked(QModelIndex))); + connect(ui_music_list, SIGNAL(customContextMenuRequested(QPoint)), this, + SLOT(on_music_list_context_menu_requested(QPoint))); + + connect(ui_music_menu_play, SIGNAL(triggered()), this, SLOT(on_music_menu_play_triggered())); + connect(ui_music_menu_insert_ooc, SIGNAL(triggered()), this, SLOT(on_music_menu_insert_ooc_triggered())); + + connect(ui_area_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_area_list_clicked())); connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_area_list_double_clicked(QModelIndex))); // connect events for shout/effect/wtce buttons happen in load_shouts(), From 991df2f09bb3f5a27aa82bb83c1fdf51694436c0 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 6 Jun 2022 18:16:30 -0400 Subject: [PATCH 742/842] Basic implementation --- src/draudiotrackmetadata.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/draudiotrackmetadata.cpp b/src/draudiotrackmetadata.cpp index 686522503..d9bf87668 100644 --- a/src/draudiotrackmetadata.cpp +++ b/src/draudiotrackmetadata.cpp @@ -49,11 +49,21 @@ void DRAudiotrackMetadata::update_cache() utils::QSettingsKeyFetcher l_fetcher(l_settings); const QStringList l_audiotrack_name_list = l_settings.childGroups(); - for (const QString &i_track_name : l_audiotrack_name_list) + for (const QString &i_section_name : l_audiotrack_name_list) { + l_settings.beginGroup(i_section_name); + + QString i_track_name = l_settings.value(l_fetcher.lookup_value("file_name")).toString(); + if (i_track_name.isEmpty()) + { + qWarning() << "error: empty file name in section" << i_section_name; + l_settings.endGroup(); + continue; + } if (!QFileInfo::exists(ao_app->get_music_path(i_track_name))) { qWarning() << "error: audiotrack not found" << i_track_name; + l_settings.endGroup(); continue; } @@ -65,7 +75,6 @@ void DRAudiotrackMetadata::update_cache() } l_new_audiotrack_origin.insert(l_lower_track_name, l_ini_path); - l_settings.beginGroup(i_track_name); DRAudiotrackMetadata l_audiotrack; l_audiotrack.m_file_name = i_track_name; l_audiotrack.m_title = l_settings.value(l_fetcher.lookup_value("title")).toString(); From 716e42929354f1996815b6f9b775a1152ad9cf8e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 7 Jun 2022 14:08:07 +0200 Subject: [PATCH 743/842] Improved audio track parsing order of execution --- src/draudiotrackmetadata.cpp | 37 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/draudiotrackmetadata.cpp b/src/draudiotrackmetadata.cpp index d9bf87668..8c05a5046 100644 --- a/src/draudiotrackmetadata.cpp +++ b/src/draudiotrackmetadata.cpp @@ -48,41 +48,38 @@ void DRAudiotrackMetadata::update_cache() qDebug() << "reading audiotrack metadata" << l_ini_path; utils::QSettingsKeyFetcher l_fetcher(l_settings); - const QStringList l_audiotrack_name_list = l_settings.childGroups(); - for (const QString &i_section_name : l_audiotrack_name_list) + const QStringList l_group_list = l_settings.childGroups(); + for (const QString &i_group : l_group_list) { - l_settings.beginGroup(i_section_name); + l_settings.beginGroup(i_group); + DRAudiotrackMetadata l_audiotrack; + l_audiotrack.m_title = l_settings.value(l_fetcher.lookup_value("title")).toString(); + l_audiotrack.m_file_name = l_settings.value(l_fetcher.lookup_value("file_name")).toString(); + l_audiotrack.m_play_once = l_settings.value(l_fetcher.lookup_value("play_once")).toBool(); + l_audiotrack.m_loop_start = l_settings.value(l_fetcher.lookup_value("loop_start")).toULongLong(); + l_audiotrack.m_loop_end = l_settings.value(l_fetcher.lookup_value("loop_end")).toULongLong(); + l_settings.endGroup(); - QString i_track_name = l_settings.value(l_fetcher.lookup_value("file_name")).toString(); - if (i_track_name.isEmpty()) + const QString l_track_name = l_audiotrack.m_file_name; + if (l_track_name.isEmpty()) { - qWarning() << "error: empty file name in section" << i_section_name; - l_settings.endGroup(); + qWarning() << "error: empty file name in section" << i_group; continue; } - if (!QFileInfo::exists(ao_app->get_music_path(i_track_name))) + else if (!QFileInfo::exists(ao_app->get_music_path(l_track_name))) { - qWarning() << "error: audiotrack not found" << i_track_name; - l_settings.endGroup(); + qWarning() << "error: audiotrack not found" << l_track_name; continue; } - const QString l_lower_track_name = i_track_name.toLower(); + const QString l_lower_track_name = l_track_name.toLower(); if (l_new_audiotrack_origin.contains(l_lower_track_name)) { - qWarning() << "warning: replacing track" << i_track_name << "; previously defined in" + qWarning() << "warning: replacing track" << l_track_name << "; previously defined in" << l_new_audiotrack_origin[l_lower_track_name]; } l_new_audiotrack_origin.insert(l_lower_track_name, l_ini_path); - - DRAudiotrackMetadata l_audiotrack; - l_audiotrack.m_file_name = i_track_name; - l_audiotrack.m_title = l_settings.value(l_fetcher.lookup_value("title")).toString(); - l_audiotrack.m_play_once = l_settings.value(l_fetcher.lookup_value("play_once")).toBool(); - l_audiotrack.m_loop_start = l_settings.value(l_fetcher.lookup_value("loop_start")).toULongLong(); - l_audiotrack.m_loop_end = l_settings.value(l_fetcher.lookup_value("loop_end")).toULongLong(); l_new_audiotrack_cache.insert(l_lower_track_name, std::move(l_audiotrack)); - l_settings.endGroup(); } } s_audiotrack_cache = std::move(l_new_audiotrack_cache); From d8c1e11da3044031f079a707812d46adf7dd5211 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 7 Jun 2022 14:23:40 +0200 Subject: [PATCH 744/842] Renamed file_name to filename --- src/courtroom.cpp | 8 ++++---- src/draudiotrackmetadata.cpp | 12 ++++++------ src/draudiotrackmetadata.h | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2624765fe..dfb1998a7 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -462,9 +462,9 @@ void Courtroom::list_music() { DRAudiotrackMetadata l_track(i_song); QListWidgetItem *l_item = new QListWidgetItem(l_track.title(), ui_music_list); - l_item->setData(Qt::UserRole, l_track.file_name()); - if (l_track.title() != l_track.file_name()) - l_item->setToolTip(l_track.file_name()); + l_item->setData(Qt::UserRole, l_track.filename()); + if (l_track.title() != l_track.filename()) + l_item->setToolTip(l_track.filename()); const QString l_song_path = ao_app->find_asset_path({ao_app->get_music_path(i_song)}, audio_extensions()); l_item->setBackground(l_song_path.isEmpty() ? l_missing_song_brush : l_song_brush); } @@ -1603,7 +1603,7 @@ void Courtroom::handle_song(QStringList p_contents) if (ao_config->log_is_recording_enabled()) { - save_textlog(l_showname + " has played a song: " + l_song_meta.file_name()); + save_textlog(l_showname + " has played a song: " + l_song_meta.filename()); } } diff --git a/src/draudiotrackmetadata.cpp b/src/draudiotrackmetadata.cpp index 8c05a5046..e85bbe46e 100644 --- a/src/draudiotrackmetadata.cpp +++ b/src/draudiotrackmetadata.cpp @@ -54,13 +54,13 @@ void DRAudiotrackMetadata::update_cache() l_settings.beginGroup(i_group); DRAudiotrackMetadata l_audiotrack; l_audiotrack.m_title = l_settings.value(l_fetcher.lookup_value("title")).toString(); - l_audiotrack.m_file_name = l_settings.value(l_fetcher.lookup_value("file_name")).toString(); + l_audiotrack.m_filename = l_settings.value(l_fetcher.lookup_value("filename")).toString(); l_audiotrack.m_play_once = l_settings.value(l_fetcher.lookup_value("play_once")).toBool(); l_audiotrack.m_loop_start = l_settings.value(l_fetcher.lookup_value("loop_start")).toULongLong(); l_audiotrack.m_loop_end = l_settings.value(l_fetcher.lookup_value("loop_end")).toULongLong(); l_settings.endGroup(); - const QString l_track_name = l_audiotrack.m_file_name; + const QString l_track_name = l_audiotrack.m_filename; if (l_track_name.isEmpty()) { qWarning() << "error: empty file name in section" << i_group; @@ -88,7 +88,7 @@ void DRAudiotrackMetadata::update_cache() DRAudiotrackMetadata::DRAudiotrackMetadata() {} -DRAudiotrackMetadata::DRAudiotrackMetadata(QString p_file_name) : m_file_name(p_file_name) +DRAudiotrackMetadata::DRAudiotrackMetadata(QString p_file_name) : m_filename(p_file_name) { const QString l_lower_file_name = p_file_name.toLower(); if (s_audiotrack_cache.contains(l_lower_file_name)) @@ -100,14 +100,14 @@ DRAudiotrackMetadata::DRAudiotrackMetadata(QString p_file_name) : m_file_name(p_ DRAudiotrackMetadata::~DRAudiotrackMetadata() {} -QString DRAudiotrackMetadata::file_name() +QString DRAudiotrackMetadata::filename() { - return m_file_name; + return m_filename; } QString DRAudiotrackMetadata::title() { - return m_title.isEmpty() ? m_file_name : m_title; + return m_title.isEmpty() ? m_filename : m_title; } bool DRAudiotrackMetadata::play_once() diff --git a/src/draudiotrackmetadata.h b/src/draudiotrackmetadata.h index 188060928..02e5de280 100644 --- a/src/draudiotrackmetadata.h +++ b/src/draudiotrackmetadata.h @@ -11,14 +11,14 @@ class DRAudiotrackMetadata DRAudiotrackMetadata(QString file_name); ~DRAudiotrackMetadata(); - QString file_name(); + QString filename(); QString title(); bool play_once(); quint64 loop_start(); quint64 loop_end(); private: - QString m_file_name; + QString m_filename; QString m_title; bool m_play_once = false; quint64 m_loop_start = 0; From 01591dccec4be3eb357724efb0e7e32fc11036c8 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 7 Jun 2022 08:35:50 -0400 Subject: [PATCH 745/842] Log music title instead of filename in client log --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index dfb1998a7..462dd6e33 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1603,7 +1603,7 @@ void Courtroom::handle_song(QStringList p_contents) if (ao_config->log_is_recording_enabled()) { - save_textlog(l_showname + " has played a song: " + l_song_meta.filename()); + save_textlog(l_showname + " has played a song: " + l_song_meta.title()); } } From 4322c6c1b7387a328a2d8e91f8c9c61975f84c82 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 7 Jun 2022 09:01:03 -0400 Subject: [PATCH 746/842] Revert "Log music title instead of filename in client log" This reverts commit 01591dccec4be3eb357724efb0e7e32fc11036c8. --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 462dd6e33..dfb1998a7 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1603,7 +1603,7 @@ void Courtroom::handle_song(QStringList p_contents) if (ao_config->log_is_recording_enabled()) { - save_textlog(l_showname + " has played a song: " + l_song_meta.title()); + save_textlog(l_showname + " has played a song: " + l_song_meta.filename()); } } From 088085b4d2cc1e0ceceee009fd9cde3443a81b6d Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 7 Jun 2022 16:59:25 -0400 Subject: [PATCH 747/842] 1.1.0 --- src/version.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.cpp b/src/version.cpp index 1e2cf69a6..01c80fb67 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -30,7 +30,7 @@ VersionNumber get_version_number() QString get_post_version() { - return "b3"; + return ""; } QString get_version_string() From c34cbd4f7301b9304782c41d5c45625debbec96d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 9 Jun 2022 22:04:02 +0200 Subject: [PATCH 748/842] Bumped version, hide character now only hides dialog emotes --- src/courtroom.cpp | 8 ++------ src/version.cpp | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index dfb1998a7..4f79c5241 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -894,12 +894,8 @@ void Courtroom::handle_chatmessage_2() // handles IC { int sfx_delay = m_chatmessage[CMSoundDelay].toInt(); m_sound_timer->start(sfx_delay); - - if (!m_hide_character) - { - play_preanim(); - return; - } + play_preanim(); + return; } handle_chatmessage_3(); diff --git a/src/version.cpp b/src/version.cpp index 01c80fb67..b00df3a3b 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -20,7 +20,7 @@ int get_major_version() int get_minor_version() { - return 0; + return 1; } VersionNumber get_version_number() From 2cdd1e9485c975662f9a934d8b6ed13f4806dcb4 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 16 Jun 2022 04:44:39 +0200 Subject: [PATCH 749/842] Changed preferred plugin for media playback --- src/main.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3c5c9fa2f..5fb7766d2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,10 @@ -#include - #include "aoapplication.h" #include "drmediatester.h" #include "lobby.h" #include "logger.h" +#include + int main(int argc, char *argv[]) { qInstallMessageHandler(logger::log); @@ -35,10 +35,10 @@ int main(int argc, char *argv[]) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, false); } -#ifdef Q_OS_MACOS - { // MacOS - qputenv("QT_MAC_WANTS_LAYER", "1"); - } +#if defined(Q_OS_WINDOWS) + qputenv("QT_MULTIMEDIA_PREFERRED_PLUGINS", "windowsmediafoundation"); +#elif defined(Q_OS_MACOS) + qputenv("QT_MAC_WANTS_LAYER", "1"); #endif AOApplication app(argc, argv); From bf15cbac832a02629ff9fd0b730ef0952d90779d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 16 Jun 2022 04:52:52 +0200 Subject: [PATCH 750/842] Change environment order --- src/main.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 5fb7766d2..588899a19 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,12 @@ int main(int argc, char *argv[]) { +#if defined(Q_OS_WINDOWS) + qputenv("QT_MULTIMEDIA_PREFERRED_PLUGINS", "windowsmediafoundation"); +#elif defined(Q_OS_MACOS) + qputenv("QT_MAC_WANTS_LAYER", "1"); +#endif + qInstallMessageHandler(logger::log); qInfo() << "Starting Danganronpa Online..."; @@ -35,12 +41,6 @@ int main(int argc, char *argv[]) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, false); } -#if defined(Q_OS_WINDOWS) - qputenv("QT_MULTIMEDIA_PREFERRED_PLUGINS", "windowsmediafoundation"); -#elif defined(Q_OS_MACOS) - qputenv("QT_MAC_WANTS_LAYER", "1"); -#endif - AOApplication app(argc, argv); DRMediaTester tester; From e93a15cbe1a1fa9b681dc6378454a12b255147f5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 17 Jun 2022 10:18:56 +0200 Subject: [PATCH 751/842] Allow to cancel name prompt --- src/courtroom.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 4f79c5241..9f6553dc1 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1656,9 +1656,19 @@ void Courtroom::send_ooc_packet(QString ooc_message) { while (ao_config->username().isEmpty()) { - ao_config->set_username(QInputDialog::getText(this, "Enter a name", "You must have a username to talk in OOC chat.", - QLineEdit::Normal, nullptr)); + QInputDialog l_dialog; + l_dialog.setWindowTitle(tr("Enter a name")); + l_dialog.setLabelText(tr("You must have a username to talk in OOC chat.")); + if (l_dialog.exec()) + { + ao_config->set_username(l_dialog.textValue()); + } + else + { + return; + } } + if (ooc_message.trimmed().isEmpty()) { append_server_chatmessage("CLIENT", "You cannot send empty messages."); From ff4c976eb08933ed3a91b51857d985a5ae5f6586 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 18 Jun 2022 18:42:54 +0200 Subject: [PATCH 752/842] Improved media player audio device switching --- src/drvideoscreen.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index 279e2267c..f1f9d3b26 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -196,22 +196,17 @@ void DRVideoWidget::update_audio_output() return; } - if (l_control->outputDescription(l_control->activeOutput()) != l_new_device_name) + bool l_device_changed = false; + const QStringList l_device_id_list = l_control->availableOutputs(); + for (const QString &i_device_id : l_device_id_list) { - bool l_device_changed = false; - const QStringList l_device_id_list = l_control->availableOutputs(); - for (const QString &i_device_id : l_device_id_list) + if (l_control->outputDescription(i_device_id) == l_new_device_name) { - if (l_control->outputDescription(i_device_id) == l_new_device_name) - { - qDebug() << "media player changed audio device;" << l_new_device_name; - l_device_changed = true; - l_control->setActiveOutput(i_device_id); - break; - } + qDebug() << "Media player changed audio device;" << l_new_device_name; + l_device_changed = true; + l_control->setActiveOutput(i_device_id); + break; } - if (!l_device_changed) - qWarning() << "audio device not found;" << l_new_device_name; } l_service->releaseControl(l_control); } From 3af6db41d5be68d8a557f55c0ac5bb826d498eff Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 20 Jun 2022 00:22:01 +0200 Subject: [PATCH 753/842] Avoid needlessly calculating fitting image --- src/drmovie.cpp | 108 +++++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/src/drmovie.cpp b/src/drmovie.cpp index 5cac603df..f8f53b20a 100644 --- a/src/drmovie.cpp +++ b/src/drmovie.cpp @@ -142,62 +142,68 @@ void DRMovie::resizeEvent(QResizeEvent *event) void DRMovie::paint_frame() { - ScalingMode l_target_mode = m_scaling_mode; - QSize l_target_size = size(); - if (l_target_mode == DynamicScaling) + QPixmap l_image = m_current_pixmap; + { - const qreal l_width_factor = (qreal)qMax(m_current_pixmap.width(), 1) / qMax(width(), 1); - const qreal l_height_factor = (qreal)qMax(m_current_pixmap.height(), 1) / qMax(height(), 1); - - const QSize l_by_width_size{ - int((qreal)m_current_pixmap.width() / l_width_factor), - int((qreal)m_current_pixmap.height() / l_width_factor), - }; - const QSize l_by_height_size{ - int((qreal)m_current_pixmap.width() / l_height_factor), - int((qreal)m_current_pixmap.height() / l_height_factor), - }; - - if (l_by_width_size.height() >= height()) - { - l_target_mode = WidthScaling; - l_target_size = l_by_width_size; - } - else if (l_by_height_size.width() >= width()) - { - l_target_mode = HeightScaling; - l_target_size = l_by_height_size; - } - else + const QSize l_image_size = m_current_pixmap.size(); + const QSize l_widget_size = size(); + + if (l_image_size != l_widget_size) { - l_target_mode = StretchScaling; - } - } + ScalingMode l_target_scaling_mode = m_scaling_mode; + if (l_target_scaling_mode == DynamicScaling) + { + const qreal l_width_factor = (qreal)qMax(l_image_size.width(), 1) / qMax(l_widget_size.width(), 1); + const qreal l_height_factor = (qreal)qMax(l_image_size.height(), 1) / qMax(l_widget_size.height(), 1); - QPixmap l_scaled_image = m_current_pixmap; - Qt::TransformationMode l_transformation = Qt::SmoothTransformation; - if (m_current_pixmap.width() < width() || m_current_pixmap.height() < height()) - { - l_transformation = Qt::FastTransformation; - } - switch (l_target_mode) - { - case StretchScaling: - [[fallthrough]]; - default: - l_scaled_image = l_scaled_image.scaled(size(), Qt::IgnoreAspectRatio, l_transformation); - break; - - case WidthScaling: - l_scaled_image = l_scaled_image.scaledToWidth(width(), l_transformation); - break; - - case HeightScaling: - l_scaled_image = l_scaled_image.scaledToHeight(height(), l_transformation); - break; + const QSize l_by_width_size{ + int((qreal)l_image_size.width() / l_width_factor), + int((qreal)l_image_size.height() / l_width_factor), + }; + const QSize l_by_height_size{ + int((qreal)l_image_size.width() / l_height_factor), + int((qreal)l_image_size.height() / l_height_factor), + }; + + if (l_by_width_size.height() >= l_widget_size.height()) + { + l_target_scaling_mode = WidthScaling; + } + else if (l_by_height_size.width() >= l_widget_size.width()) + { + l_target_scaling_mode = HeightScaling; + } + else + { + l_target_scaling_mode = StretchScaling; + } + } + + Qt::TransformationMode l_transformation = Qt::SmoothTransformation; + if (l_image_size.width() < l_widget_size.width() || l_image_size.height() < l_widget_size.height()) + { + l_transformation = Qt::FastTransformation; + } + switch (l_target_scaling_mode) + { + case StretchScaling: + [[fallthrough]]; + default: + l_image = l_image.scaled(l_widget_size, Qt::IgnoreAspectRatio, l_transformation); + break; + + case WidthScaling: + l_image = l_image.scaledToWidth(l_widget_size.width(), l_transformation); + break; + + case HeightScaling: + l_image = l_image.scaledToHeight(l_widget_size.height(), l_transformation); + break; + } + } } - setPixmap(l_scaled_image); + setPixmap(l_image); } void DRMovie::update_frame(int p_frame_number) From ba62d12dd8483a772afa5cf54436204611ffdceb Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 20 Jun 2022 03:02:11 +0200 Subject: [PATCH 754/842] Removed unused variable --- src/drvideoscreen.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index f1f9d3b26..f89a314f6 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -1,16 +1,16 @@ #include "drvideoscreen.h" -#include -#include -#include -#include - #include "aoapplication.h" #include "aoconfig.h" #include "draudiodevice.h" #include "draudioengine.h" #include "draudiostreamfamily.h" +#include +#include +#include +#include + DRVideoWidget::DRVideoWidget(QWidget *parent) : QVideoWidget(parent), ao_app(dynamic_cast(qApp)) { Q_ASSERT(ao_app); @@ -196,14 +196,12 @@ void DRVideoWidget::update_audio_output() return; } - bool l_device_changed = false; const QStringList l_device_id_list = l_control->availableOutputs(); for (const QString &i_device_id : l_device_id_list) { if (l_control->outputDescription(i_device_id) == l_new_device_name) { qDebug() << "Media player changed audio device;" << l_new_device_name; - l_device_changed = true; l_control->setActiveOutput(i_device_id); break; } From 3e59088732917fe931dc35b0a45ab2162d04c0da Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 29 Jun 2022 01:08:03 +0200 Subject: [PATCH 755/842] Don't trim names that are just space. --- src/aoconfig.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 0cc93fabd..2e7ec2d58 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -101,8 +101,7 @@ private slots: }; AOConfigPrivate::AOConfigPrivate() - : QObject(nullptr), cfg(DRPather::get_application_path() + BASE_CONFIG_INI, QSettings::IniFormat), - audio_engine(new DRAudioEngine(this)) + : QObject(nullptr), cfg(DRPather::get_application_path() + BASE_CONFIG_INI, QSettings::IniFormat), audio_engine(new DRAudioEngine(this)) { Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, @@ -320,7 +319,8 @@ namespace static QSharedPointer d; } -AOConfig::AOConfig(QObject *p_parent) : QObject(p_parent) +AOConfig::AOConfig(QObject *p_parent) + : QObject(p_parent) { // init if not created yet if (d == nullptr) @@ -661,7 +661,7 @@ void AOConfig::set_username(QString p_value) void AOConfig::set_showname(QString p_value) { const QString l_simplified_value = p_value.simplified(); - if (d->showname == l_simplified_value) + if (d->showname == l_simplified_value && !l_simplified_value.isEmpty()) return; d->showname = l_simplified_value; d->invoke_signal("showname_changed", Q_ARG(QString, d->showname)); From 4863d7fc38499772948ee9f7bc9ea08350a41b22 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 2 Jul 2022 03:45:10 +0200 Subject: [PATCH 756/842] Fixed fav servers sorting, change lobby refresh * Favorite servers are now properly sorted while being parsed. * Favorite servers are now re-parsed if you press the refresh button in the lobby in addition to asking the master server list for updated servers. --- src/lobby.cpp | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index d5eb8ec99..f1246efbc 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -15,6 +15,7 @@ #include "theme.h" #include "version.h" +#include #include #include #include @@ -28,7 +29,8 @@ #include #include -Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() +Lobby::Lobby(AOApplication *p_ao_app) + : QMainWindow() { ao_app = p_ao_app; ao_config = new AOConfig(this); @@ -292,7 +294,15 @@ void Lobby::load_favorite_server_list() QSettings l_ini(l_file_path, QSettings::IniFormat); l_ini.setIniCodec("UTF-8"); l_server_list.clear(); - for (const QString &i_group : l_ini.childGroups()) + QStringList l_group_list = l_ini.childGroups(); + + { + QCollator l_sorter; + l_sorter.setNumericMode(true); + std::sort(l_group_list.begin(), l_group_list.end(), l_sorter); + } + + for (const QString &i_group : qAsConst(l_group_list)) { l_ini.beginGroup(i_group); DRServerInfo l_server; @@ -451,6 +461,7 @@ void Lobby::on_refresh_released() { ui_refresh->set_image("refresh.png"); m_master_client->request_server_list(); + load_favorite_server_list(); } void Lobby::on_add_to_fav_pressed() @@ -494,19 +505,19 @@ void Lobby::on_connect_released() QString l_reason; switch (l_status) { - case VersionStatus::NotCompatible: - l_reason = "The server did not report any client version."; - break; - case VersionStatus::ServerOutdated: - l_reason = QString("The server is outdated.
    (Server version: %1, expected version: %2)") - .arg(ao_app->get_server_client_version().to_string(), get_version_number().to_string()); - break; - case VersionStatus::ClientOutdated: - l_reason = QString("Your client is outdated.
    (Client version: %1, expected version: %2)") - .arg(get_version_number().to_string(), ao_app->get_server_client_version().to_string()); - break; - default: - break; + case VersionStatus::NotCompatible: + l_reason = "The server did not report any client version."; + break; + case VersionStatus::ServerOutdated: + l_reason = QString("The server is outdated.
    (Server version: %1, expected version: %2)") + .arg(ao_app->get_server_client_version().to_string(), get_version_number().to_string()); + break; + case VersionStatus::ClientOutdated: + l_reason = QString("Your client is outdated.
    (Client version: %1, expected version: %2)") + .arg(get_version_number().to_string(), ao_app->get_server_client_version().to_string()); + break; + default: + break; } call_warning("You are connecting to an incompatible DRO server.

    Reason: " + l_reason + From 763e4d4e0b9f807f05249d455f4da59cdfe3005b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 9 Jul 2022 05:39:27 +0200 Subject: [PATCH 757/842] Updated build system + added qapng --- .github/ubuntu-515.yml | 136 ------ .github/workflows/build-all-merge-master.yml | 451 ------------------- .github/workflows/build-all.yml | 418 ----------------- .github/workflows/build.yml | 197 ++++++++ data/README-LINUX.md | 14 + data/dro-client.sh | 1 + 6 files changed, 212 insertions(+), 1005 deletions(-) delete mode 100644 .github/ubuntu-515.yml delete mode 100644 .github/workflows/build-all-merge-master.yml delete mode 100644 .github/workflows/build-all.yml create mode 100644 .github/workflows/build.yml create mode 100644 data/README-LINUX.md create mode 100644 data/dro-client.sh diff --git a/.github/ubuntu-515.yml b/.github/ubuntu-515.yml deleted file mode 100644 index 21029c702..000000000 --- a/.github/ubuntu-515.yml +++ /dev/null @@ -1,136 +0,0 @@ - - - ############################################################################### - # UBUNTU # - ############################################################################### - - # ubuntu: - # runs-on: ubuntu-20.04 - - # steps: - # - uses: actions/checkout@v2 - - # - name: Update worker - # shell: bash - # working-directory: ${{github.workspace}} - # run: | - # sudo apt-get update - # sudo apt-get upgrade - - # - name: Setup workspace - # shell: bash - # working-directory: ${{github.workspace}} - # run: | - # echo "parentworkspace=/home/runner/work/DRO-Client/" >> $GITHUB_ENV - # mkdir "3rd" - # mkdir "3rd/include" - # mkdir "3rd/x86_64" - - # - name: Fetch Discord external library - # shell: bash - # working-directory: ${{env.parentworkspace}} - # run: | - # curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip - # unzip discord_rpc_linux.zip - # cp ./discord-rpc/linux-dynamic/include/discord_register.h ./DRO-Client/3rd/include/discord_register.h - # cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./DRO-Client/3rd/include/discord_rpc.h - # cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./DRO-Client/3rd/x86_64/libdiscord-rpc.so - - # - name: Fetch Bass external library - # shell: bash - # working-directory: ${{env.parentworkspace}} - # run: | - # curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip - # unzip bass.zip - # cp ./bass.h ./DRO-Client/3rd/include/bass.h - # cp ./x64/libbass.so ./DRO-Client/3rd/x86_64/libbass.so - - # - name: Fetch BassOpus external library - # shell: bash - # working-directory: ${{env.parentworkspace}} - # run: | - # curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip - # unzip bassopus.zip - # cp ./bassopus.h ./DRO-Client/3rd/include/bassopus.h - # cp ./x64/libbassopus.so ./DRO-Client/3rd/x86_64/libbassopus.so - - # - name: Update Python Pip - # shell: bash - # working-directory: ${{env.parentworkspace}} - # run: | - # pip install --upgrade pip - - # - name: Install AQt - # shell: bash - # working-directory: ${{env.parentworkspace}} - # run: | - # pip install aqtinstall - - # - name: Install Qt - # shell: bash - # working-directory: ${{env.parentworkspace}} - # run: | - # aqt install-qt linux desktop 5.15.2 - # sudo apt install libgl1-mesa-dev - - # - name: Build QtApng external library - # shell: bash - # working-directory: ${{env.parentworkspace}} - # run: | - # git clone https://github.com/Skycoder42/QtApng - # cd QtApng - # ../5.15.2/gcc_64/bin/qmake - # make -j2 - # cp plugins/imageformats/libqapng.so .. - # cd .. - # rm -r -f QtApng - - # - name: Run qmake - # shell: bash - # working-directory: ${{github.workspace}} - # run: | - # ../5.15.2/gcc_64/bin/qmake - - # - name: Run Make - # shell: bash - # working-directory: ${{github.workspace}} - # run: | - # make - - # - name: Set up deploy folder - # shell: bash - # working-directory: ${{env.parentworkspace}} - # run: | - # mkdir "Danganronpa Online" - # cp -R ./DRO-Client/dro-client "./Danganronpa Online/dro-client" - - # - name: Deploy - # working-directory: "${{env.parentworkspace}}/Danganronpa Online" - # shell: bash - # run: | - # mkdir depends - # cp ../discord-rpc/linux-dynamic/lib/libdiscord-rpc.so "./depends" - # cp ../x64/libbass.so "./depends" - # cp ../x64/libbassopus.so "./depends" - # mkdir imageformats - # cp -R ../libqapng.so "./imageformats" - # echo "LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client" > "dro-client.sh" - # echo "Installation instructions - - # 1. On your terminal, run these commands, one after the other. - # pip install --upgrade pip - # pip install aqtinstall - # aqt install-qt linux desktop 5.15.2 - - # 2. Change directory to this folder, and run - # chmod +x dro-client.sh - # chmod +x dro-client - - # 3. To launch, run - # ./dro-client.sh" > "Readme (Ubuntu).txt" - - # - name: Upload Artifact - # uses: actions/upload-artifact@v2 - # with: - # name: "Danganronpa Online (Ubuntu)" - # path: "${{env.parentworkspace}}/**/Danganronpa Online" diff --git a/.github/workflows/build-all-merge-master.yml b/.github/workflows/build-all-merge-master.yml deleted file mode 100644 index 520cfdec6..000000000 --- a/.github/workflows/build-all-merge-master.yml +++ /dev/null @@ -1,451 +0,0 @@ -# Based from code from skyedeving and oldmud0 -# at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml -name: build-all-merge-master - -on: - push: - branches-ignore: - - 'master' - -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release - -jobs: - ############################################################################### - # WINDOWS # - ############################################################################### - - windows: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v2 - - run: | - git fetch --no-tags --depth=1 origin master - git checkout -b master - git checkout ${{ github.event.pull_request.head.sha }} - - - name: Merge master - shell: bash - working-directory: ${{github.workspace}} - run: | - git merge master - - - name: Setup workspace - shell: bash - working-directory: ${{github.workspace}} - run: | - echo "parentworkspace=D:\\a\\DRO-Client\\" >> $GITHUB_ENV - mkdir "3rd" - mkdir "3rd/bass" - mkdir "3rd/discord-rpc" - - - name: Fetch OpenSSL (user) - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://cdn.discordapp.com/attachments/457180869229019159/962750500632150168/openssl-1.1.1n.zip -o openssl.zip - unzip openssl.zip - - - name: Fetch Discord external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o discord_rpc_win.zip - unzip discord_rpc_win.zip - cp ./discord-rpc/win32-dynamic/include/discord_register.h ./DRO-Client/3rd/discord-rpc - cp ./discord-rpc/win32-dynamic/include/discord_rpc.h ./DRO-Client/3rd/discord-rpc - cp ./discord-rpc/win32-dynamic/bin/discord-rpc.dll ./DRO-Client/3rd - cp ./discord-rpc/win32-dynamic/lib/discord-rpc.lib ./DRO-Client/3rd - - - name: Fetch Bass external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bass24.zip -o bass.zip - unzip bass.zip - cp ./c/bass.h ./DRO-Client/3rd/bass - cp ./bass.dll ./DRO-Client/3rd - cp ./c/bass.lib ./DRO-Client/3rd - - - name: Fetch BassOpus external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bassopus24.zip -o bassopus.zip - unzip bassopus.zip - cp ./c/bassopus.h ./DRO-Client/3rd/bass - cp ./bassopus.dll ./DRO-Client/3rd - cp ./c/bassopus.lib ./DRO-Client/3rd - - - name: Fetch QtApng external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-mingw73_32-5.14.1.zip -o apng.zip - unzip apng.zip - - - name: Update Python Pip - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - python -m pip install --upgrade pip - - - name: Install Qt - uses: jurplel/install-qt-action@v2 - with: - version: '5.15.2' - arch: 'win32_mingw81' - - - name: Install AQt - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - pip install aqtinstall - - - name: Install MinGW - shell: bash - working-directory: ${{env.parentworkspace}} - run: aqt tool -O ./Qt windows tools_mingw 8.1.0-1-202004170606 qt.tools.win32_mingw810 - - - name: Setup MinGW - shell: bash - working-directory: ${{env.parentworkspace}} - # For whatever reason, qmake insists on using the MinGW64 installation that came with the host machine rather than the newly installed MinGW32 - # I could not find any way of making it use that MinGW32 folder, it always insisted on using MinGW64. - # Therefore, I just brought the MinGW32 folder to the MinGW64 folder. - # Tom Scott would be proud of this bodge - run: | - export PATH=/d/a/DRO-Client/Qt/Tools/mingw810_32/bin:$PATH - rm -r /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ - cp -r /d/a/DRO-Client/Qt/Tools/mingw810_32/ /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ - - - name: Setup branch and commit hash tags - shell: bash - working-directory: ${{github.workspace}} - run: | - git_hash=$(git rev-parse --short "$GITHUB_SHA") - git_branch=${GITHUB_REF##*/} - echo "$git_hash" >> ./res/git/git_hash.txt - echo "$git_branch" >> ./res/git/git_branch.txt - - - name: Run qmake - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - ./Qt/5.15.2/mingw81_32/bin/qmake.exe -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec win32-g++ "CONFIG+=qtquickcompiler" - - - name: Run Make - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - ./Qt/Tools/mingw810_32/bin/mingw32-make.exe -f ./Makefile qmake_all - ./Qt/Tools/mingw810_32/bin/mingw32-make.exe -j8 - - - name: Set up deploy folder - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - mkdir "Danganronpa Online" - cp ./release/dro-client.exe "./Danganronpa Online/dro-client-and-master.exe" - - - name: Deploy - working-directory: "${{env.parentworkspace}}/Danganronpa Online" - shell: bash - run: | - windeployqt.exe --quick --compiler-runtime . - cp ../libssl-1_1.dll . - cp ../libcrypto-1_1.dll . - cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . - cp ../bass.dll . - cp ../bassopus.dll . - cp ../mingw73_32/plugins/imageformats/qapng.dll ./imageformats/ - cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libgcc_s_dw2-1.dll . - cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libwinpthread-1.dll . - cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libstdc++-6.dll . - - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - name: "Danganronpa Online (Windows) (+master)" - path: "${{env.parentworkspace}}/**/Danganronpa Online" - - - name: Upload Artifact (just .exe) - uses: actions/upload-artifact@v2 - with: - name: "dro-client.exe" - path: "${{env.parentworkspace}}/Danganronpa Online/dro-client-and-master.exe" - - ############################################################################### - # MACOS # - ############################################################################### - - macos: - runs-on: macos-10.15 - - steps: - - uses: actions/checkout@v2 - - run: | - git fetch --no-tags --depth=1 origin master - git checkout -b master - git checkout ${{ github.event.pull_request.head.sha }} - - - name: Merge master - shell: bash - working-directory: ${{github.workspace}} - run: | - git merge master - - - name: Setup workspace - shell: bash - working-directory: ${{github.workspace}} - run: | - echo "parentworkspace=/Users/runner/work/DRO-Client/" >> $GITHUB_ENV - mkdir "3rd" - mkdir "3rd/bass" - mkdir "3rd/discord-rpc" - - - name: Fetch Discord external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip -o discord_rpc_osx.zip - unzip discord_rpc_osx.zip - cp ./discord-rpc/osx-dynamic/include/discord_register.h ./DRO-Client/3rd/discord-rpc - cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./DRO-Client/3rd/discord-rpc - cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./DRO-Client/3rd - - - name: Fetch Bass external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bass24-osx.zip -o bass.zip - unzip bass.zip - cp ./bass.h ./DRO-Client/3rd/bass - cp ./libbass.dylib ./DRO-Client/3rd - - - name: Fetch BassOpus external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bassopus24-osx.zip -o bassopus.zip - unzip bassopus.zip - cp ./bassopus.h ./DRO-Client/3rd/bass - cp ./libbassopus.dylib ./DRO-Client/3rd - - - name: Fetch QtApng external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-clang_64-5.14.1.tar.xz -o qtapng.tar.xz - tar -xvf qtapng.tar.xz - - - name: Update Python Pip - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - python3 -m pip install --upgrade pip - - - name: Install Qt - uses: jurplel/install-qt-action@v2 - with: - version: '5.15.2' - - - name: Setup branch and commit hash tags - shell: bash - working-directory: ${{github.workspace}} - run: | - git_hash=$(git rev-parse --short "$GITHUB_SHA") - git_branch=${GITHUB_REF##*/} - echo "$git_hash" >> ./res/git/git_hash.txt - echo "$git_branch" >> ./res/git/git_branch.txt - - - name: Run qmake - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - ./Qt/5.15.2/clang_64/bin/qmake -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec macx-clang "CONFIG+=x86_64" "CONFIG+=qtquickcompiler" - - - name: Run make - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - /usr/bin/make -f ./Makefile qmake_all - /usr/bin/make -j12 - - - name: Set up deploy folder - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - mkdir "Danganronpa Online" - cp -R ./dro-client.app "./Danganronpa Online" - mv "./Danganronpa Online/dro-client.app" "./Danganronpa Online/Danganronpa Online.app" - - - name: Deploy - working-directory: "${{env.parentworkspace}}/Danganronpa Online" - shell: bash - run: | - ../Qt/5.15.2/clang_64/bin/macdeployqt "Danganronpa Online.app" - cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/Frameworks/" - cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/MacOS/" - cp ../libbass.dylib "./Danganronpa Online.app/Contents/Frameworks/" - cp ../libbass.dylib "./Danganronpa Online.app/Contents/MacOS/" - cp ../libbassopus.dylib "./Danganronpa Online.app/Contents/Frameworks/" - cp ../libbassopus.dylib "./Danganronpa Online.app/Contents/MacOS/" - cp -R ../clang_64/plugins/imageformats "./Danganronpa Online.app/Contents/MacOS/" - - - name: Make DMG - working-directory: ${{env.parentworkspace}} - shell: bash - run: | - hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online" -ov -format UDZO "Danganronpa Online.dmg" - - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - name: "Danganronpa Online (MacOS) (+master)" - path: "${{env.parentworkspace}}/Danganronpa Online.dmg" - - ############################################################################### - # UBUNTU # - ############################################################################### - - ubuntu: - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v2 - - run: | - git fetch --no-tags --depth=1 origin master - git checkout -b master - git checkout ${{ github.event.pull_request.head.sha }} - - - name: Update worker - shell: bash - working-directory: ${{github.workspace}} - run: | - sudo apt-get update - sudo apt-get upgrade - - - name: Merge master - shell: bash - working-directory: ${{github.workspace}} - run: | - git merge master - - - name: Setup workspace - shell: bash - working-directory: ${{github.workspace}} - run: | - echo "parentworkspace=/home/runner/work/DRO-Client/" >> $GITHUB_ENV - mkdir "3rd" - mkdir "3rd/bass" - mkdir "3rd/discord-rpc" - - - name: Fetch Discord external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip - unzip discord_rpc_linux.zip - cp ./discord-rpc/linux-dynamic/include/discord_register.h ./DRO-Client/3rd/discord-rpc/discord_register.h - cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./DRO-Client/3rd/discord-rpc/discord_rpc.h - cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./DRO-Client/3rd/libdiscord-rpc.so - - - name: Fetch Bass external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip - unzip bass.zip - cp ./bass.h ./DRO-Client/3rd/bass/bass.h - cp ./x64/libbass.so ./DRO-Client/3rd/libbass.so - - - name: Fetch BassOpus external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip - unzip bassopus.zip - cp ./bassopus.h ./DRO-Client/3rd/bass/bassopus.h - cp ./x64/libbassopus.so ./DRO-Client/3rd/libbassopus.so - - - name: Install Qt - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - sudo apt-get install build-essential - sudo apt-get install qt5-default qttools5-dev - sudo apt-get install libqt5designer5 - sudo apt-get install libqt5multimedia5 - sudo apt-get install libqt5multimedia5-plugins - sudo apt-get install libqt5multimediawidgets5 - sudo apt-get install qtmultimedia5-dev - sudo apt-get install git - - - name: Build QtApng external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - git clone https://github.com/Skycoder42/QtApng - cd QtApng - qmake - make -j2 - cp plugins/imageformats/libqapng.so .. - cd .. - rm -r -f QtApng - - - name: Setup branch and commit hash tags - shell: bash - working-directory: ${{github.workspace}} - run: | - git_hash=$(git rev-parse --short "$GITHUB_SHA") - git_branch=${GITHUB_REF##*/} - echo "$git_hash" >> ./res/git/git_hash.txt - echo "$git_branch" >> ./res/git/git_branch.txt - - - name: Run qmake - shell: bash - working-directory: ${{github.workspace}} - run: | - qmake - - - name: Run Make - shell: bash - working-directory: ${{github.workspace}} - run: | - make - - - name: Set up deploy folder - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - mkdir "Danganronpa Online" - cp -R ./DRO-Client/dro-client "./Danganronpa Online/dro-client" - - - name: Deploy - working-directory: "${{env.parentworkspace}}/Danganronpa Online" - shell: bash - run: | - mkdir depends - cp ../discord-rpc/linux-dynamic/lib/libdiscord-rpc.so "./depends" - cp ../x64/libbass.so "./depends" - cp ../x64/libbassopus.so "./depends" - mkdir imageformats - cp -R ../libqapng.so "./imageformats" - echo "LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client" > "dro-client.sh" - echo "Installation instructions - 1. On your terminal, run - sudo apt-get install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools - 2. Change directory to this folder, and run - chmod +x dro-client.sh - chmod +x dro-client - 3. To launch, run - ./dro-client.sh" > "Readme (Ubuntu).txt" - - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - name: "Danganronpa Online (Ubuntu) (+master)" - path: "${{env.parentworkspace}}/**/Danganronpa Online" diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml deleted file mode 100644 index a6357379a..000000000 --- a/.github/workflows/build-all.yml +++ /dev/null @@ -1,418 +0,0 @@ -# Based from code from skyedeving and oldmud0 -# at https://github.com/AttorneyOnline/AO2-Client/blob/master/.github/workflows/build.yml -name: build-all - -on: push - -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release - -jobs: - ############################################################################### - # WINDOWS # - ############################################################################### - - windows: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v2 - - - name: Setup workspace - shell: bash - working-directory: ${{github.workspace}} - run: | - echo "parentworkspace=D:\\a\\DRO-Client\\" >> $GITHUB_ENV - mkdir "3rd" - mkdir "3rd/bass" - mkdir "3rd/discord-rpc" - - - name: Fetch OpenSSL (user) - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://cdn.discordapp.com/attachments/457180869229019159/962750500632150168/openssl-1.1.1n.zip -o openssl.zip - unzip openssl.zip - - - name: Fetch Discord external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o discord_rpc_win.zip - unzip discord_rpc_win.zip - cp ./discord-rpc/win32-dynamic/include/discord_register.h ./DRO-Client/3rd/discord-rpc - cp ./discord-rpc/win32-dynamic/include/discord_rpc.h ./DRO-Client/3rd/discord-rpc - cp ./discord-rpc/win32-dynamic/bin/discord-rpc.dll ./DRO-Client/3rd - cp ./discord-rpc/win32-dynamic/lib/discord-rpc.lib ./DRO-Client/3rd - - - name: Fetch Bass external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bass24.zip -o bass.zip - unzip bass.zip - cp ./c/bass.h ./DRO-Client/3rd/bass - cp ./bass.dll ./DRO-Client/3rd - cp ./c/bass.lib ./DRO-Client/3rd - - - name: Fetch BassOpus external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bassopus24.zip -o bassopus.zip - unzip bassopus.zip - cp ./c/bassopus.h ./DRO-Client/3rd/bass - cp ./bassopus.dll ./DRO-Client/3rd - cp ./c/bassopus.lib ./DRO-Client/3rd - - - name: Fetch QtApng external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-mingw73_32-5.14.1.zip -o apng.zip - unzip apng.zip - - - name: Update Python Pip - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - python -m pip install --upgrade pip - - - name: Install Qt - uses: jurplel/install-qt-action@v2 - with: - version: '5.15.2' - arch: 'win32_mingw81' - - - name: Install AQt - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - pip install aqtinstall - - - name: Install MinGW - shell: bash - working-directory: ${{env.parentworkspace}} - run: aqt tool -O ./Qt windows tools_mingw 8.1.0-1-202004170606 qt.tools.win32_mingw810 - - - name: Setup MinGW - shell: bash - working-directory: ${{env.parentworkspace}} - # For whatever reason, qmake insists on using the MinGW64 installation that came with the host machine rather than the newly installed MinGW32 - # I could not find any way of making it use that MinGW32 folder, it always insisted on using MinGW64. - # Therefore, I just brought the MinGW32 folder to the MinGW64 folder. - # Tom Scott would be proud of this bodge - run: | - export PATH=/d/a/DRO-Client/Qt/Tools/mingw810_32/bin:$PATH - rm -r /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ - cp -r /d/a/DRO-Client/Qt/Tools/mingw810_32/ /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/ - - - name: Setup branch and commit hash tags - shell: bash - working-directory: ${{github.workspace}} - run: | - git_hash=$(git rev-parse --short "$GITHUB_SHA") - git_branch=${GITHUB_REF##*/} - echo "$git_hash" >> ./res/git/git_hash.txt - echo "$git_branch" >> ./res/git/git_branch.txt - - - name: Run qmake - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - ./Qt/5.15.2/mingw81_32/bin/qmake.exe -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec win32-g++ "CONFIG+=qtquickcompiler" - - - name: Run Make - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - ./Qt/Tools/mingw810_32/bin/mingw32-make.exe -f ./Makefile qmake_all - ./Qt/Tools/mingw810_32/bin/mingw32-make.exe -j8 - - - name: Set up deploy folder - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - mkdir "Danganronpa Online" - cp ./release/dro-client.exe "./Danganronpa Online/dro-client.exe" - - - name: Deploy - working-directory: "${{env.parentworkspace}}/Danganronpa Online" - shell: bash - run: | - windeployqt.exe --quick --compiler-runtime . - cp ../libssl-1_1.dll . - cp ../libcrypto-1_1.dll . - cp ../discord-rpc/win32-dynamic/bin/discord-rpc.dll . - cp ../bass.dll . - cp ../bassopus.dll . - cp ../mingw73_32/plugins/imageformats/qapng.dll ./imageformats/ - cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libgcc_s_dw2-1.dll . - cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libwinpthread-1.dll . - cp /c/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libstdc++-6.dll . - - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - name: "Danganronpa Online (Windows)" - path: "${{env.parentworkspace}}/**/Danganronpa Online" - - - name: Upload Artifact (just .exe) - uses: actions/upload-artifact@v2 - with: - name: "dro-client.exe" - path: "${{env.parentworkspace}}/Danganronpa Online/dro-client.exe" - - ############################################################################### - # MACOS # - ############################################################################### - - macos: - runs-on: macos-10.15 - - steps: - - uses: actions/checkout@v2 - - - name: Setup workspace - shell: bash - working-directory: ${{github.workspace}} - run: | - echo "parentworkspace=/Users/runner/work/DRO-Client/" >> $GITHUB_ENV - mkdir "3rd" - mkdir "3rd/bass" - mkdir "3rd/discord-rpc" - - - name: Fetch Discord external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip -o discord_rpc_osx.zip - unzip discord_rpc_osx.zip - cp ./discord-rpc/osx-dynamic/include/discord_register.h ./DRO-Client/3rd/discord-rpc - cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./DRO-Client/3rd/discord-rpc - cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./DRO-Client/3rd - - - name: Fetch Bass external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bass24-osx.zip -o bass.zip - unzip bass.zip - cp ./bass.h ./DRO-Client/3rd/bass - cp ./libbass.dylib ./DRO-Client/3rd - - - name: Fetch BassOpus external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bassopus24-osx.zip -o bassopus.zip - unzip bassopus.zip - cp ./bassopus.h ./DRO-Client/3rd/bass - cp ./libbassopus.dylib ./DRO-Client/3rd - - - name: Fetch QtApng external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-clang_64-5.14.1.tar.xz -o qtapng.tar.xz - tar -xvf qtapng.tar.xz - - - name: Update Python Pip - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - python3 -m pip install --upgrade pip - - - name: Install Qt - uses: jurplel/install-qt-action@v2 - with: - version: '5.15.2' - - - name: Setup branch and commit hash tags - shell: bash - working-directory: ${{github.workspace}} - run: | - git_hash=$(git rev-parse --short "$GITHUB_SHA") - git_branch=${GITHUB_REF##*/} - echo "$git_hash" >> ./res/git/git_hash.txt - echo "$git_branch" >> ./res/git/git_branch.txt - - - name: Run qmake - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - ./Qt/5.15.2/clang_64/bin/qmake -makefile -o Makefile ./DRO-Client/dronline-client.pro -spec macx-clang "CONFIG+=x86_64" "CONFIG+=qtquickcompiler" - - - name: Run make - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - /usr/bin/make -f ./Makefile qmake_all - /usr/bin/make -j12 - - - name: Set up deploy folder - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - mkdir "Danganronpa Online" - cp -R ./dro-client.app "./Danganronpa Online" - mv "./Danganronpa Online/dro-client.app" "./Danganronpa Online/Danganronpa Online.app" - - - name: Deploy - working-directory: "${{env.parentworkspace}}/Danganronpa Online" - shell: bash - run: | - ../Qt/5.15.2/clang_64/bin/macdeployqt "Danganronpa Online.app" - cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/Frameworks/" - cp ../discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib "./Danganronpa Online.app/Contents/MacOS/" - cp ../libbass.dylib "./Danganronpa Online.app/Contents/Frameworks/" - cp ../libbass.dylib "./Danganronpa Online.app/Contents/MacOS/" - cp ../libbassopus.dylib "./Danganronpa Online.app/Contents/Frameworks/" - cp ../libbassopus.dylib "./Danganronpa Online.app/Contents/MacOS/" - cp -R ../clang_64/plugins/imageformats "./Danganronpa Online.app/Contents/MacOS/" - - - name: Make DMG - working-directory: ${{env.parentworkspace}} - shell: bash - run: | - hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online" -ov -format UDZO "Danganronpa Online.dmg" - - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - name: "Danganronpa Online (MacOS)" - path: "${{env.parentworkspace}}/Danganronpa Online.dmg" - - ############################################################################### - # UBUNTU # - ############################################################################### - - ubuntu: - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v2 - - - name: Update worker - shell: bash - working-directory: ${{github.workspace}} - run: | - sudo apt-get update - sudo apt-get upgrade - - - name: Setup workspace - shell: bash - working-directory: ${{github.workspace}} - run: | - echo "parentworkspace=/home/runner/work/DRO-Client/" >> $GITHUB_ENV - mkdir "3rd" - mkdir "3rd/bass" - mkdir "3rd/discord-rpc" - - - name: Fetch Discord external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip - unzip discord_rpc_linux.zip - cp ./discord-rpc/linux-dynamic/include/discord_register.h ./DRO-Client/3rd/discord-rpc/discord_register.h - cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./DRO-Client/3rd/discord-rpc/discord_rpc.h - cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./DRO-Client/3rd/libdiscord-rpc.so - - - name: Fetch Bass external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip - unzip bass.zip - cp ./bass.h ./DRO-Client/3rd/bass/bass.h - cp ./x64/libbass.so ./DRO-Client/3rd/libbass.so - - - name: Fetch BassOpus external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip - unzip bassopus.zip - cp ./bassopus.h ./DRO-Client/3rd/bass/bassopus.h - cp ./x64/libbassopus.so ./DRO-Client/3rd/libbassopus.so - - - name: Install Qt - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - sudo apt-get install build-essential - sudo apt-get install qt5-default qttools5-dev - sudo apt-get install libqt5designer5 - sudo apt-get install libqt5multimedia5 - sudo apt-get install libqt5multimedia5-plugins - sudo apt-get install libqt5multimediawidgets5 - sudo apt-get install qtmultimedia5-dev - sudo apt-get install git - - - name: Build QtApng external library - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - git clone https://github.com/Skycoder42/QtApng - cd QtApng - qmake - make -j2 - cp plugins/imageformats/libqapng.so .. - cd .. - rm -r -f QtApng - - - name: Setup branch and commit hash tags - shell: bash - working-directory: ${{github.workspace}} - run: | - git_hash=$(git rev-parse --short "$GITHUB_SHA") - git_branch=${GITHUB_REF##*/} - echo "$git_hash" >> ./res/git/git_hash.txt - echo "$git_branch" >> ./res/git/git_branch.txt - - - name: Run qmake - shell: bash - working-directory: ${{github.workspace}} - run: | - qmake - - - name: Run Make - shell: bash - working-directory: ${{github.workspace}} - run: | - make - - - name: Set up deploy folder - shell: bash - working-directory: ${{env.parentworkspace}} - run: | - mkdir "Danganronpa Online" - cp -R ./DRO-Client/dro-client "./Danganronpa Online/dro-client" - - - name: Deploy - working-directory: "${{env.parentworkspace}}/Danganronpa Online" - shell: bash - run: | - mkdir depends - cp ../discord-rpc/linux-dynamic/lib/libdiscord-rpc.so "./depends" - cp ../x64/libbass.so "./depends" - cp ../x64/libbassopus.so "./depends" - mkdir imageformats - cp -R ../libqapng.so "./imageformats" - echo "LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client" > "dro-client.sh" - echo "Installation instructions - 1. On your terminal, run - sudo apt-get install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools - 2. Change directory to this folder, and run - chmod +x dro-client.sh - chmod +x dro-client - 3. To launch, run - ./dro-client.sh" > "Readme (Ubuntu).txt" - - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - name: "Danganronpa Online (Ubuntu)" - path: "${{env.parentworkspace}}/**/Danganronpa Online" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..eab70dfdb --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,197 @@ +name: Build + +on: [workflow_dispatch, push] + +jobs: + Build: + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + version: [5.15.2] + include: + - os: windows-latest + os-caption: Windows + arch: win64_mingw81 + compiler: mingw81_64 + - os: ubuntu-latest + os-caption: Ubuntu + - os: macos-latest + os-caption: MacOS + + runs-on: ${{matrix.os}} + steps: + - name: Checkout + uses: actions/checkout@v2.4.2 + + - name: Fetch Git Info + shell: bash + working-directory: ${{github.workspace}} + run: | + git_hash=$(git rev-parse --short "$GITHUB_SHA") + git_branch=${GITHUB_REF##*/} + echo "$git_hash" >> ./res/git/git_hash.txt + echo "$git_branch" >> ./res/git/git_branch.txt + + - name: Setup externals + run: | + mkdir ./3rd + mkdir ./3rd/bass + mkdir ./3rd/discord-rpc + mkdir ./3rd/openssl + + - name: Install OpenSSL (Windows) + if: contains(matrix.os, 'windows') + run: | + curl -L https://mirror.firedaemon.com/OpenSSL/openssl-1.1.1q.zip -o openssl.zip + unzip -d ./openssl -o ./openssl.zip + cp ./openssl/openssl-1.1/x64/bin/*.dll ./3rd + + - name: Install BASS (Windows) + if: contains(matrix.os, 'windows') + run: | + curl http://www.un4seen.com/files/bass24.zip -o ./bass.zip + unzip -d ./bass -o ./bass.zip + cp ./bass/c/bass.h ./3rd/bass + cp ./bass/c/x64/bass.lib ./3rd + cp ./bass/x64/bass.dll ./3rd + + curl http://www.un4seen.com/files/bassopus24.zip -o ./bassopus.zip + unzip -d ./bass -o ./bassopus.zip + cp ./bass/c/bassopus.h ./3rd/bass + cp ./bass/c/x64/bassopus.lib ./3rd + cp ./bass/x64/bassopus.dll ./3rd + + - name: Install BASS (Ubuntu) + if: contains(matrix.os, 'ubuntu') + run: | + curl http://www.un4seen.com/files/bass24-linux.zip -o ./bass.zip + unzip -d ./bass -o ./bass.zip + cp ./bass/bass.h ./3rd/bass/bass.h + cp ./bass/x64/libbass.so ./3rd/libbass.so + + curl http://www.un4seen.com/files/bassopus24-linux.zip -o ./bassopus.zip + unzip -d ./bass -o ./bassopus.zip + cp ./bass/bassopus.h ./3rd/bass/bassopus.h + cp ./bass/x64/libbassopus.so ./3rd/libbassopus.so + + - name: Install BASS (MacOS) + if: contains(matrix.os, 'macos') + run: | + curl http://www.un4seen.com/files/bass24-osx.zip -o ./bass.zip + unzip -d ./bass -o ./bass.zip + cp ./bass/bass.h ./3rd/bass + cp ./bass/libbass.dylib ./3rd + + curl http://www.un4seen.com/files/bassopus24-osx.zip -o ./bassopus.zip + unzip -d ./bass -o ./bassopus.zip + cp ./bass/bassopus.h ./3rd/bass + cp ./bass/libbassopus.dylib ./3rd + + - name: Install Discord-RPC (Windows) + if: contains(matrix.os, 'windows') + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o ./discord-rpc.zip + unzip ./discord-rpc.zip + cp ./discord-rpc/win64-dynamic/include/discord_register.h ./3rd/discord-rpc + cp ./discord-rpc/win64-dynamic/include/discord_rpc.h ./3rd/discord-rpc + cp ./discord-rpc/win64-dynamic/bin/discord-rpc.dll ./3rd + cp ./discord-rpc/win64-dynamic/lib/discord-rpc.lib ./3rd + + - name: Install Discord-RPC (Ubuntu) + if: contains(matrix.os, 'ubuntu') + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o ./discord-rpc.zip + unzip ./discord-rpc.zip + cp ./discord-rpc/linux-dynamic/include/discord_register.h ./3rd/discord-rpc/discord_register.h + cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./3rd/discord-rpc/discord_rpc.h + cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./3rd/libdiscord-rpc.so + + - name: Install Discord-RPC (MacOS) + if: contains(matrix.os, 'macos') + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip -o ./discord-rpc.zip + unzip ./discord-rpc.zip + cp ./discord-rpc/osx-dynamic/include/discord_register.h ./3rd/discord-rpc + cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./3rd/discord-rpc + cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./3rd + + - name: Update Ubuntu + if: contains(matrix.os, 'ubuntu') + run: | + sudo apt-get update + sudo apt-get upgrade + + - name: Install Qt (Windows) + if: contains(matrix.os, 'windows') + uses: jurplel/install-qt-action@v2.14.0 + with: + version: ${{matrix.version}} + arch: ${{matrix.arch}} + + - name: Install Qt (Other) + if: contains(matrix.os, 'windows') != true + uses: jurplel/install-qt-action@v2.14.0 + with: + version: ${{matrix.version}} + + - name: Pull QtApng + uses: actions/checkout@v2 + with: + repository: Skycoder42/QtApng + path: ./qtapng + + - name: Build QtApng + run: | + cd ./qtapng + qmake + make -j3 + ls -R + cd .. + + - name: Build + run: | + qmake + make -j3 + + - name: Packing (Windows) + if: contains(matrix.os, 'windows') + run: | + cd ./bin + windeployqt dro-client.exe --compiler-runtime --no-quick-import --no-translations + cp ../qtapng/plugins/imageformats/qapng.dll ./imageformats + cp ../3rd/*.dll . + cp ../../Qt/${{matrix.version}}/${{matrix.compiler}}/bin/libgcc_s_seh-1.dll . + cp ../../Qt/${{matrix.version}}/${{matrix.compiler}}/bin/libwinpthread-1.dll . + cp ../../Qt/${{matrix.version}}/${{matrix.compiler}}/bin/libstdc++-6.dll . + cd .. + + - name: Packing (Ubuntu) + if: contains(matrix.os, 'ubuntu') + run: | + cd ./bin + mkdir ./depends + cp ../3rd/*.so ./depends + mkdir ./imageformats + cp ../qtapng/plugins/imageformats/libqapng.so ./imageformats + cp ../data/dro-client.sh . + cp ../data/README-LINUX.md ./README.md + cd .. + + - name: Packing (MacOS) + if: contains(matrix.os, 'macos') + run: | + cd ./bin + mv ./dro-client.app "./Danganronpa Online.app" + macdeployqt "./Danganronpa Online.app" + cp ../qtapng/plugins/imageformats/libqapng.dylib "./Danganronpa Online.app/Contents/PlugIns/imageformats" + cp ../3rd/*.dylib "./Danganronpa Online.app/Contents/Frameworks" + hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online.app" -ov -format UDZO "Danganronpa Online.dmg" + rm -rf "./Danganronpa Online.app" + cd .. + + - name: Create Artifact + uses: actions/upload-artifact@v2 + with: + name: "Danganronpa Online (${{matrix.os-caption}})" + path: ./bin diff --git a/data/README-LINUX.md b/data/README-LINUX.md new file mode 100644 index 000000000..3c2888b67 --- /dev/null +++ b/data/README-LINUX.md @@ -0,0 +1,14 @@ +# Installation instructions +1. Open your terminal and run +``` +sudo apt-get install qt5-default +``` +2. Update dro-client and dro-client.sh permissions +``` +chmod +x dro-client.sh +chmod +x dro-client +``` +3. Launch +``` +./dro-client.sh +``` diff --git a/data/dro-client.sh b/data/dro-client.sh new file mode 100644 index 000000000..33baddbdd --- /dev/null +++ b/data/dro-client.sh @@ -0,0 +1 @@ +LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client From 25e60e6b30f7104a118f690150983608b46ab5ad Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 9 Jul 2022 05:47:37 +0200 Subject: [PATCH 758/842] Added bin destination --- dronline-client.pro | 1 + 1 file changed, 1 insertion(+) diff --git a/dronline-client.pro b/dronline-client.pro index cb7b0b9cd..a361c1028 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -10,6 +10,7 @@ RC_ICONS = icon.ico INCLUDEPATH += $$PWD/include $$PWD/3rd DEPENDPATH += $$PWD/include $$PWD/3rd +DESTDIR += $$PWD/bin HEADERS += \ src/aoapplication.h \ From 7c2f81cc054516cbe867409a84009fc9697a08cf Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 9 Jul 2022 05:53:37 +0200 Subject: [PATCH 759/842] Restored src path --- dronline-client.pro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index a361c1028..2f26a07ed 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -8,8 +8,8 @@ TARGET = dro-client RC_ICONS = icon.ico -INCLUDEPATH += $$PWD/include $$PWD/3rd -DEPENDPATH += $$PWD/include $$PWD/3rd +INCLUDEPATH += $$PWD/include $$PWD/src $$PWD/3rd +DEPENDPATH += $$PWD/include $$PWD/src $$PWD/3rd DESTDIR += $$PWD/bin HEADERS += \ From ec83b1dd1377dd32213fb9aa7836edf6d5553934 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 9 Jul 2022 06:21:36 +0200 Subject: [PATCH 760/842] Removed DESTDIR --- dronline-client.pro | 1 - 1 file changed, 1 deletion(-) diff --git a/dronline-client.pro b/dronline-client.pro index 2f26a07ed..013e33cd8 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -10,7 +10,6 @@ RC_ICONS = icon.ico INCLUDEPATH += $$PWD/include $$PWD/src $$PWD/3rd DEPENDPATH += $$PWD/include $$PWD/src $$PWD/3rd -DESTDIR += $$PWD/bin HEADERS += \ src/aoapplication.h \ From b75d174e7f7095ed8743cb55e91bd18b213c455e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sat, 9 Jul 2022 06:21:54 +0200 Subject: [PATCH 761/842] Reverted expected DESTDIR pathing --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eab70dfdb..533668a98 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -158,6 +158,7 @@ jobs: if: contains(matrix.os, 'windows') run: | cd ./bin + cp ../release/dro-client.exe . windeployqt dro-client.exe --compiler-runtime --no-quick-import --no-translations cp ../qtapng/plugins/imageformats/qapng.dll ./imageformats cp ../3rd/*.dll . @@ -170,6 +171,7 @@ jobs: if: contains(matrix.os, 'ubuntu') run: | cd ./bin + cp ../dro-client . mkdir ./depends cp ../3rd/*.so ./depends mkdir ./imageformats @@ -182,7 +184,7 @@ jobs: if: contains(matrix.os, 'macos') run: | cd ./bin - mv ./dro-client.app "./Danganronpa Online.app" + mv ../dro-client.app "./Danganronpa Online.app" macdeployqt "./Danganronpa Online.app" cp ../qtapng/plugins/imageformats/libqapng.dylib "./Danganronpa Online.app/Contents/PlugIns/imageformats" cp ../3rd/*.dylib "./Danganronpa Online.app/Contents/Frameworks" From 4953485533fdd1b458f85cf2b32b667bc228594d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sat, 9 Jul 2022 06:28:18 +0200 Subject: [PATCH 762/842] Added config file for actions --- data/.qmake.conf | 1 + 1 file changed, 1 insertion(+) create mode 100644 data/.qmake.conf diff --git a/data/.qmake.conf b/data/.qmake.conf new file mode 100644 index 000000000..89cd07d0d --- /dev/null +++ b/data/.qmake.conf @@ -0,0 +1 @@ +DESTDIR = $$PWD/bin From c408e0f38f063818b890e709c4e722ed47015447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sat, 9 Jul 2022 06:30:45 +0200 Subject: [PATCH 763/842] Update build.yml --- .github/workflows/build.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 533668a98..c35db4822 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -151,6 +151,7 @@ jobs: - name: Build run: | + cp ./data/.qmake.conf . qmake make -j3 @@ -158,7 +159,6 @@ jobs: if: contains(matrix.os, 'windows') run: | cd ./bin - cp ../release/dro-client.exe . windeployqt dro-client.exe --compiler-runtime --no-quick-import --no-translations cp ../qtapng/plugins/imageformats/qapng.dll ./imageformats cp ../3rd/*.dll . @@ -171,7 +171,6 @@ jobs: if: contains(matrix.os, 'ubuntu') run: | cd ./bin - cp ../dro-client . mkdir ./depends cp ../3rd/*.so ./depends mkdir ./imageformats @@ -184,7 +183,7 @@ jobs: if: contains(matrix.os, 'macos') run: | cd ./bin - mv ../dro-client.app "./Danganronpa Online.app" + mv ./dro-client.app "./Danganronpa Online.app" macdeployqt "./Danganronpa Online.app" cp ../qtapng/plugins/imageformats/libqapng.dylib "./Danganronpa Online.app/Contents/PlugIns/imageformats" cp ../3rd/*.dylib "./Danganronpa Online.app/Contents/Frameworks" From 20a7494b488f38ed1696bac040f0d4d552f90dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Mon, 11 Jul 2022 18:40:32 +0200 Subject: [PATCH 764/842] Added preloading system derived from dro-mk2 (#316) * ADDED Preloading system derived from dro-mk2 Preloading is now available for messages. * ADDED Added missing background notifications Backgrounds that are missing will now send a notification in the OOC. * ADDED Ambient SFX system Ambient sfx may now be played through backgrounds. They will begin playback by unrolling themselves from a fade in and will fade out when no longer required. The fade in/out durations are set to 5 s * IMPROVED Better connection state updates * FIXED Emote previews will now reset when hidden * FIXED Lobby filter buttons are now properly initialized by default --- dronline-client.pro | 12 + res/ui/config_panel.ui | 244 ++++++- src/aoapplication.cpp | 157 ++++- src/aoapplication.h | 20 +- src/aoblipplayer.cpp | 2 +- src/aoconfig.cpp | 104 ++- src/aoconfig.h | 18 + src/aoconfigpanel.cpp | 85 ++- src/aoconfigpanel.h | 26 + src/aomusicplayer.cpp | 10 +- src/aosfxplayer.cpp | 117 +++- src/aosfxplayer.h | 29 +- src/aosystemplayer.cpp | 2 +- src/courtroom.cpp | 990 +++++++++++++++++---------- src/courtroom.h | 75 +- src/courtroom_sfx.cpp | 23 +- src/courtroom_widgets.cpp | 102 +-- src/datatypes.cpp | 38 + src/datatypes.h | 33 +- src/draudiostream.cpp | 77 ++- src/draudiostream.h | 43 +- src/draudiostreamfamily.cpp | 40 +- src/draudiostreamfamily.h | 6 +- src/drcharactermovie.cpp | 54 +- src/drcharactermovie.h | 2 +- src/dreffectmovie.cpp | 7 +- src/drmovie.cpp | 243 +------ src/drmovie.h | 51 +- src/drposition.cpp | 59 +- src/drposition.h | 33 +- src/drscenemovie.cpp | 16 +- src/drserversocket.cpp | 59 +- src/drserversocket.h | 16 +- src/drshoutmovie.cpp | 20 +- src/drstickermovie.cpp | 30 +- src/drvideoscreen.cpp | 3 +- src/emotes.cpp | 1 + src/lobby.cpp | 75 +- src/lobby.h | 16 + src/mk2/spritecachingreader.cpp | 169 +++++ src/mk2/spritecachingreader.h | 67 ++ src/mk2/spritedynamicreader.cpp | 214 ++++++ src/mk2/spritedynamicreader.h | 59 ++ src/mk2/spritereader.cpp | 179 +++++ src/mk2/spritereader.h | 121 ++++ src/mk2/spritereadersynchronizer.cpp | 124 ++++ src/mk2/spritereadersynchronizer.h | 66 ++ src/mk2/spriteseekingreader.cpp | 140 ++++ src/mk2/spriteseekingreader.h | 62 ++ src/mk2/spriteviewer.cpp | 305 +++++++++ src/mk2/spriteviewer.h | 112 +++ src/path_functions.cpp | 6 - src/server_socket.cpp | 21 +- src/version.cpp | 6 +- 54 files changed, 3625 insertions(+), 964 deletions(-) create mode 100644 src/mk2/spritecachingreader.cpp create mode 100644 src/mk2/spritecachingreader.h create mode 100644 src/mk2/spritedynamicreader.cpp create mode 100644 src/mk2/spritedynamicreader.h create mode 100644 src/mk2/spritereader.cpp create mode 100644 src/mk2/spritereader.h create mode 100644 src/mk2/spritereadersynchronizer.cpp create mode 100644 src/mk2/spritereadersynchronizer.h create mode 100644 src/mk2/spriteseekingreader.cpp create mode 100644 src/mk2/spriteseekingreader.h create mode 100644 src/mk2/spriteviewer.cpp create mode 100644 src/mk2/spriteviewer.h diff --git a/dronline-client.pro b/dronline-client.pro index 013e33cd8..cb993dbb9 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -67,6 +67,12 @@ HEADERS += \ src/lobby.h \ src/logger.h \ src/misc_functions.h \ + src/mk2/spritecachingreader.h \ + src/mk2/spritedynamicreader.h \ + src/mk2/spritereader.h \ + src/mk2/spritereadersynchronizer.h \ + src/mk2/spriteseekingreader.h \ + src/mk2/spriteviewer.h \ src/theme.h \ src/utils.h \ src/version.h @@ -134,8 +140,14 @@ SOURCES += \ src/logger.cpp \ src/main.cpp \ src/misc_functions.cpp \ + src/mk2/spritecachingreader.cpp \ + src/mk2/spritedynamicreader.cpp \ + src/mk2/spriteseekingreader.cpp \ src/path_functions.cpp \ src/server_socket.cpp \ + src/mk2/spritereader.cpp \ + src/mk2/spritereadersynchronizer.cpp \ + src/mk2/spriteviewer.cpp \ src/text_file_functions.cpp \ src/theme.cpp \ src/utils.cpp \ diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 2a6977683..d0b2e0162 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -32,7 +32,7 @@ - 4 + 2 @@ -428,6 +428,248 @@
    + + + Performance + + + + + + Max Memory + + + + + + + Caching + + + + + + Characters + + + + + + + Backgrounds + + + + + + + Shouts + + + + + + + Effects + + + + + + + Graphical User Interface + + + GUI + + + + + + + Stickers + + + + + + + + + + Preload Threshold + + + + + + + + + <html><head/><body><p>Determines how much preloading progress must have reached before it is deemed acceptable to start playback of animated sprites.</p></body></html> + + + 100 + + + 50 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 25 + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 50% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Loading Bar Delay + + + + + + + + + <html><head/><body><p>Determines the delay before the client prompts the loading bar symbol.</p></body></html> + + + 2000 + + + 500 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 250 + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 500ms + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Vertical + + + + 444 + 163 + + + + + + + + + + <html><head/><body><p>Determines the maximum amount of system memory the client may use for caching. Before loading an animation, given that the animation would exceed the threshold provided the animation will instead preserve memory.<br/><br/><span style=" font-weight:700;">Example</span><br/>System memory is at 50%.<br/>Threshold is at 51%.<br/>The next animation if cached would bump total usage of system memory to 55%. The threshold being exceeded means the animation system will instead attempt to preserve memory. </p></body></html> + + + 10 + + + 80 + + + 50 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 10 + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 50% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Theme diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 83927abe4..5357d9ae2 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -8,6 +8,7 @@ #include "drmasterclient.h" #include "drpacket.h" #include "drserversocket.h" +#include "file_functions.h" #include "lobby.h" #include "theme.h" #include "version.h" @@ -16,7 +17,8 @@ #include #include -AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) +AOApplication::AOApplication(int &argc, char **argv) + : QApplication(argc, argv) { ao_config = new AOConfig(this); ao_config_panel = new AOConfigPanel(this); @@ -42,7 +44,10 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) connect(ao_config, SIGNAL(discord_hide_server_changed(bool)), dr_discord, SLOT(set_hide_server(bool))); connect(ao_config, SIGNAL(discord_hide_character_changed(bool)), dr_discord, SLOT(set_hide_character(bool))); + connect(m_server_socket, SIGNAL(connecting_to_server()), this, SIGNAL(connecting_to_server())); + connect(m_server_socket, SIGNAL(connected_to_server()), this, SIGNAL(connected_to_server())); connect(m_server_socket, SIGNAL(disconnected_from_server()), this, SLOT(_p_handle_server_disconnection())); + connect(m_server_socket, SIGNAL(disconnected_from_server()), this, SIGNAL(disconnected_from_server())); connect(m_server_socket, SIGNAL(packet_received(DRPacket)), this, SLOT(_p_handle_server_packet(DRPacket))); } @@ -172,6 +177,156 @@ void AOApplication::handle_audiotracks_reloading() emit reload_audiotracks(); } +QString AOApplication::get_sfx_dir_path() +{ + return get_base_path() + "sounds/general"; +} + +QString AOApplication::get_sfx_path(QString p_sfx) +{ + return find_asset_path(get_sfx_dir_path() + "/" + p_sfx); +} + +QString AOApplication::get_sfx_noext_path(QString p_file) +{ + return find_asset_path(get_sfx_dir_path() + "/" + p_file, audio_extensions()); +} + +QString AOApplication::get_character_sprite_path(QString p_character, QString p_emote, QString p_prefix, bool p_use_placeholder) +{ + QStringList l_filelist; + QStringList l_blacklist; + for (const QString &i_character_name : get_char_include_tree(p_character)) + { + l_blacklist.append({ + get_character_path(i_character_name, "char_icon.png"), + get_character_path(i_character_name, "showname.png"), + get_character_path(i_character_name, "emotions"), + }); + + if (!p_prefix.isEmpty()) + { + l_filelist.append(get_character_path(i_character_name, QString("%1%2").arg(p_prefix, p_emote))); + } + l_filelist.append(get_character_path(i_character_name, p_emote)); + } + + QString l_file = find_asset_path(l_filelist, animated_or_static_extensions()); + if (l_file.isEmpty() && p_use_placeholder) + { + l_file = find_theme_asset_path("placeholder", animated_extensions()); + } + + for (const QString &i_blackened : qAsConst(l_blacklist)) + { + if (l_file == i_blackened) + { + l_file.clear(); + break; + } + } + + if (l_file.isEmpty()) + { + qWarning() << "error: character animation not found" + << "character:" << p_character << "emote:" << p_emote << "prefix:" << p_prefix; + } + + return l_file; +} + +QString AOApplication::get_character_sprite_pre_path(QString character, QString emote) +{ + return get_character_sprite_path(character, emote, QString{}, false); +} + +QString AOApplication::get_character_sprite_idle_path(QString character, QString emote) +{ + return get_character_sprite_path(character, emote, "(a)", true); +} + +QString AOApplication::get_character_sprite_talk_path(QString character, QString emote) +{ + return get_character_sprite_path(character, emote, "(b)", true); +} + +QString AOApplication::get_background_sprite_path(QString p_background_name, QString p_image) +{ + const QString l_target_filename = + find_asset_path(get_background_path(p_background_name) + "/" + p_image); + return l_target_filename; +} + +QString AOApplication::get_background_sprite_noext_path(QString background, QString image) +{ + return find_asset_path(get_background_path(background) + "/" + image, animated_or_static_extensions()); +} + +QString AOApplication::get_background_sfx_path(QString p_background, QString p_ambient) +{ + const QStringList l_filelist{ + get_background_path(p_background) + "/" + p_ambient, + get_sfx_path(p_ambient), + }; + + return find_asset_path(l_filelist); +} + +QString AOApplication::get_shout_sprite_path(QString p_character, QString p_shout) +{ + QString l_file_name = find_asset_path( + {get_character_path(p_character, p_shout), get_character_path(p_character, p_shout + "_bubble")}, + animated_extensions()); + + if (l_file_name.isEmpty()) + { + l_file_name = find_theme_asset_path(p_shout, animated_extensions()); + } + + if (l_file_name.isEmpty()) + { + qWarning() << "error: shout not found" + << "character:" << p_character << "shout:" << p_shout; + } + + return l_file_name; +} + +QString AOApplication::get_theme_sprite_path(QString p_file_name, QString p_character) +{ + QString l_file_path; + if (!p_character.isEmpty()) + { + QString l_character_file_name = p_file_name; + if (l_character_file_name == "custom") + { + l_character_file_name.append("_bubble"); + } + + QStringList l_path_list{ + get_character_path(p_character, l_character_file_name), + get_character_path(p_character, "overlay/" + l_character_file_name), + }; + l_file_path = find_asset_path(l_path_list, animated_or_static_extensions()); + } + + if (l_file_path.isEmpty()) + { + l_file_path = find_theme_asset_path(p_file_name, animated_or_static_extensions()); + if (l_file_path.isEmpty()) + { + l_file_path = find_theme_asset_path("placeholder", animated_or_static_extensions()); + } + } + + return l_file_path; +} + +QString AOApplication::get_theme_sprite_path(QString file_name) +{ + return get_theme_sprite_path(file_name, QString{}); +} + QString AOApplication::get_current_char() { if (!is_courtroom_constructed) diff --git a/src/aoapplication.h b/src/aoapplication.h index 54f91b37b..4e47ad4e5 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -56,7 +56,6 @@ class AOApplication : public QApplication QString get_character_folder_path(QString character); QString get_character_path(QString p_character, QString p_file); // QString get_demothings_path(); - QString get_sounds_path(QString p_file); QString get_music_folder_path(); QString get_music_path(QString p_song); @@ -186,6 +185,10 @@ public slots: void reload_character(); void reload_audiotracks(); + void connecting_to_server(); + void connected_to_server(); + void disconnected_from_server(); + private: AOConfig *ao_config = nullptr; AOConfigPanel *ao_config_panel = nullptr; @@ -228,6 +231,21 @@ private slots: void handle_theme_modification(); void handle_character_reloading(); void handle_audiotracks_reloading(); + +public: + QString get_sfx_dir_path(); + QString get_sfx_path(QString sfx); + QString get_sfx_noext_path(QString p_file); + QString get_character_sprite_path(QString character, QString emote, QString prefix, bool use_placeholder); + QString get_character_sprite_pre_path(QString character, QString emote); + QString get_character_sprite_idle_path(QString character, QString emote); + QString get_character_sprite_talk_path(QString character, QString emote); + QString get_background_sprite_path(QString background, QString image); + QString get_background_sprite_noext_path(QString background, QString image); + QString get_background_sfx_path(QString background, QString ambient); + QString get_shout_sprite_path(QString character, QString shout); + QString get_theme_sprite_path(QString file_name, QString character); + QString get_theme_sprite_path(QString file_name); }; #endif // AOAPPLICATION_H diff --git a/src/aoblipplayer.cpp b/src/aoblipplayer.cpp index 5385b29ad..2171971bc 100644 --- a/src/aoblipplayer.cpp +++ b/src/aoblipplayer.cpp @@ -16,7 +16,7 @@ void AOBlipPlayer::set_blips(QString p_blip) return; m_name = p_blip; - m_file = ao_app->get_sounds_path(m_name.value()); + m_file = ao_app->get_sfx_noext_path(m_name.value()); } void AOBlipPlayer::blip_tick() diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 2e7ec2d58..bd6d05c64 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -1,14 +1,15 @@ #include "aoconfig.h" + #include "commondefs.h" #include "datatypes.h" #include "draudioengine.h" #include "drpather.h" +#include "mk2/spritedynamicreader.h" // qt #include #include #include -#include #include #include #include @@ -32,7 +33,7 @@ public slots: void save_file(); private: - void invoke_signal(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)); + void invoke_signal(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr), QGenericArgument p_arg2 = QGenericArgument()); void update_favorite_device(); private slots: @@ -80,6 +81,12 @@ private slots: bool log_display_music_switch; bool log_is_recording; + // performance + int system_memory_threshold; + QMap sprite_caching; + int loading_bar_delay; + int caching_threshold; + // audio std::optional favorite_device_driver; int master_volume; @@ -168,6 +175,24 @@ void AOConfigPrivate::read_file() log_display_music_switch = cfg.value("music_change_log", true).toBool(); log_is_recording = cfg.value("enable_logging", true).toBool(); + // performance + { + sprite_caching.clear(); + cfg.beginGroup("sprite_caching"); + const QStringList l_key_list = sprite_category_string_list(); + for (const QString &i_key : l_key_list) + { + const SpriteCategory l_category = string_to_sprite_category(i_key); + sprite_caching.insert(l_category, cfg.value(i_key, true).toBool()); + } + cfg.endGroup(); + } + system_memory_threshold = qBound(10, cfg.value("system_memory_threshold", 50).toInt(), 80); + loading_bar_delay = qBound(0, cfg.value("loading_bar_delay", 500).toInt(), 2000); + caching_threshold = qBound(0, cfg.value("caching_threshold", 50).toInt(), 100); + mk2::SpriteDynamicReader::set_system_memory_threshold(system_memory_threshold); + + // audio if (cfg.contains("favorite_device_driver")) favorite_device_driver = cfg.value("favorite_device_driver").toString(); @@ -258,6 +283,21 @@ void AOConfigPrivate::save_file() cfg.setValue("chatlog_scrolldown", log_is_topdown); cfg.setValue("enable_logging", log_is_recording); + // performance + { + cfg.remove("sprite_caching"); + cfg.beginGroup("sprite_caching"); + for (auto it = sprite_caching.cbegin(); it != sprite_caching.cend(); ++it) + { + const QString l_category_str = sprite_category_to_string(SpriteCategory(it.key())); + cfg.setValue(l_category_str, it.value()); + } + cfg.endGroup(); + } + cfg.setValue("system_memory_threshold", system_memory_threshold); + cfg.setValue("loading_bar_delay", loading_bar_delay); + cfg.setValue("caching_threshold", caching_threshold); + // audio if (favorite_device_driver.has_value()) cfg.setValue("favorite_device_driver", favorite_device_driver.value()); @@ -289,11 +329,11 @@ void AOConfigPrivate::save_file() cfg.sync(); } -void AOConfigPrivate::invoke_signal(QString p_method_name, QGenericArgument p_arg1) +void AOConfigPrivate::invoke_signal(QString p_method_name, QGenericArgument p_arg1, QGenericArgument p_arg2) { for (QObject *i_child : qAsConst(children)) { - QMetaObject::invokeMethod(i_child, p_method_name.toStdString().c_str(), p_arg1); + QMetaObject::invokeMethod(i_child, p_method_name.toStdString().c_str(), p_arg1, p_arg2); } } @@ -557,6 +597,26 @@ bool AOConfig::log_is_recording_enabled() const return d->log_is_recording; } +int AOConfig::system_memory_threshold() const +{ + return d->system_memory_threshold; +} + +bool AOConfig::sprite_caching_enabled(int type) const +{ + return d->sprite_caching[type]; +} + +int AOConfig::loading_bar_delay() const +{ + return d->loading_bar_delay; +} + +int AOConfig::caching_threshold() const +{ + return d->caching_threshold; +} + std::optional AOConfig::favorite_device_driver() const { return d->favorite_device_driver; @@ -932,6 +992,42 @@ void AOConfig::set_suppress_background_audio(bool p_enabled) d->invoke_signal("suppress_background_audio_changed", Q_ARG(bool, p_enabled)); } +void AOConfig::set_system_memory_threshold(int p_percent) +{ + p_percent = qBound(10, p_percent, 80); + if (d->system_memory_threshold == p_percent) + return; + d->system_memory_threshold = p_percent; + mk2::SpriteDynamicReader::set_system_memory_threshold(p_percent); + d->invoke_signal("system_memory_threshold_changed", Q_ARG(int, p_percent)); +} + +void AOConfig::set_sprite_caching(int p_type, bool p_on) +{ + if (d->sprite_caching[p_type] == p_on) + return; + d->sprite_caching[p_type] = p_on; + d->invoke_signal("sprite_caching_toggled", Q_ARG(int, p_type), Q_ARG(bool, p_on)); +} + +void AOConfig::set_loading_bar_delay(int p_delay) +{ + p_delay = qBound(0, p_delay, 2000); + if (d->loading_bar_delay == p_delay) + return; + d->loading_bar_delay = p_delay; + d->invoke_signal("loading_bar_delay_changed", Q_ARG(int, p_delay)); +} + +void AOConfig::set_caching_threshold(int p_percent) +{ + p_percent = qBound(0, p_percent, 100); + if (d->caching_threshold == p_percent) + return; + d->caching_threshold = p_percent; + d->invoke_signal("caching_threshold_changed", Q_ARG(int, p_percent)); +} + void AOConfig::set_favorite_device_driver(QString p_device_driver) { if (d->favorite_device_driver.has_value() && d->favorite_device_driver.value() == p_device_driver) diff --git a/src/aoconfig.h b/src/aoconfig.h index 302127058..f32f2a361 100644 --- a/src/aoconfig.h +++ b/src/aoconfig.h @@ -55,6 +55,12 @@ class AOConfig : public QObject bool log_display_music_switch_enabled() const; bool log_is_recording_enabled() const; + // performance + bool sprite_caching_enabled(int type) const; + int system_memory_threshold() const; + int loading_bar_delay() const; + int caching_threshold() const; + // audio std::optional favorite_device_driver() const; int master_volume() const; @@ -114,6 +120,12 @@ public slots: void set_log_is_recording(bool p_enabled); void set_suppress_background_audio(bool p_enabled); + // performance + void set_sprite_caching(int type, bool on); + void set_system_memory_threshold(int percent); + void set_loading_bar_delay(int delay); + void set_caching_threshold(int percent); + // audio void set_favorite_device_driver(QString p_device_driver); void set_master_volume(int p_number); @@ -173,6 +185,12 @@ public slots: void log_display_music_switch_changed(bool); void log_is_recording_changed(bool); + // performance + void sprite_caching_toggled(int, bool); + void system_memory_threshold_changed(int); + void loading_bar_delay_changed(int); + void caching_threshold_changed(int); + // audio void favorite_device_changed(QString); void master_volume_changed(int); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 79193fa4e..5c25ddbc4 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -5,6 +5,7 @@ #include "aoguiloader.h" #include "datatypes.h" #include "drpather.h" +#include "mk2/spritedynamicreader.h" #include "version.h" #include @@ -21,9 +22,7 @@ #include AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) - : QWidget(p_parent), - m_config(new AOConfig(this)), - m_engine(new DRAudioEngine(this)) + : QWidget(p_parent), m_config(new AOConfig(this)), m_engine(new DRAudioEngine(this)) { ao_app = p_ao_app; @@ -84,6 +83,27 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_log_orientation_bottom_up = AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); ui_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); + // performance + ui_cache_backgrounds = AO_GUI_WIDGET(QCheckBox, "cache_backgrounds"); + ui_cache_characters = AO_GUI_WIDGET(QCheckBox, "cache_characters"); + ui_cache_effects = AO_GUI_WIDGET(QCheckBox, "cache_effects"); + ui_cache_shouts = AO_GUI_WIDGET(QCheckBox, "cache_shouts"); + ui_cache_gui = AO_GUI_WIDGET(QCheckBox, "cache_gui"); + ui_cache_stickers = AO_GUI_WIDGET(QCheckBox, "cache_stickers"); + ui_system_memory_threshold = AO_GUI_WIDGET(QSlider, "system_memory_threshold"); + ui_system_memory_threshold_label = AO_GUI_WIDGET(QLabel, "system_memory_threshold_label"); + ui_caching_threshold = AO_GUI_WIDGET(QSlider, "caching_threshold"); + ui_caching_threshold_label = AO_GUI_WIDGET(QLabel, "caching_threshold_label"); + ui_loading_bar_delay = AO_GUI_WIDGET(QSlider, "loading_bar_delay"); + ui_loading_bar_delay_label = AO_GUI_WIDGET(QLabel, "loading_bar_delay_label"); + + m_cache_checkbox_map.insert(SpriteStage, ui_cache_backgrounds); + m_cache_checkbox_map.insert(SpriteCharacter, ui_cache_characters); + m_cache_checkbox_map.insert(SpriteEffect, ui_cache_effects); + m_cache_checkbox_map.insert(SpriteShout, ui_cache_shouts); + m_cache_checkbox_map.insert(SpriteGUI, ui_cache_gui); + m_cache_checkbox_map.insert(SpriteSticker, ui_cache_stickers); + // audio ui_device = AO_GUI_WIDGET(QComboBox, "device"); ui_favorite_device = AO_GUI_WIDGET(QCheckBox, "favorite_device"); @@ -303,6 +323,28 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_discord_hide_server->setChecked(m_config->discord_hide_server()); ui_discord_hide_character->setChecked(m_config->discord_hide_character()); + // performance + connect(m_config, SIGNAL(sprite_caching_toggled(int, bool)), this, SLOT(set_sprite_caching_toggled(int, bool))); + connect(this, SIGNAL(emit_sprite_caching_toggled(int, bool)), m_config, SLOT(set_sprite_caching(int, bool))); + for (auto it = m_cache_checkbox_map.cbegin(); it != m_cache_checkbox_map.cend(); ++it) + { + QCheckBox *l_checkbox = it.value(); + connect(l_checkbox, SIGNAL(toggled(bool)), this, SLOT(handle_sprite_caching_toggled(bool))); + l_checkbox->setChecked(m_config->sprite_caching_enabled(it.key())); + } + + connect(m_config, SIGNAL(system_memory_threshold_changed(int)), this, SLOT(set_system_memory_threshold(int))); + connect(ui_system_memory_threshold, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_memory_threshold(int))); + set_system_memory_threshold(m_config->system_memory_threshold()); + + connect(m_config, SIGNAL(loading_bar_delay_changed(int)), this, SLOT(set_loading_bar_delay(int))); + connect(ui_loading_bar_delay, SIGNAL(valueChanged(int)), m_config, SLOT(set_loading_bar_delay(int))); + set_loading_bar_delay(m_config->loading_bar_delay()); + + connect(m_config, SIGNAL(caching_threshold_changed(int)), this, SLOT(set_caching_threshold(int))); + connect(ui_caching_threshold, SIGNAL(valueChanged(int)), m_config, SLOT(set_caching_threshold(int))); + set_caching_threshold(m_config->caching_threshold()); + // audio update_audio_device_list(); ui_master->setValue(m_config->master_volume()); @@ -596,6 +638,43 @@ void AOConfigPanel::on_blip_value_changed(int p_num) ui_blip_value->setText(QString::number(p_num) + "%"); } +void AOConfigPanel::set_sprite_caching_toggled(int p_category, bool p_enabled) +{ + QCheckBox *l_checkbox = m_cache_checkbox_map[SpriteCategory(p_category)]; + l_checkbox->setChecked(p_enabled); +} + +void AOConfigPanel::handle_sprite_caching_toggled(bool p_enabled) +{ + QObject *l_sender = sender(); + for (auto it = m_cache_checkbox_map.cbegin(); it != m_cache_checkbox_map.cend(); ++it) + { + if (l_sender == it.value()) + { + emit emit_sprite_caching_toggled(it.key(), p_enabled); + break; + } + } +} + +void AOConfigPanel::set_system_memory_threshold(int p_percent) +{ + ui_system_memory_threshold->setValue(p_percent); + ui_system_memory_threshold_label->setText(QString::number(p_percent) + "%"); +} + +void AOConfigPanel::set_loading_bar_delay(int p_number) +{ + ui_loading_bar_delay->setValue(p_number); + ui_loading_bar_delay_label->setText(QString::number(p_number) + "ms"); +} + +void AOConfigPanel::set_caching_threshold(int p_number) +{ + ui_caching_threshold->setValue(p_number); + ui_caching_threshold_label->setText(QString::number(p_number) + "%"); +} + void AOConfigPanel::username_editing_finished() { m_config->set_username(ui_username->text()); diff --git a/src/aoconfigpanel.h b/src/aoconfigpanel.h index 5c1cd76bb..612a40c56 100644 --- a/src/aoconfigpanel.h +++ b/src/aoconfigpanel.h @@ -6,6 +6,7 @@ class AOApplication; class AOConfig; +#include #include class QCheckBox; @@ -33,6 +34,8 @@ public slots: void reload_character(); void reload_audiotracks(); + void emit_sprite_caching_toggled(int type, bool enabled); + protected: void showEvent(QShowEvent *event) override; @@ -68,6 +71,13 @@ private slots: void on_video_value_changed(int p_num); void on_blip_value_changed(int p_num); + // performance + void set_sprite_caching_toggled(int type, bool enabled); + void handle_sprite_caching_toggled(bool enabled); + void set_system_memory_threshold(int megabytes); + void set_loading_bar_delay(int percent); + void set_caching_threshold(int msecs); + private: // FIXME This dependency shouldn't have come to exist. AOApplication *ao_app = nullptr; @@ -129,6 +139,22 @@ private slots: QRadioButton *ui_log_orientation_bottom_up = nullptr; QCheckBox *ui_log_is_recording = nullptr; + // performance + QCheckBox *ui_cache_backgrounds; + QCheckBox *ui_cache_characters; + QCheckBox *ui_cache_effects; + QCheckBox *ui_cache_shouts; + QCheckBox *ui_cache_gui; + QCheckBox *ui_cache_stickers; + QMap m_cache_checkbox_map; + + QSlider *ui_system_memory_threshold; + QLabel *ui_system_memory_threshold_label; + QSlider *ui_loading_bar_delay; + QLabel *ui_loading_bar_delay_label; + QSlider *ui_caching_threshold; + QLabel *ui_caching_threshold_label; + // audio QComboBox *ui_device = nullptr; QCheckBox *ui_favorite_device = nullptr; diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index b595033db..e5c57a3eb 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -2,10 +2,12 @@ #include "aoapplication.h" +#include "draudiostream.h" #include "draudiotrackmetadata.h" #include -AOMusicPlayer::AOMusicPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) +AOMusicPlayer::AOMusicPlayer(AOApplication *p_ao_app, QObject *p_parent) + : AOObject(p_ao_app, p_parent) { m_family = DRAudioEngine::get_family(DRAudio::Family::FMusic); m_family->set_capacity(1); // a single song is needed @@ -16,10 +18,10 @@ void AOMusicPlayer::play(QString p_song) stop(); m_filename = p_song; - auto l_maybe_stream = m_family->create_stream(ao_app->get_music_path(p_song)); - if (l_maybe_stream) + + DRAudioStream::ptr l_stream = m_family->create_stream(ao_app->get_music_path(p_song)); + if (l_stream) { - auto l_stream = l_maybe_stream.value(); DRAudiotrackMetadata l_audiotrack(p_song); if (!l_audiotrack.play_once()) { diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index 114b2be2b..a4e88b11e 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -2,21 +2,29 @@ #include "aoapplication.h" #include "draudioengine.h" +#include "draudiostream.h" #include "file_functions.h" #include +#include -AOSfxPlayer::AOSfxPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) +AOSfxPlayer::AOSfxPlayer(AOApplication *p_ao_app, QObject *p_parent) + : AOObject(p_ao_app, p_parent), m_player(DRAudioEngine::get_family(DRAudio::Family::FEffect)) {} -void AOSfxPlayer::play(QString p_file) +void AOSfxPlayer::play(QString p_filename) { - DRAudioEngine::get_family(DRAudio::Family::FEffect)->play_stream(p_file); + auto l_stream = m_player->play_stream(p_filename); + if (l_stream) + { + qWarning() << "Playing effect" << p_filename; + m_stream_list.append(l_stream); + } } void AOSfxPlayer::play_effect(QString p_effect) { - play(ao_app->find_asset_path({ao_app->get_sounds_path(p_effect)}, audio_extensions())); + play(ao_app->find_asset_path({ao_app->get_sfx_noext_path(p_effect)}, audio_extensions())); } void AOSfxPlayer::play_character_effect(QString p_chr, QString p_effect) @@ -37,6 +45,105 @@ void AOSfxPlayer::play_character_effect(QString p_chr, QString p_effect) void AOSfxPlayer::stop_all() { - for (DRAudioStream::ptr &i_stream : DRAudioEngine::get_family(DRAudio::Family::FEffect)->get_stream_list()) + for (const DRAudioStream::ptr &i_stream : qAsConst(m_stream_list)) + { i_stream->stop(); + } + m_stream_list.clear(); +} + +void AOSfxPlayer::play_ambient(QString p_filename) +{ + if (m_current_ambient) + { + if (m_current_ambient->get_file_name() == p_filename) + { + return; + } + + m_current_ambient->fadeOut(DEFAULT_FADE_DURATION); + m_current_ambient.clear(); + } + + DRAudioStream::ptr l_ambient; + if (!m_ambient_map.contains(p_filename)) + { + l_ambient = m_player->create_stream(p_filename); + + if (l_ambient) + { + qInfo() << "Playing ambient" << p_filename; + m_ambient_map.insert(p_filename, l_ambient); + + connect(l_ambient.data(), SIGNAL(faded(DRAudioStream::Fade)), this, SLOT(handle_ambient_fade(DRAudioStream::Fade))); + connect(l_ambient.data(), SIGNAL(finished()), this, SLOT(remove_ambient())); + + l_ambient->set_repeatable(true); + } + } + else + { + qInfo() << "Restoring ambient" << p_filename; + l_ambient = m_ambient_map[p_filename]; + } + m_current_ambient = l_ambient; + + if (m_current_ambient.isNull()) + { + return; + } + + m_current_ambient->fadeIn(DEFAULT_FADE_DURATION); + + if (!m_current_ambient->is_playing()) + { + m_current_ambient->play(); + } +} + +DRAudioStream::ptr AOSfxPlayer::get_stream_by_qobject(QObject *p_object) +{ + auto *l_stream_ptr = dynamic_cast(p_object); + if (!l_stream_ptr) + { + qCritical() << "error: object was not an audio stream" << p_object; + return nullptr; + } + + for (auto it = m_ambient_map.cbegin(); it != m_ambient_map.cend(); ++it) + { + const auto &i_stream = it.value(); + if (l_stream_ptr == i_stream) + { + return i_stream; + } + } + + return nullptr; +} + +void AOSfxPlayer::remove_ambient() +{ + auto l_stream = get_stream_by_qobject(sender()); + if (l_stream.isNull()) + { + return; + } + + qDebug() << "Removing ambient" << l_stream->get_file_name(); + m_ambient_map.remove(l_stream->get_file_name()); +} + +void AOSfxPlayer::handle_ambient_fade(DRAudioStream::Fade p_fade) +{ + const auto l_stream = get_stream_by_qobject(sender()); + if (l_stream.isNull()) + { + return; + } + + if (p_fade == DRAudioStream::FadeOut) + { + l_stream->stop(); + } } diff --git a/src/aosfxplayer.h b/src/aosfxplayer.h index 268b2025f..03ca99f8e 100644 --- a/src/aosfxplayer.h +++ b/src/aosfxplayer.h @@ -1,16 +1,37 @@ #pragma once #include "aoobject.h" +#include "draudiostream.h" +#include "draudiostreamfamily.h" + +#include +#include class AOSfxPlayer : public AOObject { Q_OBJECT public: - AOSfxPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); + static const int DEFAULT_FADE_DURATION = 5000; + + AOSfxPlayer(AOApplication *ao_app, QObject *parent = nullptr); - void play(QString p_file); - void play_effect(QString p_effect); - void play_character_effect(QString p_character, QString p_effect); + void play(QString filename); + void play_effect(QString effect); + void play_character_effect(QString character, QString effect); void stop_all(); + + void play_ambient(QString filename); + +private: + DRAudioStreamFamily::ptr m_player; + QVector m_stream_list; + QMap m_ambient_map; + DRAudioStream::ptr m_current_ambient; + + DRAudioStream::ptr get_stream_by_qobject(QObject *object); + +private slots: + void remove_ambient(); + void handle_ambient_fade(DRAudioStream::Fade fade); }; diff --git a/src/aosystemplayer.cpp b/src/aosystemplayer.cpp index e8323b9b2..7ec54b86b 100644 --- a/src/aosystemplayer.cpp +++ b/src/aosystemplayer.cpp @@ -9,6 +9,6 @@ AOSystemPlayer::AOSystemPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOO void AOSystemPlayer::play(QString p_name) { - const QString file = ao_app->find_asset_path({ao_app->get_sounds_path(p_name)}, audio_extensions()); + const QString file = ao_app->find_asset_path({ao_app->get_sfx_noext_path(p_name)}, audio_extensions()); DRAudioEngine::get_family(DRAudio::Family::FSystem)->play_stream(file); } diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 9f6553dc1..fb7e4477d 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -3,7 +3,6 @@ #include "aoapplication.h" #include "aoblipplayer.h" #include "aobutton.h" -#include "aocharbutton.h" #include "aoconfig.h" #include "aoimagedisplay.h" #include "aomusicplayer.h" @@ -21,21 +20,21 @@ #include "drdiscord.h" #include "dreffectmovie.h" #include "drpacket.h" -#include "drposition.h" #include "drscenemovie.h" #include "drshoutmovie.h" #include "drsplashmovie.h" #include "drstickermovie.h" #include "drvideoscreen.h" #include "file_functions.h" -#include "hardware_functions.h" -#include "lobby.h" +#include "mk2/spritedynamicreader.h" +#include "mk2/spriteseekingreader.h" #include "src/datatypes.h" #include #include #include #include +#include #include #include #include @@ -50,13 +49,14 @@ const int Courtroom::DEFAULT_WIDTH = 714; const int Courtroom::DEFAULT_HEIGHT = 668; -Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() +Courtroom::Courtroom(AOApplication *p_ao_app) + : QMainWindow() { ao_app = p_ao_app; ao_config = new AOConfig(this); - m_position_reader = new DRPositionReader(this); - m_chatmessage[CMPosition] = "wit"; + m_preloader_sync = new mk2::SpriteReaderSynchronizer(this); + m_preloader_sync->set_threshold(ao_config->caching_threshold()); connect(ao_app, SIGNAL(reload_theme()), this, SLOT(load_theme())); connect(ao_app, SIGNAL(reload_character()), this, SLOT(load_character())); @@ -65,13 +65,21 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() create_widgets(); connect_widgets(); + while (m_chatmessage.length() < OPTIMAL_MESSAGE_SIZE) + { + m_chatmessage.append(QString{}); + } + m_chatmessage[CMPosition] = "wit"; + setup_courtroom(); set_char_select(); load_audiotracks(); + reset_viewport(); } Courtroom::~Courtroom() { + cleanup_preload_readers(); ao_config->set_gamemode(nullptr); ao_config->set_timeofday(nullptr); stop_all_audio(); @@ -108,6 +116,11 @@ void Courtroom::setup_courtroom() load_free_blocks(); load_sfx_list_theme(); + map_viewers(); + map_viewport_viewers(); + map_viewport_readers(); + assign_readers_for_all_viewers(); + // Update widgets first, then check if everything is valid // This will also handle showing the correct shouts, effects and wtce buttons, // and cycling through them if the buttons that are supposed to be displayed @@ -120,20 +133,17 @@ void Courtroom::setup_courtroom() m_shout_state = 0; m_shout_current = 0; check_shouts(); - if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) - cycle_shout(1); + if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) cycle_shout(1); m_effect_state = 0; m_effect_current = 0; check_effects(); - if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) - cycle_effect(1); + if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) cycle_effect(1); m_wtce_current = 0; reset_wtce_buttons(); check_wtce(); - if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) - cycle_wtce(1); + if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) cycle_wtce(1); check_free_blocks(); @@ -149,6 +159,138 @@ void Courtroom::setup_courtroom() i_timer->redraw(); } +void Courtroom::map_viewers() +{ + m_mapped_viewer_list.clear(); + + // general ui elements + m_mapped_viewer_list[SpriteGUI].append({ + ui_vp_chat_arrow, + ui_vp_clock, + }); + + // backgrounds + m_mapped_viewer_list[SpriteStage].append({ + ui_vp_background, + ui_vp_desk, + }); + + // characters + m_mapped_viewer_list[SpriteCharacter].append(ui_vp_player_char); + + // shouts + m_mapped_viewer_list[SpriteShout].append(ui_vp_objection); + + // effects + m_mapped_viewer_list[SpriteEffect].append(ui_vp_effect); + + // stickers + for (DRStickerMovie *i_sticker : qAsConst(ui_free_blocks)) + { + m_mapped_viewer_list[SpriteSticker].append(i_sticker); + } +} + +void Courtroom::map_viewport_viewers() +{ + m_viewport_viewer_map.clear(); + m_viewport_viewer_map.insert(ViewportStageBack, ui_vp_background); + m_viewport_viewer_map.insert(ViewportStageFront, ui_vp_desk); + m_viewport_viewer_map.insert(ViewportCharacterPre, ui_vp_player_char); + m_viewport_viewer_map.insert(ViewportCharacterIdle, ui_vp_player_char); + m_viewport_viewer_map.insert(ViewportCharacterTalk, ui_vp_player_char); + m_viewport_viewer_map.insert(ViewportEffect, ui_vp_effect); + m_viewport_viewer_map.insert(ViewportShout, ui_vp_objection); +} + +void Courtroom::map_viewport_readers() +{ + const QList l_type_list = m_viewport_viewer_map.keys(); + for (const ViewportSprite i_type : qAsConst(l_type_list)) + { + m_reader_cache.insert(i_type, m_viewport_viewer_map[i_type]->get_reader()); + } +} + +mk2::SpriteReader::ptr Courtroom::get_viewport_reader(ViewportSprite p_type) const +{ + Q_ASSERT(m_viewport_viewer_map.contains(p_type)); + DRMovie *l_viewer = m_viewport_viewer_map[p_type]; + return l_viewer->get_reader(); +} + +void Courtroom::assign_readers_for_all_viewers() +{ + for (auto it = m_mapped_viewer_list.cbegin(); it != m_mapped_viewer_list.cend(); ++it) + { + const SpriteCategory l_cat = it.key(); + + const bool l_caching = ao_config->sprite_caching_enabled(l_cat); + assign_readers_for_viewers(l_cat, l_caching); + } +} + +void Courtroom::cleanup_preload_readers() +{ + m_preloader_sync->clear(); + m_preloader_cache.clear(); +} + +void Courtroom::swap_viewport_reader(DRMovie *p_viewer, ViewportSprite p_type) +{ + Q_ASSERT(m_reader_cache.contains(p_type)); + mk2::SpriteReader::ptr l_reader = m_reader_cache.value(p_type); + if (l_reader == p_viewer->get_reader()) + { + return; + } + p_viewer->set_reader(l_reader); +} + +void Courtroom::assign_readers_for_viewers(int p_category, bool p_caching) +{ + const SpriteCategory l_category = SpriteCategory(p_category); + + // viewport readers + const QVector &l_viewer_list = m_mapped_viewer_list[l_category]; + for (DRMovie *i_viewer : qAsConst(l_viewer_list)) + { + mk2::SpriteReader::ptr l_new_reader; + if (p_caching) + { + l_new_reader = mk2::SpriteReader::ptr(new mk2::SpriteDynamicReader); + } + else + { + l_new_reader = mk2::SpriteReader::ptr(new mk2::SpriteSeekingReader); + } + l_new_reader->set_file_name(i_viewer->get_file_name()); + + const mk2::SpriteReader::ptr l_prev_reader = i_viewer->get_reader(); + const bool l_is_running = i_viewer->is_running(); + const bool l_is_visible = i_viewer->isVisible(); + + { // update reader cache + const QList l_type_list = m_reader_cache.keys(); + for (const ViewportSprite i_type : qAsConst(l_type_list)) + { + const mk2::SpriteReader::ptr l_current_reader = m_reader_cache.value(i_type); + if (l_prev_reader == l_current_reader) + { + m_reader_cache.insert(i_type, l_new_reader); + } + } + } + + i_viewer->set_reader(l_new_reader); + if (l_is_running) + { + i_viewer->start(); + } + i_viewer->setVisible(l_is_visible); + } +} + void Courtroom::enter_courtroom(int p_cid) { qDebug() << "enter_courtroom"; @@ -162,12 +304,10 @@ void Courtroom::enter_courtroom(int p_cid) const bool l_changed_chr_id = (m_chr_id != p_cid); m_chr_id = p_cid; - if (l_changed_chr_id) - update_default_iniswap_item(); + if (l_changed_chr_id) update_default_iniswap_item(); QLineEdit *l_current_field = ui_ic_chat_message; - if (ui_ooc_chat_message->hasFocus()) - l_current_field = ui_ooc_chat_message; + if (ui_ooc_chat_message->hasFocus()) l_current_field = ui_ooc_chat_message; const int l_current_cursor_pos = l_current_field->cursorPosition(); const QString l_chr_name = get_character_ini(); @@ -188,8 +328,7 @@ void Courtroom::enter_courtroom(int p_cid) ao_app->send_server_packet(DRPacket("chrini", l_content)); } const bool l_changed_chr = l_chr_name != l_prev_chr_name; - if (l_changed_chr) - set_character_position(ao_app->get_char_side(l_chr_name)); + if (l_changed_chr) set_character_position(ao_app->get_char_side(l_chr_name)); select_base_character_iniswap(); refresh_character_content_url(); @@ -224,8 +363,7 @@ void Courtroom::enter_courtroom(int p_cid) l_current_field->setCursorPosition(l_current_cursor_pos); const QString l_showname = ao_config->showname(); - if (!l_showname.isEmpty() && !is_first_showname_sent) - send_showname_packet(l_showname); + if (!l_showname.isEmpty() && !is_first_showname_sent) send_showname_packet(l_showname); ui_char_select_background->hide(); } @@ -249,118 +387,105 @@ void Courtroom::set_window_title(QString p_title) this->setWindowTitle(p_title); } +void Courtroom::set_ambient(QString p_ambient_sfx) +{ + m_ambient_sfx = p_ambient_sfx.trimmed(); + play_ambient(); +} + +void Courtroom::play_ambient() +{ + QString l_filepath = ao_app->get_sfx_path(m_ambient_sfx); + + if (l_filepath.isEmpty()) + { + DRPosition l_position = m_position_map.get_position(m_chatmessage[CMPosition]); + l_filepath = ao_app->get_background_sfx_path(m_background_name, l_position.get_ambient_sfx()); + } + + m_effects_player->play_ambient(l_filepath); +} + void Courtroom::update_background_scene() { - const QString l_prev_background_name = m_current_background_name; - m_current_background_name = ao_app->get_current_background(); + const QString l_prev_background_name = m_background_name; + // TOD background is resolved in the method below + m_background_name = ao_app->get_current_background(); - // see TOD background list for why this method is called here - if (l_prev_background_name.isEmpty() || l_prev_background_name != m_current_background_name) + if (l_prev_background_name.isEmpty() || l_prev_background_name != m_background_name) { - m_is_legacy_background = false; const QString l_positions_ini = - ao_app->find_asset_path(ao_app->get_background_path(m_current_background_name) + "/" + "positions.ini"); - if (!l_positions_ini.isEmpty()) - { - m_position_reader->load_file(l_positions_ini); - } - else + ao_app->find_asset_path(ao_app->get_background_path(m_background_name) + "/" + "positions.ini"); + if (l_positions_ini.isEmpty() || !m_position_map.load_file(l_positions_ini)) { - m_is_legacy_background = true; + m_position_map = get_legacy_background(m_background_name); } } - if (m_is_legacy_background) - { - update_legacy_background_scene(); - return; - } - const QString l_position_id = m_chatmessage[CMPosition]; - DRPosition l_position = m_position_reader->get_position(l_position_id); + DRPosition l_position = m_position_map.get_position(m_chatmessage[CMPosition]); - ui_vp_background->show(); - ui_vp_background->set_background_image(m_current_background_name, l_position.get_back()); - if (!ui_vp_background->is_valid()) { - ui_vp_background->hide(); + const QString l_file_name = ao_app->get_background_sprite_path(m_background_name, l_position.get_back()); + ui_vp_background->set_file_name(l_file_name); } - ui_vp_desk->show(); - ui_vp_desk->set_background_image(m_current_background_name, l_position.get_front()); - const QString l_desk_mode = m_chatmessage[CMDeskModifier]; - if (!ui_vp_desk->is_valid() || l_desk_mode == "0") { - ui_vp_desk->hide(); + const QString l_file_name = ao_app->get_background_sprite_path(m_background_name, l_position.get_front()); + ui_vp_desk->set_file_name(l_file_name); } -} -void Courtroom::update_legacy_background_scene() -{ - QString l_back_image = "witnessempty"; - QString l_front_image = "stand"; - - const QString l_position_id = m_chatmessage[CMPosition]; - if (l_position_id == "def") - { - l_back_image = "defenseempty"; - l_front_image = "defensedesk"; - } - else if (l_position_id == "pro") - { - l_back_image = "prosecutorempty"; - l_front_image = "prosecutiondesk"; - } - else if (l_position_id == "jud") - { - l_back_image = "judgestand"; - l_front_image = "judgedesk"; - } - else if (l_position_id == "hld") - { - l_back_image = "helperstand"; - l_front_image = "helperdesk"; - } - else if (l_position_id == "hlp") - { - l_back_image = "prohelperstand"; - l_front_image = "prohelperdesk"; - } - else + if (m_preloader_sync->is_waiting()) { - l_back_image = "witnessempty"; - l_front_image = "stand"; + preload_chatmessage(m_pre_chatmessage); } - l_back_image = ao_app->find_asset_path(ao_app->get_background_path(m_current_background_name) + "/" + l_back_image, - animated_or_static_extensions()); - l_front_image = ao_app->find_asset_path(ao_app->get_background_path(m_current_background_name) + "/" + l_front_image, - animated_or_static_extensions()); + display_background_scene(); +} +void Courtroom::display_background_scene() +{ ui_vp_background->show(); - ui_vp_background->set_file_name(l_back_image); ui_vp_background->set_play_once(false); + swap_viewport_reader(ui_vp_background, ViewportStageBack); ui_vp_background->start(); if (!ui_vp_background->is_valid()) { ui_vp_background->hide(); } - if (m_chatmessage[CMDeskModifier] == "0") + ui_vp_desk->show(); + ui_vp_desk->set_play_once(false); + swap_viewport_reader(ui_vp_desk, ViewportStageFront); + ui_vp_desk->start(); + if (!ui_vp_desk->is_valid() || m_chatmessage[CMDeskModifier] == "0") { ui_vp_desk->hide(); } - else + + play_ambient(); +} + +DRPositionMap Courtroom::get_legacy_background(QString p_background) +{ + DRPositionMap l_position_map; + + const auto &l_legacy_map = DRPositionMap::LEGACY_POSITION_MAP; + for (auto it = l_legacy_map.cbegin(); it != l_legacy_map.cend(); ++it) { - ui_vp_desk->show(); - ui_vp_desk->set_file_name(l_front_image); - ui_vp_desk->set_play_once(false); - ui_vp_desk->start(); - if (!ui_vp_desk->is_valid()) - { - ui_vp_desk->hide(); - } + QString l_position_id = it.key(); + DRPosition l_position = it.value(); + + auto l_get_position_filename = [this, &p_background](QString p_filename) { + const QFileInfo l_filepath = ao_app->get_background_sprite_noext_path(p_background, p_filename); + return l_filepath.fileName(); + }; + l_position.set_back(l_get_position_filename(l_position.get_back())); + l_position.set_front(l_get_position_filename(l_position.get_front())); + l_position_map.set_position(l_position_id, l_position); } + + return l_position_map; } DRAreaBackground Courtroom::get_background() @@ -371,6 +496,29 @@ DRAreaBackground Courtroom::get_background() void Courtroom::set_background(DRAreaBackground p_background) { m_background = p_background; + + QStringList l_background_list{m_background.background}; + for (auto it = m_background.background_tod_map.cbegin(); it != m_background.background_tod_map.cend(); ++it) + { + l_background_list.append(it.value()); + } + + QStringList l_missing_backgrounds; + for (const QString &i_background : qAsConst(l_background_list)) + { + const QString l_background_path = ao_app->get_case_sensitive_path(ao_app->get_background_path(i_background)); + if (!dir_exists(l_background_path)) + { + l_missing_backgrounds.append(i_background); + } + } + + if (!l_missing_backgrounds.isEmpty()) + { + ui_ooc_chatlog->append_chatmessage("[WARNING]", + "Missing background(s) detected: " + l_missing_backgrounds.join(", ")); + } + update_background_scene(); } @@ -414,8 +562,7 @@ void Courtroom::update_music_text_anim() void Courtroom::handle_clock(QString time) { m_current_clock = time.toInt(); - if (m_current_clock < 0) - m_current_clock = -1; + if (m_current_clock < 0) m_current_clock = -1; qInfo() << QString("Clock time changed to %1").arg(m_current_clock); ui_vp_clock->hide(); @@ -463,8 +610,7 @@ void Courtroom::list_music() DRAudiotrackMetadata l_track(i_song); QListWidgetItem *l_item = new QListWidgetItem(l_track.title(), ui_music_list); l_item->setData(Qt::UserRole, l_track.filename()); - if (l_track.title() != l_track.filename()) - l_item->setToolTip(l_track.filename()); + if (l_track.title() != l_track.filename()) l_item->setToolTip(l_track.filename()); const QString l_song_path = ao_app->find_asset_path({ao_app->get_music_path(i_song)}, audio_extensions()); l_item->setBackground(l_song_path.isEmpty() ? l_missing_song_brush : l_song_brush); } @@ -505,14 +651,12 @@ void Courtroom::list_note_files() QString line = in.readLine().trimmed(); QStringList f_contents = line.split("="); - if (f_contents.size() < 2) - continue; + if (f_contents.size() < 2) continue; int f_index = f_contents.at(0).toInt(); f_filestring = f_filename = f_contents.at(1).trimmed(); - if (f_contents.size() > 2) - f_filename = f_contents.at(2).trimmed(); + if (f_contents.size() > 2) f_filename = f_contents.at(2).trimmed(); while (f_index >= f_layout->count()) on_add_button_clicked(); @@ -529,15 +673,13 @@ void Courtroom::load_note() // that notepad text is kept in client if the user has decided not to choose a // file to save to. Of course, this is ephimeral storage, it will be erased // after the client closes or when the user decides to load a file. - if (current_file.isEmpty()) - return; + if (current_file.isEmpty()) return; QString f_text = ao_app->read_note(current_file); ui_vp_notepad->setText(f_text); } void Courtroom::save_note() { - QString f_text = ui_vp_notepad->toPlainText(); ao_app->write_note(f_text, current_file); @@ -553,8 +695,7 @@ void Courtroom::save_textlog(QString p_text) void Courtroom::append_server_chatmessage(QString p_name, QString p_message) { ui_ooc_chatlog->append_chatmessage(p_name, p_message); - if (ao_config->log_is_recording_enabled()) - save_textlog("(OOC)" + p_name + ": " + p_message); + if (ao_config->log_is_recording_enabled()) save_textlog("(OOC)" + p_name + ": " + p_message); } void Courtroom::ignore_next_showname() @@ -605,11 +746,9 @@ void Courtroom::on_character_ini_changed() void Courtroom::on_ic_message_return_pressed() { - if (ui_ic_chat_message->text() == "" || is_client_muted) - return; + if (ui_ic_chat_message->text() == "" || is_client_muted) return; - if ((anim_state < 3 || text_state < 2) && m_shout_state == 0) - return; + if ((anim_state < 3 || text_state < 2) && m_shout_state == 0) return; // MS // deskmod @@ -735,52 +874,188 @@ void Courtroom::handle_acknowledged_ms() clear_sfx_selection(); } -void Courtroom::handle_chatmessage(QStringList p_contents) +void Courtroom::next_chatmessage(QStringList p_chatmessage) { - if (p_contents.size() < 15) + if (p_chatmessage.length() < MINIMUM_MESSAGE_SIZE) + { return; - while (p_contents.length() < MESSAGE_SIZE) - p_contents.append(nullptr); + } + + while (p_chatmessage.length() < OPTIMAL_MESSAGE_SIZE) + { + p_chatmessage.append(QString{}); + } - for (int i = 0; i < MESSAGE_SIZE; ++i) - m_chatmessage[i] = p_contents[i]; + const int l_message_chr_id = p_chatmessage[CMChrId].toInt(); + const bool l_system_speaking = l_message_chr_id == SpectatorId; + + QString l_showname = p_chatmessage[CMShowName]; + if (l_showname.isEmpty() && !l_system_speaking) + { + if (!m_chr_list.isEmpty()) + { + l_showname = ao_app->get_showname(m_chr_list.at(l_message_chr_id).name); + } + else + { + l_showname = tr("???"); + } + } + + const QString l_message = p_chatmessage[CMMessage]; + if (l_message_chr_id == SpectatorId) + { + append_system_text(l_showname, l_message); + } + else if (l_message_chr_id >= 0 && l_message_chr_id < m_chr_list.length()) + { + const int l_client_id = p_chatmessage[CMClientId].toInt(); + append_ic_text(l_showname, l_message, false, false, l_client_id, m_chr_id == l_message_chr_id); + + if (ao_config->log_is_recording_enabled() && !l_message.isEmpty()) + { + save_textlog(l_showname + ": " + l_message); + } + } + + preload_chatmessage(p_chatmessage); +} + +void Courtroom::reset_viewport() +{ + QStringList l_chatmessage; + + while (l_chatmessage.length() < OPTIMAL_MESSAGE_SIZE) + { + l_chatmessage.append(QString{}); + } + l_chatmessage[CMChrId] = QString::number(SpectatorId); + l_chatmessage[CMHideCharacter] = "1"; + l_chatmessage[CMClientId] = QString::number(SpectatorId); + l_chatmessage[CMPosition] = m_chatmessage.value(CMPosition, "wit"); + + next_chatmessage(l_chatmessage); +} + +void Courtroom::preload_chatmessage(QStringList p_contents) +{ + cleanup_preload_readers(); + m_loading_timer->stop(); + m_pre_chatmessage = p_contents; if (ao_app->is_server_client_version_compatible()) { bool l_ok; - const int l_client_id = m_chatmessage[CMClientId].toInt(&l_ok); + const int l_client_id = m_pre_chatmessage[CMClientId].toInt(&l_ok); if (l_ok && l_client_id == ao_app->get_client_id()) { handle_acknowledged_ms(); } } + QMap l_file_list; + const QString l_position_id = m_pre_chatmessage[CMPosition]; + const QString l_character = m_pre_chatmessage[CMChrName]; + const QString l_emote_anim = m_pre_chatmessage[CMPreAnim]; + const QString l_emote = m_pre_chatmessage[CMEmote]; + const int l_effect_id = m_pre_chatmessage[CMEffectState].toInt(); + const int l_shout_id = m_pre_chatmessage[CMShoutModifier].toInt(); + + { // backgrounds + DRPosition l_position = m_position_map.get_position(l_position_id); + l_file_list.insert(ViewportStageBack, + ao_app->get_background_sprite_path(m_background_name, l_position.get_back())); + l_file_list.insert(ViewportStageFront, + ao_app->get_background_sprite_path(m_background_name, l_position.get_front())); + } + + // characters + l_file_list.insert(ViewportCharacterPre, ao_app->get_character_sprite_pre_path(l_character, l_emote_anim)); + l_file_list.insert(ViewportCharacterIdle, ao_app->get_character_sprite_idle_path(l_character, l_emote)); + l_file_list.insert(ViewportCharacterTalk, ao_app->get_character_sprite_talk_path(l_character, l_emote)); + + // shouts + l_file_list.insert(ViewportShout, ao_app->get_theme_sprite_path(get_shout_name(l_shout_id), l_character)); + + // effects + l_file_list.insert(ViewportEffect, ao_app->get_theme_sprite_path(get_effect_name(l_effect_id))); + + for (auto it = l_file_list.cbegin(); it != l_file_list.cend(); ++it) + { + const ViewportSprite l_type = it.key(); + const QString &l_file_name = it.value(); + + DRMovie *l_viewer = m_viewport_viewer_map.value(l_type); + const QString l_current_file_name = l_viewer->get_file_name(); + + // reuse readers when available + mk2::SpriteReader::ptr l_reader = l_viewer->get_reader(); + if (l_file_name != l_current_file_name) + { + const SpriteCategory l_category = viewport_sprite_to_sprite_category(l_type); + if (ao_config->sprite_caching_enabled(l_category)) + { + l_reader = mk2::SpriteReader::ptr(new mk2::SpriteDynamicReader); + } + else + { + l_reader = mk2::SpriteReader::ptr(new mk2::SpriteSeekingReader); + } + l_reader->set_file_name(l_file_name); + } + m_preloader_cache.insert(l_type, l_reader); + m_preloader_sync->add(l_reader); + } + m_loading_timer->start(); + m_preloader_sync->start(); +} + +void Courtroom::start_chatmessage() +{ + m_preloader_sync->clear(); + m_reader_cache = std::move(m_preloader_cache); + + m_loading_timer->stop(); + ui_vp_loading->hide(); + + m_tick_timer->stop(); + m_chatmessage = m_pre_chatmessage; + + handle_chatmessage(); +} + +void Courtroom::handle_chatmessage() +{ + qDebug() << "handle_chatmessage"; m_hide_character = m_chatmessage[CMHideCharacter].toInt(); m_play_pre = false; m_play_zoom = false; const int l_emote_mod = m_chatmessage[CMEmoteModifier].toInt(); switch (l_emote_mod) { - case IdleEmoteMod: - default: - break; - case PreEmoteMod: - m_play_pre = true; - break; - case ZoomEmoteMod: - m_play_zoom = true; - break; - case PreZoomEmoteMod: - m_play_pre = true; - m_play_zoom = true; - break; + case IdleEmoteMod: + default: + break; + case PreEmoteMod: + m_play_pre = true; + break; + case ZoomEmoteMod: + m_play_zoom = true; + break; + case PreZoomEmoteMod: + m_play_pre = true; + m_play_zoom = true; + break; } m_speaker_chr_id = m_chatmessage[CMChrId].toInt(); is_system_speaking = (m_speaker_chr_id == SpectatorId); if (m_speaker_chr_id != SpectatorId && (m_speaker_chr_id < 0 || m_speaker_chr_id >= m_chr_list.length())) + { + post_chatmessage(); return; + } const QString l_message = QString(m_chatmessage[CMMessage]) .remove(QRegularExpression("(?stop(); + ui_vp_effect->hide(); - if (is_system_speaking) - append_system_text(m_speaker_showname, l_message); - else + ui_video->play_character_video(m_chatmessage[CMChrName], m_chatmessage[CMVideoName]); +} + +QString Courtroom::get_shout_name(int p_shout_index) +{ + if (p_shout_index < 1 || p_shout_index > ui_shouts.length()) { - const int l_client_id = m_chatmessage[CMClientId].toInt(); - append_ic_text(m_speaker_showname, l_message, false, false, l_client_id, m_speaker_chr_id == m_chr_id); + return QString{}; } - if (ao_config->log_is_recording_enabled() && (!chatmessage_is_empty || !is_system_speaking)) + return shout_names.at(p_shout_index - 1); +} + +QString Courtroom::get_effect_name(int effect_index) +{ + if (effect_index < 1 || effect_index > ui_effects.length()) { - save_textlog(m_speaker_showname + ": " + l_message); + return QString{}; } - ui_video->play_character_video(m_chatmessage[CMChrName], m_chatmessage[CMVideoName]); + return effect_names.at(effect_index - 1); } void Courtroom::video_finished() { - int objection_mod = m_chatmessage[CMShoutModifier].toInt(); - QString f_char = m_chatmessage[CMChrName]; + const QString l_character = m_chatmessage[CMChrName]; + const int l_shout_index = m_chatmessage[CMShoutModifier].toInt(); - // if an objection is to be used used - if (objection_mod > 0 && objection_mod <= ui_shouts.size()) + const QString l_shout_name = get_shout_name(l_shout_index); + if (l_shout_name.isEmpty()) { - m_play_pre = true; - const int l_target_obj_id = objection_mod - 1; - ui_vp_objection->play_interjection(f_char, shout_names.at(l_target_obj_id)); - m_shouts_player->play(f_char, shout_names.at(l_target_obj_id)); - } - else handle_chatmessage_2(); + return; + } + + qDebug() << "[viewport] Starting shout..." << l_shout_name; + m_play_pre = true; + ui_vp_objection->set_play_once(true); + swap_viewport_reader(ui_vp_objection, ViewportShout); + ui_vp_objection->start(); + m_shouts_player->play(l_character, l_shout_name); } void Courtroom::objection_done() { + if (anim_state != 0) + { + return; + } handle_chatmessage_2(); } @@ -880,9 +1170,9 @@ void Courtroom::handle_chatmessage_2() // handles IC const bool l_is_self = (ao_config->log_display_self_highlight_enabled() && m_speaker_chr_id == m_chr_id); ui_vp_chatbox->set_chatbox_image(l_chatbox_name, l_is_self); - if (m_msg_is_first_person == false) + if (!m_msg_is_first_person) { - update_background_scene(); + display_background_scene(); } if (m_chatmessage[CMFlipState].toInt() == 1) @@ -905,6 +1195,8 @@ void Courtroom::handle_chatmessage_3() { qDebug() << "handle_chatmessage_3"; + ui_vp_player_char->set_play_once(false); + setup_chat(); int f_anim_state = 0; @@ -918,8 +1210,7 @@ void Courtroom::handle_chatmessage_3() // idle f_anim_state = 3; - if (f_anim_state <= anim_state) - return; + if (f_anim_state <= anim_state) return; ui_vp_player_char->stop(); const QString f_char = m_chatmessage[CMChrName]; @@ -955,52 +1246,61 @@ void Courtroom::handle_chatmessage_3() // 3. No valid showname image was found ui_vp_player_char->hide(); if (ui_vp_player_char->is_running()) + { ui_vp_player_char->stop(); + } switch (f_anim_state) { - case 2: - if (!m_hide_character && !m_msg_is_first_person) - { - ui_vp_player_char->play_talk(f_char, f_emote); - } - anim_state = 2; - break; - default: - qDebug() << "W: invalid anim_state: " << f_anim_state; - [[fallthrough]]; - case 3: - if (!m_hide_character && !m_msg_is_first_person) - { - ui_vp_player_char->play_idle(f_char, f_emote); - } - anim_state = 3; - break; + case 2: + if (!m_hide_character && !m_msg_is_first_person) + { + swap_viewport_reader(ui_vp_player_char, ViewportCharacterTalk); + ui_vp_player_char->start(); + } + anim_state = 2; + break; + default: + qDebug() << "W: invalid anim_state: " << f_anim_state; + [[fallthrough]]; + case 3: + if (!m_hide_character && !m_msg_is_first_person) + { + swap_viewport_reader(ui_vp_player_char, ViewportCharacterIdle); + ui_vp_player_char->start(); + } + anim_state = 3; + break; } - int effect = m_chatmessage[CMEffectState].toInt(); - - if (effect > 0 && effect <= ui_effects.size() && effect_names.size() > 0) // check to prevent crashing { - QStringList offset = ao_app->get_effect_offset(f_char, effect); - ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), ui_viewport->y() + offset.at(1).toInt()); + bool l_effect_index_result; + const int l_effect_index = m_chatmessage[CMEffectState].toInt(&l_effect_index_result); + if (l_effect_index_result) + { + const QString l_effect_name = get_effect_name(l_effect_index); + if (!l_effect_name.isEmpty()) // check to prevent crashing + { + QStringList offset = ao_app->get_effect_offset(f_char, l_effect_index); + ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), ui_viewport->y() + offset.at(1).toInt()); - QString s_eff = effect_names.at(effect - 1); - QStringList f_eff = ao_app->get_effect(effect); + QString s_eff = effect_names.at(l_effect_index - 1); + QStringList f_eff = ao_app->get_effect(l_effect_index); - bool once = f_eff.at(1).trimmed().toInt(); + bool once = f_eff.at(1).trimmed().toInt(); - QStringList overlay = ao_app->get_overlay(f_char, effect); - QString overlay_name = overlay.at(0); - QString overlay_sfx = overlay.at(1); + QStringList overlay = ao_app->get_overlay(f_char, l_effect_index); + QString overlay_name = overlay.at(0); + QString overlay_sfx = overlay.at(1); - if (overlay_sfx == "") - overlay_sfx = ao_app->get_sfx(s_eff); - m_effects_player->play_effect(overlay_sfx); + if (overlay_sfx == "") overlay_sfx = ao_app->get_sfx(s_eff); + m_effects_player->play_effect(overlay_sfx); - if (overlay_name == "") - overlay_name = s_eff; - ui_vp_effect->set_play_once(once); - ui_vp_effect->play(overlay_name, f_char); + if (overlay_name == "") overlay_name = s_eff; + ui_vp_effect->set_play_once(once); + swap_viewport_reader(ui_vp_effect, ViewportEffect); + ui_vp_effect->start(); + } + } } QString f_message = m_chatmessage[CMMessage]; @@ -1016,8 +1316,7 @@ void Courtroom::handle_chatmessage_3() const QString message = ui_vp_showname->toPlainText() + " has called you via your callword \"" + word + "\": \"" + f_message + "\""; ui_ooc_chatlog->append_chatmessage(name, message); - if (ao_config->log_is_recording_enabled()) - save_textlog("(OOC)" + name + ": " + message); + if (ao_config->log_is_recording_enabled()) save_textlog("(OOC)" + name + ": " + message); break; } } @@ -1052,8 +1351,7 @@ void Courtroom::load_ic_text_format() set_format_color("showname", m_ic_log_format.name); m_ic_log_format.selfname = m_ic_log_format.name; - if (ao_config->log_display_self_highlight_enabled()) - set_format_color("selfname", m_ic_log_format.selfname); + if (ao_config->log_display_self_highlight_enabled()) set_format_color("selfname", m_ic_log_format.selfname); m_ic_log_format.message = m_ic_log_format.base; set_format_color("message", m_ic_log_format.message); @@ -1104,11 +1402,9 @@ void Courtroom::update_ic_log(bool p_reset_log) const DRChatRecord l_record = m_ic_record_queue.takeFirst(); m_ic_record_list.append(l_record); - if (!ao_config->log_display_empty_messages_enabled() && l_record.get_message().trimmed().isEmpty()) - continue; + if (!ao_config->log_display_empty_messages_enabled() && l_record.get_message().trimmed().isEmpty()) continue; - if (!ao_config->log_display_music_switch_enabled() && l_record.is_music()) - continue; + if (!ao_config->log_display_music_switch_enabled() && l_record.is_music()) continue; l_cursor.movePosition(move_type); @@ -1117,8 +1413,7 @@ void Courtroom::update_ic_log(bool p_reset_log) l_cursor.insertText(l_linefeed + QString(l_use_newline ? l_linefeed : nullptr), l_message_format); l_log_is_empty = false; - if (!l_topdown_orientation) - l_cursor.movePosition(move_type); + if (!l_topdown_orientation) l_cursor.movePosition(move_type); // self-highlight check const QTextCharFormat &l_target_name_format = @@ -1210,11 +1505,9 @@ void Courtroom::on_ic_chatlog_scroll_bottomup_clicked() void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, int p_client_id, bool p_self) { - if (p_name.trimmed().isEmpty()) - p_name = "Anonymous"; + if (p_name.trimmed().isEmpty()) p_name = "Anonymous"; - if (p_line.trimmed().isEmpty()) - p_line = p_line.trimmed(); + if (p_line.trimmed().isEmpty()) p_line = p_line.trimmed(); DRChatRecord new_record(p_name, p_line); new_record.set_system(p_system); @@ -1233,9 +1526,10 @@ void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bo */ void Courtroom::append_system_text(QString p_showname, QString p_line) { - if (chatmessage_is_empty) + if (p_line.isEmpty()) + { return; - + } append_ic_text(p_showname, p_line, true, false, NoClientId, false); } @@ -1247,19 +1541,26 @@ void Courtroom::play_preanim() if (m_msg_is_first_person) { + qDebug() << "[viewport] Skipping character animation as first person."; preanim_done(); return; } const QString l_chr_name = m_chatmessage[CMChrName]; const QString l_anim_name = m_chatmessage[CMPreAnim]; - qDebug() << "Playing character animation; character:" << l_chr_name << "animation: " << l_anim_name + qDebug() << "[viewport] Playing character animation; character:" << l_chr_name << "animation: " << l_anim_name << "file:" << ui_vp_player_char->file_name(); - ui_vp_player_char->play_pre(l_chr_name, l_anim_name); + ui_vp_player_char->set_play_once(true); + swap_viewport_reader(ui_vp_player_char, ViewportCharacterPre); + ui_vp_player_char->start(); } void Courtroom::preanim_done() { + if (anim_state != 1) + { + return; + } handle_chatmessage_3(); } @@ -1276,8 +1577,7 @@ void Courtroom::setup_chat() m_rainbow_step = 0; // we need to ensure that the text isn't already ticking because this function // can be called by two logic paths - if (text_state != 0) - return; + if (text_state != 0) return; if (chatmessage_is_empty) { @@ -1309,22 +1609,21 @@ void Courtroom::setup_chat() void Courtroom::start_chat_timer() { if (m_tick_timer->isActive()) + { return; + } m_tick_timer->start(); } void Courtroom::stop_chat_timer() { - if (!m_tick_timer->isActive()) - return; m_tick_timer->stop(); } void Courtroom::calculate_chat_tick_interval() { double l_tick_rate = ao_config->chat_tick_interval(); - if (m_server_tick_rate.has_value()) - l_tick_rate = qMax(m_server_tick_rate.value(), 0); + if (m_server_tick_rate.has_value()) l_tick_rate = qMax(m_server_tick_rate.value(), 0); l_tick_rate = qBound(0.0, l_tick_rate * (1.0 - qBound(-1.0, 0.4 * m_tick_speed, 1.0)), l_tick_rate * 2.0); m_tick_timer->setInterval(l_tick_rate); } @@ -1334,7 +1633,7 @@ void Courtroom::next_chat_letter() const QString &f_message = m_chatmessage[CMMessage]; if (m_tick_step >= f_message.length() || ui_vp_chatbox->isHidden()) { - post_chat(); + post_chatmessage(); return; } @@ -1361,7 +1660,6 @@ void Courtroom::next_chat_letter() m_tick_speed = qBound(-3, m_tick_speed + (is_positive ? 1 : -1), 3); calculate_chat_tick_interval(); next_chat_letter(); - start_chat_timer(); return; } else if (f_character == Qt::Key_Space) @@ -1374,21 +1672,21 @@ void Courtroom::next_chat_letter() switch (m_rainbow_step) { - case 0: - html_color = "#BA1518"; - break; - case 1: - html_color = "#D55900"; - break; - case 2: - html_color = "#E7CE4E"; - break; - case 3: - html_color = "#65C856"; - break; - default: - html_color = "#1596C8"; - m_rainbow_step = -1; + case 0: + html_color = "#BA1518"; + break; + case 1: + html_color = "#D55900"; + break; + case 2: + html_color = "#E7CE4E"; + break; + case 3: + html_color = "#65C856"; + break; + default: + html_color = "#1596C8"; + m_rainbow_step = -1; } ++m_rainbow_step; @@ -1406,8 +1704,7 @@ void Courtroom::next_chat_letter() // render_character should only be false if the character is a highlight // character specifically marked as a character that should not be // rendered. - if (m_message_color_stack.isEmpty()) - m_message_color_stack.push(""); + if (m_message_color_stack.isEmpty()) m_message_color_stack.push(""); if (!is_ignore_next_letter) { @@ -1442,8 +1739,7 @@ void Courtroom::next_chat_letter() { if (f_character == col[0][1]) { - if (m_message_color_stack.size() > 1) - m_message_color_stack.pop(); + if (m_message_color_stack.size() > 1) m_message_color_stack.pop(); m_future_string_color = m_message_color_stack.top(); render_character = (col[2] != "0"); break; @@ -1451,8 +1747,7 @@ void Courtroom::next_chat_letter() } } - if (render_character) - ui_vp_message->textCursor().insertText(f_character, vp_message_format); + if (render_character) ui_vp_message->textCursor().insertText(f_character, vp_message_format); m_message_color_name = m_future_string_color; } @@ -1466,7 +1761,6 @@ void Courtroom::next_chat_letter() if ((f_message.at(m_tick_step) != ' ' || ao_config->blank_blips_enabled())) { - if (m_blip_step % ao_config->blip_rate() == 0) { m_blip_step = 0; @@ -1477,22 +1771,23 @@ void Courtroom::next_chat_letter() ++m_blip_step; } - ui_vp_message->repaint(); ++m_tick_step; is_ignore_next_letter = false; + m_tick_timer->start(); } -void Courtroom::post_chat() +void Courtroom::post_chatmessage() { + m_tick_timer->stop(); text_state = 2; anim_state = 3; - stop_chat_timer(); if (!m_hide_character && !m_msg_is_first_person) { - ui_vp_player_char->play_idle(m_chatmessage[CMChrName], m_chatmessage[CMEmote]); + swap_viewport_reader(ui_vp_player_char, ViewportCharacterIdle); + ui_vp_player_char->start(); } m_message_color_name = ""; @@ -1508,12 +1803,16 @@ void Courtroom::post_chat() void Courtroom::play_sfx() { const QString l_effect = m_chatmessage[CMSoundName]; - if (l_effect.isEmpty() || l_effect == "0" || l_effect == "1") - return; + if (l_effect.isEmpty() || l_effect == "0" || l_effect == "1") return; const QString l_chr = m_chatmessage[CMChrName]; m_effects_player->play_character_effect(l_chr, l_effect); } +void Courtroom::on_loading_bar_delay_changed(int p_delay) +{ + m_loading_timer->setInterval(p_delay); +} + void Courtroom::set_text_color() { const QMap color_map = ao_app->get_chatmessage_colors(); @@ -1525,8 +1824,7 @@ void Courtroom::set_text_color() void Courtroom::set_muted(bool p_muted, int p_cid) { - if (p_cid != m_chr_id && p_cid != SpectatorId) - return; + if (p_cid != m_chr_id && p_cid != SpectatorId) return; if (p_muted) ui_muted->show(); @@ -1545,8 +1843,7 @@ void Courtroom::set_muted(bool p_muted, int p_cid) void Courtroom::set_ban(int p_cid) { - if (p_cid != m_chr_id && p_cid != SpectatorId) - return; + if (p_cid != m_chr_id && p_cid != SpectatorId) return; call_notice("You have been banned."); @@ -1557,8 +1854,7 @@ void Courtroom::set_ban(int p_cid) void Courtroom::handle_song(QStringList p_contents) { const bool l_server_compatible = ao_app->is_server_client_version_compatible(); - if (p_contents.size() < (l_server_compatible ? 4 : 3)) - return; + if (p_contents.size() < (l_server_compatible ? 4 : 3)) return; QString l_song = p_contents.at(0); for (auto &i_extension : audio_extensions()) @@ -1579,8 +1875,7 @@ void Courtroom::handle_song(QStringList p_contents) if (l_server_compatible) { const bool l_restart = p_contents.at(3).toInt(); - if (m_current_song == l_song && !l_restart) - return; + if (m_current_song == l_song && !l_restart) return; } m_current_song = l_song; @@ -1622,8 +1917,7 @@ void Courtroom::handle_wtce(QString p_wtce) void Courtroom::set_hp_bar(int p_bar, int p_state) { - if (p_state < 0 || p_state > 10) - return; + if (p_state < 0 || p_state > 10) return; if (p_bar == 1) { @@ -1640,8 +1934,7 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) void Courtroom::set_character_position(QString p_pos) { int index = ui_pos_dropdown->findData(p_pos); - if (index != -1) - ui_pos_dropdown->setCurrentIndex(index); + if (index != -1) ui_pos_dropdown->setCurrentIndex(index); // enable judge mechanics if appropriate set_judge_enabled(p_pos == "jud"); @@ -1685,8 +1978,7 @@ void Courtroom::mod_called(QString p_ip) { m_system_player->play(ao_app->get_sfx("mod_call")); ao_app->alert(this); - if (ao_config->log_is_recording_enabled()) - save_textlog("(OOC)(MOD CALL)" + p_ip); + if (ao_config->log_is_recording_enabled()) save_textlog("(OOC)(MOD CALL)" + p_ip); } } @@ -1758,8 +2050,7 @@ void Courtroom::on_ooc_return_pressed() // Note arguments[0] == "/ts", so every index (and thus length) is off by // one. - if (size > 5) - return; + if (size > 5) return; int timer_id = (size > 1 ? arguments[1].toInt() : 0); int new_time = (size > 2 ? arguments[2].toInt() : 300) * 1000; @@ -1792,37 +2083,35 @@ void Courtroom::on_pos_dropdown_changed(int p_index) { ui_ic_chat_message->setFocus(); - if (p_index < 0 || p_index > 5) - return; + if (p_index < 0 || p_index > 5) return; QString f_pos; switch (p_index) { - case 0: - f_pos = "wit"; - break; - case 1: - f_pos = "def"; - break; - case 2: - f_pos = "pro"; - break; - case 3: - f_pos = "jud"; - break; - case 4: - f_pos = "hld"; - break; - case 5: - f_pos = "hlp"; - break; - default: - f_pos = ""; - } - - if (f_pos == "" || ao_config->username() == "") - return; + case 0: + f_pos = "wit"; + break; + case 1: + f_pos = "def"; + break; + case 2: + f_pos = "pro"; + break; + case 3: + f_pos = "jud"; + break; + case 4: + f_pos = "hld"; + break; + case 5: + f_pos = "hlp"; + break; + default: + f_pos = ""; + } + + if (f_pos == "" || ao_config->username() == "") return; set_judge_enabled(f_pos == "jud"); @@ -1868,8 +2157,8 @@ void Courtroom::on_music_list_double_clicked(QModelIndex p_model) void Courtroom::on_music_list_context_menu_requested(QPoint p_point) { - const QPoint l_global_pos = ui_music_list->viewport()->mapToGlobal(p_point); - ui_music_menu->popup(l_global_pos); + const QPoint l_global_point = ui_music_list->viewport()->mapToGlobal(p_point); + ui_music_menu->popup(mapToGlobal(l_global_point)); } void Courtroom::on_music_menu_play_triggered() @@ -1906,8 +2195,7 @@ void Courtroom::on_music_search_edited() void Courtroom::send_mc_packet(QString p_song) { - if (is_client_muted) - return; + if (is_client_muted) return; ao_app->send_server_packet(DRPacket("MC", {p_song, QString::number(m_chr_id)})); } @@ -1928,19 +2216,16 @@ void Courtroom::reset_shout_buttons() void Courtroom::on_shout_button_clicked(const bool p_checked) { AOButton *l_button = dynamic_cast(sender()); - if (l_button == nullptr) - return; + if (l_button == nullptr) return; bool l_ok = false; const int l_id = l_button->property("shout_id").toInt(&l_ok); - if (!l_ok) - return; + if (!l_ok) return; // disable all other buttons for (AOButton *i_button : qAsConst(ui_shouts)) { - if (i_button == l_button) - continue; + if (i_button == l_button) continue; i_button->setChecked(false); } m_shout_state = p_checked ? l_id : 0; @@ -1951,17 +2236,14 @@ void Courtroom::on_shout_button_clicked(const bool p_checked) void Courtroom::on_shout_button_toggled(const bool p_checked) { AOButton *l_button = dynamic_cast(sender()); - if (l_button == nullptr) - return; + if (l_button == nullptr) return; const QString l_name = l_button->property("shout_name").toString(); - if (l_name.isEmpty()) - return; + if (l_name.isEmpty()) return; const QString l_image_name(QString("%1%2.png").arg(l_name, QString(p_checked ? "_selected" : nullptr))); l_button->set_image(l_image_name); - if (!l_button->has_image()) - l_button->setText(l_name); + if (!l_button->has_image()) l_button->setText(l_name); } void Courtroom::on_cycle_clicked() @@ -1971,26 +2253,26 @@ void Courtroom::on_cycle_clicked() switch (f_cycle_id) { - case 5: - cycle_wtce(-1); - break; - case 4: - cycle_wtce(1); - break; - case 3: - cycle_effect(-1); - break; - case 2: - cycle_effect(1); - break; - case 1: - cycle_shout(-1); - break; - case 0: - cycle_shout(1); - break; - default: - break; + case 5: + cycle_wtce(-1); + break; + case 4: + cycle_wtce(1); + break; + case 3: + cycle_effect(-1); + break; + case 2: + cycle_effect(1); + break; + case 1: + cycle_shout(-1); + break; + case 0: + cycle_shout(1); + break; + default: + break; } if (ao_app->read_theme_ini_bool("enable_cycle_ding", COURTROOM_CONFIG_INI)) @@ -2053,19 +2335,16 @@ void Courtroom::reset_effect_buttons() void Courtroom::on_effect_button_clicked(const bool p_checked) { AOButton *l_button = dynamic_cast(sender()); - if (l_button == nullptr) - return; + if (l_button == nullptr) return; bool l_ok = false; const int l_id = l_button->property("effect_id").toInt(&l_ok); - if (!l_ok) - return; + if (!l_ok) return; // disable all other buttons for (AOButton *i_button : qAsConst(ui_effects)) { - if (i_button == l_button) - continue; + if (i_button == l_button) continue; i_button->setChecked(false); } @@ -2076,49 +2355,42 @@ void Courtroom::on_effect_button_clicked(const bool p_checked) void Courtroom::on_effect_button_toggled(const bool p_checked) { AOButton *l_button = dynamic_cast(sender()); - if (l_button == nullptr) - return; + if (l_button == nullptr) return; const QString l_name = l_button->property("effect_name").toString(); - if (l_name.isEmpty()) - return; + if (l_name.isEmpty()) return; const QString l_image_name(QString("%1%2.png").arg(l_name, QString(p_checked ? "_pressed" : nullptr))); l_button->set_image(l_image_name); - if (!l_button->has_image()) - l_button->setText(l_name); + if (!l_button->has_image()) l_button->setText(l_name); } void Courtroom::on_defense_minus_clicked() { int f_state = defense_bar_state - 1; - if (f_state >= 0) - ao_app->send_server_packet(DRPacket("HP", {QString::number(1), QString::number(f_state)})); + if (f_state >= 0) ao_app->send_server_packet(DRPacket("HP", {QString::number(1), QString::number(f_state)})); } void Courtroom::on_defense_plus_clicked() { int f_state = defense_bar_state + 1; - if (f_state <= 10) - ao_app->send_server_packet(DRPacket("HP", {QString::number(1), QString::number(f_state)})); + if (f_state <= 10) ao_app->send_server_packet(DRPacket("HP", {QString::number(1), QString::number(f_state)})); } void Courtroom::on_prosecution_minus_clicked() { int f_state = prosecution_bar_state - 1; - if (f_state >= 0) - ao_app->send_server_packet(DRPacket("HP", {QString::number(2), QString::number(f_state)})); + if (f_state >= 0) ao_app->send_server_packet(DRPacket("HP", {QString::number(2), QString::number(f_state)})); } void Courtroom::on_prosecution_plus_clicked() { int f_state = prosecution_bar_state + 1; - if (f_state <= 10) - ao_app->send_server_packet(DRPacket("HP", {QString::number(2), QString::number(f_state)})); + if (f_state <= 10) ao_app->send_server_packet(DRPacket("HP", {QString::number(2), QString::number(f_state)})); } void Courtroom::on_text_color_changed(int p_color) @@ -2155,8 +2427,7 @@ void Courtroom::reset_wtce_buttons() void Courtroom::on_wtce_clicked() { // qDebug() << "AA: wtce clicked!"; - if (is_client_muted) - return; + if (is_client_muted) return; AOButton *f_sig = static_cast(sender()); QString id = f_sig->property("wtce_id").toString(); @@ -2187,7 +2458,6 @@ void Courtroom::load_theme() } setup_courtroom(); - update_background_scene(); } void Courtroom::load_character() @@ -2204,8 +2474,7 @@ void Courtroom::load_audiotracks() void Courtroom::on_back_to_lobby_clicked() { - if (m_back_to_lobby_clicked) - return; + if (m_back_to_lobby_clicked) return; m_back_to_lobby_clicked = true; // hide so we don't get the 'disconnected from server' prompt @@ -2240,8 +2509,7 @@ void Courtroom::on_call_mod_clicked() void Courtroom::on_switch_area_music_clicked() { - if (is_area_music_list_separated()) - return; + if (is_area_music_list_separated()) return; if (ui_area_list->isHidden()) { @@ -2316,8 +2584,7 @@ void Courtroom::changeEvent(QEvent *event) if (event->type() == QEvent::WindowStateChange) { m_is_maximized = windowState().testFlag(Qt::WindowMaximized); - if (!m_is_maximized) - resize(m_default_size); + if (!m_is_maximized) resize(m_default_size); } } @@ -2337,40 +2604,35 @@ void Courtroom::on_set_notes_clicked() void Courtroom::resume_timer(int p_id) { - if (p_id < 0 || p_id >= ui_timers.length()) - return; + if (p_id < 0 || p_id >= ui_timers.length()) return; AOTimer *l_timer = ui_timers.at(p_id); l_timer->resume(); } void Courtroom::set_timer_time(int p_id, int new_time) { - if (p_id < 0 || p_id >= ui_timers.length()) - return; + if (p_id < 0 || p_id >= ui_timers.length()) return; AOTimer *l_timer = ui_timers.at(p_id); l_timer->set_time(QTime(0, 0).addMSecs(new_time)); } void Courtroom::set_timer_timestep(int p_id, int timestep_length) { - if (p_id < 0 || p_id >= ui_timers.length()) - return; + if (p_id < 0 || p_id >= ui_timers.length()) return; AOTimer *l_timer = ui_timers.at(p_id); l_timer->set_timestep_length(timestep_length); } void Courtroom::set_timer_firing(int p_id, int firing_interval) { - if (p_id < 0 || p_id >= ui_timers.length()) - return; + if (p_id < 0 || p_id >= ui_timers.length()) return; AOTimer *l_timer = ui_timers.at(p_id); l_timer->set_firing_interval(firing_interval); } void Courtroom::pause_timer(int p_id) { - if (p_id < 0 || p_id >= ui_timers.length()) - return; + if (p_id < 0 || p_id >= ui_timers.length()) return; AOTimer *l_timer = ui_timers.at(p_id); l_timer->pause(); } diff --git a/src/courtroom.h b/src/courtroom.h index 42a31dac4..776a1cdd7 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -2,6 +2,8 @@ #define COURTROOM_H #include "datatypes.h" +#include "drposition.h" +#include "mk2/spritereadersynchronizer.h" class AOApplication; class AOBlipPlayer; @@ -22,8 +24,8 @@ class AOSystemPlayer; class AOTimer; class DRCharacterMovie; class DRChatLog; +class DRMovie; class DREffectMovie; -class DRPositionReader; class DRSceneMovie; class DRShoutMovie; class DRSplashMovie; @@ -35,6 +37,7 @@ class DRVideoWidget; #include #include #include +#include #include #include @@ -92,11 +95,17 @@ class Courtroom : public QMainWindow // called when a DONE#% from the server was received void done_received(); - // updates background and front based on the position given from the chatmessage + void set_ambient(QString ambient_sfx); + void play_ambient(); + + // updates background based on the position given from the chatmessage; will reset preloading if active void update_background_scene(); - // updates background and front based on legacy background - void update_legacy_background_scene(); + // displays the current background + void display_background_scene(); + + // returns a position map based on legacy background implementation + DRPositionMap get_legacy_background(QString background); // sets text color based on text color in chatmessage void set_text_color(); @@ -159,7 +168,10 @@ class Courtroom : public QMainWindow // these functions handle chatmessages sequentially. // The process itself is very convoluted and merits separate documentation // But the general idea is objection animation->pre animation->talking->idle - void handle_chatmessage(QStringList p_contents); + void next_chatmessage(QStringList p_contents); + void reset_viewport(); + void preload_chatmessage(QStringList p_contents); + void handle_chatmessage(); void handle_chatmessage_2(); void handle_chatmessage_3(); @@ -271,8 +283,12 @@ class Courtroom : public QMainWindow // Generate a File Name based on the time you launched the client QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); - static const int MESSAGE_SIZE = 19; - QString m_chatmessage[MESSAGE_SIZE]; + static const int MINIMUM_MESSAGE_SIZE = 15; + static const int OPTIMAL_MESSAGE_SIZE = 19; + QStringList m_pre_chatmessage; + QTimer *m_loading_timer; + mk2::SpriteReaderSynchronizer *m_preloader_sync; + QStringList m_chatmessage; int m_speaker_chr_id = SpectatorId; QString m_speaker_showname; bool m_hide_character = false; @@ -338,10 +354,10 @@ class Courtroom : public QMainWindow int m_current_clock = -1; + QString m_ambient_sfx; DRAreaBackground m_background; - QString m_current_background_name; - bool m_is_legacy_background = false; - DRPositionReader *m_position_reader = nullptr; + QString m_background_name; + DRPositionMap m_position_map; AOImageDisplay *ui_background = nullptr; @@ -363,6 +379,20 @@ class Courtroom : public QMainWindow DRSplashMovie *ui_vp_wtce = nullptr; DRShoutMovie *ui_vp_objection = nullptr; DRStickerMovie *ui_vp_chat_arrow = nullptr; + DRStickerMovie *ui_vp_loading = nullptr; + + QMap> m_mapped_viewer_list; + QMap m_viewport_viewer_map; + QMap m_preloader_cache; + QMap m_reader_cache; + + void map_viewers(); + void map_viewport_viewers(); + void map_viewport_readers(); + mk2::SpriteReader::ptr get_viewport_reader(ViewportSprite type) const; + void assign_readers_for_all_viewers(); + void swap_viewport_reader(DRMovie *viewer, ViewportSprite type); + void cleanup_preload_readers(); AOImageDisplay *ui_vp_music_display_a = nullptr; AOImageDisplay *ui_vp_music_display_b = nullptr; @@ -398,6 +428,9 @@ class Courtroom : public QMainWindow const QString m_sfx_default_file = "__DEFAULT__"; QColor m_sfx_color_found; QColor m_sfx_color_missing; + QMenu *ui_sfx_menu = nullptr; + QAction *ui_sfx_menu_preview = nullptr; + QAction *ui_sfx_menu_insert_ooc = nullptr; QLineEdit *ui_ic_chat_showname = nullptr; QLineEdit *ui_ic_chat_message = nullptr; @@ -513,7 +546,8 @@ class Courtroom : public QMainWindow void reset_widget_names(); void insert_widget_name(QString p_widget_name, QWidget *p_widget); void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); - template void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); + template + void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); void set_widget_layers(); void construct_char_select(); @@ -536,6 +570,9 @@ class Courtroom : public QMainWindow bool is_spectating(); + QString get_shout_name(int shout_index); + QString get_effect_name(int effect_index); + public slots: void video_finished(); void objection_done(); @@ -549,11 +586,14 @@ private slots: void setup_chat(); void play_sfx(); + void on_loading_bar_delay_changed(int p_delay); + void start_chatmessage(); + void start_chat_timer(); void stop_chat_timer(); void calculate_chat_tick_interval(); void next_chat_letter(); - void post_chat(); + void post_chatmessage(); void on_showname_changed(QString); void on_showname_placeholder_changed(QString); @@ -666,6 +706,9 @@ private slots: void ping_server(); + // performance + void assign_readers_for_viewers(int p_type, bool p_caching); + // character // =========================================================================== @@ -705,7 +748,10 @@ public slots: void set_sfx_item_color(QListWidgetItem *item); private slots: - void _p_sfxCurrentItemChanged(QListWidgetItem *current_item, QListWidgetItem *previous_item); + void on_sfx_list_current_item_changed(QListWidgetItem *current_item, QListWidgetItem *previous_item); + void on_sfx_list_context_menu_requested(QPoint point); + void on_sfx_menu_preview_triggered(); + void on_sfx_menu_insert_ooc_triggered(); /*! * ============================================================================= @@ -734,7 +780,8 @@ public slots: void closeEvent(QCloseEvent *event) override; }; -template void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) +template +void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) { QVector widgets; diff --git a/src/courtroom_sfx.cpp b/src/courtroom_sfx.cpp index 0aa5a8017..7d66fe9fc 100644 --- a/src/courtroom_sfx.cpp +++ b/src/courtroom_sfx.cpp @@ -1,6 +1,7 @@ #include "courtroom.h" #include "aoapplication.h" +#include "aosfxplayer.h" #include "commondefs.h" #include "file_functions.h" @@ -8,6 +9,7 @@ #include #include #include +#include #include @@ -53,7 +55,7 @@ void Courtroom::load_current_character_sfx_list() const QString l_name = l_sfx_entry.at(l_sfx_entry.size() - 1).trimmed(); const QString l_file = QString(l_sfx_entry.size() >= 2 ? l_sfx_entry.at(0) : nullptr).trimmed(); - const bool l_is_found = !ao_app->find_asset_path({ao_app->get_sounds_path(l_file)}, audio_extensions()).isEmpty(); + const bool l_is_found = !ao_app->find_asset_path({ao_app->get_sfx_noext_path(l_file)}, audio_extensions()).isEmpty(); m_sfx_list.append(DRSfx(l_name, l_file, l_is_found)); } @@ -122,10 +124,27 @@ void Courtroom::update_all_sfx_item_color() set_sfx_item_color(ui_sfx_list->item(i)); } -void Courtroom::_p_sfxCurrentItemChanged(QListWidgetItem *p_current_item, QListWidgetItem *p_previous_item) +void Courtroom::on_sfx_list_current_item_changed(QListWidgetItem *p_current_item, QListWidgetItem *p_previous_item) { set_sfx_item_color(p_current_item); set_sfx_item_color(p_previous_item); ui_pre->setChecked(ui_pre->isChecked() || current_sfx().has_value()); ui_ic_chat_message->setFocus(); } + +void Courtroom::on_sfx_list_context_menu_requested(QPoint p_point) +{ + const QPoint l_global_point = ui_sfx_list->viewport()->mapToGlobal(p_point); + ui_sfx_menu->popup(l_global_point); +} + +void Courtroom::on_sfx_menu_preview_triggered() +{ + m_effects_player->play_effect(current_sfx_file()); +} + +void Courtroom::on_sfx_menu_insert_ooc_triggered() +{ + ui_ooc_chat_message->insert(current_sfx_file()); + ui_ooc_chat_message->setFocus(); +} diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 1eddd594a..8b44883ab 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -47,8 +47,8 @@ void Courtroom::create_widgets() m_keepalive_timer->start(60000); m_tick_timer = new QTimer(this); - m_tick_timer->setSingleShot(false); - m_tick_timer->setTimerType(Qt::PreciseTimer); // Prevents drift + m_tick_timer->setSingleShot(true); + m_tick_timer->setTimerType(Qt::PreciseTimer); m_sound_timer = new QTimer(this); m_sound_timer->setSingleShot(true); @@ -101,13 +101,16 @@ void Courtroom::create_widgets() ui_vp_showname_image = new AOImageDisplay(this, ao_app); ui_vp_effect = new DREffectMovie(this); - ui_vp_effect->set_hide_on_done(true); ui_vp_wtce = new DRSplashMovie(this); - ui_vp_wtce->set_hide_on_done(true); ui_vp_objection = new DRShoutMovie(this); - ui_vp_objection->set_hide_on_done(true); ui_vp_chat_arrow = new DRStickerMovie(this); + ui_vp_loading = new DRStickerMovie(this); + ui_vp_loading->hide(); + + m_loading_timer = new QTimer(this); + m_loading_timer->setSingleShot(true); + m_loading_timer->setInterval(ao_config->loading_bar_delay()); ui_iniswap_dropdown = new QComboBox(this); ui_iniswap_dropdown->setInsertPolicy(QComboBox::NoInsert); @@ -143,8 +146,12 @@ void Courtroom::create_widgets() ui_music_menu_insert_ooc = ui_music_menu->addAction(tr("Insert to OOC")); ui_sfx_list = new QListWidget(this); + ui_sfx_list->setContextMenuPolicy(Qt::CustomContextMenu); ui_sfx_search = new QLineEdit(this); ui_sfx_search->setFrame(false); + ui_sfx_menu = new QMenu(this); + ui_sfx_menu_preview = ui_sfx_menu->addAction(tr("Preview")); + ui_sfx_menu_insert_ooc = ui_sfx_menu->addAction(tr("Insert to OOC")); ui_ic_chat_showname = new QLineEdit(this); ui_ic_chat_showname->setFrame(false); @@ -347,10 +354,22 @@ void Courtroom::connect_widgets() connect(ui_hide_character, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); connect(ui_sfx_list, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, - SLOT(_p_sfxCurrentItemChanged(QListWidgetItem *, QListWidgetItem *))); + SLOT(on_sfx_list_current_item_changed(QListWidgetItem *, QListWidgetItem *))); + connect(ui_sfx_list, SIGNAL(customContextMenuRequested(QPoint)), this, + SLOT(on_sfx_list_context_menu_requested(QPoint))); + + connect(ui_sfx_menu_preview, SIGNAL(triggered()), this, SLOT(on_sfx_menu_preview_triggered())); + connect(ui_sfx_menu_insert_ooc, SIGNAL(triggered()), this, SLOT(on_sfx_menu_insert_ooc_triggered())); connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, SLOT(on_add_button_clicked())); connect(ui_set_notes, SIGNAL(clicked(bool)), this, SLOT(on_set_notes_clicked())); + + // performance + connect(ao_config, SIGNAL(sprite_caching_toggled(int, bool)), this, SLOT(assign_readers_for_viewers(int, bool))); + connect(ao_config, SIGNAL(caching_threshold_changed(int)), m_preloader_sync, SLOT(set_threshold(int))); + connect(m_preloader_sync, SIGNAL(finished()), this, SLOT(start_chatmessage())); + connect(ao_config, SIGNAL(loading_bar_delay_changed(int)), this, SLOT(on_loading_bar_delay_changed(int))); + connect(m_loading_timer, SIGNAL(timeout()), ui_vp_loading, SLOT(show())); } void Courtroom::reset_widget_names() @@ -377,6 +396,7 @@ void Courtroom::reset_widget_names() {"vp_wtce", ui_vp_wtce}, {"vp_objection", ui_vp_objection}, {"chat_arrow", ui_vp_chat_arrow}, + {"loading", ui_vp_loading}, {"ic_chatlog", ui_ic_chatlog}, {"ic_chatlog_scroll_topdown", ui_ic_chatlog_scroll_topdown}, {"ic_chatlog_scroll_bottomup", ui_ic_chatlog_scroll_bottomup}, @@ -450,8 +470,7 @@ void Courtroom::insert_widget_name(QString p_widget_name, QWidget *p_widget) void Courtroom::insert_widget_names(QVector &p_name_list, QVector &p_widget_list) { - if (p_name_list.length() != p_widget_list.length()) - qFatal("[WARNING] Length of names and widgets differs!"); + if (p_name_list.length() != p_widget_list.length()) qFatal("[WARNING] Length of names and widgets differs!"); for (int i = 0; i < p_widget_list.length(); ++i) insert_widget_name(p_name_list[i], p_widget_list[i]); } @@ -504,8 +523,7 @@ void Courtroom::set_widget_layers() QString line = in.readLine().trimmed(); // skip if line is empty - if (line.isEmpty()) - continue; + if (line.isEmpty()) continue; // revert to default parent if we encounter an end scope if (line.startsWith("[\\")) @@ -522,21 +540,18 @@ void Courtroom::set_widget_layers() else { // if the child is already known, skip - if (recorded_widgets.contains(line)) - continue; + if (recorded_widgets.contains(line)) continue; // make the child known recorded_widgets.append(line); // attach the children to the parents' QWidget *child = widget_names[line]; // if child is null, then it does not exist - if (!child) - continue; + if (!child) continue; QWidget *parent = widget_names[parent_name]; // if parent is null, attach main parent - if (!parent) - parent = widget_names["courtroom"]; + if (!parent) parent = widget_names["courtroom"]; // set child to parent bool was_visible = child->isVisible(); @@ -547,8 +562,7 @@ void Courtroom::set_widget_layers() // parent I don't know why, I don't want to know why, I shouldn't have // to wonder why, but for whatever reason these stupid panels aren't // laying out correctly unless we do this terribleness - if (child->isVisible() != was_visible) - child->setVisible(was_visible); + if (child->isVisible() != was_visible) child->setVisible(was_visible); } } } @@ -631,6 +645,13 @@ void Courtroom::set_widgets() set_sticker_play_once(ui_vp_chat_arrow, "chat_arrow", COURTROOM_CONFIG_INI, ao_app); ui_vp_chat_arrow->hide(); + { + const bool l_visible = ui_vp_loading->isVisible(); + set_size_and_pos(ui_vp_loading, "loading", COURTROOM_DESIGN_INI, ao_app); + ui_vp_loading->play("loading"); + ui_vp_loading->setVisible(l_visible); + } + ui_vp_effect->move(ui_viewport->x(), ui_viewport->y()); ui_vp_effect->resize(ui_viewport->width(), ui_viewport->height()); ui_vp_effect->hide(); @@ -678,8 +699,7 @@ void Courtroom::set_widgets() ui_vp_music_display_b->show(); set_size_and_pos(ui_vp_clock, "clock", COURTROOM_DESIGN_INI, ao_app); - if (m_current_clock == -1) - ui_vp_clock->hide(); + if (m_current_clock == -1) ui_vp_clock->hide(); set_sticker_play_once(ui_vp_clock, "clock", COURTROOM_CONFIG_INI, ao_app); ui_vp_chatbox->set_theme_image("chatmed.png"); @@ -863,24 +883,19 @@ void Courtroom::set_widgets() // set_image first tries the gamemode-timeofday folder, then the theme // folder, then falls back to the default theme ui_change_character->set_image("changecharacter.png"); - if (ui_change_character->get_image().isEmpty()) - ui_change_character->setText("Change Character"); + if (ui_change_character->get_image().isEmpty()) ui_change_character->setText("Change Character"); ui_call_mod->set_image("callmod.png"); - if (ui_call_mod->get_image().isEmpty()) - ui_call_mod->setText("Call Mod"); + if (ui_call_mod->get_image().isEmpty()) ui_call_mod->setText("Call Mod"); ui_switch_area_music->set_image("switch_area_music.png"); - if (ui_switch_area_music->get_image().isEmpty()) - ui_switch_area_music->setText("A/M"); + if (ui_switch_area_music->get_image().isEmpty()) ui_switch_area_music->setText("A/M"); ui_config_panel->set_image("config_panel.png"); - if (ui_config_panel->get_image().isEmpty()) - ui_config_panel->setText("Config"); + if (ui_config_panel->get_image().isEmpty()) ui_config_panel->setText("Config"); ui_note_button->set_image("notebutton.png"); - if (ui_note_button->get_image().isEmpty()) - ui_note_button->setText("Notes"); + if (ui_note_button->get_image().isEmpty()) ui_note_button->setText("Notes"); } // The config panel has a special property. If it is displayed beyond the right or lower limit of the window, it will @@ -894,8 +909,7 @@ void Courtroom::set_widgets() ui_config_panel->move(0, 0); // Moreover, if the width or height is invalid, change it to some fixed // values - if (ui_config_panel->width() <= 0 || ui_config_panel->height() <= 0) - ui_config_panel->resize(64, 64); + if (ui_config_panel->width() <= 0 || ui_config_panel->height() <= 0) ui_config_panel->resize(64, 64); } set_size_and_pos(ui_pre, "pre", COURTROOM_DESIGN_INI, ao_app); @@ -1104,8 +1118,7 @@ void Courtroom::check_effects() { QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), effect_names.at(i))}, animated_extensions()); - if (path.isEmpty()) - path = ao_app->find_theme_asset_path(effect_names.at(i), animated_extensions()); + if (path.isEmpty()) path = ao_app->find_theme_asset_path(effect_names.at(i), animated_extensions()); effects_enabled[i] = (!path.isEmpty()); } } @@ -1123,8 +1136,7 @@ void Courtroom::check_free_blocks() { QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), free_block_names.at(i))}, animated_extensions()); - if (path.isEmpty()) - path = ao_app->find_theme_asset_path(free_block_names.at(i), animated_extensions()); + if (path.isEmpty()) path = ao_app->find_theme_asset_path(free_block_names.at(i), animated_extensions()); free_blocks_enabled[i] = (!path.isEmpty()); } } @@ -1143,8 +1155,7 @@ void Courtroom::check_shouts() QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), shout_names.at(i))}, animated_extensions()); - if (path.isEmpty()) - path = ao_app->find_theme_asset_path(shout_names.at(i), animated_extensions()); + if (path.isEmpty()) path = ao_app->find_theme_asset_path(shout_names.at(i), animated_extensions()); shouts_enabled[i] = (!path.isEmpty()); } @@ -1163,8 +1174,7 @@ void Courtroom::check_wtce() { QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), wtce_names.at(i))}, animated_extensions()); - if (path.isEmpty()) - path = ao_app->find_theme_asset_path(wtce_names.at(i), animated_extensions()); + if (path.isEmpty()) path = ao_app->find_theme_asset_path(wtce_names.at(i), animated_extensions()); wtce_enabled[i] = (!path.isEmpty()); } } @@ -1177,8 +1187,7 @@ void Courtroom::delete_widget(QWidget *p_widget) // transfer the children to our grandparent since our parent is about to be deleted QWidget *grand_parent = p_widget->parentWidget(); // if we don't have a grand parent, attach ourselves to courtroom - if (!grand_parent) - grand_parent = this; + if (!grand_parent) grand_parent = this; // set new parent for (QWidget *child : p_widget->findChildren(nullptr, Qt::FindDirectChildrenOnly)) @@ -1342,8 +1351,7 @@ void Courtroom::set_shouts() { for (auto &shout : ui_shouts) shout->hide(); - if (ui_shouts.size() > 0) - ui_shouts[m_shout_current]->show(); // check to prevent crashing + if (ui_shouts.size() > 0) ui_shouts[m_shout_current]->show(); // check to prevent crashing } /** @@ -1356,8 +1364,7 @@ void Courtroom::set_effects() effect->hide(); // check to prevent crashing - if (ui_effects.size() > 0) - ui_effects[m_effect_current]->show(); + if (ui_effects.size() > 0) ui_effects[m_effect_current]->show(); } void Courtroom::set_judge_enabled(bool p_enabled) @@ -1391,8 +1398,7 @@ void Courtroom::set_judge_wtce() ui_wtce_down->setVisible(is_judge && is_single_wtce); // prevent going ahead if we have no wtce - if (!is_judge || ui_wtce.length() == 0) - return; + if (!is_judge || ui_wtce.length() == 0) return; // set visibility based off parameter if (is_single_wtce == true) diff --git a/src/datatypes.cpp b/src/datatypes.cpp index 42f638689..9b366a8a6 100644 --- a/src/datatypes.cpp +++ b/src/datatypes.cpp @@ -75,3 +75,41 @@ bool VersionNumber::operator>(const VersionNumber &other) const { return release > other.release || (release == other.release && major > other.major); } + +const QMap s_sprite_category_string_map{ + {SpriteGUI, "gui"}, + {SpriteStage, "background"}, + {SpriteCharacter, "character"}, + {SpriteEffect, "effect"}, + {SpriteShout, "shout"}, + {SpriteSticker, "sticker"}, +}; + +SpriteCategory string_to_sprite_category(QString p_category) +{ + return s_sprite_category_string_map.key(p_category, SpriteGUI); +} + +QString sprite_category_to_string(SpriteCategory p_category) +{ + return s_sprite_category_string_map.value(p_category, "gui"); +} + +QStringList sprite_category_string_list() +{ + return s_sprite_category_string_map.values(); +} + +SpriteCategory viewport_sprite_to_sprite_category(ViewportSprite p_type) +{ + return QMap{ + {ViewportStageBack, SpriteStage}, + {ViewportStageFront, SpriteStage}, + {ViewportCharacterPre, SpriteCharacter}, + {ViewportCharacterIdle, SpriteCharacter}, + {ViewportCharacterTalk, SpriteCharacter}, + {ViewportEffect, SpriteEffect}, + {ViewportShout, SpriteShout}, + } + .value(p_type, SpriteGUI); +} diff --git a/src/datatypes.h b/src/datatypes.h index d2a77d821..c8a4172e0 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -35,7 +35,8 @@ enum ClientId class DRChatRecord { public: - DRChatRecord(QString p_name, QString p_message) : name(p_name), message(p_message) {} + DRChatRecord(QString p_name, QString p_message) + : name(p_name), message(p_message) {} QDateTime get_timestamp() const { return timestamp; } QString get_name() const { return name; } @@ -130,6 +131,33 @@ struct pos_size_type int height = 0; }; +enum SpriteCategory +{ + SpriteGUI, + SpriteStage, + SpriteCharacter, + SpriteEffect, + SpriteShout, + SpriteSticker, +}; + +SpriteCategory string_to_sprite_category(QString p_category); +QString sprite_category_to_string(SpriteCategory p_category); +QStringList sprite_category_string_list(); + +enum ViewportSprite +{ + ViewportStageBack, + ViewportStageFront, + ViewportCharacterPre, + ViewportCharacterIdle, + ViewportCharacterTalk, + ViewportEffect, + ViewportShout, +}; + +SpriteCategory viewport_sprite_to_sprite_category(ViewportSprite p_type); + enum ChatMessage : int32_t { CMDeskModifier = 0, @@ -205,7 +233,8 @@ struct ColorInfo { public: ColorInfo() = default; - ColorInfo(QString p_showname, QString p_code) : name(p_showname.toLower()), showname(p_showname), code(p_code) {} + ColorInfo(QString p_showname, QString p_code) + : name(p_showname.toLower()), showname(p_showname), code(p_code) {} QString name; QString showname; diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index e98512694..cc961540f 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -8,13 +8,22 @@ #include #include "draudioengine.h" -#include "draudiostreamfamily.h" -DRAudioStream::DRAudioStream(DRAudio::Family p_family) : m_engine(new DRAudioEngine(this)), m_family(p_family) +DRAudioStream::DRAudioStream(DRAudio::Family p_family) + : m_engine(new DRAudioEngine(this)) + , m_family(p_family) + , m_fade(NoFade) { + registerMetatypes(); + connect(m_engine, &DRAudioEngine::current_device_changed, this, &DRAudioStream::update_device); } +void DRAudioStream::registerMetatypes() +{ + qRegisterMetaType(); +} + DRAudioStream::~DRAudioStream() { if (m_hstream) @@ -30,7 +39,7 @@ DRAudio::Family DRAudioStream::get_family() const QString DRAudioStream::get_file_name() const { - return m_file_name; + return m_filename; } bool DRAudioStream::is_playing() const @@ -52,7 +61,7 @@ void DRAudioStream::play() if (!BASS_ChannelPlay(m_hstream, FALSE)) { qWarning() - << QString("error: failed to play file: %1 (file: \"%1\")").arg(DRAudio::get_last_bass_error(), m_file_name); + << QString("error: failed to play file: %1 (file: \"%1\")").arg(DRAudio::get_last_bass_error(), m_filename); Q_EMIT finished(); } } @@ -67,13 +76,13 @@ void DRAudioStream::stop() std::optional DRAudioStream::set_file_name(QString p_file_name) { - m_file_name = p_file_name; + m_filename = p_file_name; m_init_state = InitNotDone; if (!ensure_init()) { return DRAudioError("failed to set file"); } - emit file_name_changed(m_file_name); + emit file_name_changed(m_filename); return std::nullopt; } @@ -82,7 +91,7 @@ void DRAudioStream::set_volume(float p_volume) if (!ensure_init()) return; m_volume = p_volume; - BASS_ChannelSetAttribute(m_hstream, BASS_ATTRIB_VOL, float(p_volume) * 0.01f); + update_volume(); } void DRAudioStream::set_repeatable(bool p_enabled) @@ -105,6 +114,32 @@ void DRAudioStream::set_loop(quint64 p_start, quint64 p_end) init_loop(); } +void DRAudioStream::fade(Fade p_fade, int p_duration) +{ + if (p_fade == NoFade || p_fade == m_fade || !ensure_init()) + { + return; + } + + float l_volume = m_volume * 0.01f; + if (m_fade == NoFade) + { + BASS_ChannelSetAttribute(m_hstream, BASS_ATTRIB_VOL, (p_fade == FadeOut ? l_volume : 0)); + } + m_fade = p_fade; + BASS_ChannelSlideAttribute(m_hstream, BASS_ATTRIB_VOL, (p_fade == FadeOut ? 0 : l_volume), qMax(0, p_duration)); +} + +void DRAudioStream::fadeIn(int p_duration) +{ + fade(FadeIn, p_duration); +} + +void DRAudioStream::fadeOut(int p_duration) +{ + fade(FadeOut, p_duration); +} + void DRAudioStream::end_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata) { Q_UNUSED(hsync); @@ -132,32 +167,44 @@ void DRAudioStream::loop_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata) } } +void DRAudioStream::fade_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata) +{ + Q_UNUSED(hsync); + Q_UNUSED(ch); + Q_UNUSED(data); + + DRAudioStream *l_stream = static_cast(userdata); + emit l_stream->faded(l_stream->m_fade); +} + bool DRAudioStream::ensure_init() { if (m_init_state != InitNotDone) return m_init_state == InitFinished; m_init_state = InitError; - if (m_file_name.isEmpty()) + if (m_filename.isEmpty()) return false; HSTREAM l_hstream; - if (m_file_name.endsWith("opus", Qt::CaseInsensitive)) - l_hstream = BASS_OPUS_StreamCreateFile(FALSE, m_file_name.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); + if (m_filename.endsWith("opus", Qt::CaseInsensitive)) + l_hstream = BASS_OPUS_StreamCreateFile(FALSE, m_filename.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); else l_hstream = - BASS_StreamCreateFile(FALSE, m_file_name.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE | BASS_STREAM_PRESCAN); + BASS_StreamCreateFile(FALSE, m_filename.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE | BASS_STREAM_PRESCAN); if (!l_hstream) { - qWarning() << "error: failed to create audio stream (file:" << m_file_name << ")"; + qWarning() << "error: failed to create audio stream (file:" << m_filename << ")"; return false; } m_hstream = l_hstream; BASS_ChannelSetSync(l_hstream, BASS_SYNC_END, 0, &end_sync, this); + BASS_ChannelSetSync(l_hstream, BASS_SYNC_SLIDE, 0, &fade_sync, this); m_init_state = InitFinished; init_loop(); + update_volume(); return true; } @@ -208,3 +255,9 @@ void DRAudioStream::update_device(DRAudioDevice p_device) } } } + +void DRAudioStream::update_volume() +{ + const float l_volume = m_volume * 0.01f; + BASS_ChannelSetAttribute(m_hstream, BASS_ATTRIB_VOL, (m_fade == FadeOut ? 0 : l_volume)); +} diff --git a/src/draudiostream.h b/src/draudiostream.h index 8d72942a3..03c551c99 100644 --- a/src/draudiostream.h +++ b/src/draudiostream.h @@ -29,32 +29,59 @@ class DRAudioStream : public QObject DRAudioStream(DRAudio::Family p_family); public: + enum Fade + { + NoFade, + FadeIn, + FadeOut, + }; + Q_ENUM(Fade) + using ptr = QSharedPointer; + static void registerMetatypes(); + ~DRAudioStream(); DRAudio::Family get_family() const; QString get_file_name() const; bool is_repeatable() const; bool is_playing() const; + DRAudioStream::Fade get_fade() const; public slots: - std::optional set_file_name(QString m_file); - void set_volume(float p_volume); + std::optional set_file_name(QString file); + void set_volume(float volume); void set_repeatable(bool); void set_loop(quint64 start, quint64 end); + /** + * @brief Fades in or out the channel predicated by the duration. + * + * @param type The type of fade. + * + * @param duration The duration of the fade in milliseconds. + */ + void fade(DRAudioStream::Fade type, int duration); + void fadeIn(int duration); + void fadeOut(int duration); + void play(); void stop(); signals: - void file_name_changed(QString p_file); - void finished(); + void file_name_changed(QString file); + + void faded(DRAudioStream::Fade type); + void looped(); + void finished(); + public: static void CALLBACK end_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata); static void CALLBACK loop_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata); + static void CALLBACK fade_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata); private: friend class DRAudioStreamFamily; @@ -68,8 +95,9 @@ public slots: DRAudioEngine *m_engine = nullptr; DRAudio::Family m_family; - QString m_file_name; - InitState m_init_state = InitNotDone; + QString m_filename; + DRAudioStream::InitState m_init_state = InitNotDone; + DRAudioStream::Fade m_fade; HSTREAM m_hstream = 0; float m_volume = 0.0f; bool m_repeatable = false; @@ -85,6 +113,9 @@ public slots: void init_loop(); void seek_loop_start(); +private slots: + void update_volume(); + signals: void device_error(QPrivateSignal); }; diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp index 2628dfbd0..395a79443 100644 --- a/src/draudiostreamfamily.cpp +++ b/src/draudiostreamfamily.cpp @@ -1,3 +1,4 @@ +#include "draudiostream.h" #define NOMINMAX #include "draudiostreamfamily.h" @@ -6,7 +7,8 @@ #include -DRAudioStreamFamily::DRAudioStreamFamily(DRAudio::Family p_family) : m_family(p_family) +DRAudioStreamFamily::DRAudioStreamFamily(DRAudio::Family p_family) + : m_family(p_family) {} int32_t DRAudioStreamFamily::get_capacity() const @@ -31,7 +33,7 @@ bool DRAudioStreamFamily::is_ignore_suppression() const void DRAudioStreamFamily::set_capacity(int32_t p_capacity) { - p_capacity = std::max(p_capacity, 0); + p_capacity = qMax(0, p_capacity); if (m_capacity == p_capacity) return; @@ -77,34 +79,36 @@ void DRAudioStreamFamily::set_ignore_suppression(bool p_enabled) set_options(options); } -std::optional DRAudioStreamFamily::create_stream(QString p_file) +DRAudioStream::ptr DRAudioStreamFamily::create_stream(QString p_filename) { - DRAudioStream::ptr stream(new DRAudioStream(m_family)); - - if (auto err = stream->set_file_name(p_file); err) + DRAudioStream::ptr l_stream(new DRAudioStream(m_family)); + if (auto err = l_stream->set_file_name(p_filename); err) { qWarning() << err->what(); - return std::nullopt; + return nullptr; } - m_stream_list.append(stream); + m_stream_list.append(l_stream); + + connect(l_stream.get(), SIGNAL(finished()), this, SLOT(on_stream_finished())); + update_capacity(); - stream->set_volume(calculate_volume()); - connect(stream.get(), SIGNAL(finished()), this, SLOT(on_stream_finished())); - return stream; + l_stream->set_volume(calculate_volume()); + + return l_stream; } -std::optional DRAudioStreamFamily::play_stream(QString p_file) +DRAudioStream::ptr DRAudioStreamFamily::play_stream(QString p_filename) { - std::optional r_stream = create_stream(p_file); - if (r_stream.has_value()) + DRAudioStream::ptr l_stream = create_stream(p_filename); + if (!l_stream.isNull()) { - auto stream = r_stream.value(); - qInfo() << "Playing" << stream->get_file_name(); - stream->play(); + qInfo() << "Playing" << l_stream->get_file_name(); + l_stream->play(); } - return r_stream; + + return l_stream; } DRAudioStreamFamily::stream_list DRAudioStreamFamily::get_stream_list() const diff --git a/src/draudiostreamfamily.h b/src/draudiostreamfamily.h index 52118f3ac..cacad2e07 100644 --- a/src/draudiostreamfamily.h +++ b/src/draudiostreamfamily.h @@ -6,8 +6,6 @@ #include #include -#include - class DRAudioEngine; class DRAudioEngineData; @@ -19,8 +17,8 @@ class DRAudioStreamFamily : public QObject using ptr = QSharedPointer; using stream_list = QVector; - std::optional create_stream(QString p_file); - std::optional play_stream(QString p_file); + DRAudioStream::ptr create_stream(QString p_file); + DRAudioStream::ptr play_stream(QString p_file); // get stream_list get_stream_list() const; diff --git a/src/drcharactermovie.cpp b/src/drcharactermovie.cpp index bb516499e..4d5711c8f 100644 --- a/src/drcharactermovie.cpp +++ b/src/drcharactermovie.cpp @@ -5,72 +5,34 @@ #include -DRCharacterMovie::DRCharacterMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_cast(qApp)) +DRCharacterMovie::DRCharacterMovie(QWidget *parent) + : DRMovie(parent), ao_app(dynamic_cast(qApp)) { Q_ASSERT(ao_app); - set_scale_mode(DRMovie::HeightScaling); + set_scaling_mode(ScalingMode::HeightScaling); } DRCharacterMovie::~DRCharacterMovie() {} -void DRCharacterMovie::play(QString p_character, QString p_emote, QString p_prefix, bool p_play_once) +void DRCharacterMovie::play(QString p_character, QString p_emote, QString p_prefix, bool p_use_placeholder, bool p_play_once) { - QStringList l_filelist; - QStringList l_blacklist; - for (const QString &i_characterName : ao_app->get_char_include_tree(p_character)) - { - l_blacklist.append({ - ao_app->get_character_path(i_characterName, "char_icon.png"), - ao_app->get_character_path(i_characterName, "showname.png"), - ao_app->get_character_path(i_characterName, "emotions"), - }); - - if (!p_play_once) - { - l_filelist.append(ao_app->get_character_path(i_characterName, QString("%1%2").arg(p_prefix, p_emote))); - } - - l_filelist.append(ao_app->get_character_path(i_characterName, p_emote)); - } - - QString l_file = ao_app->find_asset_path(l_filelist, animated_or_static_extensions()); - if (l_file.isEmpty() && !p_play_once) - { - l_file = ao_app->find_theme_asset_path("placeholder", animated_extensions()); - } - - for (const QString &i_blackened : qAsConst(l_blacklist)) - { - if (l_file == i_blackened) - { - l_file.clear(); - break; - } - } - - if (l_file.isEmpty()) - { - qWarning() << "error: character animation not found" - << "character:" << p_character << "emote:" << p_emote << "prefix:" << p_prefix; - } - - set_file_name(l_file); + set_file_name(ao_app->get_character_sprite_path(p_character, p_emote, p_prefix, p_use_placeholder)); set_play_once(p_play_once); start(); } void DRCharacterMovie::play_pre(QString p_character, QString p_emote) { - play(p_character, p_emote, nullptr, true); + play(p_character, p_emote, nullptr, false, true); } void DRCharacterMovie::play_talk(QString p_character, QString p_emote) { - play(p_character, p_emote, "(b)", false); + play(p_character, p_emote, "(b)", true, false); } void DRCharacterMovie::play_idle(QString p_character, QString p_emote) { - play(p_character, p_emote, "(a)", false); + play(p_character, p_emote, "(a)", true, false); } diff --git a/src/drcharactermovie.h b/src/drcharactermovie.h index aeac0f092..2cbc965f8 100644 --- a/src/drcharactermovie.h +++ b/src/drcharactermovie.h @@ -12,7 +12,7 @@ class DRCharacterMovie : public DRMovie explicit DRCharacterMovie(QWidget *parent = nullptr); ~DRCharacterMovie(); - void play(QString character, QString emote, QString prefix, bool play_once); + void play(QString character, QString emote, QString prefix, bool p_use_placeholder, bool play_once); void play_pre(QString character, QString emote); void play_talk(QString character, QString emote); void play_idle(QString character, QString emote); diff --git a/src/dreffectmovie.cpp b/src/dreffectmovie.cpp index 6b281941f..90cc5d555 100644 --- a/src/dreffectmovie.cpp +++ b/src/dreffectmovie.cpp @@ -1,7 +1,10 @@ #include "dreffectmovie.h" -DREffectMovie::DREffectMovie(QWidget *parent) : DRStickerMovie(parent) -{} +DREffectMovie::DREffectMovie(QWidget *parent) + : DRStickerMovie(parent) +{ + set_hide_on_done(true); +} DREffectMovie::~DREffectMovie() {} diff --git a/src/drmovie.cpp b/src/drmovie.cpp index f8f53b20a..90717f72d 100644 --- a/src/drmovie.cpp +++ b/src/drmovie.cpp @@ -1,18 +1,13 @@ #include "drmovie.h" -#include -#include - -DRMovie::DRMovie(QWidget *parent) : QLabel(parent) +DRMovie::DRMovie(QWidget *parent) + : SpriteViewer{parent} + , m_hide_when_done{false} + , m_mirrored{false} { - setAlignment(Qt::AlignCenter); - - m_movie.setCacheMode(QMovie::CacheNone); - m_frame_timer.setSingleShot(true); - m_frame_timer.setTimerType(Qt::PreciseTimer); - - connect(&m_movie, SIGNAL(frameChanged(int)), this, SLOT(update_frame(int))); - connect(&m_frame_timer, SIGNAL(timeout()), this, SLOT(jump_next_frame())); + connect(this, SIGNAL(started()), this, SLOT(show())); + connect(this, SIGNAL(finished()), this, SIGNAL(done())); + connect(this, SIGNAL(finished()), this, SLOT(_p_update_visibility())); } DRMovie::~DRMovie() @@ -20,241 +15,23 @@ DRMovie::~DRMovie() QString DRMovie::file_name() { - return m_file_name; -} - -/** - * @brief Sets the property filename. - * - * stop() will be called prior to updating setting the property. - */ -void DRMovie::set_file_name(QString p_file_name) -{ - stop(); - m_file_name = p_file_name; - m_file_exists = QFile::exists(m_file_name); -} - -/** - * @brief Sets the property play_once on if true or off if false. - * - * If on, the movie will repeat until either conditions are met: - * - setting play_once to off - * - calling stop() - * - * The signal done() will not be emitted while play_once is on. - * - * By default, play_once is off. - */ -void DRMovie::set_play_once(bool p_on) -{ - m_play_once = p_on; + return get_file_name(); } -/** - * @brief Sets the property hide_when_done on if on true or off if false. - * - * If on, the widget will hide automatically right before signaling done() - * - * By default, hide_when_done is off. - */ void DRMovie::set_hide_on_done(bool p_on) { m_hide_when_done = p_on; } -/** - * @brief Sets the property mirrored on if true or off if false. - * - * If on, painted frames will be mirrored horizontally. - * - * The current frame will NOT be updated. - * - * By default, mirrored is off. - */ void DRMovie::set_mirrored(bool p_on) { - m_mirrored = p_on; -} - -/** - * @brief Sets the property scale_to_height on if true or off if false. - * - * If on, painted frames will preserve their aspect ratio and scale based - * on the height of the widget. - * - * The current frame will NOT be updated. - * - * By default, scale_to_height is off. - */ -void DRMovie::set_scale_mode(DRMovie::ScalingMode p_mode) -{ - m_scaling_mode = p_mode; + set_mirror(p_on); } -/** - * @brief Starts (or restarts) the movie. - * - * If the movie isn't valid and the property play_once is on, - * the movie will immediately emit the signal done() - */ -void DRMovie::start() +void DRMovie::_p_update_visibility() { - m_frame_timer.stop(); - m_movie.stop(); - m_movie.setFileName(m_file_name); - m_frame_count = m_movie.frameCount(); - m_frame_number = m_movie.currentFrameNumber(); - if (is_valid()) - { - m_movie.start(); - m_movie.setPaused(true); - show(); - } - else if (m_play_once) - { - emit done(); - } -} - -bool DRMovie::is_running() -{ - return m_movie.state() != QMovie::NotRunning; -} - -void DRMovie::stop() -{ - m_frame_timer.stop(); - m_movie.stop(); - m_movie.setFileName(""); // clean up QMovie's cache - if (m_hide_when_done) { hide(); } } - -void DRMovie::resizeEvent(QResizeEvent *event) -{ - QLabel::resizeEvent(event); - paint_frame(); -} - -void DRMovie::paint_frame() -{ - QPixmap l_image = m_current_pixmap; - - { - const QSize l_image_size = m_current_pixmap.size(); - const QSize l_widget_size = size(); - - if (l_image_size != l_widget_size) - { - ScalingMode l_target_scaling_mode = m_scaling_mode; - if (l_target_scaling_mode == DynamicScaling) - { - const qreal l_width_factor = (qreal)qMax(l_image_size.width(), 1) / qMax(l_widget_size.width(), 1); - const qreal l_height_factor = (qreal)qMax(l_image_size.height(), 1) / qMax(l_widget_size.height(), 1); - - const QSize l_by_width_size{ - int((qreal)l_image_size.width() / l_width_factor), - int((qreal)l_image_size.height() / l_width_factor), - }; - const QSize l_by_height_size{ - int((qreal)l_image_size.width() / l_height_factor), - int((qreal)l_image_size.height() / l_height_factor), - }; - - if (l_by_width_size.height() >= l_widget_size.height()) - { - l_target_scaling_mode = WidthScaling; - } - else if (l_by_height_size.width() >= l_widget_size.width()) - { - l_target_scaling_mode = HeightScaling; - } - else - { - l_target_scaling_mode = StretchScaling; - } - } - - Qt::TransformationMode l_transformation = Qt::SmoothTransformation; - if (l_image_size.width() < l_widget_size.width() || l_image_size.height() < l_widget_size.height()) - { - l_transformation = Qt::FastTransformation; - } - switch (l_target_scaling_mode) - { - case StretchScaling: - [[fallthrough]]; - default: - l_image = l_image.scaled(l_widget_size, Qt::IgnoreAspectRatio, l_transformation); - break; - - case WidthScaling: - l_image = l_image.scaledToWidth(l_widget_size.width(), l_transformation); - break; - - case HeightScaling: - l_image = l_image.scaledToHeight(l_widget_size.height(), l_transformation); - break; - } - } - } - - setPixmap(l_image); -} - -void DRMovie::update_frame(int p_frame_number) -{ - m_frame_number = p_frame_number; - m_frame_timer.start(m_movie.nextFrameDelay()); - - if (m_mirrored) - { - m_current_pixmap = QPixmap::fromImage(m_movie.currentImage().mirrored(m_mirrored, false)); - } - else - { - m_current_pixmap = m_movie.currentPixmap(); - } - - paint_frame(); -} - -void DRMovie::jump_next_frame() -{ - const int l_next_frame_number = m_frame_number + 1; - if (l_next_frame_number >= m_frame_count) - { - m_movie.stop(); - m_frame_number = m_movie.currentFrameNumber(); - - if (m_play_once) - { - if (m_hide_when_done) - { - hide(); - } - - emit done(); - return; - } - - if (m_frame_count > 1) - { - m_movie.start(); - m_movie.setPaused(true); - } - } - else - { - m_movie.jumpToNextFrame(); - } -} - -bool DRMovie::is_valid() -{ - return m_file_exists && m_movie.isValid(); -} diff --git a/src/drmovie.h b/src/drmovie.h index f08672ee1..1ae9afe37 100644 --- a/src/drmovie.h +++ b/src/drmovie.h @@ -1,61 +1,26 @@ #pragma once -#include -#include -#include +#include "mk2/spriteviewer.h" -class DRMovie : public QLabel +class DRMovie : public mk2::SpriteViewer { Q_OBJECT public: - enum ScalingMode - { - StretchScaling, - WidthScaling, - HeightScaling, - DynamicScaling, - }; - Q_ENUM(ScalingMode) - - explicit DRMovie(QWidget *parent = nullptr); + DRMovie(QWidget *parent = nullptr); ~DRMovie(); QString file_name(); - void set_file_name(QString file_name); - - void set_play_once(bool); - void set_mirrored(bool); - void set_scale_mode(ScalingMode); - void set_hide_on_done(bool); - - bool is_running(); - bool is_valid(); - void start(); - void stop(); + void set_hide_on_done(bool on); + void set_mirrored(bool on); signals: void done(); -protected: - virtual void resizeEvent(QResizeEvent *event) override; - private: - bool m_play_once = false; - bool m_mirrored = false; - ScalingMode m_scaling_mode = StretchScaling; - bool m_hide_when_done = false; - - QString m_file_name; - bool m_file_exists = false; - QMovie m_movie; - int m_frame_count = 0; - int m_frame_number = 0; - QTimer m_frame_timer; - QPixmap m_current_pixmap; + bool m_hide_when_done; + bool m_mirrored; private slots: - void update_frame(int); - void jump_next_frame(); - void paint_frame(); + void _p_update_visibility(); }; diff --git a/src/drposition.cpp b/src/drposition.cpp index 0bfc951ff..6096fffbe 100644 --- a/src/drposition.cpp +++ b/src/drposition.cpp @@ -3,37 +3,42 @@ #include #include -const QMap DRPositionReader::READONLY_POSITIONS{ +const QMap DRPositionMap::LEGACY_POSITION_MAP{ { "wit", - DRPosition("witnessempty.png", "stand.png"), + DRPosition("witnessempty", "stand"), }, { "def", - DRPosition("defenseempty.png", "defensedesk.png"), + DRPosition("defenseempty", "defensedesk"), }, { "pro", - DRPosition("prosecutorempty.png", "prosecutiondesk.png"), + DRPosition("prosecutorempty", "prosecutiondesk"), }, { "jud", - DRPosition("judgestand.png", "judgedesk.png"), + DRPosition("judgestand", "judgedesk"), }, { "hld", - DRPosition("helperstand.png", "helperdesk.png"), + DRPosition("helperstand", "helperdesk"), }, { "hlp", - DRPosition("prohelperstand.png", "prohelperdesk.png"), + DRPosition("prohelperstand", "prohelperdesk"), }, }; DRPosition::DRPosition() {} -DRPosition::DRPosition(QString p_back, QString p_front) : m_back(p_back), m_front(p_front) +DRPosition::DRPosition(QString p_back, QString p_front) + : m_back(p_back), m_front(p_front) +{} + +DRPosition::DRPosition(QString p_back, QString p_front, QString p_ambient_sfx) + : m_back(p_back), m_front(p_front), m_ambient_sfx(p_ambient_sfx) {} DRPosition::~DRPosition() @@ -49,6 +54,11 @@ QString DRPosition::get_front() return m_front; } +QString DRPosition::get_ambient_sfx() +{ + return m_ambient_sfx; +} + void DRPosition::set_back(QString p_back) { m_back = p_back; @@ -59,31 +69,41 @@ void DRPosition::set_front(QString p_front) m_front = p_front; } -DRPositionReader::DRPositionReader(QObject *parent) : QObject(parent) +void DRPosition::set_ambient_sfx(QString p_ambient_sfx) +{ + m_ambient_sfx = p_ambient_sfx; +} + +DRPositionMap::DRPositionMap() {} -DRPositionReader::~DRPositionReader() +DRPositionMap::~DRPositionMap() {} -DRPosition DRPositionReader::get_position(QString p_id) +DRPosition DRPositionMap::get_position(QString p_id) +{ + const QString l_lower_id = p_id.toLower(); + return m_position_map.contains(l_lower_id) ? m_position_map.value(l_lower_id) : LEGACY_POSITION_MAP.value(l_lower_id, LEGACY_POSITION_MAP.value("wit")); +} + +void DRPositionMap::set_position(QString p_id, DRPosition p_position) { - p_id = p_id.toLower(); - return m_positions.contains(p_id) ? m_positions.value(p_id) : READONLY_POSITIONS.value(p_id); + m_position_map.insert(p_id.toLower(), p_position); } -void DRPositionReader::load_file(QString p_filename) +bool DRPositionMap::load_file(QString p_filename) { QSettings l_settings(p_filename, QSettings::IniFormat); l_settings.setIniCodec("UTF-8"); if (l_settings.status() != QSettings::NoError) { - qWarning() << "[Position Reader]" + qWarning() << "[Position Map]" << "warning:" << "could not load positions.ini file"; - return; + return false; } - m_positions.clear(); + QMap l_position_map; const QStringList l_group_list = l_settings.childGroups(); for (const QString &i_group : l_group_list) { @@ -91,7 +111,10 @@ void DRPositionReader::load_file(QString p_filename) l_settings.beginGroup(i_group); const QString l_back = l_settings.value("back").toString(); const QString l_front = l_settings.value("front").toString(); - m_positions.insert(l_lower_group, DRPosition(l_back, l_front)); + const QString l_ambient_sfx = l_settings.value("ambient_sfx").toString(); + l_position_map.insert(l_lower_group, DRPosition(l_back, l_front, l_ambient_sfx)); l_settings.endGroup(); } + m_position_map = l_position_map; + return true; } diff --git a/src/drposition.h b/src/drposition.h index 54a05febb..c2f5f0995 100644 --- a/src/drposition.h +++ b/src/drposition.h @@ -8,7 +8,9 @@ class DRPosition public: DRPosition(); - DRPosition(QString p_back, QString p_front); + DRPosition(QString back, QString front); + + DRPosition(QString back, QString front, QString ambient_sfx); ~DRPosition(); @@ -16,31 +18,36 @@ class DRPosition QString get_front(); - void set_back(QString p_back); + QString get_ambient_sfx(); + + void set_back(QString back); - void set_front(QString p_front); + void set_front(QString front); + + void set_ambient_sfx(QString ambient_sfx); private: QString m_back; QString m_front; + + QString m_ambient_sfx; }; -class DRPositionReader : public QObject +class DRPositionMap { - Q_OBJECT - public: - DRPositionReader(QObject *parent = nullptr); + static const QMap LEGACY_POSITION_MAP; - ~DRPositionReader(); + DRPositionMap(); + ~DRPositionMap(); - DRPosition get_position(QString p_id); + DRPosition get_position(QString id); - void load_file(QString p_filename); + void set_position(QString id, DRPosition position); -private: - static const QMap READONLY_POSITIONS; + bool load_file(QString filename); - QMap m_positions; +private: + QMap m_position_map; }; diff --git a/src/drscenemovie.cpp b/src/drscenemovie.cpp index 141113b1e..9210dfe4c 100644 --- a/src/drscenemovie.cpp +++ b/src/drscenemovie.cpp @@ -3,10 +3,11 @@ #include "aoapplication.h" #include "file_functions.h" -DRSceneMovie::DRSceneMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_cast(qApp)) +DRSceneMovie::DRSceneMovie(QWidget *parent) + : DRMovie(parent), ao_app(dynamic_cast(qApp)) { Q_ASSERT(ao_app); - set_scale_mode(DRMovie::DynamicScaling); + set_scaling_mode(ScalingMode::DynamicScaling); } DRSceneMovie::~DRSceneMovie() @@ -14,12 +15,11 @@ DRSceneMovie::~DRSceneMovie() void DRSceneMovie::set_background_image(QString p_background_name, QString p_image) { - const QString l_filename = file_name(); - const QString l_target_filename = - ao_app->find_asset_path(ao_app->get_background_path(p_background_name) + "/" + p_image); - if (l_filename == l_target_filename) + const QString l_filename = ao_app->get_background_sprite_path(p_background_name, p_image); + if (l_filename == file_name()) + { return; - - set_file_name(l_target_filename); + } + set_file_name(l_filename); start(); } diff --git a/src/drserversocket.cpp b/src/drserversocket.cpp index dcaac1465..b4b95d3c2 100644 --- a/src/drserversocket.cpp +++ b/src/drserversocket.cpp @@ -1,9 +1,10 @@ #include "drserversocket.h" +#include "qabstractsocket.h" #include #include -const int DRServerSocket::RECONNECT_DELAY = 5000; +const int DRServerSocket::CONNECTING_DELAY = 5000; namespace { @@ -14,20 +15,22 @@ QString drFormatServerInfo(const DRServerInfo &server) } } // namespace -DRServerSocket::DRServerSocket(QObject *p_parent) : QObject(p_parent) +DRServerSocket::DRServerSocket(QObject *p_parent) + : QObject(p_parent) { m_socket = new QTcpSocket(this); - m_reconnect_timer = new QTimer(this); + m_connecting_timer = new QTimer(this); - m_reconnect_timer->setSingleShot(false); - m_reconnect_timer->setInterval(RECONNECT_DELAY); + m_connecting_timer->setSingleShot(true); + m_connecting_timer->setInterval(CONNECTING_DELAY); connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(_p_check_socket_error())); connect(m_socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(_p_update_state(QAbstractSocket::SocketState))); connect(m_socket, SIGNAL(readyRead()), this, SLOT(_p_read_socket())); - connect(m_reconnect_timer, SIGNAL(timeout()), this, SLOT(_p_reconnect_to_server())); + connect(m_connecting_timer, SIGNAL(timeout()), this, SLOT(disconnect_from_server())); + m_socket->close(); } bool DRServerSocket::is_connected() const @@ -35,13 +38,10 @@ bool DRServerSocket::is_connected() const return m_socket->state() == QTcpSocket::ConnectedState; } -void DRServerSocket::connect_to_server(DRServerInfo p_server, bool p_is_reconnectable) +void DRServerSocket::connect_to_server(DRServerInfo p_server) { disconnect_from_server(); m_server = p_server; - is_reconnectable = p_is_reconnectable; - if (is_reconnectable) - m_reconnect_timer->start(); m_socket->connectToHost(p_server.address, p_server.port); } @@ -68,33 +68,28 @@ void DRServerSocket::_p_update_state(QAbstractSocket::SocketState p_state) { switch (p_state) { - case QAbstractSocket::ConnectedState: - m_is_connected = true; - m_reconnect_timer->stop(); - Q_EMIT connected_to_server(); - break; - - case QAbstractSocket::UnconnectedState: - if (m_is_connected) - { - m_is_connected = false; - if (is_reconnectable) - m_reconnect_timer->start(); + case QAbstractSocket::ConnectingState: + m_connecting_timer->start(); + Q_EMIT connecting_to_server(); + break; + + case QAbstractSocket::ConnectedState: + m_connecting_timer->stop(); + m_connected = true; + Q_EMIT connected_to_server(); + break; + + case QAbstractSocket::UnconnectedState: + m_connecting_timer->stop(); + m_connected = false; Q_EMIT disconnected_from_server(); - } - break; + break; - default: - break; + default: + break; } } -void DRServerSocket::_p_reconnect_to_server() -{ - Q_EMIT reconnecting_to_server(); - connect_to_server(m_server, is_reconnectable); -} - void DRServerSocket::_p_check_socket_error() { const QString l_error = QString("Server%1 error: %2").arg(drFormatServerInfo(m_server), m_socket->errorString()); diff --git a/src/drserversocket.h b/src/drserversocket.h index 79ce73088..1c28c380a 100644 --- a/src/drserversocket.h +++ b/src/drserversocket.h @@ -17,32 +17,32 @@ class DRServerSocket : public QObject DRServerSocket(QObject *parent = nullptr); bool is_connected() const; - void connect_to_server(DRServerInfo server, bool is_reconnectable); - void disconnect_from_server(); public slots: + void connect_to_server(DRServerInfo server); + + void disconnect_from_server(); + void send_packet(DRPacket packet); signals: void connected_to_server(); + void connecting_to_server(); void disconnected_from_server(); - void reconnecting_to_server(); void packet_received(DRPacket); void socket_error(QString); private: - static const int RECONNECT_DELAY; + static const int CONNECTING_DELAY; DRServerInfo m_server; QTcpSocket *m_socket = nullptr; - bool m_is_connected = false; - QTimer *m_reconnect_timer = nullptr; - bool is_reconnectable = false; + QTimer *m_connecting_timer = nullptr; + bool m_connected = false; QString m_buffer; private slots: void _p_update_state(QAbstractSocket::SocketState); - void _p_reconnect_to_server(); void _p_check_socket_error(); void _p_read_socket(); }; diff --git a/src/drshoutmovie.cpp b/src/drshoutmovie.cpp index 43879642f..948f82463 100644 --- a/src/drshoutmovie.cpp +++ b/src/drshoutmovie.cpp @@ -5,7 +5,8 @@ #include -DRShoutMovie::DRShoutMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_cast(qApp)) +DRShoutMovie::DRShoutMovie(QWidget *parent) + : DRMovie(parent), ao_app(dynamic_cast(qApp)) { Q_ASSERT(ao_app); set_play_once(true); @@ -17,22 +18,7 @@ DRShoutMovie::~DRShoutMovie() void DRShoutMovie::play_interjection(QString p_character, QString p_shout) { - QString l_file_name = ao_app->find_asset_path( - {ao_app->get_character_path(p_character, p_shout), ao_app->get_character_path(p_character, p_shout + "_bubble")}, - animated_extensions()); - - if (l_file_name.isEmpty()) - { - l_file_name = ao_app->find_theme_asset_path(p_shout, animated_extensions()); - } - - if (l_file_name.isEmpty()) - { - qWarning() << "error: shout not found" - << "character:" << p_character << "shout:" << p_shout; - } - - set_file_name(l_file_name); + set_file_name(ao_app->get_shout_sprite_path(p_character, p_shout)); set_play_once(true); start(); } diff --git a/src/drstickermovie.cpp b/src/drstickermovie.cpp index 0f816b6bc..128a20913 100644 --- a/src/drstickermovie.cpp +++ b/src/drstickermovie.cpp @@ -3,7 +3,8 @@ #include "aoapplication.h" #include "file_functions.h" -DRStickerMovie::DRStickerMovie(QWidget *parent) : DRMovie(parent), ao_app(dynamic_cast(qApp)) +DRStickerMovie::DRStickerMovie(QWidget *parent) + : DRMovie(parent), ao_app(dynamic_cast(qApp)) {} DRStickerMovie::~DRStickerMovie() @@ -11,32 +12,7 @@ DRStickerMovie::~DRStickerMovie() void DRStickerMovie::play(QString p_file_name, QString p_character) { - QString l_file_path; - if (!p_character.isEmpty()) - { - QString l_character_file_name = p_file_name; - if (l_character_file_name == "custom") - { - l_character_file_name.append("_bubble"); - } - - QStringList l_path_list{ - ao_app->get_character_path(p_character, l_character_file_name), - ao_app->get_character_path(p_character, "overlay/" + l_character_file_name), - }; - l_file_path = ao_app->find_asset_path(l_path_list, animated_or_static_extensions()); - } - - if (l_file_path.isEmpty()) - { - l_file_path = ao_app->find_theme_asset_path(p_file_name, animated_or_static_extensions()); - if (l_file_path.isEmpty()) - { - l_file_path = ao_app->find_theme_asset_path("placeholder", animated_or_static_extensions()); - } - } - - set_file_name(l_file_path); + set_file_name(ao_app->get_theme_sprite_path(p_file_name, p_character)); start(); } diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp index f89a314f6..eafc2263b 100644 --- a/src/drvideoscreen.cpp +++ b/src/drvideoscreen.cpp @@ -11,7 +11,8 @@ #include #include -DRVideoWidget::DRVideoWidget(QWidget *parent) : QVideoWidget(parent), ao_app(dynamic_cast(qApp)) +DRVideoWidget::DRVideoWidget(QWidget *parent) + : QVideoWidget(parent), ao_app(dynamic_cast(qApp)) { Q_ASSERT(ao_app); diff --git a/src/emotes.cpp b/src/emotes.cpp index 76ae83e49..b9e1b6e16 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -241,6 +241,7 @@ void Courtroom::hide_emote_tooltip(int p_id) return; m_emote_preview_id = -1; ui_emote_preview->hide(); + ui_emote_preview_character->set_file_name(nullptr); ui_emote_preview_character->stop(); } diff --git a/src/lobby.cpp b/src/lobby.cpp index f1246efbc..a4ecc5bc1 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -31,6 +31,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() + , m_connection_state(NotConnectedState) { ao_app = p_ao_app; ao_config = new AOConfig(this); @@ -77,6 +78,10 @@ Lobby::Lobby(AOApplication *p_ao_app) connect(m_master_client, SIGNAL(motd_changed()), this, SLOT(update_motd())); connect(m_master_client, SIGNAL(server_list_changed()), this, SLOT(update_server_list())); + connect(ao_app, SIGNAL(connecting_to_server()), this, SLOT(_p_on_connecting_to_server())); + connect(ao_app, SIGNAL(connected_to_server()), this, SLOT(_p_on_connected_to_server())); + connect(ao_app, SIGNAL(disconnected_from_server()), this, SLOT(_p_on_disconnected_from_server())); + connect(ui_public_server_filter, SIGNAL(clicked()), this, SLOT(toggle_public_server_filter())); connect(ui_favorite_server_filter, SIGNAL(clicked()), this, SLOT(toggle_favorite_server_filter())); connect(ui_refresh, SIGNAL(pressed()), this, SLOT(on_refresh_pressed())); @@ -90,9 +95,9 @@ Lobby::Lobby(AOApplication *p_ao_app) connect(ui_server_list, SIGNAL(currentRowChanged(int)), this, SLOT(connect_to_server(int))); connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); - update_widgets(); load_settings(); load_favorite_server_list(); + update_widgets(); m_master_client->set_address(ao_config->server_advertiser()); set_choose_a_server(); } @@ -205,6 +210,8 @@ void Lobby::update_widgets() set_fonts(); set_stylesheets(); update_server_listing(); + + update_server_filter_buttons(); } void Lobby::set_fonts() @@ -357,6 +364,12 @@ void Lobby::save_favorite_server_list() l_ini.sync(); } +void Lobby::set_connection_state(ConnectionState p_connection_state) +{ + m_connection_state = p_connection_state; + _p_update_description(); +} + void Lobby::request_advertiser_update() { m_master_client->request_motd(); @@ -549,12 +562,62 @@ void Lobby::connect_to_server(int p_row) m_current_server = m_combined_server_list.at(p_row); if (l_prev_server != m_current_server) { - ui_player_count->setText("Connecting..."); - ui_description->setHtml("Connecting to " + m_current_server.name + "..."); + ui_player_count->setText(nullptr); ao_app->connect_to_server(m_current_server); } } +void Lobby::_p_on_connecting_to_server() +{ + set_connection_state(ConnectingState); +} + +void Lobby::_p_on_connected_to_server() +{ + set_connection_state(ConnectedState); +} + +void Lobby::_p_on_disconnected_from_server() +{ + set_connection_state(NotConnectedState); +} + +void Lobby::_p_update_description() +{ + QString l_message; + { + const QString l_format = QMap{ + { + NotConnectedState, + tr("Failed to connect to server (%1)"), + }, + { + ConnectingState, + tr("Connecting to server (%1)"), + }, + { + ConnectedState, + tr("Connected to server (%1)"), + }, + }[m_connection_state]; + l_message = QString(l_format).arg(m_current_server.name.toHtmlEscaped()); + } + + if (m_connection_state == ConnectedState) + { + l_message += "\n\n" + m_current_server.description.toHtmlEscaped(); + + const QRegExp l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); + if (l_message.contains(l_regex)) + { + l_message.replace(l_regex, "\\1"); + } + + l_message.replace("\n", "
    "); + } + ui_description->setHtml(l_message); +} + void Lobby::set_choose_a_server() { ui_player_count->setText(nullptr); @@ -566,10 +629,4 @@ void Lobby::set_player_count(int players_online, int max_players) const QString f_string = "Connected: " + QString::number(players_online) + "/" + QString::number(max_players); ui_player_count->setText(f_string); ui_player_count->setAlignment(Qt::AlignHCenter); - - QString l_text = m_current_server.description.toHtmlEscaped(); - const QRegExp l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); - if (l_text.contains(l_regex)) - l_text.replace(l_regex, "\\1"); - ui_description->setHtml(l_text.replace("\n", "
    ")); } diff --git a/src/lobby.h b/src/lobby.h index 32da304fe..885a11a64 100644 --- a/src/lobby.h +++ b/src/lobby.h @@ -55,6 +55,14 @@ class Lobby : public QMainWindow DRServerInfoList m_combined_server_list; DRServerInfo m_current_server; + enum ConnectionState + { + NotConnectedState, + ConnectingState, + ConnectedState, + }; + ConnectionState m_connection_state; + // ui AOImageDisplay *ui_background = nullptr; AOButton *ui_public_server_filter = nullptr; @@ -87,6 +95,8 @@ class Lobby : public QMainWindow void load_legacy_favorite_server_list(); void save_favorite_server_list(); + void set_connection_state(ConnectionState state); + private slots: void update_widgets(); @@ -111,6 +121,12 @@ private slots: void on_config_pressed(); void on_config_released(); void connect_to_server(int row); + + void _p_on_connecting_to_server(); + void _p_on_connected_to_server(); + void _p_on_disconnected_from_server(); + + void _p_update_description(); }; #endif // LOBBY_H diff --git a/src/mk2/spritecachingreader.cpp b/src/mk2/spritecachingreader.cpp new file mode 100644 index 000000000..c061ef754 --- /dev/null +++ b/src/mk2/spritecachingreader.cpp @@ -0,0 +1,169 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "mk2/spritecachingreader.h" + +#include +#include +#include +#include +#include + +#include + +using namespace mk2; + +SpriteCachingReader::SpriteCachingReader(QObject *parent) + : SpriteReader{parent} + , m_sprite_size{} + , m_frame_count{0} + , m_exit_task{false} +{} + +SpriteCachingReader::~SpriteCachingReader() +{ + _p_stop_preload(); +} + +QSize SpriteCachingReader::get_sprite_size() const +{ + return m_sprite_size; +} + +int SpriteCachingReader::get_frame_count() const +{ + return m_frame_count; +} + +SpriteFrame SpriteCachingReader::get_frame(int p_number) +{ + if (!is_valid()) + { + return SpriteFrame{}; + } + p_number = qBound(0, p_number, qMax(m_frame_count - 1, 0)); + m_available_frames.acquire(p_number + 1); + QSemaphoreReleaser l_releaser(m_available_frames, p_number + 1); + QMutexLocker l_locker(&m_lock); + return m_frame_list.at(p_number); +} + +QVector SpriteCachingReader::get_frame_list() +{ + m_available_frames.acquire(get_frame_count()); + QSemaphoreReleaser l_releaser(m_available_frames, get_frame_count()); + QMutexLocker l_locker(&m_lock); + return m_frame_list; +} + +void SpriteCachingReader::load() +{ + _p_stop_preload(); + m_available_frames.acquire(m_available_frames.available()); + m_sprite_size = QSize{}; + m_frame_count = 0; + m_frame_list.clear(); + + QByteArray l_raw_data; + QIODevice *l_device = get_device(); + const int l_prev_pos = l_device->pos(); + if (!l_device->isOpen() && !l_device->open(QIODevice::ReadOnly)) + { + set_error(Error::DeviceError); + return; + } + l_device->seek(0); + l_raw_data = l_device->readAll(); + l_device->seek(l_prev_pos); + + { + QBuffer l_buffer(&l_raw_data); + QImageReader l_reader(&l_buffer); + if (!l_reader.canRead()) + { + set_error(Error::InvalidDataError); + return; + } + m_sprite_size = l_reader.size(); + m_frame_count = l_reader.imageCount(); + } + + m_exit_task = false; + m_task = QtConcurrent::run(this, &SpriteCachingReader::_p_preload, l_raw_data); +} + +void SpriteCachingReader::_p_stop_preload() +{ + m_exit_task = true; + m_task.waitForFinished(); +} + +void SpriteCachingReader::_p_preload(QByteArray p_raw_data) +{ + set_state(State::NotLoaded); + set_loading_progress(0); + + QBuffer l_buffer(&p_raw_data); + QImageReader l_reader(&l_buffer); + const QSize l_size = l_reader.size(); + const int l_frame_count = l_reader.imageCount(); + if (l_frame_count > 0) + { + QElapsedTimer l_elapsed_timer; + l_elapsed_timer.start(); + + // create a buffer for images + QVector l_image_buffer_list; + for (int i = 0; i < l_frame_count; ++i) + { + l_image_buffer_list.append(QImage(l_size, QImage::Format_ARGB32)); + } + + int l_frame_number = 0; + int l_percent_progress = 0; + while (!m_exit_task && l_frame_number < l_frame_count && l_reader.canRead()) + { + SpriteFrame l_frame; + QImage l_image_buffer = l_image_buffer_list.takeFirst(); + l_reader.read(&l_image_buffer); + l_frame.image = QPixmap::fromImage(l_image_buffer); + l_frame.delay = l_reader.nextImageDelay(); + { + QMutexLocker locker(&m_lock); + m_frame_list.append(std::move(l_frame)); + l_frame_number = m_frame_list.length(); + m_available_frames.release(); + } + + l_percent_progress = ((double)l_frame_number / (l_frame_count + 1)) * 100; + set_loading_progress(l_percent_progress); + } + + if (!m_exit_task) + { + set_loading_progress(100); + set_state(State::FullyLoaded); + } + } + else + { + set_error(Error::InvalidDataError); + } +} diff --git a/src/mk2/spritecachingreader.h b/src/mk2/spritecachingreader.h new file mode 100644 index 000000000..2125a99a9 --- /dev/null +++ b/src/mk2/spritecachingreader.h @@ -0,0 +1,67 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include "mk2/spritereader.h" + +#include +#include +#include + +#include + +namespace mk2 +{ +class SpriteCachingReader : public SpriteReader +{ + Q_OBJECT + +public: + explicit SpriteCachingReader(QObject *parent = nullptr); + virtual ~SpriteCachingReader(); + +public: + QSize get_sprite_size() const final; + + int get_frame_count() const final; + + SpriteFrame get_frame(int number) final; + + QVector get_frame_list() final; + +protected: + void load() final; + +private: + mutable QMutex m_lock; + + QSize m_sprite_size; + int m_frame_count; + mutable QSemaphore m_available_frames; + QVector m_frame_list; + + QFuture m_task; + std::atomic_bool m_exit_task; + + void _p_preload(QByteArray raw_data); + void _p_stop_preload(); +}; +} // namespace mk2 diff --git a/src/mk2/spritedynamicreader.cpp b/src/mk2/spritedynamicreader.cpp new file mode 100644 index 000000000..91bb0fd63 --- /dev/null +++ b/src/mk2/spritedynamicreader.cpp @@ -0,0 +1,214 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "spritedynamicreader.h" + +#include "spritecachingreader.h" +#include "spriteseekingreader.h" + +#include +#include +#include +#include + +#if defined(Q_OS_WINDOWS) +#include +#include + +double get_mem_usage_percent(quint64 p_requested_mem_size) +{ + MEMORYSTATUSEX l_mem_status; + l_mem_status.dwLength = sizeof(MEMORYSTATUSEX); + if (GlobalMemoryStatusEx(&l_mem_status)) + { + const quint64 l_used_ram = (l_mem_status.ullTotalPhys - l_mem_status.ullAvailPhys) + p_requested_mem_size; + if (l_used_ram < l_mem_status.ullTotalPhys) + { + const double l_percent = std::abs(((double)l_used_ram / l_mem_status.ullTotalPhys) * 100); + return l_percent; + } + else + { + return 100.0; + } + } + else + { + return 100.0; + } +} +#elif defined(Q_OS_LINUX) +#include + +double get_mem_usage_percent(quint64 p_needed_ram) +{ + struct sysinfo l_sys_info; + if (sysinfo(&l_sys_info) != -1) + { + const quint64 l_used_ram = (l_sys_info.totalram - l_sys_info.freeram) + p_needed_ram; + if (l_used_ram < l_sys_info.totalram) + { + const double l_percent = std::abs(((double)l_used_ram / l_sys_info.totalram) * 100); + return l_percent; + } + else + { + return 100.0; + } + } + else + { + return 100.0; + } +} +#elif defined(Q_OS_MAC) +#include +#include + +double get_mem_usage_percent(quint64 p_requested_mem_size) +{ + mach_port_t l_mach_port = mach_host_self(); + + vm_size_t l_page_size; + mach_msg_type_number_t count = sizeof(vm_statistics64_data_t) / sizeof(natural_t); + if (host_page_size(l_mach_port, &l_page_size) != KERN_SUCCESS) + { + return 100; + } + + vm_statistics64_data_t vm_stats; + if (host_statistics64(l_mach_port, HOST_VM_INFO, + (host_info64_t)&vm_stats, &count) != KERN_SUCCESS) + { + return 100; + } + + const quint64 l_free_ram = (quint64)vm_stats.free_count * l_page_size; + const quint64 l_total_ram = ((quint64)vm_stats.free_count + vm_stats.active_count + vm_stats.inactive_count + vm_stats.wire_count + vm_stats.zero_fill_count) * l_page_size; + + const quint64 l_used_ram = (l_total_ram - l_free_ram) + p_requested_mem_size; + if (l_used_ram < l_total_ram) + { + const double l_percent = std::abs(((double)l_used_ram / l_total_ram) * 100); + return l_percent; + } + else + { + return 100.0; + } +} +#else +#error Unsupported platform. +#endif + +using namespace mk2; + +std::atomic_int s_system_memory_threshold = 50; +std::atomic_uint64_t s_total_memory_used = 0; + +int SpriteDynamicReader::get_system_memory_threshold() +{ + return s_system_memory_threshold; +} + +void SpriteDynamicReader::set_system_memory_threshold(int p_percent) +{ + s_system_memory_threshold = qBound(10, p_percent, 80); +} + +SpriteDynamicReader::SpriteDynamicReader(QObject *parent) + : SpriteReader{parent} + , m_used_memory{0} +{ + _p_create_reader(true); +} + +SpriteDynamicReader::~SpriteDynamicReader() +{ + _p_free_memory(); +} + +QSize SpriteDynamicReader::get_sprite_size() const +{ + return m_reader->get_sprite_size(); +} + +int SpriteDynamicReader::get_frame_count() const +{ + return m_reader->get_frame_count(); +} + +SpriteFrame SpriteDynamicReader::get_frame(int number) +{ + return m_reader->get_frame(number); +} + +QVector SpriteDynamicReader::get_frame_list() +{ + return m_reader->get_frame_list(); +} + +void SpriteDynamicReader::load() +{ + _p_free_memory(); + QImageReader l_image_reader(get_device()); + const QSize l_size = l_image_reader.size(); + + bool l_caching = true; + qint64 l_projected_memory = 0; + if (l_size.isValid() && l_image_reader.imageCount() > 0) + { + l_projected_memory = qint64(((qint64)l_image_reader.size().width() * l_image_reader.size().height()) * l_image_reader.imageCount() * 4); + + if (get_mem_usage_percent(s_total_memory_used + l_projected_memory) > s_system_memory_threshold) + { + l_caching = false; + l_projected_memory = 0; + } + } + m_used_memory = l_projected_memory; + s_total_memory_used += m_used_memory; + + _p_create_reader(l_caching); + m_reader->set_device(get_device()); +} + +void SpriteDynamicReader::_p_create_reader(bool p_caching) +{ + mk2::SpriteReader *l_reader = nullptr; + if (p_caching) + { + l_reader = new SpriteCachingReader; + } + else + { + l_reader = new SpriteSeekingReader; + } + connect(l_reader, SIGNAL(state_changed(mk2::SpriteReader::State)), this, SLOT(set_state(mk2::SpriteReader::State))); + connect(l_reader, SIGNAL(loading_progress_changed(int)), this, SLOT(set_loading_progress(int))); + connect(l_reader, SIGNAL(error(mk2::SpriteReader::Error)), this, SLOT(set_error(mk2::SpriteReader::Error))); + m_reader = mk2::SpriteReader::ptr(l_reader); +} + +void SpriteDynamicReader::_p_free_memory() +{ + s_total_memory_used -= m_used_memory; + m_used_memory = 0; +} diff --git a/src/mk2/spritedynamicreader.h b/src/mk2/spritedynamicreader.h new file mode 100644 index 000000000..34910d59d --- /dev/null +++ b/src/mk2/spritedynamicreader.h @@ -0,0 +1,59 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include "mk2/spritereader.h" + +#include +#include + +namespace mk2 +{ +class SpriteDynamicReader : public SpriteReader +{ + Q_OBJECT + +public: + static int get_system_memory_threshold(); + static void set_system_memory_threshold(int percent); + + explicit SpriteDynamicReader(QObject *parent = nullptr); + virtual ~SpriteDynamicReader(); + + QSize get_sprite_size() const final; + + int get_frame_count() const final; + + SpriteFrame get_frame(int number) final; + + QVector get_frame_list() final; + +protected: + void load() final; + +private: + QSharedPointer m_reader; + std::atomic_uint64_t m_used_memory; + + void _p_create_reader(bool caching); + void _p_free_memory(); +}; +} // namespace mk2 diff --git a/src/mk2/spritereader.cpp b/src/mk2/spritereader.cpp new file mode 100644 index 000000000..39c865e6e --- /dev/null +++ b/src/mk2/spritereader.cpp @@ -0,0 +1,179 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "mk2/spritereader.h" + +#include +#include +#include +#include + +using namespace mk2; + +SpriteFrame::SpriteFrame() + : delay{0} +{} + +SpriteFrame::~SpriteFrame() +{} + +void SpriteReader::registerMetatypes() +{ + static bool s_registerRequired = true; + if (s_registerRequired) + { + s_registerRequired = false; + qRegisterMetaType(); + qRegisterMetaType(); + } +} + +SpriteReader::SpriteReader(QObject *parent) + : QObject{parent} + , m_device{new QFile} + , m_own_device{true} + , m_state{State::NotLoaded} + , m_last_error{Error::NoError} + , m_loading_progress{0} +{ + registerMetatypes(); +} + +SpriteReader::~SpriteReader() +{ + _p_delete_device(); +} + +QString SpriteReader::get_file_name() const +{ + QFile *l_file = dynamic_cast(m_device); + return l_file ? l_file->fileName() : QString{}; +} + +QIODevice *SpriteReader::get_device() const +{ + return m_device; +} + +bool SpriteReader::is_valid() const +{ + return get_frame_count() > 0; +} + +QSize SpriteReader::get_sprite_size() const +{ + return QSize{}; +} + +int SpriteReader::get_frame_count() const +{ + return 0; +} + +SpriteFrame SpriteReader::get_frame(int p_number) +{ + Q_UNUSED(p_number); + return SpriteFrame{}; +} + +QVector SpriteReader::get_frame_list() +{ + return QVector{}; +} + +SpriteReader::State SpriteReader::get_state() const +{ + return m_state; +} + +bool SpriteReader::is_loaded() const +{ + return m_state == State::FullyLoaded; +} + +int SpriteReader::get_loading_progress() const +{ + return m_loading_progress; +} + +SpriteReader::Error SpriteReader::get_last_error() const +{ + return m_last_error; +} + +void SpriteReader::set_file_name(QString p_file_name) +{ + set_device(new QFile(p_file_name)); + m_own_device = true; + emit file_name_changed(get_file_name()); +} + +void SpriteReader::set_device(QIODevice *p_device) +{ + _p_delete_device(); + m_device = p_device; + m_own_device = false; + set_state(State::NotLoaded); + set_error(Error::NoError); + set_loading_progress(0); + load(); +} + +void SpriteReader::load() +{} + +void SpriteReader::set_state(State p_state) +{ + if (m_state == p_state) + { + return; + } + m_state = p_state; + emit state_changed(m_state); +} + +void SpriteReader::set_loading_progress(int p_progress) +{ + p_progress = qBound(0, p_progress, 100); + if (m_loading_progress == p_progress) + { + return; + } + m_loading_progress = p_progress; + emit loading_progress_changed(m_loading_progress); +} + +void SpriteReader::set_error(Error p_error) +{ + m_last_error = p_error; + if (m_last_error != Error::NoError) + { + emit error(m_last_error); + } +} + +void SpriteReader::_p_delete_device() +{ + if (m_own_device) + { + delete m_device; + m_device = nullptr; + } +} diff --git a/src/mk2/spritereader.h b/src/mk2/spritereader.h new file mode 100644 index 000000000..34fa498eb --- /dev/null +++ b/src/mk2/spritereader.h @@ -0,0 +1,121 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace mk2 +{ +class SpriteFrame +{ +public: + QPixmap image; + int delay = 0; + + SpriteFrame(); + ~SpriteFrame(); +}; + +class SpriteReader : public QObject +{ + Q_OBJECT + +public: + using ptr = QSharedPointer; + + enum class State + { + NotLoaded, + FullyLoaded, + }; + Q_ENUM(State) + + enum class Error + { + NoError, + DeviceError, + InvalidDataError, + }; + Q_ENUM(Error) + + static void registerMetatypes(); + + explicit SpriteReader(QObject *parent = nullptr); + virtual ~SpriteReader(); + + QString get_file_name() const; + + QIODevice *get_device() const; + + virtual bool is_valid() const; + + virtual QSize get_sprite_size() const; + + virtual int get_frame_count() const; + + virtual SpriteFrame get_frame(int number); + + virtual QVector get_frame_list(); + + mk2::SpriteReader::State get_state() const; + + bool is_loaded() const; + + int get_loading_progress() const; + + Error get_last_error() const; + +public slots: + void set_file_name(QString file_name); + + void set_device(QIODevice *device); + +signals: + void file_name_changed(QString file_name); + + void state_changed(mk2::SpriteReader::State state); + + void loading_progress_changed(int); + + void error(mk2::SpriteReader::Error error); + +protected: + virtual void load(); + +protected slots: + void set_state(mk2::SpriteReader::State state); + + void set_loading_progress(int percent); + + void set_error(mk2::SpriteReader::Error error); + +private: + QIODevice *m_device; + bool m_own_device; + std::atomic m_state; + std::atomic m_last_error; + std::atomic_int m_loading_progress; + + void _p_delete_device(); +}; +} // namespace mk2 diff --git a/src/mk2/spritereadersynchronizer.cpp b/src/mk2/spritereadersynchronizer.cpp new file mode 100644 index 000000000..c253e74c0 --- /dev/null +++ b/src/mk2/spritereadersynchronizer.cpp @@ -0,0 +1,124 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "mk2/spritereadersynchronizer.h" + +using namespace mk2; + +SpriteReaderSynchronizer::SpriteReaderSynchronizer(QObject *parent) + : QObject{parent}, m_waiting{false}, m_finished{false}, m_threshold{50} +{} + +SpriteReaderSynchronizer::~SpriteReaderSynchronizer() +{} + +int SpriteReaderSynchronizer::get_threshold() const +{ + return m_threshold; +} + +QVector SpriteReaderSynchronizer::get_reader_list() const +{ + return m_reader_list; +} + +bool SpriteReaderSynchronizer::is_waiting() const +{ + return m_waiting; +} + +bool SpriteReaderSynchronizer::is_finished() const +{ + return m_finished; +} + +void SpriteReaderSynchronizer::set_threshold(int p_threshold) +{ + p_threshold = qBound(0, p_threshold, 100); + if (m_threshold == p_threshold) + { + return; + } + const int l_prev_threshold = m_threshold; + m_threshold = p_threshold; + if (m_threshold < l_prev_threshold) + { + _p_check_progress(); + } +} + +void SpriteReaderSynchronizer::add(mk2::SpriteReader::ptr p_reader) +{ + if (m_reader_list.contains(p_reader)) + { + return; + } + m_finished = false; + m_reader_list.append(p_reader); + connect(p_reader.data(), SIGNAL(loading_progress_changed(int)), this, SLOT(_p_check_progress())); + _p_check_progress(); +} + +void SpriteReaderSynchronizer::clear() +{ + m_waiting = false; + m_finished = false; + for (const mk2::SpriteReader::ptr &i_reader : qAsConst(m_reader_list)) + { + i_reader->disconnect(this); + } + m_reader_list.clear(); +} + +void SpriteReaderSynchronizer::start() +{ + m_waiting = true; + if (m_finished) + { + emit finished(); + } + else + { + _p_check_progress(); + } +} + +void SpriteReaderSynchronizer::_p_check_progress() +{ + if (m_finished || m_reader_list.isEmpty()) + { + return; + } + + for (const mk2::SpriteReader::ptr &i_reader : qAsConst(m_reader_list)) + { + // if the reader is invalid, it's the same as being fully loaded + if (i_reader->is_valid() && i_reader->get_loading_progress() < m_threshold) + { + return; + } + } + + m_finished = true; + if (m_waiting) + { + emit finished(); + } +} diff --git a/src/mk2/spritereadersynchronizer.h b/src/mk2/spritereadersynchronizer.h new file mode 100644 index 000000000..b35042365 --- /dev/null +++ b/src/mk2/spritereadersynchronizer.h @@ -0,0 +1,66 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include "mk2/spritereader.h" + +#include + +namespace mk2 +{ +class SpriteReaderSynchronizer : public QObject +{ + Q_OBJECT + +public: + SpriteReaderSynchronizer(QObject *parent = nullptr); + ~SpriteReaderSynchronizer(); + + int get_threshold() const; + + QVector get_reader_list() const; + + bool is_waiting() const; + + bool is_finished() const; + +public slots: + void set_threshold(int percent); + + void add(mk2::SpriteReader::ptr reader); + + void clear(); + + void start(); + +signals: + void finished(); + +private: + QVector m_reader_list; + bool m_waiting; + bool m_finished; + int m_threshold; + +private slots: + void _p_check_progress(); +}; +} // namespace mk2 diff --git a/src/mk2/spriteseekingreader.cpp b/src/mk2/spriteseekingreader.cpp new file mode 100644 index 000000000..ba6cea079 --- /dev/null +++ b/src/mk2/spriteseekingreader.cpp @@ -0,0 +1,140 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "mk2/spriteseekingreader.h" + +using namespace mk2; + +SpriteSeekingReader::SpriteSeekingReader(QObject *parent) + : SpriteReader{parent} + , m_sprite_size(QSize{}) + , m_frame_count{0} + , m_frame_number{-1} +{} + +SpriteSeekingReader::~SpriteSeekingReader() +{} + +QSize SpriteSeekingReader::get_sprite_size() const +{ + return m_sprite_size; +} + +int SpriteSeekingReader::get_frame_count() const +{ + return m_frame_count; +} + +mk2::SpriteFrame SpriteSeekingReader::get_frame(int p_number) +{ + if (!is_valid() || p_number < 0 || p_number >= m_frame_count) + { + return SpriteFrame{}; + } + + if (p_number == m_frame_number) + { + return m_current_frame; + } + + { // check whatever we need to rewind the device or not + const int l_next_frame_number = m_frame_number + 1; + if (p_number < l_next_frame_number) + { + _p_reset_buffer_device(); + } + } + + // seek frame + QImage l_image(m_reader.size(), QImage::Format_ARGB32); + for (; m_frame_number < p_number; ++m_frame_number) + { + m_reader.read(&l_image); + m_current_frame.delay = m_reader.nextImageDelay(); + } + m_current_frame.image = QPixmap::fromImage(l_image); + + return m_current_frame; +} + +QVector SpriteSeekingReader::get_frame_list() +{ + QVector l_frame_list; + + if (is_valid()) + { + l_frame_list.resize(m_reader.imageCount()); + + // always seek the frame after the current one + int l_next_frame_number = m_frame_number + 1; + for (int i = 0; i < m_frame_count; ++i) + { + l_next_frame_number %= m_frame_count; + l_frame_list.replace(l_next_frame_number, get_frame(l_next_frame_number)); + ++l_next_frame_number; + } + } + + return l_frame_list; +} + +void SpriteSeekingReader::load() +{ + m_raw_data.clear(); + m_sprite_size = QSize{}; + m_frame_count = 0; + + QIODevice *l_device = get_device(); + const int l_prev_pos = l_device->pos(); + if (!l_device->isOpen() && !l_device->open(QIODevice::ReadOnly)) + { + set_error(Error::DeviceError); + return; + } + l_device->seek(0); + m_raw_data = l_device->readAll(); + l_device->seek(l_prev_pos); + _p_reset_buffer_device(); + if (!m_reader.canRead()) + { + set_error(Error::InvalidDataError); + return; + } + m_sprite_size = m_reader.size(); + m_frame_count = m_reader.imageCount(); + m_current_frame = SpriteFrame{}; + set_loading_progress(100); + set_state(State::FullyLoaded); +} + +void SpriteSeekingReader::_p_reset_buffer_device() +{ + m_data_buffer.reset(new QBuffer(&m_raw_data)); + + if (m_data_buffer->open(QIODevice::ReadOnly)) + { + m_reader.setDevice(m_data_buffer.data()); + m_frame_number = -1; + } + else + { + set_error(Error::DeviceError); + } +} diff --git a/src/mk2/spriteseekingreader.h b/src/mk2/spriteseekingreader.h new file mode 100644 index 000000000..2372ecf99 --- /dev/null +++ b/src/mk2/spriteseekingreader.h @@ -0,0 +1,62 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include "mk2/spritereader.h" + +#include +#include +#include +#include + +namespace mk2 +{ +class SpriteSeekingReader : public SpriteReader +{ + Q_OBJECT + +public: + explicit SpriteSeekingReader(QObject *parent = nullptr); + virtual ~SpriteSeekingReader(); + + QSize get_sprite_size() const final; + + int get_frame_count() const final; + + SpriteFrame get_frame(int number) final; + + QVector get_frame_list() final; + +protected: + void load() final; + +private: + QByteArray m_raw_data; + QImageReader m_reader; + QScopedPointer m_data_buffer; + QSize m_sprite_size; + int m_frame_count; + int m_frame_number; + SpriteFrame m_current_frame; + + void _p_reset_buffer_device(); +}; +} // namespace mk2 diff --git a/src/mk2/spriteviewer.cpp b/src/mk2/spriteviewer.cpp new file mode 100644 index 000000000..6b77d40ed --- /dev/null +++ b/src/mk2/spriteviewer.cpp @@ -0,0 +1,305 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "mk2/spriteviewer.h" +#include "mk2/spritedynamicreader.h" + +#include +#include + +using namespace mk2; + +SpriteViewer::SpriteViewer(QWidget *parent) + : QLabel{parent} + , m_reader{new SpriteDynamicReader} + , m_scaling_mode{StretchScaling} + , m_resolved_scaling_mode{StretchScaling} + , m_transform{Qt::SmoothTransformation} + , m_running{false} + , m_mirror{false} + , m_play_once{false} + , m_frame_count{0} + , m_frame_number{0} +{ + setAlignment(Qt::AlignCenter); + + m_frame_timer.setTimerType(Qt::PreciseTimer); + m_frame_timer.setSingleShot(true); + + m_repaint_timer.setInterval(200); + m_repaint_timer.setSingleShot(true); + + connect(&m_frame_timer, SIGNAL(timeout()), this, SLOT(_p_paint_next_frame())); + connect(&m_repaint_timer, SIGNAL(timeout()), this, SLOT(_p_paint_frame())); +} + +SpriteViewer::~SpriteViewer() +{} + +QString SpriteViewer::get_file_name() const +{ + return m_reader->get_file_name(); +} + +QIODevice *SpriteViewer::get_device() const +{ + return nullptr; +} + +SpriteViewer::ScalingMode SpriteViewer::get_scaling_mode() const +{ + return m_scaling_mode; +} + +void SpriteViewer::set_file_name(QString p_file_name) +{ + if (!p_file_name.isEmpty() && p_file_name == get_file_name()) + { + return; + } + stop(); + m_reader->set_file_name(p_file_name); + m_frame_count = m_reader->get_frame_count(); + emit file_name_changed(p_file_name); +} + +void SpriteViewer::set_device(QIODevice *p_device) +{ + const QString l_prev_file_name = get_file_name(); + stop(); + m_reader->set_device(p_device); + m_frame_count = m_reader->get_frame_count(); + const QString l_file_name = get_file_name(); + if (l_file_name != l_prev_file_name) + { + emit file_name_changed(l_file_name); + } +} + +void SpriteViewer::set_play_once(bool p_on) +{ + m_play_once = p_on; +} + +void SpriteViewer::set_mirror(bool p_on) +{ + m_mirror = p_on; +} + +void SpriteViewer::set_scaling_mode(ScalingMode scaling_mode) +{ + if (m_scaling_mode == scaling_mode) + { + return; + } + m_scaling_mode = scaling_mode; + _p_resolve_scaling_mode(); + _p_paint_frame(); +} + +SpriteReader::ptr SpriteViewer::get_reader() const +{ + return m_reader; +} + +void SpriteViewer::set_reader(SpriteReader::ptr p_reader) +{ + const QString l_prev_file_name = get_file_name(); + stop(); + if (p_reader == nullptr) + { + p_reader = SpriteReader::ptr(new SpriteDynamicReader); + } + m_reader = p_reader; + m_frame_count = m_reader->get_frame_count(); + const QString l_file_name = get_file_name(); + if (l_file_name != l_prev_file_name) + { + emit file_name_changed(l_file_name); + } +} + +bool SpriteViewer::is_valid() const +{ + return m_reader->is_valid(); +} + +bool SpriteViewer::is_running() const +{ + return m_running; +} + +void SpriteViewer::start() +{ + m_running = true; + m_elapsed_timer.start(); + emit started(); + _p_resolve_scaling_mode(); + _p_paint_next_frame(); +} + +void SpriteViewer::restart() +{ + stop(); + start(); +} + +void SpriteViewer::stop() +{ + m_running = false; + m_frame_timer.stop(); + m_frame_number = 0; +} + +void SpriteViewer::resizeEvent(QResizeEvent *p_event) +{ + QLabel::resizeEvent(p_event); + _p_resolve_scaling_mode(); + m_repaint_timer.start(); +} + +void SpriteViewer::_p_resolve_scaling_mode() +{ + m_resolved_scaling_mode = m_scaling_mode; + + const QSize p_size = size(); + const QSize p_image_size = m_reader->get_sprite_size(); + if (p_size == p_image_size || !p_image_size.isValid()) + { + m_resolved_scaling_mode = NoScaling; + } + else if (m_resolved_scaling_mode == DynamicScaling) + { + const qreal l_width_factor = (qreal)qMax(p_image_size.width(), 1) / qMax(p_size.width(), 1); + const qreal l_height_factor = (qreal)qMax(p_image_size.height(), 1) / qMax(p_size.height(), 1); + + const QSize l_by_width_size{ + int((qreal)p_image_size.width() / l_width_factor), + int((qreal)p_image_size.height() / l_width_factor), + }; + const QSize l_by_height_size{ + int((qreal)p_image_size.width() / l_height_factor), + int((qreal)p_image_size.height() / l_height_factor), + }; + + if (l_by_width_size.height() >= p_size.height()) + { + m_resolved_scaling_mode = WidthScaling; + } + else if (l_by_height_size.width() >= p_size.width()) + { + m_resolved_scaling_mode = HeightScaling; + } + else + { + m_resolved_scaling_mode = StretchScaling; + } + } + + m_transform = Qt::SmoothTransformation; + if (p_image_size.width() < p_size.width() || p_image_size.height() < p_size.height()) + { + m_transform = Qt::FastTransformation; + } +} + +void SpriteViewer::_p_paint_next_frame() +{ + QElapsedTimer l_timer; + l_timer.start(); + + if (!is_valid()) + { + if (m_running && m_play_once) + { + m_running = false; + emit finished(); + } + + return; + } + + if (!m_running) + { + return; + } + + if (m_frame_number >= m_frame_count) + { + if (m_play_once) + { + m_running = false; + emit finished(); + return; + } + + if (m_frame_count > 1) + { + m_frame_number = 0; + } + else + { + return; + } + } + + const int l_current_frame_number = m_frame_number; + m_current_frame = m_reader->get_frame(m_frame_number++); + _p_paint_frame(); + + const int l_next_delay = m_current_frame.delay - l_timer.elapsed(); + m_frame_timer.start(qMax(0, l_next_delay)); +} + +void SpriteViewer::_p_paint_frame() +{ + QPixmap l_image = m_current_frame.image; + + if (!l_image.isNull()) + { + switch (m_resolved_scaling_mode) + { + case NoScaling: + [[fallthrough]]; + default: + break; + + case StretchScaling: + l_image = l_image.scaled(size(), Qt::IgnoreAspectRatio, m_transform); + break; + + case WidthScaling: + l_image = l_image.scaledToWidth(width(), m_transform); + break; + + case HeightScaling: + l_image = l_image.scaledToHeight(height(), m_transform); + break; + } + } + + // slow operation... + if (m_mirror) + { + l_image = QPixmap::fromImage(l_image.toImage().mirrored(true, false)); + } + + setPixmap(l_image); +} diff --git a/src/mk2/spriteviewer.h b/src/mk2/spriteviewer.h new file mode 100644 index 000000000..2cdb9a871 --- /dev/null +++ b/src/mk2/spriteviewer.h @@ -0,0 +1,112 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include "spritereader.h" + +#include +#include +#include + +#include + +namespace mk2 +{ +class SpriteViewer : public QLabel +{ + Q_OBJECT + +public: + enum ScalingMode + { + NoScaling, + WidthScaling, + HeightScaling, + StretchScaling, + DynamicScaling, + }; + Q_ENUM(ScalingMode) + + SpriteViewer(QWidget *parent = nullptr); + ~SpriteViewer(); + + QString get_file_name() const; + + QIODevice *get_device() const; + + SpriteViewer::ScalingMode get_scaling_mode() const; + + SpriteReader::ptr get_reader() const; + + void set_reader(SpriteReader::ptr reader); + + bool is_valid() const; + + bool is_running() const; + +public slots: + void set_file_name(QString file_name); + + void set_device(QIODevice *device); + + void set_play_once(bool on); + + void set_mirror(bool on); + + void set_scaling_mode(SpriteViewer::ScalingMode scaling_mode); + + void start(); + void restart(); + void stop(); + +signals: + void file_name_changed(QString); + + void reader_changed(); + + void started(); + void finished(); + +protected: + void resizeEvent(QResizeEvent *event) final; + +private: + SpriteReader::ptr m_reader; + SpriteFrame m_current_frame; + SpriteViewer::ScalingMode m_scaling_mode; + SpriteViewer::ScalingMode m_resolved_scaling_mode; + Qt::TransformationMode m_transform; + bool m_running; + bool m_mirror; + bool m_play_once; + int m_frame_count; + int m_frame_number; + QElapsedTimer m_elapsed_timer; + QTimer m_frame_timer; + QTimer m_repaint_timer; + + void _p_resolve_scaling_mode(); + +private slots: + void _p_paint_next_frame(); + void _p_paint_frame(); +}; +} // namespace mk2 diff --git a/src/path_functions.cpp b/src/path_functions.cpp index b00f782c2..da9ead958 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -43,12 +43,6 @@ QString AOApplication::get_character_path(QString p_chr, QString p_file) return get_case_sensitive_path(r_path); } -QString AOApplication::get_sounds_path(QString p_file) -{ - QString path = get_base_path() + "sounds/general/" + p_file; - return get_case_sensitive_path(path); -} - QString AOApplication::get_music_folder_path() { const QString l_path = get_base_path() + "sounds/music/"; diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 61bd45203..bd2738dcb 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -15,7 +15,7 @@ void AOApplication::connect_to_server(DRServerInfo p_server) { - m_server_socket->connect_to_server(p_server, false); + m_server_socket->connect_to_server(p_server); } void AOApplication::send_server_packet(DRPacket p_packet) @@ -270,6 +270,13 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) destruct_lobby(); } + else if (l_header == "joined_area") + { + if (!is_courtroom_constructed) + return; + + m_courtroom->reset_viewport(); + } else if (l_header == "BN") { if (l_content.size() < 1) @@ -293,6 +300,16 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) m_courtroom->set_background(l_area_bg); } + else if (l_header == "area_ambient") + { + if (l_content.size() < 1) + return; + + if (!is_courtroom_constructed) + return; + + m_courtroom->set_ambient(l_content.at(0)); + } else if (l_header == "chat_tick_rate") { if (l_content.size() < 1) @@ -313,7 +330,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) else if (l_header == "MS") { if (is_courtroom_constructed && is_courtroom_loaded) - m_courtroom->handle_chatmessage(l_content); + m_courtroom->next_chatmessage(l_content); } else if (l_header == "ackMS") { diff --git a/src/version.cpp b/src/version.cpp index b00df3a3b..0ddd89de5 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -15,12 +15,12 @@ int get_release_version() int get_major_version() { - return 1; + return 2; } int get_minor_version() { - return 1; + return 0; } VersionNumber get_version_number() @@ -40,7 +40,7 @@ QString get_version_string() const QString l_post = get_post_version(); if (!l_post.isEmpty()) { - l_version += QString("-" + l_post); + l_version += QString("+" + l_post); } return l_version; From 9c06682e02640d84a294c3cedaa8bd58c24e7c45 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 11 Jul 2022 22:02:46 +0200 Subject: [PATCH 765/842] Removed compatibility checks meant for 1.1.0 --- src/courtroom.cpp | 4 +--- src/courtroom_character.cpp | 9 +-------- src/server_socket.cpp | 2 +- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index fb7e4477d..2cf9be35b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -943,7 +943,6 @@ void Courtroom::preload_chatmessage(QStringList p_contents) m_loading_timer->stop(); m_pre_chatmessage = p_contents; - if (ao_app->is_server_client_version_compatible()) { bool l_ok; const int l_client_id = m_pre_chatmessage[CMClientId].toInt(&l_ok); @@ -1853,8 +1852,7 @@ void Courtroom::set_ban(int p_cid) void Courtroom::handle_song(QStringList p_contents) { - const bool l_server_compatible = ao_app->is_server_client_version_compatible(); - if (p_contents.size() < (l_server_compatible ? 4 : 3)) return; + if (p_contents.size() < 4) return; QString l_song = p_contents.at(0); for (auto &i_extension : audio_extensions()) diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp index 0991915e8..ccaaadda1 100644 --- a/src/courtroom_character.cpp +++ b/src/courtroom_character.cpp @@ -125,14 +125,7 @@ void Courtroom::refresh_character_content_url() if (m_character_content_url == l_new_content_url) return; m_character_content_url = l_new_content_url; - if (ao_app->is_server_client_version_compatible()) - { - ao_app->send_server_packet(DRPacket("FS", {m_character_content_url})); - } - else - { - send_ooc_packet("/files_set " + m_character_content_url); - } + ao_app->send_server_packet(DRPacket("FS", {m_character_content_url})); } void Courtroom::on_iniswap_dropdown_changed(int p_index) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index bd2738dcb..f166efc20 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -334,7 +334,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) } else if (l_header == "ackMS") { - if (is_courtroom_constructed && is_courtroom_loaded && !is_server_client_version_compatible()) + if (is_courtroom_constructed && is_courtroom_loaded &&) m_courtroom->handle_acknowledged_ms(); } else if (l_header == "MC") From c3ce82966f0d10d55da539ec7557f274f232e1cb Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 11 Jul 2022 22:10:41 +0200 Subject: [PATCH 766/842] Forgot some extra --- src/courtroom.cpp | 1 - src/server_socket.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2cf9be35b..44dcf4100 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1870,7 +1870,6 @@ void Courtroom::handle_song(QStringList p_contents) QString l_showname = p_contents.at(2); - if (l_server_compatible) { const bool l_restart = p_contents.at(3).toInt(); if (m_current_song == l_song && !l_restart) return; diff --git a/src/server_socket.cpp b/src/server_socket.cpp index f166efc20..f9aba016a 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -334,7 +334,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) } else if (l_header == "ackMS") { - if (is_courtroom_constructed && is_courtroom_loaded &&) + if (is_courtroom_constructed && is_courtroom_loaded) m_courtroom->handle_acknowledged_ms(); } else if (l_header == "MC") From 5a255e17474733bb7ad1eb94d23c7a596096ebe4 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 12 Jul 2022 15:09:01 +0200 Subject: [PATCH 767/842] Properly reset lobby status --- src/aoapplication.cpp | 6 +++++- src/aoapplication.h | 1 + src/lobby.cpp | 20 +++++++++++++++----- src/lobby.h | 2 ++ 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 5357d9ae2..9ebb45d52 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -138,7 +138,11 @@ void AOApplication::destruct_courtroom() } // gracefully close our connection to the current server - m_server_socket->disconnect_from_server(); + if (m_server_socket->is_connected()) + { + m_server_socket->disconnect_from_server(); + emit closed_connection_to_server(); + } } DRDiscord *AOApplication::get_discord() const diff --git a/src/aoapplication.h b/src/aoapplication.h index 4e47ad4e5..f461f5752 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -187,6 +187,7 @@ public slots: void connecting_to_server(); void connected_to_server(); + void closed_connection_to_server(); void disconnected_from_server(); private: diff --git a/src/lobby.cpp b/src/lobby.cpp index a4ecc5bc1..dcdf42524 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -80,6 +80,7 @@ Lobby::Lobby(AOApplication *p_ao_app) connect(ao_app, SIGNAL(connecting_to_server()), this, SLOT(_p_on_connecting_to_server())); connect(ao_app, SIGNAL(connected_to_server()), this, SLOT(_p_on_connected_to_server())); + connect(ao_app, SIGNAL(closed_connection_to_server()), this, SLOT(_p_on_closed_connection_to_server())); connect(ao_app, SIGNAL(disconnected_from_server()), this, SLOT(_p_on_disconnected_from_server())); connect(ui_public_server_filter, SIGNAL(clicked()), this, SLOT(toggle_public_server_filter())); @@ -577,11 +578,16 @@ void Lobby::_p_on_connected_to_server() set_connection_state(ConnectedState); } -void Lobby::_p_on_disconnected_from_server() +void Lobby::_p_on_closed_connection_to_server() { set_connection_state(NotConnectedState); } +void Lobby::_p_on_disconnected_from_server() +{ + set_connection_state(LostConnectionState); +} + void Lobby::_p_update_description() { QString l_message; @@ -589,15 +595,19 @@ void Lobby::_p_update_description() const QString l_format = QMap{ { NotConnectedState, - tr("Failed to connect to server (%1)"), + tr("Choose a server."), }, { ConnectingState, - tr("Connecting to server (%1)"), + tr("Connecting to server (%1)..."), }, { ConnectedState, - tr("Connected to server (%1)"), + tr("Connected to server (%1)."), + }, + { + LostConnectionState, + tr("Failed to connect to server (%1)."), }, }[m_connection_state]; l_message = QString(l_format).arg(m_current_server.name.toHtmlEscaped()); @@ -621,7 +631,7 @@ void Lobby::_p_update_description() void Lobby::set_choose_a_server() { ui_player_count->setText(nullptr); - ui_description->setHtml(tr("Choose a server.")); + _p_update_description(); } void Lobby::set_player_count(int players_online, int max_players) diff --git a/src/lobby.h b/src/lobby.h index 885a11a64..f63c8aae8 100644 --- a/src/lobby.h +++ b/src/lobby.h @@ -60,6 +60,7 @@ class Lobby : public QMainWindow NotConnectedState, ConnectingState, ConnectedState, + LostConnectionState, }; ConnectionState m_connection_state; @@ -124,6 +125,7 @@ private slots: void _p_on_connecting_to_server(); void _p_on_connected_to_server(); + void _p_on_closed_connection_to_server(); void _p_on_disconnected_from_server(); void _p_update_description(); From 7245a8b7af56091d3f23e3245b0ef4e1b67521b4 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 13 Jul 2022 05:05:26 +0200 Subject: [PATCH 768/842] Context menu for music list is no longer at odd locations --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 44dcf4100..5d9355e9b 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2155,7 +2155,7 @@ void Courtroom::on_music_list_double_clicked(QModelIndex p_model) void Courtroom::on_music_list_context_menu_requested(QPoint p_point) { const QPoint l_global_point = ui_music_list->viewport()->mapToGlobal(p_point); - ui_music_menu->popup(mapToGlobal(l_global_point)); + ui_music_menu->popup(l_global_point); } void Courtroom::on_music_menu_play_triggered() From c9f5f80fa81e76c58f08a9b4f1fd12176757665e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 13 Jul 2022 22:14:24 +0200 Subject: [PATCH 769/842] Use MSVC2019 (x64) Use MSVC2019 (x64) rather than MinGW (x64) for Windows --- .github/workflows/build.yml | 47 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c35db4822..a8cfde68c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,9 @@ name: Build +env: + # Multi-threaded compilation for MSVC + CL: /MP + on: [workflow_dispatch, push] jobs: @@ -12,8 +16,6 @@ jobs: include: - os: windows-latest os-caption: Windows - arch: win64_mingw81 - compiler: mingw81_64 - os: ubuntu-latest os-caption: Ubuntu - os: macos-latest @@ -21,7 +23,7 @@ jobs: runs-on: ${{matrix.os}} steps: - - name: Checkout + - name: Clone Project uses: actions/checkout@v2.4.2 - name: Fetch Git Info @@ -121,38 +123,50 @@ jobs: run: | sudo apt-get update sudo apt-get upgrade - - - name: Install Qt (Windows) - if: contains(matrix.os, 'windows') - uses: jurplel/install-qt-action@v2.14.0 - with: - version: ${{matrix.version}} - arch: ${{matrix.arch}} - - name: Install Qt (Other) - if: contains(matrix.os, 'windows') != true + - name: Install Qt uses: jurplel/install-qt-action@v2.14.0 with: version: ${{matrix.version}} - - name: Pull QtApng + - name: Clone QtApng uses: actions/checkout@v2 with: repository: Skycoder42/QtApng path: ./qtapng - - name: Build QtApng + - name: Make QtApng run: | cd ./qtapng qmake + + - name: Build QtApng (Windows) + if: contains(matrix.os, 'windows') + run: | make -j3 + + - name: Build QtApng (Others) + if: contains(matrix.os, 'windows') != true + run: | + nmake + + - name: Post Build QtApng ls -R cd .. - - name: Build + - name: Make Project run: | cp ./data/.qmake.conf . qmake + + - name: Build Project (Windows) + if: contains(matrix.os, 'windows') + run: | + nmake + + - name: Build Project (Others) + if: contains(matrix.os, 'windows') != true + run: | make -j3 - name: Packing (Windows) @@ -162,9 +176,6 @@ jobs: windeployqt dro-client.exe --compiler-runtime --no-quick-import --no-translations cp ../qtapng/plugins/imageformats/qapng.dll ./imageformats cp ../3rd/*.dll . - cp ../../Qt/${{matrix.version}}/${{matrix.compiler}}/bin/libgcc_s_seh-1.dll . - cp ../../Qt/${{matrix.version}}/${{matrix.compiler}}/bin/libwinpthread-1.dll . - cp ../../Qt/${{matrix.version}}/${{matrix.compiler}}/bin/libstdc++-6.dll . cd .. - name: Packing (Ubuntu) From 85b4da5089be9ec07a2edab88baacc122e4d368e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 13 Jul 2022 22:15:25 +0200 Subject: [PATCH 770/842] Added run field --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a8cfde68c..7fa5a7124 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -151,6 +151,7 @@ jobs: nmake - name: Post Build QtApng + run: | ls -R cd .. From 3ded38b1f4a88f82445312cf0c74899a41f98820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 13 Jul 2022 22:19:21 +0200 Subject: [PATCH 771/842] Inverted Window and Others build commands --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7fa5a7124..3e3289063 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -143,12 +143,12 @@ jobs: - name: Build QtApng (Windows) if: contains(matrix.os, 'windows') run: | - make -j3 + nmake - name: Build QtApng (Others) if: contains(matrix.os, 'windows') != true run: | - nmake + make -j3 - name: Post Build QtApng run: | From 9562068fc762f38cdbd3d232e7fd7f6b0f53faa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 13 Jul 2022 22:25:15 +0200 Subject: [PATCH 772/842] Remove extra step --- .github/workflows/build.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3e3289063..4631e01f2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -135,25 +135,19 @@ jobs: repository: Skycoder42/QtApng path: ./qtapng - - name: Make QtApng - run: | - cd ./qtapng - qmake - - name: Build QtApng (Windows) if: contains(matrix.os, 'windows') run: | + cd ./qtapng + qmake nmake - name: Build QtApng (Others) if: contains(matrix.os, 'windows') != true run: | + cd ./qtapng + qmake make -j3 - - - name: Post Build QtApng - run: | - ls -R - cd .. - name: Make Project run: | From c7ce246533cee09c83af7f65a22729b5bec74360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 13 Jul 2022 22:32:41 +0200 Subject: [PATCH 773/842] Configure MSVC --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4631e01f2..880458b75 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -118,7 +118,7 @@ jobs: cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./3rd/discord-rpc cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./3rd - - name: Update Ubuntu + - name: Update packages (Ubuntu) if: contains(matrix.os, 'ubuntu') run: | sudo apt-get update @@ -128,6 +128,10 @@ jobs: uses: jurplel/install-qt-action@v2.14.0 with: version: ${{matrix.version}} + + - name: Configure MSVC (Windows) + if: contains(matrix.os, 'windows') + uses: ilammy/msvc-dev-cmd@v1 - name: Clone QtApng uses: actions/checkout@v2 From 2a3bd685a62e424b5cff30d769ff8c8829b4c630 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 14 Jul 2022 00:48:25 +0200 Subject: [PATCH 774/842] Assume 1.1.0 features to be active --- src/courtroom.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 5d9355e9b..901f46a0c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -847,17 +847,14 @@ void Courtroom::on_ic_message_return_pressed() f_text_color = QString::number(m_text_color); packet_contents.append(f_text_color); - if (ao_app->get_server_client_version_status() == VersionStatus::Ok) - { - // showname - packet_contents.append(ao_config->showname()); + // showname + packet_contents.append(ao_config->showname()); - // video name - packet_contents.append(!l_emote.video_file.isEmpty() ? l_emote.video_file : "0"); + // video name + packet_contents.append(!l_emote.video_file.isEmpty() ? l_emote.video_file : "0"); - // hide character - packet_contents.append(QString::number(ui_hide_character->isChecked())); - } + // hide character + packet_contents.append(QString::number(ui_hide_character->isChecked())); ao_app->send_server_packet(DRPacket("MS", packet_contents)); } From 2eed031e3016dc17ba741d19a81dc5bc86545ff1 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 14 Jul 2022 05:58:40 +0200 Subject: [PATCH 775/842] Added IC message limit * IC messages are now limited to 255 characters. A number on the side shows the current length and will warn the user (through a tooltip) if they reach that limit. --- src/courtroom.cpp | 64 +++++++++++++++++++++++---------------- src/courtroom.h | 8 +++-- src/courtroom_sfx.cpp | 2 +- src/courtroom_widgets.cpp | 63 +++++++++++++++++++++++++++----------- src/emotes.cpp | 6 ++-- 5 files changed, 93 insertions(+), 50 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 901f46a0c..040babf0f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -44,6 +45,7 @@ #include #include #include +#include #include const int Courtroom::DEFAULT_WIDTH = 714; @@ -306,7 +308,7 @@ void Courtroom::enter_courtroom(int p_cid) if (l_changed_chr_id) update_default_iniswap_item(); - QLineEdit *l_current_field = ui_ic_chat_message; + QLineEdit *l_current_field = ui_ic_chat_message_field; if (ui_ooc_chat_message->hasFocus()) l_current_field = ui_ooc_chat_message; const int l_current_cursor_pos = l_current_field->cursorPosition(); @@ -356,7 +358,7 @@ void Courtroom::enter_courtroom(int p_cid) ui_emotes->setHidden(is_spectating()); ui_emote_dropdown->setDisabled(is_spectating()); ui_iniswap_dropdown->setDisabled(is_spectating()); - ui_ic_chat_message->setDisabled(is_spectating()); + ui_ic_chat_message_field->setDisabled(is_spectating()); // restore line field focus l_current_field->setFocus(); @@ -746,7 +748,7 @@ void Courtroom::on_character_ini_changed() void Courtroom::on_ic_message_return_pressed() { - if (ui_ic_chat_message->text() == "" || is_client_muted) return; + if (ui_ic_chat_message_field->text() == "" || is_client_muted) return; if ((anim_state < 3 || text_state < 2) && m_shout_state == 0) return; @@ -786,7 +788,7 @@ void Courtroom::on_ic_message_return_pressed() else packet_contents.append(l_emote.dialog); - packet_contents.append(ui_ic_chat_message->text()); + packet_contents.append(ui_ic_chat_message_field->text()); const QString l_side = ao_app->get_char_side(get_character_ini()); packet_contents.append(l_side); @@ -859,9 +861,19 @@ void Courtroom::on_ic_message_return_pressed() ao_app->send_server_packet(DRPacket("MS", packet_contents)); } +void Courtroom::on_ic_message_text_changed(QString p_text) +{ + const int l_length = p_text.length(); + ui_ic_chat_message_counter->setText(l_length ? QString::number(l_length) : nullptr); + if (l_length == 255) + { + QToolTip::showText(QCursor::pos(), QString(tr("Your message cannot be longer than %1 character(s).")).arg(ui_ic_chat_message_field->maxLength())); + } +} + void Courtroom::handle_acknowledged_ms() { - ui_ic_chat_message->clear(); + ui_ic_chat_message_field->clear(); // reset states ui_pre->setChecked(ao_config->always_pre_enabled()); @@ -1827,14 +1839,14 @@ void Courtroom::set_muted(bool p_muted, int p_cid) else { ui_muted->hide(); - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } - ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); + ui_muted->resize(ui_ic_chat_message_field->width(), ui_ic_chat_message_field->height()); ui_muted->set_theme_image("muted.png"); is_client_muted = p_muted; - ui_ic_chat_message->setEnabled(!p_muted); + ui_ic_chat_message_field->setEnabled(!p_muted); } void Courtroom::set_ban(int p_cid) @@ -1995,7 +2007,7 @@ void Courtroom::on_ooc_name_editing_finished() ao_config->set_username(l_text); } -void Courtroom::on_ooc_return_pressed() +void Courtroom::on_ooc_message_return_pressed() { const QString l_message = ui_ooc_chat_message->text(); @@ -2075,7 +2087,7 @@ void Courtroom::on_ooc_return_pressed() void Courtroom::on_pos_dropdown_changed(int p_index) { - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); if (p_index < 0 || p_index > 5) return; @@ -2117,14 +2129,14 @@ void Courtroom::on_pos_dropdown_changed(int p_index) void Courtroom::on_area_list_clicked() { - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_area_list_double_clicked(QModelIndex p_model) { const QString l_area_name = ui_area_list->item(p_model.row())->text(); ao_app->send_server_packet(DRPacket("MC", {l_area_name, QString::number(m_chr_id)})); - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_area_search_edited(QString p_filter) @@ -2139,14 +2151,14 @@ void Courtroom::on_area_search_edited() void Courtroom::on_music_list_clicked() { - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_music_list_double_clicked(QModelIndex p_model) { const QString l_song_name = ui_music_list->item(p_model.row())->data(Qt::UserRole).toString(); send_mc_packet(l_song_name); - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_music_list_context_menu_requested(QPoint p_point) @@ -2163,7 +2175,7 @@ void Courtroom::on_music_menu_play_triggered() const QString l_song = l_item->data(Qt::UserRole).toString(); send_mc_packet(l_song); } - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_music_menu_insert_ooc_triggered() @@ -2224,7 +2236,7 @@ void Courtroom::on_shout_button_clicked(const bool p_checked) } m_shout_state = p_checked ? l_id : 0; - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_shout_button_toggled(const bool p_checked) @@ -2273,7 +2285,7 @@ void Courtroom::on_cycle_clicked() m_system_player->play(ao_app->get_sfx("cycle")); set_shouts(); - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } /** @@ -2343,7 +2355,7 @@ void Courtroom::on_effect_button_clicked(const bool p_checked) } m_effect_state = p_checked ? l_id : 0; - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_effect_button_toggled(const bool p_checked) @@ -2390,7 +2402,7 @@ void Courtroom::on_prosecution_plus_clicked() void Courtroom::on_text_color_changed(int p_color) { m_text_color = p_color; - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } /** @@ -2428,7 +2440,7 @@ void Courtroom::on_wtce_clicked() ao_app->send_server_packet(DRPacket("RT", {QString("testimony%1").arg(id)})); - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_change_character_clicked() @@ -2498,7 +2510,7 @@ void Courtroom::on_call_mod_clicked() else qDebug() << "Did not call mod"; - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_switch_area_music_clicked() @@ -2523,23 +2535,23 @@ void Courtroom::on_switch_area_music_clicked() void Courtroom::on_pre_clicked() { - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_flip_clicked() { - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_hidden_clicked() { - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_config_panel_clicked() { ao_app->toggle_config_panel(); - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_note_button_clicked() @@ -2557,7 +2569,7 @@ void Courtroom::on_note_button_clicked() save_note(); ui_vp_notepad_image->hide(); ui_vp_notepad->hide(); - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); is_note_shown = false; } } diff --git a/src/courtroom.h b/src/courtroom.h index 776a1cdd7..8c6761db2 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -51,6 +51,7 @@ class QMenu; class QPropertyAnimation; class QScrollArea; class QSignalMapper; +class QLabel; #include @@ -433,7 +434,9 @@ class Courtroom : public QMainWindow QAction *ui_sfx_menu_insert_ooc = nullptr; QLineEdit *ui_ic_chat_showname = nullptr; - QLineEdit *ui_ic_chat_message = nullptr; + QWidget *ui_ic_chat_message = nullptr; + QLineEdit *ui_ic_chat_message_field = nullptr; + QLabel *ui_ic_chat_message_counter = nullptr; QLineEdit *ui_ooc_chat_name = nullptr; QLineEdit *ui_ooc_chat_message = nullptr; @@ -600,6 +603,7 @@ private slots: void on_character_ini_changed(); void on_ic_showname_editing_finished(); void on_ic_message_return_pressed(); + void on_ic_message_text_changed(QString text); void on_chat_config_changed(); void on_ic_chatlog_scroll_changed(); @@ -607,7 +611,7 @@ private slots: void on_ic_chatlog_scroll_bottomup_clicked(); void on_ooc_name_editing_finished(); - void on_ooc_return_pressed(); + void on_ooc_message_return_pressed(); void on_area_list_clicked(); void on_area_list_double_clicked(QModelIndex p_model); diff --git a/src/courtroom_sfx.cpp b/src/courtroom_sfx.cpp index 7d66fe9fc..ff71b6ab4 100644 --- a/src/courtroom_sfx.cpp +++ b/src/courtroom_sfx.cpp @@ -129,7 +129,7 @@ void Courtroom::on_sfx_list_current_item_changed(QListWidgetItem *p_current_item set_sfx_item_color(p_current_item); set_sfx_item_color(p_previous_item); ui_pre->setChecked(ui_pre->isChecked() || current_sfx().has_value()); - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_sfx_list_context_menu_requested(QPoint p_point) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 8b44883ab..2dd8a9266 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -158,22 +159,37 @@ void Courtroom::create_widgets() ui_ic_chat_showname->setPlaceholderText("Showname"); ui_ic_chat_showname->setText(ao_config->showname()); - ui_ic_chat_message = new QLineEdit(this); - ui_ic_chat_message->setFrame(false); - ui_ic_chat_message->setPlaceholderText(tr("Say something in-character.")); + ui_ic_chat_message = new QWidget(this); - ui_muted = new AOImageDisplay(ui_ic_chat_message, ao_app); - ui_muted->hide(); + ui_ic_chat_message_field = new QLineEdit(ui_ic_chat_message); + ui_ic_chat_message_field->setFrame(false); + ui_ic_chat_message_field->setPlaceholderText(tr("Say something in-character.")); + ui_ic_chat_message_field->setMaxLength(255); - ui_ooc_chat_message = new QLineEdit(this); - ui_ooc_chat_message->setFrame(false); - ui_ooc_chat_message->setPlaceholderText(tr("Say something out-of-character.")); + ui_ic_chat_message_counter = new QLabel(ui_ic_chat_message); + ui_ic_chat_message_counter->setAlignment(Qt::AlignCenter); + ui_ic_chat_message_counter->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); + + { + auto l_layout = new QHBoxLayout(ui_ic_chat_message); + l_layout->setContentsMargins(0, 0, l_layout->contentsMargins().right(), 0); + l_layout->addWidget(ui_ic_chat_message_field); + l_layout->addWidget(ui_ic_chat_message_counter); + } + + ui_muted = new AOImageDisplay(ui_ic_chat_message_field, ao_app); + ui_muted->hide(); ui_ooc_chat_name = new QLineEdit(this); ui_ooc_chat_name->setFrame(false); ui_ooc_chat_name->setPlaceholderText("Name"); ui_ooc_chat_name->setText(ao_config->username()); + ui_ooc_chat_message = new QLineEdit(this); + ui_ooc_chat_message->setFrame(false); + ui_ooc_chat_message->setPlaceholderText(tr("Say something out-of-character.")); + ui_ooc_chat_message->setMaxLength(255); + ui_note_area = new AONoteArea(this, ao_app); ui_note_area->add_button = new AOButton(ui_note_area, ao_app); ui_note_area->m_layout = new QVBoxLayout(ui_note_area); @@ -288,13 +304,14 @@ void Courtroom::connect_widgets() SLOT(on_showname_placeholder_changed(QString))); connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(on_character_ini_changed())); connect(ui_ic_chat_showname, SIGNAL(editingFinished()), this, SLOT(on_ic_showname_editing_finished())); - connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ic_message_return_pressed())); + connect(ui_ic_chat_message_field, SIGNAL(returnPressed()), this, SLOT(on_ic_message_return_pressed())); + connect(ui_ic_chat_message_field, SIGNAL(textChanged(QString)), this, SLOT(on_ic_message_text_changed(QString))); connect(ui_ic_chatlog->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(on_ic_chatlog_scroll_changed())); connect(ui_ic_chatlog_scroll_topdown, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_topdown_clicked())); connect(ui_ic_chatlog_scroll_bottomup, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_bottomup_clicked())); connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); connect(ui_ooc_chat_name, SIGNAL(editingFinished()), this, SLOT(on_ooc_name_editing_finished())); - connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); + connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_message_return_pressed())); connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_music_list_clicked())); connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_music_list_double_clicked(QModelIndex))); @@ -677,12 +694,18 @@ void Courtroom::set_widgets() set_size_and_pos(ui_ic_chat_showname, "ic_chat_name", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_ic_chat_showname, "ic_chat_name", COURTROOM_FONTS_INI, ao_app); if (!set_stylesheet(ui_ic_chat_showname, "[IC NAME LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) + { ui_ic_chat_showname->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + } set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message", COURTROOM_DESIGN_INI, ao_app); - set_text_alignment(ui_ic_chat_message, "ao2_ic_chat_message", COURTROOM_FONTS_INI, ao_app); + set_text_alignment(ui_ic_chat_message_field, "ao2_ic_chat_message", COURTROOM_FONTS_INI, ao_app); if (!set_stylesheet(ui_ic_chat_message, "[IC LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) - ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); + { + ui_ic_chat_message->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + } + ui_ic_chat_message_field->setStyleSheet(ui_ic_chat_message->styleSheet()); + ui_ic_chat_message_counter->setStyleSheet(ui_ic_chat_message->styleSheet()); set_size_and_pos(ui_vp_chatbox, "ao2_chatbox", COURTROOM_DESIGN_INI, ao_app); @@ -705,18 +728,22 @@ void Courtroom::set_widgets() ui_vp_chatbox->set_theme_image("chatmed.png"); ui_vp_chatbox->hide(); - ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); + ui_muted->resize(ui_ic_chat_message_field->width(), ui_ic_chat_message_field->height()); ui_muted->set_theme_image("muted.png"); - set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_DESIGN_INI, ao_app); - set_text_alignment(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_FONTS_INI, ao_app); - if (!set_stylesheet(ui_ooc_chat_message, "[OOC LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) - ui_ooc_chat_message->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); - set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_ooc_chat_name, "ooc_chat_name", COURTROOM_FONTS_INI, ao_app); if (!set_stylesheet(ui_ooc_chat_name, "[OOC NAME LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) + { ui_ooc_chat_name->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + } + + set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_FONTS_INI, ao_app); + if (!set_stylesheet(ui_ooc_chat_message, "[OOC LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) + { + ui_ooc_chat_message->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + } set_size_and_pos(ui_sfx_search, "sfx_search", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_sfx_search, "sfx_search", COURTROOM_FONTS_INI, ao_app); diff --git a/src/emotes.cpp b/src/emotes.cpp index b9e1b6e16..8360602d9 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -195,7 +195,7 @@ void Courtroom::select_emote(int p_id) ui_emote_dropdown->setCurrentIndex(m_emote_id); - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_emote_clicked(int p_id) @@ -257,7 +257,7 @@ void Courtroom::on_emote_left_clicked() refresh_emote_page(); - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_emote_right_clicked() @@ -266,7 +266,7 @@ void Courtroom::on_emote_right_clicked() refresh_emote_page(); - ui_ic_chat_message->setFocus(); + ui_ic_chat_message_field->setFocus(); } void Courtroom::on_emote_dropdown_changed(int p_index) From 0320a110c22c63f81d805c98fef3a33e3f4b524e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 14 Jul 2022 06:06:56 +0200 Subject: [PATCH 776/842] Tweaked character number visibility * IC character counter will only appear if the current count of characters is at or above 70% of the maximum length allowed. --- src/courtroom.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 040babf0f..f6a56e171 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -864,8 +864,10 @@ void Courtroom::on_ic_message_return_pressed() void Courtroom::on_ic_message_text_changed(QString p_text) { const int l_length = p_text.length(); - ui_ic_chat_message_counter->setText(l_length ? QString::number(l_length) : nullptr); - if (l_length == 255) + const int l_max_length = ui_ic_chat_message_field->maxLength(); + ui_ic_chat_message_counter->setText(QString::number(l_length)); + ui_ic_chat_message_counter->setVisible(l_length >= int(l_max_length * 0.8)); + if (l_length == l_max_length) { QToolTip::showText(QCursor::pos(), QString(tr("Your message cannot be longer than %1 character(s).")).arg(ui_ic_chat_message_field->maxLength())); } From 3e8c9b244c3ea4969b60b5eb99a48c02b01e982d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 14 Jul 2022 06:10:18 +0200 Subject: [PATCH 777/842] Further tweaked counter. * Further tweaked counter. The counter now shows the number of available characters left. --- src/courtroom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f6a56e171..660854486 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -865,7 +865,7 @@ void Courtroom::on_ic_message_text_changed(QString p_text) { const int l_length = p_text.length(); const int l_max_length = ui_ic_chat_message_field->maxLength(); - ui_ic_chat_message_counter->setText(QString::number(l_length)); + ui_ic_chat_message_counter->setText(QString::number(l_max_length - l_length)); ui_ic_chat_message_counter->setVisible(l_length >= int(l_max_length * 0.8)); if (l_length == l_max_length) { From 9cb88b818045e54b60a27217e8d4e08b2a2ede7d Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 14 Jul 2022 06:51:49 +0200 Subject: [PATCH 778/842] Added customizable message length threshold, ... * Tweaked message counter indentation. --- res/ui/config_panel.ui | 1580 +++++++++++++++++++------------------ src/aoconfig.cpp | 16 + src/aoconfig.h | 5 + src/aoconfigpanel.cpp | 17 + src/aoconfigpanel.h | 6 + src/courtroom.cpp | 6 +- src/courtroom.h | 2 +- src/courtroom_widgets.cpp | 8 +- 8 files changed, 869 insertions(+), 771 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index d0b2e0162..e368ce128 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -7,7 +7,7 @@ 0 0 487 - 447 + 514
    @@ -32,7 +32,7 @@ - 2 + 4 @@ -192,229 +192,440 @@ - + - Game + Audio - - - + + + + + + + Device: + + + + + + + + + + Master: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + System: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Music: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Effects: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Blips: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Video: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + <html><head/><body><p>If enabled, audio will be temporarily suppressed when the client is not in focus.<br/><br/>This option is never applied to the System audio channel.</p></body></html> + - Character + Suppress Background Audio - - - + + true + + + false + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<span style=" color:#0000ff;">Suppress Background Audio</span>] is enabled and activated.</p></body></html> + - Name: + Ignore Music - - - The name of your character + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + Ignore Blips - - - - - - Character options - - - - - - Searchable iniswap - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - 0 - 121 - - - - Emote options - - - - - 12 - 87 - 78 - 22 - - - - Sticky SFX - - - - - - 12 - 31 - 141 - 22 - - - - Emote preview tooltip - - - - - - 12 - 59 - 113 - 22 - - - - - 0 - 0 - - - - Always-on Anim - - - false - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - <html><head/><body><p>Reload the currently selected character. If a character is not selected, nothing happens.</p></body></html> - - - Reload - - - - - - - - - - - - - 0 - 101 - - - - Dialog - - - + + + false + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + - Chat-tick interval: + Ignore System + + + true + + + true - - - - ms + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> - - 0 + + Ignore Effects - - 999 + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<span style=" color:#0000ff;">Suppress Background Audio</span>] is enabled and activated.</p></body></html> - - - - - Blip Rate: + Ignore Video - - - - - - 1 - - - 1000000000 - - - 1000000000 - - - - - - - - 0 - 0 - - - - Blanks - - - - - - - + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reload + + + + + + + Qt::Vertical @@ -426,6 +637,13 @@
    + + + + Audiotracks: + + + @@ -637,654 +855,495 @@ Qt::Horizontal - QSlider::TicksBelow - - - 10 - - -
    - - - - - 40 - 0 - - - - - 40 - 0 - - - - 50% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - -
    - - - - - Theme - - - - - - - - - 0 - 0 - - - - Manual - - - - - - - - 0 - 0 - - - - Gamemode: - - - - - - - - 0 - 0 - - - - Time of day: - - - - - - - - 0 - 0 - - - - <html><head/><body><p>Allows you to manually switch to a gamemode interface.</p></body></html> - - - Manual - - - - - - - - 0 - 0 - - - - Folder: - - - - - - - - - false - - - - true - - - - <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> gamemode.</p></body></html> - - - <default> - - - false - - - true - - - - - - - true - - - <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> gamemode.</p></body></html> - - - - - - - - - - - false - - - - true - - - - <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> time of day.</p></body></html> - - - <default> - - - false - - - true - - - - - - - <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> time of day.</p></body></html> - - - - - - - + QSlider::TicksBelow + + + 10 + + - - - - - 0 - 0 - + + + + + 40 + 0 + + + + + 40 + 0 + - Reload + 50% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - Qt::Vertical - - - - 20 - 40 - - - - - + - Audio + Theme - - - - - + + + + + + + + 0 + 0 + + - Device: + Manual - - - - - + + + + + 0 + 0 + + - Master: + Gamemode: - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - + + + + + 0 + 0 + + - System: + Time of day: - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - + + + + + 0 + 0 + + + + <html><head/><body><p>Allows you to manually switch to a gamemode interface.</p></body></html> + - Music: + Manual - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - + + + + + 0 + 0 + + - Effects: + Folder: - - + + - - - - 0 - 0 - + + + false - - - 0 - 20 - + + + true + - - 100 + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> gamemode.</p></body></html> - - Qt::Horizontal + + <default> + + + false + + + true - - - - 40 - 0 - - - - 0% + + + true - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> gamemode.</p></body></html> - - - - Blips: - - - - - + + - - - - 0 - 0 - + + + false - - - 0 - 20 - + + + true + - - 100 + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> time of day.</p></body></html> - - Qt::Horizontal + + <default> + + + false + + + true - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> time of day.</p></body></html> - - + + + + + + + + 0 + 0 + + - Video: + Reload - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - 0% - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - <html><head/><body><p>If enabled, audio will be temporarily suppressed when the client is not in focus.<br/><br/>This option is never applied to the System audio channel.</p></body></html> - + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Game + + + + + + Character + + + + + + Name: + + + + + + + The name of your character + + + + + + + + + Character options + + + + + + Searchable iniswap + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 0 + 121 + + + + Emote options + + + + + 12 + 87 + 78 + 22 + + + + Sticky SFX + + + + + + 12 + 31 + 141 + 22 + + + + Emote preview tooltip + + + + + + 12 + 59 + 113 + 22 + + + + + 0 + 0 + + + + Always-on Anim + + + false + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p>Reload the currently selected character. If a character is not selected, nothing happens.</p></body></html> + + + Reload + + + + + + + + + + - Suppress Background Audio - - - true - - - false + Message - - - - - <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<span style=" color:#0000ff;">Suppress Background Audio</span>] is enabled and activated.</p></body></html> - + + + - Ignore Music + Warning threshold: - - - - <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> - + + + + + + <html><head/><body><p>When the currently inputted in-game message length reaches at or above the defined threshold a counter will be shown indicating the number of available characters left.</p></body></html> + + + 100 + + + 70 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 70% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + 0 + 101 + + + + Dialog + + + + - Ignore Blips + Chat-tick interval: - - - - false - - - <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> - - - Ignore System + + + + ms - - true + + 0 - - true + + 999 - - - <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> - + - Ignore Effects + Blip Rate: - - - - <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<span style=" color:#0000ff;">Suppress Background Audio</span>] is enabled and activated.</p></body></html> - - - Ignore Video - - + + + + + + 1 + + + 1000000000 + + + 1000000000 + + + + + + + + 0 + 0 + + + + Blanks + + + + - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Reload - - - - - - - + + Qt::Vertical @@ -1296,13 +1355,6 @@ - - - - Audiotracks: - - - diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index bd6d05c64..f523a23d6 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -71,6 +71,7 @@ private slots: int chat_tick_interval; bool emote_preview; bool sticky_sfx; + int message_length_threshold; int log_max_lines; bool log_display_timestamp; bool log_display_client_id; @@ -165,6 +166,7 @@ void AOConfigPrivate::read_file() chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); emote_preview = cfg.value("emote_preview", true).toBool(); sticky_sfx = cfg.value("sticky_sfx", false).toBool(); + message_length_threshold = cfg.value("message_length_threshold", 70).toInt(); log_max_lines = cfg.value("chatlog_limit", 100).toInt(); log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); log_display_timestamp = cfg.value("chatlog_display_timestamp", true).toBool(); @@ -273,6 +275,7 @@ void AOConfigPrivate::save_file() cfg.setValue("chat_tick_interval", chat_tick_interval); cfg.setValue("emote_preview", emote_preview); cfg.setValue("sticky_sfx", sticky_sfx); + cfg.setValue("message_length_threshold", message_length_threshold); cfg.setValue("chatlog_limit", log_max_lines); cfg.setValue("chatlog_display_timestamp", log_display_timestamp); cfg.setValue("chatlog_display_client_id", log_display_client_id); @@ -552,6 +555,11 @@ bool AOConfig::sticky_sfx_enabled() const return d->sticky_sfx; } +int AOConfig::message_length_threshold() const +{ + return d->message_length_threshold; +} + int AOConfig::log_max_lines() const { return d->log_max_lines; @@ -903,6 +911,14 @@ void AOConfig::set_sticky_sfx(bool p_enabled) d->invoke_signal("sticky_sfx_changed", Q_ARG(bool, p_enabled)); } +void AOConfig::set_message_length_threshold(int p_number) +{ + if (d->message_length_threshold == p_number) + return; + d->message_length_threshold = p_number; + d->invoke_signal("message_length_threshold_changed", Q_ARG(int, p_number)); +} + void AOConfig::set_log_max_lines(int p_number) { if (d->log_max_lines == p_number) diff --git a/src/aoconfig.h b/src/aoconfig.h index f32f2a361..e35e8c78d 100644 --- a/src/aoconfig.h +++ b/src/aoconfig.h @@ -45,6 +45,7 @@ class AOConfig : public QObject int chat_tick_interval() const; bool emote_preview_enabled() const; bool sticky_sfx_enabled() const; + int message_length_threshold() const; int log_max_lines() const; bool log_display_timestamp_enabled() const; bool log_display_client_id_enabled() const; @@ -109,6 +110,7 @@ public slots: void set_chat_tick_interval(int p_number); void set_emote_preview(bool p_enabled); void set_sticky_sfx(bool p_enabled); + void set_message_length_threshold(int percent); void set_log_max_lines(int p_number); void set_log_display_timestamp(bool p_enabled); void set_log_display_client_id(bool p_enabled); @@ -174,6 +176,9 @@ public slots: void manual_timeofday_changed(QString); void manual_timeofday_selection_changed(bool); + // ic + void message_length_threshold_changed(int); + // log void log_max_lines_changed(int); void log_display_timestamp_changed(bool); diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 5c25ddbc4..408b211cc 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -71,6 +71,10 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_emote_preview = AO_GUI_WIDGET(QCheckBox, "emote_preview"); ui_sticky_sfx = AO_GUI_WIDGET(QCheckBox, "sticky_sfx"); + // IC message + ui_length_threshold = AO_GUI_WIDGET(QSlider, "length_threshold"); + ui_length_threshold_label = AO_GUI_WIDGET(QLabel, "length_threshold_label"); + // IC Chatlog ui_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); ui_log_display_timestamp = AO_GUI_WIDGET(QCheckBox, "log_display_timestamp"); @@ -244,6 +248,11 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(ui_emote_preview, SIGNAL(toggled(bool)), m_config, SLOT(set_emote_preview(bool))); connect(ui_sticky_sfx, SIGNAL(toggled(bool)), m_config, SLOT(set_sticky_sfx(bool))); + // ic message + connect(m_config, SIGNAL(message_length_threshold_changed(int)), ui_length_threshold, SLOT(setValue(int))); + connect(ui_length_threshold, SIGNAL(valueChanged(int)), m_config, SLOT(set_message_length_threshold(int))); + connect(ui_length_threshold, SIGNAL(valueChanged(int)), this, SLOT(on_length_threshold_value_changed(int))); + // out, log connect(ui_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); connect(ui_log_display_timestamp, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_timestamp(bool))); @@ -299,6 +308,9 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) ui_emote_preview->setChecked(m_config->emote_preview_enabled()); ui_sticky_sfx->setChecked(m_config->sticky_sfx_enabled()); + // ic message + ui_length_threshold->setValue(m_config->message_length_threshold()); + // log ui_log_max_lines->setValue(m_config->log_max_lines()); @@ -638,6 +650,11 @@ void AOConfigPanel::on_blip_value_changed(int p_num) ui_blip_value->setText(QString::number(p_num) + "%"); } +void AOConfigPanel::on_length_threshold_value_changed(int p_number) +{ + ui_length_threshold_label->setText(QString::number(p_number) + "%"); +} + void AOConfigPanel::set_sprite_caching_toggled(int p_category, bool p_enabled) { QCheckBox *l_checkbox = m_cache_checkbox_map[SpriteCategory(p_category)]; diff --git a/src/aoconfigpanel.h b/src/aoconfigpanel.h index 612a40c56..433a95c66 100644 --- a/src/aoconfigpanel.h +++ b/src/aoconfigpanel.h @@ -71,6 +71,8 @@ private slots: void on_video_value_changed(int p_num); void on_blip_value_changed(int p_num); + void on_length_threshold_value_changed(int p_number); + // performance void set_sprite_caching_toggled(int type, bool enabled); void handle_sprite_caching_toggled(bool enabled); @@ -127,6 +129,10 @@ private slots: QComboBox *ui_manual_timeofday = nullptr; QCheckBox *ui_manual_timeofday_selection = nullptr; + // messages + QSlider *ui_length_threshold = nullptr; + QLabel *ui_length_threshold_label = nullptr; + // chatlog QSpinBox *ui_log_max_lines = nullptr; QCheckBox *ui_log_display_timestamp = nullptr; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 660854486..5e04bfa26 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -861,12 +861,12 @@ void Courtroom::on_ic_message_return_pressed() ao_app->send_server_packet(DRPacket("MS", packet_contents)); } -void Courtroom::on_ic_message_text_changed(QString p_text) +void Courtroom::handle_ic_message_length() { - const int l_length = p_text.length(); + const int l_length = ui_ic_chat_message_field->text().length(); const int l_max_length = ui_ic_chat_message_field->maxLength(); ui_ic_chat_message_counter->setText(QString::number(l_max_length - l_length)); - ui_ic_chat_message_counter->setVisible(l_length >= int(l_max_length * 0.8)); + ui_ic_chat_message_counter->setVisible(l_length >= int(l_max_length * (ao_config->message_length_threshold() * 0.01))); if (l_length == l_max_length) { QToolTip::showText(QCursor::pos(), QString(tr("Your message cannot be longer than %1 character(s).")).arg(ui_ic_chat_message_field->maxLength())); diff --git a/src/courtroom.h b/src/courtroom.h index 8c6761db2..0566364b2 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -603,7 +603,7 @@ private slots: void on_character_ini_changed(); void on_ic_showname_editing_finished(); void on_ic_message_return_pressed(); - void on_ic_message_text_changed(QString text); + void handle_ic_message_length(); void on_chat_config_changed(); void on_ic_chatlog_scroll_changed(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 2dd8a9266..765addc2e 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -167,12 +167,13 @@ void Courtroom::create_widgets() ui_ic_chat_message_field->setMaxLength(255); ui_ic_chat_message_counter = new QLabel(ui_ic_chat_message); - ui_ic_chat_message_counter->setAlignment(Qt::AlignCenter); + ui_ic_chat_message_counter->setAlignment(Qt::AlignVCenter | Qt::AlignRight); ui_ic_chat_message_counter->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); { auto l_layout = new QHBoxLayout(ui_ic_chat_message); - l_layout->setContentsMargins(0, 0, l_layout->contentsMargins().right(), 0); + ui_ic_chat_message_counter->setIndent(l_layout->contentsMargins().right()); + l_layout->setContentsMargins(0, 0, 0, 0); l_layout->addWidget(ui_ic_chat_message_field); l_layout->addWidget(ui_ic_chat_message_counter); } @@ -305,7 +306,8 @@ void Courtroom::connect_widgets() connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(on_character_ini_changed())); connect(ui_ic_chat_showname, SIGNAL(editingFinished()), this, SLOT(on_ic_showname_editing_finished())); connect(ui_ic_chat_message_field, SIGNAL(returnPressed()), this, SLOT(on_ic_message_return_pressed())); - connect(ui_ic_chat_message_field, SIGNAL(textChanged(QString)), this, SLOT(on_ic_message_text_changed(QString))); + connect(ao_config, SIGNAL(message_length_threshold_changed(int)), this, SLOT(handle_ic_message_length())); + connect(ui_ic_chat_message_field, SIGNAL(textChanged(QString)), this, SLOT(handle_ic_message_length())); connect(ui_ic_chatlog->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(on_ic_chatlog_scroll_changed())); connect(ui_ic_chatlog_scroll_topdown, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_topdown_clicked())); connect(ui_ic_chatlog_scroll_bottomup, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_bottomup_clicked())); From ac45c35eafa651c818b3ff1f71f692c16c3d763e Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 16 Jul 2022 13:30:32 -0400 Subject: [PATCH 779/842] Update readmes for MacOS and Linux+Move to RC1 --- .github/workflows/build.yml | 1 + .gitignore | 1 + data/README-LINUX.md | 5 ++++- data/README-MACOS.md | 5 +++++ src/version.cpp | 2 +- 5 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 data/README-MACOS.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 880458b75..b9a0e6d76 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -199,6 +199,7 @@ jobs: cp ../3rd/*.dylib "./Danganronpa Online.app/Contents/Frameworks" hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online.app" -ov -format UDZO "Danganronpa Online.dmg" rm -rf "./Danganronpa Online.app" + cp ../data/README-MACOS.txt ./README.txt cd .. - name: Create Artifact diff --git a/.gitignore b/.gitignore index dda04fb26..08f5ea69f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ 3rd/ +3rd_x86/ 3rdparty/ base/ docs/ diff --git a/data/README-LINUX.md b/data/README-LINUX.md index 3c2888b67..499cfb2c9 100644 --- a/data/README-LINUX.md +++ b/data/README-LINUX.md @@ -1,7 +1,10 @@ # Installation instructions -1. Open your terminal and run +1. Open your terminal and run the following commands in order. ``` +sudo apt-get update +sudo apt-get upgrade sudo apt-get install qt5-default +sudo apt-get install libxvidcore-dev ``` 2. Update dro-client and dro-client.sh permissions ``` diff --git a/data/README-MACOS.md b/data/README-MACOS.md new file mode 100644 index 000000000..2eec8ebad --- /dev/null +++ b/data/README-MACOS.md @@ -0,0 +1,5 @@ +To extract the application properly, double click Danganronpa Online.dmg, drag "Danganronpa Online" to your Danganronpa Online folder, and delete the dmg file. + +If you attempt to open the application as is, MacOS will give you a warning indicating it has blocked the launch of the application as the developer could not be verified. If you downloaded these files from the Danganronpa Online discord, you can trust these files. To open Danganronpa Online for the first time, you have to Right Click the application while holding the Control key, then click Open, and on the prompt click Open again. + +If at some point you get a prompt indicating application would like to access files in your Desktop folder (or wherever you put your files), accept. Otherwise, the application will be unable to display images or play audio files. \ No newline at end of file diff --git a/src/version.cpp b/src/version.cpp index 0ddd89de5..f7d2fc62f 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -30,7 +30,7 @@ VersionNumber get_version_number() QString get_post_version() { - return ""; + return "RC1"; } QString get_version_string() From 585bec8dbdddd5c9007efdad90f17faf2735318b Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 16 Jul 2022 13:39:10 -0400 Subject: [PATCH 780/842] Fix MacOS readme name --- data/{README-MACOS.md => README-MACOS.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename data/{README-MACOS.md => README-MACOS.txt} (100%) diff --git a/data/README-MACOS.md b/data/README-MACOS.txt similarity index 100% rename from data/README-MACOS.md rename to data/README-MACOS.txt From 19003ca39ebc87123b7243514e2a4d876222698a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sat, 16 Jul 2022 23:12:06 +0200 Subject: [PATCH 781/842] Update build steps for Ubuntu --- .github/workflows/build.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b9a0e6d76..c29038205 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -121,10 +121,16 @@ jobs: - name: Update packages (Ubuntu) if: contains(matrix.os, 'ubuntu') run: | - sudo apt-get update - sudo apt-get upgrade - - - name: Install Qt + sudo apt update + sudo apt upgrade + + - name: Install Qt (Ubuntu) + if: contains(matrix.os, 'ubuntu') + run: | + sudo apt install qt5-default qttools5-dev + + - name: Install Qt (Others) + if: contains(matrix.os, 'ubuntu') != true uses: jurplel/install-qt-action@v2.14.0 with: version: ${{matrix.version}} From d09d068163e519266abe00768f9195e56c77fb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sat, 16 Jul 2022 23:15:19 +0200 Subject: [PATCH 782/842] Prefer apt-get --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c29038205..ad4214302 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -121,13 +121,13 @@ jobs: - name: Update packages (Ubuntu) if: contains(matrix.os, 'ubuntu') run: | - sudo apt update - sudo apt upgrade + sudo apt-get update + sudo apt-get upgrade - name: Install Qt (Ubuntu) if: contains(matrix.os, 'ubuntu') run: | - sudo apt install qt5-default qttools5-dev + sudo apt-get install qt5-default qttools5-dev - name: Install Qt (Others) if: contains(matrix.os, 'ubuntu') != true From ec1fbca67f417f9d0e5f6fb5a3f37fdaca194b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sat, 16 Jul 2022 23:17:30 +0200 Subject: [PATCH 783/842] Install required modules --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad4214302..b99a6e34a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,7 +127,7 @@ jobs: - name: Install Qt (Ubuntu) if: contains(matrix.os, 'ubuntu') run: | - sudo apt-get install qt5-default qttools5-dev + sudo apt-get install qt5-default libqt5designer5 qttools5-dev libqt5multimedia5 libqt5multimedia5-plugins libqt5multimediawidgets5 qtmultimedia5-dev - name: Install Qt (Others) if: contains(matrix.os, 'ubuntu') != true From fc0f16776efc7887c0201bf9d15627e067ba1989 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 17 Jul 2022 18:08:12 +0200 Subject: [PATCH 784/842] Possible attempt to fix ambient playing... * Possible attempt to fix ambient playing properly after rejoining the same server. --- src/aosfxplayer.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index a4e88b11e..05d6eecd9 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -62,7 +62,6 @@ void AOSfxPlayer::play_ambient(QString p_filename) } m_current_ambient->fadeOut(DEFAULT_FADE_DURATION); - m_current_ambient.clear(); } DRAudioStream::ptr l_ambient; @@ -80,6 +79,10 @@ void AOSfxPlayer::play_ambient(QString p_filename) l_ambient->set_repeatable(true); } + else + { + return; + } } else { @@ -132,6 +135,10 @@ void AOSfxPlayer::remove_ambient() qDebug() << "Removing ambient" << l_stream->get_file_name(); m_ambient_map.remove(l_stream->get_file_name()); + if (m_current_ambient == l_stream) + { + m_current_ambient.reset(); + } } void AOSfxPlayer::handle_ambient_fade(DRAudioStream::Fade p_fade) From a0affbaca93da7daf196cd33085be429486dbebb Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 17 Jul 2022 18:20:38 +0200 Subject: [PATCH 785/842] Reorganized widgets --- src/courtroom.cpp | 21 +-------------------- src/courtroom.h | 6 ------ src/courtroom_widgets.cpp | 24 +++++++++--------------- 3 files changed, 10 insertions(+), 41 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 5e04bfa26..d8253d0ff 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -748,7 +748,7 @@ void Courtroom::on_character_ini_changed() void Courtroom::on_ic_message_return_pressed() { - if (ui_ic_chat_message_field->text() == "" || is_client_muted) return; + if (ui_ic_chat_message_field->text() == "") return; if ((anim_state < 3 || text_state < 2) && m_shout_state == 0) return; @@ -1832,25 +1832,6 @@ void Courtroom::set_text_color() m_message_color.setNamedColor(color_code); } -void Courtroom::set_muted(bool p_muted, int p_cid) -{ - if (p_cid != m_chr_id && p_cid != SpectatorId) return; - - if (p_muted) - ui_muted->show(); - else - { - ui_muted->hide(); - ui_ic_chat_message_field->setFocus(); - } - - ui_muted->resize(ui_ic_chat_message_field->width(), ui_ic_chat_message_field->height()); - ui_muted->set_theme_image("muted.png"); - - is_client_muted = p_muted; - ui_ic_chat_message_field->setEnabled(!p_muted); -} - void Courtroom::set_ban(int p_cid) { if (p_cid != m_chr_id && p_cid != SpectatorId) return; diff --git a/src/courtroom.h b/src/courtroom.h index 0566364b2..959267eca 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -111,10 +111,6 @@ class Courtroom : public QMainWindow // sets text color based on text color in chatmessage void set_text_color(); - // disables chat if current cid matches second argument - // enables if p_muted is false - void set_muted(bool p_muted, int p_cid); - // send a message that the player is banned and quits the server void set_ban(int p_cid); @@ -522,8 +518,6 @@ class Courtroom : public QMainWindow QComboBox *ui_text_color = nullptr; - AOImageDisplay *ui_muted = nullptr; - AOButton *ui_note_button = nullptr; AOImageDisplay *ui_char_select_background = nullptr; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 765addc2e..5114589df 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -154,11 +154,6 @@ void Courtroom::create_widgets() ui_sfx_menu_preview = ui_sfx_menu->addAction(tr("Preview")); ui_sfx_menu_insert_ooc = ui_sfx_menu->addAction(tr("Insert to OOC")); - ui_ic_chat_showname = new QLineEdit(this); - ui_ic_chat_showname->setFrame(false); - ui_ic_chat_showname->setPlaceholderText("Showname"); - ui_ic_chat_showname->setText(ao_config->showname()); - ui_ic_chat_message = new QWidget(this); ui_ic_chat_message_field = new QLineEdit(ui_ic_chat_message); @@ -166,6 +161,11 @@ void Courtroom::create_widgets() ui_ic_chat_message_field->setPlaceholderText(tr("Say something in-character.")); ui_ic_chat_message_field->setMaxLength(255); + ui_ooc_chat_message = new QLineEdit(this); + ui_ooc_chat_message->setFrame(false); + ui_ooc_chat_message->setPlaceholderText(tr("Say something out-of-character.")); + ui_ooc_chat_message->setMaxLength(255); + ui_ic_chat_message_counter = new QLabel(ui_ic_chat_message); ui_ic_chat_message_counter->setAlignment(Qt::AlignVCenter | Qt::AlignRight); ui_ic_chat_message_counter->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); @@ -178,18 +178,15 @@ void Courtroom::create_widgets() l_layout->addWidget(ui_ic_chat_message_counter); } - ui_muted = new AOImageDisplay(ui_ic_chat_message_field, ao_app); - ui_muted->hide(); - ui_ooc_chat_name = new QLineEdit(this); ui_ooc_chat_name->setFrame(false); ui_ooc_chat_name->setPlaceholderText("Name"); ui_ooc_chat_name->setText(ao_config->username()); - ui_ooc_chat_message = new QLineEdit(this); - ui_ooc_chat_message->setFrame(false); - ui_ooc_chat_message->setPlaceholderText(tr("Say something out-of-character.")); - ui_ooc_chat_message->setMaxLength(255); + ui_ic_chat_showname = new QLineEdit(this); + ui_ic_chat_showname->setFrame(false); + ui_ic_chat_showname->setPlaceholderText("Showname"); + ui_ic_chat_showname->setText(ao_config->showname()); ui_note_area = new AONoteArea(this, ao_app); ui_note_area->add_button = new AOButton(ui_note_area, ao_app); @@ -730,9 +727,6 @@ void Courtroom::set_widgets() ui_vp_chatbox->set_theme_image("chatmed.png"); ui_vp_chatbox->hide(); - ui_muted->resize(ui_ic_chat_message_field->width(), ui_ic_chat_message_field->height()); - ui_muted->set_theme_image("muted.png"); - set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name", COURTROOM_DESIGN_INI, ao_app); set_text_alignment(ui_ooc_chat_name, "ooc_chat_name", COURTROOM_FONTS_INI, ao_app); if (!set_stylesheet(ui_ooc_chat_name, "[OOC NAME LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) From f8c8ef67ef7838267adda726f2137de0a1855c98 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 17 Jul 2022 19:30:59 +0200 Subject: [PATCH 786/842] More preventive measures --- src/audio_functions.cpp | 4 ++-- src/draudiostream.cpp | 22 +++++++++++++++++++--- src/draudiostream.h | 2 ++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/audio_functions.cpp b/src/audio_functions.cpp index e0395508d..a9b633e35 100644 --- a/src/audio_functions.cpp +++ b/src/audio_functions.cpp @@ -9,13 +9,13 @@ bool Courtroom::is_audio_suppressed() const void Courtroom::suppress_audio(bool p_enabled) { - if (is_audio_muted == p_enabled) - return; is_audio_muted = p_enabled; // suppress audio for (auto &family : DRAudioEngine::get_family_list()) + { family->set_suppressed(is_audio_muted); + } } void Courtroom::stop_all_audio() diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index cc961540f..e03bb9423 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -91,7 +91,16 @@ void DRAudioStream::set_volume(float p_volume) if (!ensure_init()) return; m_volume = p_volume; - update_volume(); + if (m_fade_running) + { + const Fade l_prev_fade = m_fade; + m_fade = NoFade; + fade(l_prev_fade, m_fade_duration); + } + else + { + update_volume(); + } } void DRAudioStream::set_repeatable(bool p_enabled) @@ -127,6 +136,8 @@ void DRAudioStream::fade(Fade p_fade, int p_duration) BASS_ChannelSetAttribute(m_hstream, BASS_ATTRIB_VOL, (p_fade == FadeOut ? l_volume : 0)); } m_fade = p_fade; + m_fade_duration = p_duration; + m_fade_running = true; BASS_ChannelSlideAttribute(m_hstream, BASS_ATTRIB_VOL, (p_fade == FadeOut ? 0 : l_volume), qMax(0, p_duration)); } @@ -174,6 +185,7 @@ void DRAudioStream::fade_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata) Q_UNUSED(data); DRAudioStream *l_stream = static_cast(userdata); + l_stream->m_fade_running = false; emit l_stream->faded(l_stream->m_fade); } @@ -258,6 +270,10 @@ void DRAudioStream::update_device(DRAudioDevice p_device) void DRAudioStream::update_volume() { - const float l_volume = m_volume * 0.01f; - BASS_ChannelSetAttribute(m_hstream, BASS_ATTRIB_VOL, (m_fade == FadeOut ? 0 : l_volume)); + float l_volume = m_volume * 0.01f; + if (m_fade_running) + { + BASS_ChannelGetAttribute(m_hstream, BASS_ATTRIB_VOL, &l_volume); + } + BASS_ChannelSetAttribute(m_hstream, BASS_ATTRIB_VOL, l_volume); } diff --git a/src/draudiostream.h b/src/draudiostream.h index 03c551c99..0f9f7464b 100644 --- a/src/draudiostream.h +++ b/src/draudiostream.h @@ -98,6 +98,8 @@ public slots: QString m_filename; DRAudioStream::InitState m_init_state = InitNotDone; DRAudioStream::Fade m_fade; + int m_fade_duration = 0; + bool m_fade_running = false; HSTREAM m_hstream = 0; float m_volume = 0.0f; bool m_repeatable = false; From 87291177bdf8b04c19b0503630c31cb71fa7e163 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 17 Jul 2022 19:44:04 +0200 Subject: [PATCH 787/842] Bumped version --- src/version.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.cpp b/src/version.cpp index f7d2fc62f..a1fc54358 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -30,7 +30,7 @@ VersionNumber get_version_number() QString get_post_version() { - return "RC1"; + return "rc.1"; } QString get_version_string() From 2d88861e02af424240f8e05732308fdee93141ec Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 18 Jul 2022 05:09:43 +0200 Subject: [PATCH 788/842] IC symbols no longer appears in chatlog --- src/courtroom.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d8253d0ff..fe1823075 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -913,7 +913,9 @@ void Courtroom::next_chatmessage(QStringList p_chatmessage) } } - const QString l_message = p_chatmessage[CMMessage]; + const QString l_message = QString(p_chatmessage[CMMessage]) + .remove(QRegularExpression("(? Date: Wed, 20 Jul 2022 02:05:38 +0200 Subject: [PATCH 789/842] Completely reworked the rendering process The rendering process has been completely overhauled. This allows media videos to be rendered without having to do shenanigans through layering and has no risk of hiding videos. This also remove the issue of media videos not displaying properly on Ubuntu. --- dronline-client.pro | 16 +- src/aoapplication.cpp | 20 ++ src/aoapplication.h | 3 + src/charselect.cpp | 19 +- src/courtroom.cpp | 112 ++++++----- src/courtroom.h | 37 ++-- src/courtroom_widgets.cpp | 266 +++++++++++--------------- src/drcharactermovie.cpp | 5 +- src/drcharactermovie.h | 2 +- src/dreffectmovie.cpp | 18 +- src/dreffectmovie.h | 14 +- src/drgraphicscene.cpp | 59 ++++++ src/drgraphicscene.h | 35 ++++ src/drmovie.cpp | 16 +- src/drmovie.h | 8 +- src/drscenemovie.cpp | 6 +- src/drscenemovie.h | 2 +- src/drshoutmovie.cpp | 5 +- src/drshoutmovie.h | 2 +- src/drsplashmovie.cpp | 17 +- src/drsplashmovie.h | 14 +- src/drstickermovie.cpp | 22 --- src/drstickermovie.h | 20 -- src/drstickerviewer.cpp | 41 ++++ src/drstickerviewer.h | 28 +++ src/drthememovie.cpp | 16 ++ src/drthememovie.h | 20 ++ src/drvideoscreen.cpp | 225 ---------------------- src/drvideoscreen.h | 56 ------ src/emotes.cpp | 15 +- src/mk2/graphicsspriteitem.cpp | 162 ++++++++++++++++ src/mk2/graphicsspriteitem.h | 99 ++++++++++ src/mk2/graphicsvideoscreen.cpp | 247 ++++++++++++++++++++++++ src/mk2/graphicsvideoscreen.h | 92 +++++++++ src/mk2/spritecachingreader.cpp | 2 +- src/mk2/spriteplayer.cpp | 320 ++++++++++++++++++++++++++++++++ src/mk2/spriteplayer.h | 121 ++++++++++++ src/mk2/spritereader.h | 2 +- src/mk2/spriteseekingreader.cpp | 2 +- src/mk2/spriteviewer.cpp | 243 ++++-------------------- src/mk2/spriteviewer.h | 51 ++--- src/theme.cpp | 6 +- src/theme.h | 4 +- src/version.cpp | 2 +- 44 files changed, 1630 insertions(+), 842 deletions(-) create mode 100644 src/drgraphicscene.cpp create mode 100644 src/drgraphicscene.h delete mode 100644 src/drstickermovie.cpp delete mode 100644 src/drstickermovie.h create mode 100644 src/drstickerviewer.cpp create mode 100644 src/drstickerviewer.h create mode 100644 src/drthememovie.cpp create mode 100644 src/drthememovie.h delete mode 100644 src/drvideoscreen.cpp delete mode 100644 src/drvideoscreen.h create mode 100644 src/mk2/graphicsspriteitem.cpp create mode 100644 src/mk2/graphicsspriteitem.h create mode 100644 src/mk2/graphicsvideoscreen.cpp create mode 100644 src/mk2/graphicsvideoscreen.h create mode 100644 src/mk2/spriteplayer.cpp create mode 100644 src/mk2/spriteplayer.h diff --git a/dronline-client.pro b/dronline-client.pro index cb993dbb9..6fc1f9e85 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -49,6 +49,7 @@ HEADERS += \ src/drchatlog.h \ src/drdiscord.h \ src/dreffectmovie.h \ + src/drgraphicscene.h \ src/drmasterclient.h \ src/drmediatester.h \ src/drmovie.h \ @@ -59,16 +60,19 @@ HEADERS += \ src/drserversocket.h \ src/drshoutmovie.h \ src/drsplashmovie.h \ - src/drstickermovie.h \ + src/drstickerviewer.h \ src/drtextedit.h \ - src/drvideoscreen.h \ + src/drthememovie.h \ src/file_functions.h \ src/hardware_functions.h \ src/lobby.h \ src/logger.h \ src/misc_functions.h \ + src/mk2/graphicsspriteitem.h \ + src/mk2/graphicsvideoscreen.h \ src/mk2/spritecachingreader.h \ src/mk2/spritedynamicreader.h \ + src/mk2/spriteplayer.h \ src/mk2/spritereader.h \ src/mk2/spritereadersynchronizer.h \ src/mk2/spriteseekingreader.h \ @@ -119,6 +123,7 @@ SOURCES += \ src/drcharactermovie.cpp \ src/drchatlog.cpp \ src/dreffectmovie.cpp \ + src/drgraphicscene.cpp \ src/drmasterclient.cpp \ src/drmediatester.cpp \ src/drmovie.cpp \ @@ -129,10 +134,10 @@ SOURCES += \ src/drserversocket.cpp \ src/drshoutmovie.cpp \ src/drsplashmovie.cpp \ - src/drstickermovie.cpp \ + src/drstickerviewer.cpp \ src/drtextedit.cpp \ src/drdiscord.cpp \ - src/drvideoscreen.cpp \ + src/drthememovie.cpp \ src/emotes.cpp \ src/file_functions.cpp \ src/hardware_functions.cpp \ @@ -140,8 +145,11 @@ SOURCES += \ src/logger.cpp \ src/main.cpp \ src/misc_functions.cpp \ + src/mk2/graphicsspriteitem.cpp \ + src/mk2/graphicsvideoscreen.cpp \ src/mk2/spritecachingreader.cpp \ src/mk2/spritedynamicreader.cpp \ + src/mk2/spriteplayer.cpp \ src/mk2/spriteseekingreader.cpp \ src/path_functions.cpp \ src/server_socket.cpp \ diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 9ebb45d52..74b383243 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -397,3 +397,23 @@ void AOApplication::on_courtroom_destroyed() { ao_config_panel->hide(); } + +bool AOApplication::notify(QObject *receiver, QEvent *event) +{ + bool l_done = true; + + try + { + l_done = QApplication::notify(receiver, event); + } + catch (const std::exception &e) + { + qCritical() << "Caught exception:" << e.what(); + } + catch (...) + { + qFatal("Caught UNKNOWN exception. Cannot recover."); + } + + return l_done; +} diff --git a/src/aoapplication.h b/src/aoapplication.h index f461f5752..0a7f45166 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -247,6 +247,9 @@ private slots: QString get_shout_sprite_path(QString character, QString shout); QString get_theme_sprite_path(QString file_name, QString character); QString get_theme_sprite_path(QString file_name); + +public: + bool notify(QObject *receiver, QEvent *event) override; }; #endif // AOAPPLICATION_H diff --git a/src/charselect.cpp b/src/charselect.cpp index 737a1ba20..e8e403df1 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -49,8 +49,7 @@ void Courtroom::construct_char_select() void Courtroom::reconstruct_char_select() { - while (!ui_char_button_list.isEmpty()) - delete ui_char_button_list.takeLast(); + qDeleteAll(ui_char_button_list.begin(), ui_char_button_list.end()); QPoint f_spacing = ao_app->get_button_spacing("char_button_spacing", COURTROOM_DESIGN_INI); @@ -62,26 +61,26 @@ void Courtroom::reconstruct_char_select() int y_spacing = f_spacing.y(); int y_mod_count = 0; - set_size_and_pos(ui_char_buttons, "char_buttons", COURTROOM_DESIGN_INI, ao_app); - char_columns = ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + 1; char_rows = ((ui_char_buttons->height() - button_height) / (y_spacing + button_height)) + 1; m_page_max_chr_count = qMax(1, char_columns * char_rows); + + ui_char_button_list.clear(); for (int n = 0; n < m_page_max_chr_count; ++n) { int x_pos = (button_width + x_spacing) * x_mod_count; int y_pos = (button_height + y_spacing) * y_mod_count; - AOCharButton *button = new AOCharButton(ui_char_buttons, ao_app, x_pos, y_pos); - ui_char_button_list.append(button); + AOCharButton *l_button = new AOCharButton(ui_char_buttons, ao_app, x_pos, y_pos); + ui_char_button_list.append(l_button); - connect(button, SIGNAL(clicked()), char_button_mapper, SLOT(map())); - char_button_mapper->setMapping(button, n); + connect(l_button, SIGNAL(clicked()), char_button_mapper, SLOT(map())); + char_button_mapper->setMapping(l_button, n); // mouse events - connect(button, SIGNAL(mouse_entered(AOCharButton *)), this, SLOT(char_mouse_entered(AOCharButton *))); - connect(button, SIGNAL(mouse_left()), this, SLOT(char_mouse_left())); + connect(l_button, SIGNAL(mouse_entered(AOCharButton *)), this, SLOT(char_mouse_entered(AOCharButton *))); + connect(l_button, SIGNAL(mouse_left()), this, SLOT(char_mouse_left())); ++x_mod_count; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index fe1823075..f76723576 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -23,9 +23,9 @@ #include "drscenemovie.h" #include "drshoutmovie.h" #include "drsplashmovie.h" -#include "drstickermovie.h" -#include "drvideoscreen.h" +#include "drstickerviewer.h" #include "file_functions.h" +#include "mk2/graphicsvideoscreen.h" #include "mk2/spritedynamicreader.h" #include "mk2/spriteseekingreader.h" #include "src/datatypes.h" @@ -51,8 +51,8 @@ const int Courtroom::DEFAULT_WIDTH = 714; const int Courtroom::DEFAULT_HEIGHT = 668; -Courtroom::Courtroom(AOApplication *p_ao_app) - : QMainWindow() +Courtroom::Courtroom(AOApplication *p_ao_app, QWidget *parent) + : QWidget(parent) { ao_app = p_ao_app; ao_config = new AOConfig(this); @@ -123,15 +123,6 @@ void Courtroom::setup_courtroom() map_viewport_readers(); assign_readers_for_all_viewers(); - // Update widgets first, then check if everything is valid - // This will also handle showing the correct shouts, effects and wtce buttons, - // and cycling through them if the buttons that are supposed to be displayed - // do not exist It will also take care of free blocks - - const bool l_chr_select_visible = ui_char_select_background->isVisible(); - set_widgets(); - ui_char_select_background->setVisible(l_chr_select_visible); - m_shout_state = 0; m_shout_current = 0; check_shouts(); @@ -147,18 +138,29 @@ void Courtroom::setup_courtroom() check_wtce(); if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) cycle_wtce(1); - check_free_blocks(); - ui_flip->show(); list_music(); list_areas(); + // Update widgets first, then check if everything is valid + // This will also handle showing the correct shouts, effects and wtce buttons, + // and cycling through them if the buttons that are supposed to be displayed + // do not exist It will also take care of free blocks + const bool l_chr_select_visible = ui_char_select_background->isVisible(); + set_widget_names(); + set_widgets(); set_widget_layers(); + // char select + reconstruct_char_select(); + ui_char_select_background->setVisible(l_chr_select_visible); + for (AOTimer *i_timer : qAsConst(ui_timers)) + { i_timer->redraw(); + } } void Courtroom::map_viewers() @@ -167,42 +169,42 @@ void Courtroom::map_viewers() // general ui elements m_mapped_viewer_list[SpriteGUI].append({ - ui_vp_chat_arrow, - ui_vp_clock, + ui_vp_chat_arrow->get_player(), + ui_vp_clock->get_player(), }); // backgrounds m_mapped_viewer_list[SpriteStage].append({ - ui_vp_background, - ui_vp_desk, + ui_vp_background->get_player(), + ui_vp_desk->get_player(), }); // characters - m_mapped_viewer_list[SpriteCharacter].append(ui_vp_player_char); + m_mapped_viewer_list[SpriteCharacter].append(ui_vp_player_char->get_player()); // shouts - m_mapped_viewer_list[SpriteShout].append(ui_vp_objection); + m_mapped_viewer_list[SpriteShout].append(ui_vp_objection->get_player()); // effects - m_mapped_viewer_list[SpriteEffect].append(ui_vp_effect); + m_mapped_viewer_list[SpriteEffect].append(ui_vp_effect->get_player()); // stickers - for (DRStickerMovie *i_sticker : qAsConst(ui_free_blocks)) + for (DRStickerViewer *i_sticker : qAsConst(ui_free_blocks)) { - m_mapped_viewer_list[SpriteSticker].append(i_sticker); + m_mapped_viewer_list[SpriteSticker].append(i_sticker->get_player()); } } void Courtroom::map_viewport_viewers() { m_viewport_viewer_map.clear(); - m_viewport_viewer_map.insert(ViewportStageBack, ui_vp_background); - m_viewport_viewer_map.insert(ViewportStageFront, ui_vp_desk); - m_viewport_viewer_map.insert(ViewportCharacterPre, ui_vp_player_char); - m_viewport_viewer_map.insert(ViewportCharacterIdle, ui_vp_player_char); - m_viewport_viewer_map.insert(ViewportCharacterTalk, ui_vp_player_char); - m_viewport_viewer_map.insert(ViewportEffect, ui_vp_effect); - m_viewport_viewer_map.insert(ViewportShout, ui_vp_objection); + m_viewport_viewer_map.insert(ViewportStageBack, ui_vp_background->get_player()); + m_viewport_viewer_map.insert(ViewportStageFront, ui_vp_desk->get_player()); + m_viewport_viewer_map.insert(ViewportCharacterPre, ui_vp_player_char->get_player()); + m_viewport_viewer_map.insert(ViewportCharacterIdle, ui_vp_player_char->get_player()); + m_viewport_viewer_map.insert(ViewportCharacterTalk, ui_vp_player_char->get_player()); + m_viewport_viewer_map.insert(ViewportEffect, ui_vp_effect->get_player()); + m_viewport_viewer_map.insert(ViewportShout, ui_vp_objection->get_player()); } void Courtroom::map_viewport_readers() @@ -217,7 +219,7 @@ void Courtroom::map_viewport_readers() mk2::SpriteReader::ptr Courtroom::get_viewport_reader(ViewportSprite p_type) const { Q_ASSERT(m_viewport_viewer_map.contains(p_type)); - DRMovie *l_viewer = m_viewport_viewer_map[p_type]; + auto l_viewer = m_viewport_viewer_map[p_type]; return l_viewer->get_reader(); } @@ -225,10 +227,9 @@ void Courtroom::assign_readers_for_all_viewers() { for (auto it = m_mapped_viewer_list.cbegin(); it != m_mapped_viewer_list.cend(); ++it) { - const SpriteCategory l_cat = it.key(); - - const bool l_caching = ao_config->sprite_caching_enabled(l_cat); - assign_readers_for_viewers(l_cat, l_caching); + const SpriteCategory l_category = it.key(); + const bool l_caching = ao_config->sprite_caching_enabled(l_category); + assign_readers_for_viewers(l_category, l_caching); } } @@ -254,8 +255,8 @@ void Courtroom::assign_readers_for_viewers(int p_category, bool p_caching) const SpriteCategory l_category = SpriteCategory(p_category); // viewport readers - const QVector &l_viewer_list = m_mapped_viewer_list[l_category]; - for (DRMovie *i_viewer : qAsConst(l_viewer_list)) + const auto &l_viewer_list = m_mapped_viewer_list[l_category]; + for (auto i_viewer : qAsConst(l_viewer_list)) { mk2::SpriteReader::ptr l_new_reader; if (p_caching) @@ -270,7 +271,7 @@ void Courtroom::assign_readers_for_viewers(int p_category, bool p_caching) const mk2::SpriteReader::ptr l_prev_reader = i_viewer->get_reader(); const bool l_is_running = i_viewer->is_running(); - const bool l_is_visible = i_viewer->isVisible(); + const bool l_is_visible = i_viewer->property("visible").toBool(); { // update reader cache const QList l_type_list = m_reader_cache.keys(); @@ -289,7 +290,7 @@ void Courtroom::assign_readers_for_viewers(int p_category, bool p_caching) { i_viewer->start(); } - i_viewer->setVisible(l_is_visible); + i_viewer->setProperty("visible", l_is_visible); } } @@ -583,7 +584,7 @@ void Courtroom::handle_clock(QString time) qDebug() << "Asset not found; aborting."; return; } - ui_vp_clock->play(clock_filename); + ui_vp_clock->set_theme_image(clock_filename); ui_vp_clock->show(); } @@ -997,7 +998,7 @@ void Courtroom::preload_chatmessage(QStringList p_contents) const ViewportSprite l_type = it.key(); const QString &l_file_name = it.value(); - DRMovie *l_viewer = m_viewport_viewer_map.value(l_type); + auto l_viewer = m_viewport_viewer_map.value(l_type); const QString l_current_file_name = l_viewer->get_file_name(); // reuse readers when available @@ -1109,6 +1110,16 @@ void Courtroom::handle_chatmessage() ui_vp_effect->stop(); ui_vp_effect->hide(); + /** + * WARNING No check prior to changing will cause an unrecoverable + * exception. You have been warned! + * + * Qt Version: <= 5.15 + */ + if (!ui_video->isVisible()) + { + ui_video->show(); + } ui_video->play_character_video(m_chatmessage[CMChrName], m_chatmessage[CMVideoName]); } @@ -1134,6 +1145,17 @@ QString Courtroom::get_effect_name(int effect_index) void Courtroom::video_finished() { + /** + * WARNING No check prior to changing will cause an unrecoverable + * exception. You have been warned! + * + * Qt Version: <= 5.15 + */ + if (ui_video->isVisible()) + { + ui_video->hide(); + } + const QString l_character = m_chatmessage[CMChrName]; const int l_shout_index = m_chatmessage[CMShoutModifier].toInt(); @@ -1293,7 +1315,7 @@ void Courtroom::handle_chatmessage_3() if (!l_effect_name.isEmpty()) // check to prevent crashing { QStringList offset = ao_app->get_effect_offset(f_char, l_effect_index); - ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), ui_viewport->y() + offset.at(1).toInt()); + ui_vp_effect->setPos(ui_viewport->x() + offset.at(0).toInt(), ui_viewport->y() + offset.at(1).toInt()); QString s_eff = effect_names.at(l_effect_index - 1); QStringList f_eff = ao_app->get_effect(l_effect_index); @@ -2571,7 +2593,7 @@ void Courtroom::ping_server() void Courtroom::changeEvent(QEvent *event) { - QMainWindow::changeEvent(event); + QWidget::changeEvent(event); if (event->type() == QEvent::WindowStateChange) { m_is_maximized = windowState().testFlag(Qt::WindowMaximized); @@ -2581,7 +2603,7 @@ void Courtroom::changeEvent(QEvent *event) void Courtroom::closeEvent(QCloseEvent *event) { - QMainWindow::closeEvent(event); + QWidget::closeEvent(event); Q_EMIT closing(); } diff --git a/src/courtroom.h b/src/courtroom.h index 959267eca..20fcb16cc 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -2,7 +2,11 @@ #define COURTROOM_H #include "datatypes.h" +#include "drgraphicscene.h" #include "drposition.h" +#include "drthememovie.h" +#include "mk2/graphicsvideoscreen.h" +#include "mk2/spriteplayer.h" #include "mk2/spritereadersynchronizer.h" class AOApplication; @@ -29,9 +33,8 @@ class DREffectMovie; class DRSceneMovie; class DRShoutMovie; class DRSplashMovie; -class DRStickerMovie; +class DRStickerViewer; class DRTextEdit; -class DRVideoWidget; #include #include @@ -55,7 +58,7 @@ class QLabel; #include -class Courtroom : public QMainWindow +class Courtroom : public QWidget { Q_OBJECT @@ -63,7 +66,7 @@ class Courtroom : public QMainWindow static const int DEFAULT_WIDTH; static const int DEFAULT_HEIGHT; - explicit Courtroom(AOApplication *p_ao_app); + Courtroom(AOApplication *p_ao_app, QWidget *parent = nullptr); ~Courtroom(); QVector get_character_list(); @@ -153,7 +156,6 @@ class Courtroom : public QMainWindow void set_judge_wtce(); - void set_free_blocks(); void set_judge_enabled(bool p_enabled); // these are for OOC chat @@ -218,7 +220,6 @@ class Courtroom : public QMainWindow void check_shouts(); void check_effects(); void check_wtce(); - void check_free_blocks(); void resume_timer(int timer_id); void set_timer_time(int timer_id, int new_time); @@ -358,8 +359,8 @@ class Courtroom : public QMainWindow AOImageDisplay *ui_background = nullptr; - QWidget *ui_viewport = nullptr; - DRVideoWidget *ui_video = nullptr; + DRGraphicsView *ui_viewport = nullptr; + DRVideoScreen *ui_video = nullptr; DRSceneMovie *ui_vp_background = nullptr; DRCharacterMovie *ui_vp_player_char = nullptr; DRSceneMovie *ui_vp_desk = nullptr; @@ -375,11 +376,11 @@ class Courtroom : public QMainWindow DREffectMovie *ui_vp_effect = nullptr; DRSplashMovie *ui_vp_wtce = nullptr; DRShoutMovie *ui_vp_objection = nullptr; - DRStickerMovie *ui_vp_chat_arrow = nullptr; - DRStickerMovie *ui_vp_loading = nullptr; + DRStickerViewer *ui_vp_chat_arrow = nullptr; + DRStickerViewer *ui_vp_loading = nullptr; - QMap> m_mapped_viewer_list; - QMap m_viewport_viewer_map; + QMap> m_mapped_viewer_list; + QMap m_viewport_viewer_map; QMap m_preloader_cache; QMap m_reader_cache; @@ -401,7 +402,7 @@ class Courtroom : public QMainWindow QWidget *ui_vp_music_area = nullptr; - DRStickerMovie *ui_vp_clock = nullptr; + DRStickerViewer *ui_vp_clock = nullptr; QVector ui_timers; DRTextEdit *ui_ic_chatlog = nullptr; @@ -443,8 +444,9 @@ class Courtroom : public QMainWindow QVector ui_emote_list; AOButton *ui_emote_left = nullptr; AOButton *ui_emote_right = nullptr; - AOImageDisplay *ui_emote_preview = nullptr; - DRCharacterMovie *ui_emote_preview_character = nullptr; + DRGraphicsView *ui_emote_preview; + DRThemeMovie *ui_emote_preview_background; + DRCharacterMovie *ui_emote_preview_character; QComboBox *ui_emote_dropdown = nullptr; QComboBox *ui_iniswap_dropdown = nullptr; @@ -470,7 +472,7 @@ class Courtroom : public QMainWindow // holds all the shout buttons objects QVector ui_wtce; // holds all the free block objects - QVector ui_free_blocks; + QVector ui_free_blocks; // holds all the names for sound files for the shouts QVector shout_names; @@ -481,9 +483,6 @@ class Courtroom : public QMainWindow // holds all the names for sound/anim files for the shouts QVector wtce_names; - // holds all the names for free blocks - QVector free_block_names; - // holds whether the animation file exists for a determined shout/effect QVector shouts_enabled; QVector effects_enabled; diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 5114589df..2501efdad 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -21,10 +21,10 @@ #include "drscenemovie.h" #include "drshoutmovie.h" #include "drsplashmovie.h" -#include "drstickermovie.h" +#include "drstickerviewer.h" #include "drtextedit.h" -#include "drvideoscreen.h" #include "file_functions.h" +#include "mk2/graphicsvideoscreen.h" #include "theme.h" #include @@ -67,12 +67,38 @@ void Courtroom::create_widgets() ui_background = new AOImageDisplay(this, ao_app); - ui_viewport = new QWidget(this); - ui_video = new DRVideoWidget(this); - ui_video->hide(); - ui_vp_background = new DRSceneMovie(ui_viewport); - ui_vp_player_char = new DRCharacterMovie(ui_viewport); - ui_vp_desk = new DRSceneMovie(ui_viewport); + ui_viewport = new DRGraphicsView(this); + + { // populate scene + auto *l_scene = ui_viewport->scene(); + + ui_vp_background = new DRSceneMovie(ao_app); + l_scene->addItem(ui_vp_background); + + ui_vp_player_char = new DRCharacterMovie(ao_app); + l_scene->addItem(ui_vp_player_char); + + ui_vp_desk = new DRSceneMovie(ao_app); + l_scene->addItem(ui_vp_desk); + + ui_vp_effect = new DREffectMovie(ao_app); + l_scene->addItem(ui_vp_effect); + + ui_vp_wtce = new DRSplashMovie(ao_app); + l_scene->addItem(ui_vp_wtce); + + ui_vp_objection = new DRShoutMovie(ao_app); + l_scene->addItem(ui_vp_objection); + + ui_video = new DRVideoScreen(ao_app); + l_scene->addItem(ui_video); + + // move to the corner + for (auto i_item : l_scene->items()) + { + i_item->setPos(0, 0); + } + } ui_vp_music_display_a = new AOImageDisplay(this, ao_app); ui_vp_music_display_b = new AOImageDisplay(this, ao_app); @@ -85,7 +111,7 @@ void Courtroom::create_widgets() music_anim = new QPropertyAnimation(ui_vp_music_name, "geometry", this); set_music_text("DANGANRONPA ONLINE"); - ui_vp_clock = new DRStickerMovie(this); + ui_vp_clock = new DRStickerViewer(ao_app, this); ui_vp_chatbox = new AOImageDisplay(this, ao_app); ui_vp_showname = new DRTextEdit(ui_vp_chatbox); @@ -101,12 +127,8 @@ void Courtroom::create_widgets() ui_vp_showname_image = new AOImageDisplay(this, ao_app); - ui_vp_effect = new DREffectMovie(this); - ui_vp_wtce = new DRSplashMovie(this); - ui_vp_objection = new DRShoutMovie(this); - - ui_vp_chat_arrow = new DRStickerMovie(this); - ui_vp_loading = new DRStickerMovie(this); + ui_vp_chat_arrow = new DRStickerViewer(ao_app, this); + ui_vp_loading = new DRStickerViewer(ao_app, this); ui_vp_loading->hide(); m_loading_timer = new QTimer(this); @@ -267,8 +289,6 @@ void Courtroom::create_widgets() ui_timers.resize(1); ui_timers[0] = new AOTimer(this); - load_free_blocks(); // Done last so they are guaranteed to be at bottom - construct_char_select(); } @@ -276,8 +296,6 @@ void Courtroom::connect_widgets() { connect(m_keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); - connect(ui_video, SIGNAL(started()), ui_video, SLOT(show())); - connect(ui_video, SIGNAL(finished()), ui_video, SLOT(hide())); connect(ui_video, SIGNAL(finished()), this, SLOT(video_finished())); connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); @@ -394,10 +412,6 @@ void Courtroom::reset_widget_names() widget_names = { {"courtroom", this}, {"viewport", ui_viewport}, - {"video", ui_video}, - {"background", ui_vp_background}, //* - {"player_char", ui_vp_player_char}, //* - {"desk", ui_vp_desk}, //* {"music_display_a", ui_vp_music_display_a}, {"music_display_b", ui_vp_music_display_b}, {"music_area", ui_vp_music_area}, @@ -408,9 +422,6 @@ void Courtroom::reset_widget_names() {"showname", ui_vp_showname}, {"message", ui_vp_message}, {"showname_image", ui_vp_showname_image}, - {"vp_effect", ui_vp_effect}, - {"vp_wtce", ui_vp_wtce}, - {"vp_objection", ui_vp_objection}, {"chat_arrow", ui_vp_chat_arrow}, {"loading", ui_vp_loading}, {"ic_chatlog", ui_ic_chatlog}, @@ -502,83 +513,88 @@ void Courtroom::set_widget_names() // setup table of widgets and names insert_widget_names(effect_names, ui_effects); - insert_widget_names(free_block_names, ui_free_blocks); insert_widget_names(shout_names, ui_shouts); insert_widget_names(wtce_names, ui_wtce); + for (auto *i_block : qAsConst(ui_free_blocks)) + { + widget_names.insert(i_block->objectName(), i_block); + } + // timers are special children QVector timer_names; for (int i = 0; i < ui_timers.length(); ++i) + { timer_names.append("timer_" + QString::number(i)); + } insert_widget_names(timer_names, ui_timers); } void Courtroom::set_widget_layers() { - // File lookup order - // 1. In the theme folder (gamemode-timeofday/main/default), look for - - QString path = ao_app->find_theme_asset_path(COURTROOM_LAYERS_INI); - QFile layer_ini(path); - // needed to avoid cyclic parenting - QStringList recorded_widgets; + QStringList l_widget_records; - // Given the purpose of find_theme_asset_path, this step failing would be - // weird - if (layer_ini.open(QFile::ReadOnly)) + const QString l_ini_path = ao_app->find_theme_asset_path(COURTROOM_LAYERS_INI); + QFile l_layer_ini(l_ini_path); + if (l_layer_ini.open(QFile::ReadOnly)) { - QTextStream in(&layer_ini); + QTextStream in(&l_layer_ini); - // current parent's name - QString parent_name = "courtroom"; - // the courtroom is ALWAYS going to be recorded - recorded_widgets.append(parent_name); + const QString l_default_parent_name = objectName(); + l_widget_records.append(l_default_parent_name); + + QString l_parent_name = l_default_parent_name; while (!in.atEnd()) { - QString line = in.readLine().trimmed(); + QString l_line = in.readLine().trimmed(); // skip if line is empty - if (line.isEmpty()) continue; + if (l_line.isEmpty()) + { + continue; + } // revert to default parent if we encounter an end scope - if (line.startsWith("[\\")) + if (l_line.startsWith("[\\")) { - parent_name = "courtroom"; + l_parent_name = l_default_parent_name; } // is this a parent? - else if (line.startsWith("[")) + else if (l_line.startsWith("[")) { // update the current parent - parent_name = line.remove(0, 1).chopped(1).trimmed(); + l_parent_name = l_line.remove(0, 1).chopped(1).trimmed(); } // if this is not a parent, it's a child else { // if the child is already known, skip - if (recorded_widgets.contains(line)) continue; - // make the child known - recorded_widgets.append(line); + if (l_widget_records.contains(l_line)) + { + qWarning() << "error: widget already recorded:" << l_line; + continue; + } + l_widget_records.append(l_line); // attach the children to the parents' - QWidget *child = widget_names[line]; - // if child is null, then it does not exist - if (!child) continue; - - QWidget *parent = widget_names[parent_name]; - // if parent is null, attach main parent - if (!parent) parent = widget_names["courtroom"]; + if (!widget_names.contains(l_line)) + { + qWarning() << "widget does not exist:" << l_line; + continue; + } + QWidget *l_widget = widget_names.value(l_line); + QWidget *l_parent = widget_names.value(l_parent_name, this); // set child to parent - bool was_visible = child->isVisible(); - child->setParent(parent); - child->raise(); - - // Readjust visibility in case this changed after the widget changed - // parent I don't know why, I don't want to know why, I shouldn't have - // to wonder why, but for whatever reason these stupid panels aren't - // laying out correctly unless we do this terribleness - if (child->isVisible() != was_visible) child->setVisible(was_visible); + qDebug() << "attaching widget" << l_widget->objectName() << "to parent" << l_parent->objectName(); + const bool l_visible = l_widget->isVisible(); + l_widget->setParent(l_parent); + if (l_visible) + { + l_widget->setVisible(l_visible); + } + l_widget->raise(); } } } @@ -587,11 +603,11 @@ void Courtroom::set_widget_layers() // particular, make it visible and raise it in front of all assets. This can // help assist a theme designer who accidentally missed config_panel and would // have become unable to reload themes had they closed the config panel - if (!recorded_widgets.contains("config_panel")) + if (!l_widget_records.contains("config_panel")) { ui_config_panel->setParent(this); - ui_config_panel->raise(); ui_config_panel->setVisible(true); + ui_config_panel->raise(); } } @@ -623,19 +639,6 @@ void Courtroom::set_widgets() set_size_and_pos(ui_viewport, "viewport", COURTROOM_DESIGN_INI, ao_app); - ui_video->move(ui_viewport->pos()); - ui_video->resize(ui_viewport->size()); - - ui_vp_background->move(0, 0); - ui_vp_background->resize(ui_viewport->size()); - - ui_vp_player_char->move(0, 0); - ui_vp_player_char->resize(ui_viewport->size()); - - // the AO2 desk element - ui_vp_desk->move(0, 0); - ui_vp_desk->resize(ui_viewport->size()); - set_size_and_pos(ui_vp_notepad_image, "notepad_image", COURTROOM_DESIGN_INI, ao_app); ui_vp_notepad_image->set_theme_image("notepad_image.png"); ui_vp_notepad_image->hide(); @@ -656,7 +659,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_vp_chat_arrow, "chat_arrow", COURTROOM_DESIGN_INI, ao_app); if (!ao_app->find_theme_asset_path("chat_arrow", animated_or_static_extensions()).isEmpty()) { - ui_vp_chat_arrow->play("chat_arrow"); + ui_vp_chat_arrow->set_theme_image("chat_arrow"); } set_sticker_play_once(ui_vp_chat_arrow, "chat_arrow", COURTROOM_CONFIG_INI, ao_app); ui_vp_chat_arrow->hide(); @@ -664,20 +667,10 @@ void Courtroom::set_widgets() { const bool l_visible = ui_vp_loading->isVisible(); set_size_and_pos(ui_vp_loading, "loading", COURTROOM_DESIGN_INI, ao_app); - ui_vp_loading->play("loading"); + ui_vp_loading->set_theme_image("loading"); ui_vp_loading->setVisible(l_visible); } - ui_vp_effect->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_effect->resize(ui_viewport->width(), ui_viewport->height()); - ui_vp_effect->hide(); - - ui_vp_wtce->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_wtce->resize(ui_viewport->width(), ui_viewport->height()); - - ui_vp_objection->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_objection->resize(ui_viewport->width(), ui_viewport->height()); - set_size_and_pos(ui_ic_chatlog, "ic_chatlog", COURTROOM_DESIGN_INI, ao_app); set_size_and_pos(ui_ic_chatlog_scroll_topdown, "ic_chatlog_scroll_topdown", COURTROOM_DESIGN_INI, ao_app); ui_ic_chatlog_scroll_topdown->set_image("ic_chatlog_scroll_topdown.png"); @@ -763,9 +756,6 @@ void Courtroom::set_widgets() ui_switch_area_music->setHidden(l_is_area_music_list_separated); } - // char select - reconstruct_char_select(); - // emotes set_size_and_pos(ui_emotes, "emotes", COURTROOM_DESIGN_INI, ao_app); construct_emote_page_layout(); @@ -784,8 +774,8 @@ void Courtroom::set_widgets() l_emote_preview_size.height = 192; } ui_emote_preview->resize(l_emote_preview_size.width, l_emote_preview_size.height); - ui_emote_preview->set_theme_image("emote_preview.png"); - ui_emote_preview_character->resize(l_emote_preview_size.width, l_emote_preview_size.height); + ui_emote_preview_background->set_theme_image("emote_preview.png"); + ui_emote_preview_character->set_size(QSizeF(l_emote_preview_size.width, l_emote_preview_size.height)); } set_size_and_pos(ui_emote_dropdown, "emote_dropdown", COURTROOM_DESIGN_INI, ao_app); @@ -874,11 +864,14 @@ void Courtroom::set_widgets() set_judge_wtce(); reset_wtce_buttons(); - for (int i = 0; i < free_block_names.size(); ++i) + for (DRStickerViewer *i_sticker : ui_free_blocks) { - set_size_and_pos(ui_free_blocks[i], free_block_names[i], COURTROOM_DESIGN_INI, ao_app); + const QString l_name = i_sticker->objectName(); + set_size_and_pos(i_sticker, l_name, COURTROOM_DESIGN_INI, ao_app); + i_sticker->set_theme_image(l_name); + set_sticker_play_once(i_sticker, l_name, COURTROOM_CONFIG_INI, ao_app); + i_sticker->show(); } - set_free_blocks(); // Set the default values for the buttons, then determine if they should be // replaced by images @@ -1006,11 +999,11 @@ void Courtroom::set_widgets() ui_char_button_selector->set_theme_image("char_selector.png"); ui_char_button_selector->hide(); + set_size_and_pos(ui_char_buttons, "char_buttons", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_back_to_lobby, "back_to_lobby", COURTROOM_DESIGN_INI, ao_app); ui_back_to_lobby->setText("Back to Lobby"); - set_size_and_pos(ui_char_buttons, "char_buttons", COURTROOM_DESIGN_INI, ao_app); - set_size_and_pos(ui_chr_select_left, "char_select_left", COURTROOM_DESIGN_INI, ao_app); ui_chr_select_left->set_image("arrow_left.png"); @@ -1146,24 +1139,6 @@ void Courtroom::check_effects() } } -void Courtroom::check_free_blocks() -{ - // Asset lookup order - // 1. In the character folder, look for - // `free_block_names.at(i)` + extensions in `exts` in order - // 2. In the theme folder (gamemode-timeofday/main/default), look for - // `free_block_names.at(i)` + extensions in `exts` in order - // Only enable buttons where a file was found - - for (int i = 0; i < ui_free_blocks.size(); ++i) - { - QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), free_block_names.at(i))}, - animated_extensions()); - if (path.isEmpty()) path = ao_app->find_theme_asset_path(free_block_names.at(i), animated_extensions()); - free_blocks_enabled[i] = (!path.isEmpty()); - } -} - void Courtroom::check_shouts() { // Asset lookup order @@ -1265,30 +1240,21 @@ void Courtroom::load_free_blocks() for (QWidget *widget : qAsConst(ui_free_blocks)) delete_widget(widget); - // And create new free block buttons - int free_block_number = ao_app->read_theme_ini_int("free_block_number", COURTROOM_CONFIG_INI); - free_blocks_enabled.resize(free_block_number); - ui_free_blocks.resize(free_block_number); - - for (int i = 0; i < ui_free_blocks.size(); ++i) - { - ui_free_blocks[i] = new DRStickerMovie(this); - // ui_free_blocks[i]->setProperty("free_block_id", i+1); - ui_free_blocks[i]->set_play_once(false); - ui_free_blocks[i]->stackUnder(ui_vp_player_char); - } - - // And add names - free_block_names.clear(); - for (int i = 0; i < ui_free_blocks.size(); ++i) + ui_free_blocks.clear(); + const int l_block_count = ao_app->read_theme_ini_int("free_block_number", COURTROOM_CONFIG_INI); + for (int i = 0; i < l_block_count; ++i) { - QString name = "free_block_" + ao_app->get_spbutton("[FREE BLOCKS]", i + 1).trimmed(); - if (!name.isEmpty()) + const QString l_name = ao_app->get_spbutton("[FREE BLOCKS]", i + 1).trimmed(); + if (l_name.isEmpty()) { - free_block_names.append(name); - widget_names[name] = ui_free_blocks[i]; - ui_free_blocks[i]->setObjectName(name); + qWarning() << "error: block index" << i << "has no block name."; + continue; } + const QString l_block_name = "free_block_" + l_name; + auto *l_block = new DRStickerViewer(ao_app, this); + l_block->setObjectName(l_block_name); + ui_free_blocks.append(l_block); + widget_names.insert(l_block_name, l_block); } } @@ -1435,20 +1401,6 @@ void Courtroom::set_judge_wtce() } } -/** - * @brief Show all free blocks and restart their animations. - */ -void Courtroom::set_free_blocks() -{ - for (int i = 0; i < ui_free_blocks.size(); i++) - { - DRStickerMovie *l_block = ui_free_blocks[i]; - const QString l_block_name = free_block_names[i]; - l_block->play(l_block_name); - set_sticker_play_once(l_block, l_block_name, COURTROOM_CONFIG_INI, ao_app); - } -} - void Courtroom::set_fonts() { set_drtextedit_font(ui_vp_showname, "showname", COURTROOM_FONTS_INI, ao_app); diff --git a/src/drcharactermovie.cpp b/src/drcharactermovie.cpp index 4d5711c8f..932edd506 100644 --- a/src/drcharactermovie.cpp +++ b/src/drcharactermovie.cpp @@ -5,8 +5,9 @@ #include -DRCharacterMovie::DRCharacterMovie(QWidget *parent) - : DRMovie(parent), ao_app(dynamic_cast(qApp)) +DRCharacterMovie::DRCharacterMovie(AOApplication *ao_app, QGraphicsItem *parent) + : DRMovie(parent) + , ao_app(ao_app) { Q_ASSERT(ao_app); set_scaling_mode(ScalingMode::HeightScaling); diff --git a/src/drcharactermovie.h b/src/drcharactermovie.h index 2cbc965f8..d62a620aa 100644 --- a/src/drcharactermovie.h +++ b/src/drcharactermovie.h @@ -9,7 +9,7 @@ class DRCharacterMovie : public DRMovie Q_OBJECT public: - explicit DRCharacterMovie(QWidget *parent = nullptr); + explicit DRCharacterMovie(AOApplication *ao_app, QGraphicsItem *parent = nullptr); ~DRCharacterMovie(); void play(QString character, QString emote, QString prefix, bool p_use_placeholder, bool play_once); diff --git a/src/dreffectmovie.cpp b/src/dreffectmovie.cpp index 90cc5d555..76dd1496a 100644 --- a/src/dreffectmovie.cpp +++ b/src/dreffectmovie.cpp @@ -1,10 +1,24 @@ #include "dreffectmovie.h" -DREffectMovie::DREffectMovie(QWidget *parent) - : DRStickerMovie(parent) +#include "aoapplication.h" + +DREffectMovie::DREffectMovie(AOApplication *ao_app, QGraphicsItem *parent) + : DRMovie(parent) + , ao_app(ao_app) { set_hide_on_done(true); } DREffectMovie::~DREffectMovie() {} + +void DREffectMovie::play(QString p_file_name, QString p_character) +{ + set_file_name(ao_app->get_theme_sprite_path(p_file_name, p_character)); + start(); +} + +void DREffectMovie::play(QString p_file_name) +{ + play(p_file_name, ""); +} diff --git a/src/dreffectmovie.h b/src/dreffectmovie.h index 8ecfef210..c5a290619 100644 --- a/src/dreffectmovie.h +++ b/src/dreffectmovie.h @@ -1,12 +1,20 @@ #pragma once -#include "drstickermovie.h" +#include "drmovie.h" -class DREffectMovie : public DRStickerMovie +class AOApplication; + +class DREffectMovie : public DRMovie { Q_OBJECT public: - explicit DREffectMovie(QWidget *parent = nullptr); + explicit DREffectMovie(AOApplication *ao_app, QGraphicsItem *parent = nullptr); ~DREffectMovie(); + + void play(QString p_file_name, QString p_character); + void play(QString p_file_name); + +private: + AOApplication *ao_app; }; diff --git a/src/drgraphicscene.cpp b/src/drgraphicscene.cpp new file mode 100644 index 000000000..08a6def9b --- /dev/null +++ b/src/drgraphicscene.cpp @@ -0,0 +1,59 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ +#include "drgraphicscene.h" + +#include +#include +#include + +#include + +DRGraphicsView::DRGraphicsView(QWidget *parent) + : QGraphicsView(parent) +{ + setInteractive(false); + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + setFrameShape(QFrame::NoFrame); + setFrameStyle(0); + + setScene(new QGraphicsScene(this)); + + setBackgroundBrush(Qt::black); +} + +DRGraphicsView::~DRGraphicsView() +{} + +void DRGraphicsView::resizeEvent(QResizeEvent *event) +{ + QGraphicsView::resizeEvent(event); + for (QGraphicsItem *i_item : scene()->items()) + { + auto l_object = dynamic_cast(i_item); + if (l_object) + { + l_object->setProperty("size", event->size()); + } + } + setSceneRect(rect()); +} diff --git a/src/drgraphicscene.h b/src/drgraphicscene.h new file mode 100644 index 000000000..9d7bf6db1 --- /dev/null +++ b/src/drgraphicscene.h @@ -0,0 +1,35 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ +#pragma once + +#include +#include + +class DRGraphicsView : public QGraphicsView +{ + Q_OBJECT + +public: + DRGraphicsView(QWidget *parent = nullptr); + ~DRGraphicsView(); + +protected: + void resizeEvent(QResizeEvent *event) final; +}; diff --git a/src/drmovie.cpp b/src/drmovie.cpp index 90717f72d..2a25059c4 100644 --- a/src/drmovie.cpp +++ b/src/drmovie.cpp @@ -1,13 +1,13 @@ #include "drmovie.h" -DRMovie::DRMovie(QWidget *parent) - : SpriteViewer{parent} +DRMovie::DRMovie(QGraphicsItem *parent) + : mk2::GraphicsSpriteItem{parent} , m_hide_when_done{false} , m_mirrored{false} { - connect(this, SIGNAL(started()), this, SLOT(show())); + connect(this, SIGNAL(started()), this, SLOT(update_visibility())); connect(this, SIGNAL(finished()), this, SIGNAL(done())); - connect(this, SIGNAL(finished()), this, SLOT(_p_update_visibility())); + connect(this, SIGNAL(finished()), this, SLOT(update_visibility())); } DRMovie::~DRMovie() @@ -28,9 +28,13 @@ void DRMovie::set_mirrored(bool p_on) set_mirror(p_on); } -void DRMovie::_p_update_visibility() +void DRMovie::update_visibility() { - if (m_hide_when_done) + if (is_running()) + { + show(); + } + else if (m_hide_when_done) { hide(); } diff --git a/src/drmovie.h b/src/drmovie.h index 1ae9afe37..60bc3b816 100644 --- a/src/drmovie.h +++ b/src/drmovie.h @@ -1,13 +1,13 @@ #pragma once -#include "mk2/spriteviewer.h" +#include "mk2/graphicsspriteitem.h" -class DRMovie : public mk2::SpriteViewer +class DRMovie : public mk2::GraphicsSpriteItem { Q_OBJECT public: - DRMovie(QWidget *parent = nullptr); + DRMovie(QGraphicsItem *parent = nullptr); ~DRMovie(); QString file_name(); @@ -22,5 +22,5 @@ class DRMovie : public mk2::SpriteViewer bool m_mirrored; private slots: - void _p_update_visibility(); + void update_visibility(); }; diff --git a/src/drscenemovie.cpp b/src/drscenemovie.cpp index 9210dfe4c..f5888c5c8 100644 --- a/src/drscenemovie.cpp +++ b/src/drscenemovie.cpp @@ -3,10 +3,10 @@ #include "aoapplication.h" #include "file_functions.h" -DRSceneMovie::DRSceneMovie(QWidget *parent) - : DRMovie(parent), ao_app(dynamic_cast(qApp)) +DRSceneMovie::DRSceneMovie(AOApplication *ao_app, QGraphicsItem *parent) + : DRMovie(parent) + , ao_app(ao_app) { - Q_ASSERT(ao_app); set_scaling_mode(ScalingMode::DynamicScaling); } diff --git a/src/drscenemovie.h b/src/drscenemovie.h index b79f44183..5adc1cf89 100644 --- a/src/drscenemovie.h +++ b/src/drscenemovie.h @@ -9,7 +9,7 @@ class DRSceneMovie : public DRMovie Q_OBJECT public: - explicit DRSceneMovie(QWidget *parent = nullptr); + explicit DRSceneMovie(AOApplication *ao_app, QGraphicsItem *parent = nullptr); ~DRSceneMovie(); void set_background_image(QString p_background_name, QString p_image); diff --git a/src/drshoutmovie.cpp b/src/drshoutmovie.cpp index 948f82463..95d5a737d 100644 --- a/src/drshoutmovie.cpp +++ b/src/drshoutmovie.cpp @@ -5,8 +5,9 @@ #include -DRShoutMovie::DRShoutMovie(QWidget *parent) - : DRMovie(parent), ao_app(dynamic_cast(qApp)) +DRShoutMovie::DRShoutMovie(AOApplication *ao_app, QGraphicsItem *parent) + : DRMovie(parent) + , ao_app(ao_app) { Q_ASSERT(ao_app); set_play_once(true); diff --git a/src/drshoutmovie.h b/src/drshoutmovie.h index 37f027a64..50351fd6e 100644 --- a/src/drshoutmovie.h +++ b/src/drshoutmovie.h @@ -9,7 +9,7 @@ class DRShoutMovie : public DRMovie Q_OBJECT public: - explicit DRShoutMovie(QWidget *parent = nullptr); + explicit DRShoutMovie(AOApplication *ao_app, QGraphicsItem *parent = nullptr); ~DRShoutMovie(); void play_interjection(QString p_char_name, QString p_interjection_name); diff --git a/src/drsplashmovie.cpp b/src/drsplashmovie.cpp index 5e43738fa..608df0544 100644 --- a/src/drsplashmovie.cpp +++ b/src/drsplashmovie.cpp @@ -1,6 +1,10 @@ #include "drsplashmovie.h" -DRSplashMovie::DRSplashMovie(QWidget *parent) : DRStickerMovie(parent) +#include "aoapplication.h" + +DRSplashMovie::DRSplashMovie(AOApplication *ao_app, QGraphicsItem *parent) + : DRMovie(parent) + , ao_app(ao_app) { set_play_once(true); set_hide_on_done(true); @@ -8,3 +12,14 @@ DRSplashMovie::DRSplashMovie(QWidget *parent) : DRStickerMovie(parent) DRSplashMovie::~DRSplashMovie() {} + +void DRSplashMovie::play(QString p_file_name, QString p_character) +{ + set_file_name(ao_app->get_theme_sprite_path(p_file_name, p_character)); + start(); +} + +void DRSplashMovie::play(QString p_file_name) +{ + play(p_file_name, ""); +} diff --git a/src/drsplashmovie.h b/src/drsplashmovie.h index ebcb379e2..9b68045f0 100644 --- a/src/drsplashmovie.h +++ b/src/drsplashmovie.h @@ -1,12 +1,20 @@ #pragma once -#include "drstickermovie.h" +#include "drmovie.h" -class DRSplashMovie : public DRStickerMovie +class AOApplication; + +class DRSplashMovie : public DRMovie { Q_OBJECT public: - explicit DRSplashMovie(QWidget *parent = nullptr); + explicit DRSplashMovie(AOApplication *ao_app, QGraphicsItem *parent = nullptr); ~DRSplashMovie(); + + void play(QString p_file_name, QString p_character); + void play(QString p_file_name); + +private: + AOApplication *ao_app; }; diff --git a/src/drstickermovie.cpp b/src/drstickermovie.cpp deleted file mode 100644 index 128a20913..000000000 --- a/src/drstickermovie.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "drstickermovie.h" - -#include "aoapplication.h" -#include "file_functions.h" - -DRStickerMovie::DRStickerMovie(QWidget *parent) - : DRMovie(parent), ao_app(dynamic_cast(qApp)) -{} - -DRStickerMovie::~DRStickerMovie() -{} - -void DRStickerMovie::play(QString p_file_name, QString p_character) -{ - set_file_name(ao_app->get_theme_sprite_path(p_file_name, p_character)); - start(); -} - -void DRStickerMovie::play(QString p_file_name) -{ - play(p_file_name, ""); -} diff --git a/src/drstickermovie.h b/src/drstickermovie.h deleted file mode 100644 index 55b8c130a..000000000 --- a/src/drstickermovie.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "drmovie.h" - -class AOApplication; - -class DRStickerMovie : public DRMovie -{ - Q_OBJECT - -public: - explicit DRStickerMovie(QWidget *parent = nullptr); - ~DRStickerMovie(); - - void play(QString p_file_name, QString p_character); - void play(QString p_file_name); - -private: - AOApplication *ao_app = nullptr; -}; diff --git a/src/drstickerviewer.cpp b/src/drstickerviewer.cpp new file mode 100644 index 000000000..238030222 --- /dev/null +++ b/src/drstickerviewer.cpp @@ -0,0 +1,41 @@ +#include "drstickerviewer.h" + +#include "aoapplication.h" + +DRStickerViewer::DRStickerViewer(AOApplication *ao_app, QWidget *parent) + : mk2::SpriteViewer(parent) + , ao_app(ao_app) + , m_hide_when_done(false) +{ + connect(this, SIGNAL(started()), this, SLOT(show())); + connect(this, SIGNAL(finished()), this, SLOT(maybe_hide())); +} + +DRStickerViewer::~DRStickerViewer() +{} + +void DRStickerViewer::set_hide_when_done(bool p_on) +{ + if (m_hide_when_done == p_on) + { + return; + } + m_hide_when_done = p_on; + if (!is_running()) + { + maybe_hide(); + } +} + +void DRStickerViewer::set_theme_image(QString p_file_name) +{ + set_file_name(ao_app->get_theme_sprite_path(p_file_name, "")); +} + +void DRStickerViewer::maybe_hide() +{ + if (m_hide_when_done) + { + hide(); + } +} diff --git a/src/drstickerviewer.h b/src/drstickerviewer.h new file mode 100644 index 000000000..bcbdd98de --- /dev/null +++ b/src/drstickerviewer.h @@ -0,0 +1,28 @@ +#pragma once + +#include "mk2/spriteviewer.h" + +class AOApplication; + +class DRStickerViewer : public mk2::SpriteViewer +{ + Q_OBJECT + +public: + explicit DRStickerViewer(AOApplication *ao_app, QWidget *parent = nullptr); + ~DRStickerViewer(); + + void set_hide_when_done(bool on); + + void set_theme_image(QString p_file_name, QString p_character); + + void set_theme_image(QString p_file_name); + +private: + AOApplication *ao_app = nullptr; + + bool m_hide_when_done; + +private slots: + void maybe_hide(); +}; diff --git a/src/drthememovie.cpp b/src/drthememovie.cpp new file mode 100644 index 000000000..691635529 --- /dev/null +++ b/src/drthememovie.cpp @@ -0,0 +1,16 @@ +#include "drthememovie.h" + +#include "aoapplication.h" + +DRThemeMovie::DRThemeMovie(AOApplication *app, QGraphicsItem *parent) + : DRMovie(parent), ao_app(app) +{} + +DRThemeMovie::~DRThemeMovie() +{} + +void DRThemeMovie::set_theme_image(QString p_image) +{ + set_file_name(ao_app->find_theme_asset_path(p_image)); + start(); +} diff --git a/src/drthememovie.h b/src/drthememovie.h new file mode 100644 index 000000000..7218b6cc2 --- /dev/null +++ b/src/drthememovie.h @@ -0,0 +1,20 @@ +#pragma once + +#include "drmovie.h" + +class AOApplication; + +class DRThemeMovie : public DRMovie +{ + Q_OBJECT + +public: + DRThemeMovie(AOApplication *app, QGraphicsItem *parent = nullptr); + ~DRThemeMovie(); + +public slots: + void set_theme_image(QString p_image); + +private: + AOApplication *ao_app; +}; diff --git a/src/drvideoscreen.cpp b/src/drvideoscreen.cpp deleted file mode 100644 index eafc2263b..000000000 --- a/src/drvideoscreen.cpp +++ /dev/null @@ -1,225 +0,0 @@ -#include "drvideoscreen.h" - -#include "aoapplication.h" -#include "aoconfig.h" -#include "draudiodevice.h" -#include "draudioengine.h" -#include "draudiostreamfamily.h" - -#include -#include -#include -#include - -DRVideoWidget::DRVideoWidget(QWidget *parent) - : QVideoWidget(parent), ao_app(dynamic_cast(qApp)) -{ - Q_ASSERT(ao_app); - - m_config = new AOConfig(this); - m_engine = new DRAudioEngine(this); - m_family = m_engine->get_family(DRAudio::Family::FVideo); - m_player = new QMediaPlayer(this); - m_player->setVideoOutput(this); - - connect(m_engine, SIGNAL(current_device_changed(DRAudioDevice)), this, SLOT(update_device(DRAudioDevice))); - connect(m_config, SIGNAL(video_volume_changed(int)), this, SLOT(update_volume())); - connect(m_engine, SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); - connect(m_engine, SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); - connect(m_family.get(), SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); - connect(m_family.get(), SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); - connect(m_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, - SLOT(check_media_status(QMediaPlayer::MediaStatus))); - connect(m_player, SIGNAL(videoAvailableChanged(bool)), this, SLOT(check_video_availability(bool))); - connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(check_state(QMediaPlayer::State))); - - const std::optional l_device = m_engine->get_current_device(); - update_device(l_device.value_or(m_device)); -} - -DRVideoWidget::~DRVideoWidget() -{} - -QString DRVideoWidget::file_name() -{ - return m_file_name; -} - -void DRVideoWidget::set_file_name(QString p_file_name) -{ - if (m_file_name == p_file_name) - return; - m_file_name = p_file_name; - m_scanned = false; - m_readable = false; - m_running = false; - stop(); - if (m_file_name.isEmpty()) - { - m_scanned = true; - return; - } - m_player->setMuted(true); - m_player->setMedia(QUrl(p_file_name)); - if (m_player->error()) - { - qDebug() << "error: failed to load media file" << (m_file_name.isEmpty() ? "" : m_file_name); - } -} - -/** - * @brief Attempts to play a video file located in a character's videos directory. - * - * If the file isn't valid for any reason (not found, error, file has no video track, etc) the signal done() will be - * emitted. - * - * If a video file is already playing, it will immediately stop it - * without emitting done() before playing the newly designated file - */ -void DRVideoWidget::play_character_video(QString p_character, QString p_video) -{ - qInfo() << "loading character media file" << p_character << p_video; - - QStringList l_filelist; - const QString l_video_path = QString("videos/%1").arg(p_video); - for (const QString &i_character_name : ao_app->get_char_include_tree(p_character)) - { - l_filelist.append(ao_app->get_character_path(i_character_name, l_video_path)); - } - - const QString l_file = ao_app->find_asset_path(l_filelist); - set_file_name(l_file); - play(); -} - -void DRVideoWidget::play() -{ - if (!m_scanned) - { - m_running = true; - return; - } - else if (!m_readable) - { - emit finished(); - return; - } - - stop(); - m_running = true; - m_player->setMuted(false); - m_player->blockSignals(true); // prevents scan results from being altered - m_player->setMedia(QUrl(m_file_name)); - m_player->blockSignals(false); - m_player->play(); -} - -void DRVideoWidget::stop() -{ - m_running = false; - m_player->blockSignals(true); - m_player->stop(); - m_player->setMedia(nullptr); - m_player->blockSignals(false); -} - -bool DRVideoWidget::is_playable() -{ - return m_readable; -} - -void DRVideoWidget::check_media_status(QMediaPlayer::MediaStatus p_status) -{ - if (p_status == QMediaPlayer::InvalidMedia) - { - qWarning() << "error: invalid media file" << m_file_name; - m_scanned = true; - handle_scan_error(); - } -} - -void DRVideoWidget::check_video_availability(bool p_state) -{ - m_scanned = true; - m_readable = p_state; - m_player->setMuted(!m_readable); - if (!m_readable) - { - qWarning() << "error: media file is not a video" << m_file_name; - handle_scan_error(); - } - else if (m_running && m_player->state() != QMediaPlayer::PlayingState) - { - m_player->play(); - } -} - -void DRVideoWidget::check_state(QMediaPlayer::State p_state) -{ - if (p_state == QMediaPlayer::PlayingState) - { - update_audio_output(); - emit started(); - } - else if (m_readable && m_running && p_state == QMediaPlayer::StoppedState) - { - qInfo() << "finished media file playback" << m_file_name; - stop(); - emit finished(); - } -} - -void DRVideoWidget::handle_scan_error() -{ - if (m_running) - { - stop(); - emit finished(); - } -} - -void DRVideoWidget::update_device(DRAudioDevice p_device) -{ - if (m_device == p_device) - return; - m_device = p_device; - update_audio_output(); -} - -void DRVideoWidget::update_audio_output() -{ - const QString l_new_device_name = m_device.get_name(); - QMediaService *l_service = m_player->service(); - QAudioOutputSelectorControl *l_control = l_service->requestControl(); - if (!l_control) - { - qWarning() << "error: missing audio output control, device unchanged"; - return; - } - - const QStringList l_device_id_list = l_control->availableOutputs(); - for (const QString &i_device_id : l_device_id_list) - { - if (l_control->outputDescription(i_device_id) == l_new_device_name) - { - qDebug() << "Media player changed audio device;" << l_new_device_name; - l_control->setActiveOutput(i_device_id); - break; - } - } - l_service->releaseControl(l_control); -} - -void DRVideoWidget::update_volume() -{ - int l_volume = (m_family->get_volume() * m_engine->get_volume()) / 100; - - if (!m_family->is_ignore_suppression() && (m_family->is_suppressed() || m_engine->is_suppressed())) - { - l_volume = 0; - } - - if (m_player->volume() == l_volume) - return; - m_player->setVolume(l_volume); -} diff --git a/src/drvideoscreen.h b/src/drvideoscreen.h deleted file mode 100644 index 4a3194d2b..000000000 --- a/src/drvideoscreen.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include -#include - -#include "draudiodevice.h" -#include "draudiostreamfamily.h" - -class AOApplication; -class AOConfig; -class DRAudioEngine; - -class DRVideoWidget : public QVideoWidget -{ - Q_OBJECT - -public: - DRVideoWidget(QWidget *parent = nullptr); - ~DRVideoWidget(); - - QString file_name(); - void set_file_name(QString file_name); - - void play_character_video(QString character, QString video); - void play(); - void stop(); - - bool is_playable(); - -signals: - void started(); - void finished(); - -private: - AOConfig *m_config; - AOApplication *ao_app; - DRAudioEngine *m_engine; - DRAudioDevice m_device; - DRAudioStreamFamily::ptr m_family; - - QString m_file_name; - bool m_scanned = true; - bool m_readable = false; - QMediaPlayer *m_player; - bool m_running = false; - - void handle_scan_error(); - -private slots: - void update_device(DRAudioDevice); - void update_audio_output(); - void update_volume(); - void check_media_status(QMediaPlayer::MediaStatus); - void check_video_availability(bool); - void check_state(QMediaPlayer::State); -}; diff --git a/src/emotes.cpp b/src/emotes.cpp index 8360602d9..c2ed55203 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -7,6 +7,7 @@ #include "aoimagedisplay.h" #include "commondefs.h" #include "drcharactermovie.h" +#include "drgraphicscene.h" #include "theme.h" #include @@ -22,11 +23,19 @@ void Courtroom::construct_emotes() ui_emote_left = new AOButton(this, ao_app); ui_emote_right = new AOButton(this, ao_app); - ui_emote_preview = new AOImageDisplay(nullptr, ao_app); + ui_emote_preview = new DRGraphicsView(nullptr); ui_emote_preview->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::BypassGraphicsProxyWidget); ui_emote_preview->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_emote_preview_character = new DRCharacterMovie(ui_emote_preview); - ui_emote_preview_character->setAttribute(Qt::WA_TransparentForMouseEvents); + + { + auto *l_scene = ui_emote_preview->scene(); + + ui_emote_preview_background = new DRThemeMovie(ao_app); + l_scene->addItem(ui_emote_preview_background); + + ui_emote_preview_character = new DRCharacterMovie(ao_app); + l_scene->addItem(ui_emote_preview_character); + } ui_emote_dropdown = new QComboBox(this); ui_pos_dropdown = new QComboBox(this); diff --git a/src/mk2/graphicsspriteitem.cpp b/src/mk2/graphicsspriteitem.cpp new file mode 100644 index 000000000..e25045596 --- /dev/null +++ b/src/mk2/graphicsspriteitem.cpp @@ -0,0 +1,162 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ +#include "graphicsspriteitem.h" + +#include + +#include + +using namespace mk2; + +GraphicsSpriteItem::GraphicsSpriteItem(QGraphicsItem *parent) + : QGraphicsObject(parent) + , m_player(new SpritePlayer) +{ + connect(m_player.get(), SIGNAL(finished()), this, SLOT(notify_size())); + connect(m_player.get(), SIGNAL(current_frame_changed()), this, SLOT(notify_update())); + connect(m_player.get(), SIGNAL(file_name_changed(QString)), this, SIGNAL(file_name_changed(QString))); + connect(m_player.get(), SIGNAL(reader_changed()), this, SIGNAL(reader_changed())); + connect(m_player.get(), SIGNAL(started()), this, SIGNAL(started())); + connect(m_player.get(), SIGNAL(finished()), this, SIGNAL(finished())); +} + +GraphicsSpriteItem::~GraphicsSpriteItem() +{} + +SpritePlayer::ScalingMode GraphicsSpriteItem::get_scaling_mode() const +{ + return m_player->get_scaling_mode(); +} + +void GraphicsSpriteItem::set_scaling_mode(SpritePlayer::ScalingMode p_scaling_mode) +{ + m_player->set_scaling_mode(p_scaling_mode); +} + +QSizeF GraphicsSpriteItem::get_size() const +{ + return m_player->get_size(); +} + +void GraphicsSpriteItem::set_size(QSizeF p_size) +{ + m_player->set_size(p_size.toSize()); +} + +void GraphicsSpriteItem::set_play_once(bool p_on) +{ + m_player->set_play_once(p_on); +} + +void GraphicsSpriteItem::set_mirror(bool p_on) +{ + m_player->set_mirror(p_on); +} + +QString GraphicsSpriteItem::get_file_name() const +{ + return m_player->get_file_name(); +} + +void GraphicsSpriteItem::set_file_name(QString p_file_name) +{ + m_player->set_file_name(p_file_name); +} + +QIODevice *GraphicsSpriteItem::get_device() const +{ + return m_player->get_device(); +} + +void GraphicsSpriteItem::set_device(QIODevice *p_device) +{ + m_player->set_device(p_device); +} + +SpriteReader::ptr GraphicsSpriteItem::get_reader() const +{ + return m_player->get_reader(); +} + +void GraphicsSpriteItem::set_reader(SpriteReader::ptr p_reader) +{ + m_player->set_reader(p_reader); +} + +SpritePlayer *GraphicsSpriteItem::get_player() const +{ + return m_player.get(); +} + +void GraphicsSpriteItem::start() +{ + m_player->start(); +} + +void GraphicsSpriteItem::restart() +{ + stop(); + start(); +} + +void GraphicsSpriteItem::stop() +{ + m_player->stop(); +} + +bool GraphicsSpriteItem::is_valid() const +{ + return m_player->is_valid(); +} + +bool GraphicsSpriteItem::is_running() const +{ + return m_player->is_running(); +} + +QRectF GraphicsSpriteItem::boundingRect() const +{ + return QRectF(0, 0, m_player->get_size().width(), m_player->get_size().height()); +} + +void GraphicsSpriteItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + const QImage l_image = m_player->get_current_frame(); + if (!l_image.isNull()) + { + painter->save(); + painter->setCompositionMode(QPainter::CompositionMode_SourceOver); + painter->drawImage(0, 0, m_player->get_current_frame()); + painter->restore(); + } +} + +void GraphicsSpriteItem::notify_size() +{ + emit size_changed(QSizeF(m_player->get_size())); +} + +void GraphicsSpriteItem::notify_update() +{ + update(); +} diff --git a/src/mk2/graphicsspriteitem.h b/src/mk2/graphicsspriteitem.h new file mode 100644 index 000000000..023b5d925 --- /dev/null +++ b/src/mk2/graphicsspriteitem.h @@ -0,0 +1,99 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ +#pragma once + +#include "spriteplayer.h" +#include "spritereader.h" + +#include +#include + +namespace mk2 +{ +class GraphicsSpriteItem : public QGraphicsObject +{ + Q_OBJECT + + Q_PROPERTY(QSizeF size READ get_size WRITE set_size NOTIFY size_changed) + +public: + using ScalingMode = SpritePlayer::ScalingMode; + + GraphicsSpriteItem(QGraphicsItem *parent = nullptr); + ~GraphicsSpriteItem(); + + SpritePlayer::ScalingMode get_scaling_mode() const; + + QSizeF get_size() const; + + QString get_file_name() const; + + QIODevice *get_device() const; + + SpriteReader::ptr get_reader() const; + + SpritePlayer *get_player() const; + + bool is_valid() const; + + bool is_running() const; + + QRectF boundingRect() const final; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) final; + +public slots: + void set_scaling_mode(SpritePlayer::ScalingMode scaling_mode); + + void set_size(QSizeF size); + + void set_play_once(bool on); + + void set_mirror(bool on); + + void set_file_name(QString file_name); + + void set_device(QIODevice *device); + + void set_reader(SpriteReader::ptr reader); + + void start(); + void restart(); + void stop(); + +signals: + void size_changed(QSizeF); + + void file_name_changed(QString); + + void reader_changed(); + + void started(); + void finished(); + +private: + QScopedPointer m_player; + +private slots: + void notify_size(); + + void notify_update(); +}; +} // namespace mk2 diff --git a/src/mk2/graphicsvideoscreen.cpp b/src/mk2/graphicsvideoscreen.cpp new file mode 100644 index 000000000..3863fa0c4 --- /dev/null +++ b/src/mk2/graphicsvideoscreen.cpp @@ -0,0 +1,247 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ +#include "graphicsvideoscreen.h" + +#include +#include +#include +#include +#include + +DRVideoScreen::DRVideoScreen(AOApplication *ao_app, QGraphicsItem *parent) + : QGraphicsVideoItem(parent) + , ao_app(ao_app) + , m_config(new AOConfig(this)) + , m_engine(new DRAudioEngine(this)) + , m_family(m_engine->get_family(DRAudio::Family::FVideo)) + , m_scanned(false) + , m_video_available(false) + , m_running(false) + , m_player(new QMediaPlayer(this, QMediaPlayer::LowLatency)) +{ + setAspectRatioMode(Qt::KeepAspectRatioByExpanding); + + m_player->setVideoOutput(this); + + connect(m_player, SIGNAL(videoAvailableChanged(bool)), this, SLOT(update_video_availability(bool))); + connect(m_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(check_status(QMediaPlayer::MediaStatus))); + connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(check_state(QMediaPlayer::State))); + + connect(m_engine, SIGNAL(current_device_changed(DRAudioDevice)), this, SLOT(update_audio_device(DRAudioDevice))); + connect(m_config, SIGNAL(video_volume_changed(int)), this, SLOT(update_volume())); + connect(m_engine, SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); + connect(m_engine, SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); + connect(m_family.get(), SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); + connect(m_family.get(), SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); +} + +DRVideoScreen::~DRVideoScreen() +{} + +QString DRVideoScreen::get_file_name() const +{ + return m_file_name; +} + +void DRVideoScreen::set_file_name(QString p_file_name) +{ + if (m_file_name == p_file_name) + { + return; + } + stop(); + qInfo() << "loading media file" << p_file_name; + m_scanned = false; + m_video_available = false; + m_file_name = p_file_name; + if (m_file_name.isEmpty()) + { + m_scanned = true; + } + m_player->setMedia(QUrl::fromLocalFile(m_file_name)); +} + +void DRVideoScreen::play_character_video(QString p_character, QString p_video) +{ + QStringList l_filepath_list; + const QString l_video_path = QString("videos/%1").arg(p_video); + for (const QString &i_character_name : ao_app->get_char_include_tree(p_character)) + { + l_filepath_list.append(ao_app->get_character_path(i_character_name, l_video_path)); + } + + const QString l_filepath = ao_app->find_asset_path(l_filepath_list); + if (l_filepath.isEmpty()) + { + qWarning() << "error: no character media file" << p_character << p_video; + finish_playback(); + return; + } + set_file_name(l_filepath); + play(); +} + +void DRVideoScreen::play() +{ + stop(); + m_running = true; + if (!m_scanned) + { + return; + } + if (!m_video_available) + { + finish_playback(); + return; + } + start_playback(); +} + +void DRVideoScreen::stop() +{ + m_running = false; + if (m_player->state() != QMediaPlayer::StoppedState) + { + m_player->stop(); + } +} + +void DRVideoScreen::update_video_availability(bool p_video_available) +{ + m_video_available = p_video_available; +} + +void DRVideoScreen::check_status(QMediaPlayer::MediaStatus p_status) +{ + if (m_running) + { + switch (p_status) + { + case QMediaPlayer::InvalidMedia: + m_scanned = true; + qWarning() << "error: media file is invalid:" << m_file_name; + finish_playback(); + break; + + case QMediaPlayer::NoMedia: + m_scanned = true; + finish_playback(); + + case QMediaPlayer::LoadedMedia: + m_scanned = true; + if (m_video_available) + { + start_playback(); + } + else + { + finish_playback(); + } + break; + + default: + break; + } + } +} + +void DRVideoScreen::check_state(QMediaPlayer::State p_state) +{ + switch (p_state) + { + case QMediaPlayer::PlayingState: + emit started(); + break; + + case QMediaPlayer::StoppedState: + if (m_running) + { + finish_playback(); + } + break; + + default: + break; + } +} + +void DRVideoScreen::start_playback() +{ + if (m_player->state() == QMediaPlayer::StoppedState) + { + m_player->play(); + } +} + +void DRVideoScreen::finish_playback() +{ + stop(); + emit finished(); +} + +void DRVideoScreen::update_audio_device(DRAudioDevice p_device) +{ + if (m_device == p_device) + { + return; + } + m_device = p_device; + update_audio_output(); +} + +void DRVideoScreen::update_audio_output() +{ + const QString l_new_device_name = m_device.get_name(); + QMediaService *l_service = m_player->service(); + QAudioOutputSelectorControl *l_control = l_service->requestControl(); + if (!l_control) + { + qWarning() << "error: missing audio output control, device unchanged"; + return; + } + + const QStringList l_device_id_list = l_control->availableOutputs(); + for (const QString &i_device_id : l_device_id_list) + { + if (l_control->outputDescription(i_device_id) == l_new_device_name) + { + qDebug() << "Media player changed audio device;" << l_new_device_name; + l_control->setActiveOutput(i_device_id); + break; + } + } + l_service->releaseControl(l_control); +} + +void DRVideoScreen::update_volume() +{ + int l_volume = (m_family->get_volume() * m_engine->get_volume()) / 100; + + if (!m_family->is_ignore_suppression() && (m_family->is_suppressed() || m_engine->is_suppressed())) + { + l_volume = 0; + } + + if (m_player->volume() == l_volume) + { + return; + } + m_player->setVolume(l_volume); +} diff --git a/src/mk2/graphicsvideoscreen.h b/src/mk2/graphicsvideoscreen.h new file mode 100644 index 000000000..1101d9f68 --- /dev/null +++ b/src/mk2/graphicsvideoscreen.h @@ -0,0 +1,92 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ +#pragma once + +#include "aoapplication.h" +#include "aoconfig.h" +#include "draudiodevice.h" +#include "draudioengine.h" +#include "draudiostreamfamily.h" + +#include +#include + +class DRVideoScreen : public QGraphicsVideoItem +{ + Q_OBJECT + +public: + DRVideoScreen(AOApplication *ao_app, QGraphicsItem *parent = nullptr); + ~DRVideoScreen(); + + QString get_file_name() const; + +public slots: + void set_file_name(QString file_name); + + void play_character_video(QString character, QString video); + + void play(); + + void stop(); + +signals: + void started(); + + void finished(); + +private: + AOApplication *ao_app; + + AOConfig *m_config; + + DRAudioEngine *m_engine; + + DRAudioDevice m_device; + + DRAudioStreamFamily::ptr m_family; + + QString m_file_name; + + bool m_scanned; + + bool m_video_available; + + bool m_running; + + QMediaPlayer *m_player; + + void start_playback(); + + void finish_playback(); + +private slots: + void update_video_availability(bool); + + void check_status(QMediaPlayer::MediaStatus); + + void check_state(QMediaPlayer::State); + + void update_audio_device(DRAudioDevice); + + void update_audio_output(); + + void update_volume(); +}; diff --git a/src/mk2/spritecachingreader.cpp b/src/mk2/spritecachingreader.cpp index c061ef754..aa8bd3ec7 100644 --- a/src/mk2/spritecachingreader.cpp +++ b/src/mk2/spritecachingreader.cpp @@ -143,7 +143,7 @@ void SpriteCachingReader::_p_preload(QByteArray p_raw_data) SpriteFrame l_frame; QImage l_image_buffer = l_image_buffer_list.takeFirst(); l_reader.read(&l_image_buffer); - l_frame.image = QPixmap::fromImage(l_image_buffer); + l_frame.image = l_image_buffer; l_frame.delay = l_reader.nextImageDelay(); { QMutexLocker locker(&m_lock); diff --git a/src/mk2/spriteplayer.cpp b/src/mk2/spriteplayer.cpp new file mode 100644 index 000000000..1d5fe0bfd --- /dev/null +++ b/src/mk2/spriteplayer.cpp @@ -0,0 +1,320 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "mk2/spritedynamicreader.h" +#include "mk2/spriteviewer.h" + +#include +#include + +using namespace mk2; + +SpritePlayer::SpritePlayer(QObject *parent) + : QObject{parent} + , m_reader{new SpriteDynamicReader} + , m_scaling_mode{StretchScaling} + , m_resolved_scaling_mode{StretchScaling} + , m_transform{Qt::SmoothTransformation} + , m_running{false} + , m_mirror{false} + , m_play_once{false} + , m_frame_count{0} + , m_frame_number{0} +{ + m_frame_timer.setTimerType(Qt::PreciseTimer); + m_frame_timer.setSingleShot(true); + + m_repaint_timer.setInterval(200); + m_repaint_timer.setSingleShot(true); + + connect(&m_frame_timer, SIGNAL(timeout()), this, SLOT(fetch_next_frame())); + connect(&m_repaint_timer, SIGNAL(timeout()), this, SLOT(scale_current_frame())); +} + +SpritePlayer::~SpritePlayer() +{} + +QImage SpritePlayer::get_current_frame() const +{ + return m_scaled_current_frame; +} + +QString SpritePlayer::get_file_name() const +{ + return m_reader->get_file_name(); +} + +QIODevice *SpritePlayer::get_device() const +{ + return nullptr; +} + +SpritePlayer::ScalingMode SpritePlayer::get_scaling_mode() const +{ + return m_scaling_mode; +} + +QSize SpritePlayer::get_size() const +{ + return m_size; +} + +void SpritePlayer::set_file_name(QString p_file_name) +{ + if (!p_file_name.isEmpty() && p_file_name == get_file_name()) + { + return; + } + stop(); + m_reader->set_file_name(p_file_name); + m_frame_count = m_reader->get_frame_count(); + emit file_name_changed(p_file_name); +} + +void SpritePlayer::set_device(QIODevice *p_device) +{ + const QString l_prev_file_name = get_file_name(); + stop(); + m_reader->set_device(p_device); + m_frame_count = m_reader->get_frame_count(); + const QString l_file_name = get_file_name(); + if (l_file_name != l_prev_file_name) + { + emit file_name_changed(l_file_name); + } +} + +void SpritePlayer::set_play_once(bool p_on) +{ + m_play_once = p_on; +} + +void SpritePlayer::set_mirror(bool p_on) +{ + m_mirror = p_on; +} + +void SpritePlayer::set_scaling_mode(ScalingMode scaling_mode) +{ + if (m_scaling_mode == scaling_mode) + { + return; + } + m_scaling_mode = scaling_mode; + resolve_scaling_mode(); + scale_current_frame(); +} + +void SpritePlayer::set_size(QSize p_size) +{ + if (m_size == p_size) + { + return; + } + m_size = p_size; + resolve_scaling_mode(); + m_repaint_timer.start(); + emit size_changed(m_size); +} + +SpriteReader::ptr SpritePlayer::get_reader() const +{ + return m_reader; +} + +void SpritePlayer::set_reader(SpriteReader::ptr p_reader) +{ + const QString l_prev_file_name = get_file_name(); + stop(); + if (p_reader == nullptr) + { + p_reader = SpriteReader::ptr(new SpriteDynamicReader); + } + m_reader = p_reader; + m_frame_count = m_reader->get_frame_count(); + const QString l_file_name = get_file_name(); + if (l_file_name != l_prev_file_name) + { + emit file_name_changed(l_file_name); + } +} + +bool SpritePlayer::is_valid() const +{ + return m_reader->is_valid(); +} + +bool SpritePlayer::is_running() const +{ + return m_running; +} + +void SpritePlayer::start() +{ + m_running = true; + m_elapsed_timer.start(); + emit started(); + resolve_scaling_mode(); + fetch_next_frame(); +} + +void SpritePlayer::restart() +{ + stop(); + start(); +} + +void SpritePlayer::stop() +{ + m_running = false; + m_frame_timer.stop(); + m_frame_number = 0; +} + +void SpritePlayer::resolve_scaling_mode() +{ + m_resolved_scaling_mode = m_scaling_mode; + + const QSize l_image_size = m_reader->get_sprite_size(); + if (m_size == l_image_size || !l_image_size.isValid()) + { + m_resolved_scaling_mode = NoScaling; + } + else if (m_resolved_scaling_mode == DynamicScaling) + { + const qreal l_width_factor = (qreal)qMax(l_image_size.width(), 1) / qMax(m_size.width(), 1); + const qreal l_height_factor = (qreal)qMax(l_image_size.height(), 1) / qMax(m_size.height(), 1); + + const QSize l_by_width_size{ + int((qreal)l_image_size.width() / l_width_factor), + int((qreal)l_image_size.height() / l_width_factor), + }; + const QSize l_by_height_size{ + int((qreal)l_image_size.width() / l_height_factor), + int((qreal)l_image_size.height() / l_height_factor), + }; + + if (l_by_width_size.height() >= m_size.height()) + { + m_resolved_scaling_mode = WidthScaling; + } + else if (l_by_height_size.width() >= m_size.width()) + { + m_resolved_scaling_mode = HeightScaling; + } + else + { + m_resolved_scaling_mode = StretchScaling; + } + } + + m_transform = Qt::SmoothTransformation; + if (l_image_size.width() < m_size.width() || l_image_size.height() < m_size.height()) + { + m_transform = Qt::FastTransformation; + } +} + +void SpritePlayer::fetch_next_frame() +{ + QElapsedTimer l_timer; + l_timer.start(); + + if (!is_valid()) + { + if (m_running && m_play_once) + { + m_running = false; + emit finished(); + } + + return; + } + + if (!m_running) + { + return; + } + + if (m_frame_number >= m_frame_count) + { + if (m_play_once) + { + m_running = false; + emit finished(); + return; + } + + if (m_frame_count > 1) + { + m_frame_number = 0; + } + else + { + return; + } + } + + const int l_current_frame_number = m_frame_number; + m_current_frame = m_reader->get_frame(l_current_frame_number); + m_frame_number++; + + const int l_next_delay = m_current_frame.delay - l_timer.elapsed(); + m_frame_timer.start(qMax(0, l_next_delay)); + + scale_current_frame(); +} + +void SpritePlayer::scale_current_frame() +{ + QImage l_image = m_current_frame.image; + + if (!l_image.isNull()) + { + switch (m_resolved_scaling_mode) + { + case NoScaling: + [[fallthrough]]; + default: + break; + + case StretchScaling: + l_image = l_image.scaled(m_size, Qt::IgnoreAspectRatio, m_transform); + break; + + case WidthScaling: + l_image = l_image.scaledToWidth(m_size.width(), m_transform); + break; + + case HeightScaling: + l_image = l_image.scaledToHeight(m_size.height(), m_transform); + break; + } + } + + // slow operation... + if (m_mirror) + { + l_image = l_image.mirrored(true, false); + } + + m_scaled_current_frame = l_image; + emit current_frame_changed(); +} diff --git a/src/mk2/spriteplayer.h b/src/mk2/spriteplayer.h new file mode 100644 index 000000000..95dffaf13 --- /dev/null +++ b/src/mk2/spriteplayer.h @@ -0,0 +1,121 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include "spritereader.h" + +#include +#include +#include + +#include + +namespace mk2 +{ +class SpritePlayer : public QObject +{ + Q_OBJECT + +public: + enum ScalingMode + { + NoScaling, + WidthScaling, + HeightScaling, + StretchScaling, + DynamicScaling, + }; + Q_ENUM(ScalingMode) + + SpritePlayer(QObject *parent = nullptr); + ~SpritePlayer(); + + QImage get_current_frame() const; + + SpritePlayer::ScalingMode get_scaling_mode() const; + + QSize get_size() const; + + QString get_file_name() const; + + QIODevice *get_device() const; + + SpriteReader::ptr get_reader() const; + + bool is_valid() const; + + bool is_running() const; + +public slots: + void set_play_once(bool on); + + void set_mirror(bool on); + + void set_scaling_mode(SpritePlayer::ScalingMode scaling_mode); + + void set_size(QSize size); + + void set_file_name(QString file_name); + + void set_device(QIODevice *device); + + void set_reader(SpriteReader::ptr reader); + + void start(); + void restart(); + void stop(); + +signals: + void current_frame_changed(); + + void size_changed(QSize); + + void file_name_changed(QString); + + void reader_changed(); + + void started(); + void finished(); + +private: + SpriteReader::ptr m_reader; + SpriteFrame m_current_frame; + QImage m_scaled_current_frame; + SpritePlayer::ScalingMode m_scaling_mode; + SpritePlayer::ScalingMode m_resolved_scaling_mode; + Qt::TransformationMode m_transform; + QSize m_size; + bool m_running; + bool m_mirror; + bool m_play_once; + int m_frame_count; + int m_frame_number; + QElapsedTimer m_elapsed_timer; + QTimer m_frame_timer; + QTimer m_repaint_timer; + + void resolve_scaling_mode(); + +private slots: + void fetch_next_frame(); + void scale_current_frame(); +}; +} // namespace mk2 diff --git a/src/mk2/spritereader.h b/src/mk2/spritereader.h index 34fa498eb..0a8a14809 100644 --- a/src/mk2/spritereader.h +++ b/src/mk2/spritereader.h @@ -29,7 +29,7 @@ namespace mk2 class SpriteFrame { public: - QPixmap image; + QImage image; int delay = 0; SpriteFrame(); diff --git a/src/mk2/spriteseekingreader.cpp b/src/mk2/spriteseekingreader.cpp index ba6cea079..2444ee7a4 100644 --- a/src/mk2/spriteseekingreader.cpp +++ b/src/mk2/spriteseekingreader.cpp @@ -69,7 +69,7 @@ mk2::SpriteFrame SpriteSeekingReader::get_frame(int p_number) m_reader.read(&l_image); m_current_frame.delay = m_reader.nextImageDelay(); } - m_current_frame.image = QPixmap::fromImage(l_image); + m_current_frame.image = l_image; return m_current_frame; } diff --git a/src/mk2/spriteviewer.cpp b/src/mk2/spriteviewer.cpp index 6b77d40ed..9cd2ac5b1 100644 --- a/src/mk2/spriteviewer.cpp +++ b/src/mk2/spriteviewer.cpp @@ -28,131 +28,88 @@ using namespace mk2; SpriteViewer::SpriteViewer(QWidget *parent) : QLabel{parent} - , m_reader{new SpriteDynamicReader} - , m_scaling_mode{StretchScaling} - , m_resolved_scaling_mode{StretchScaling} - , m_transform{Qt::SmoothTransformation} - , m_running{false} - , m_mirror{false} - , m_play_once{false} - , m_frame_count{0} - , m_frame_number{0} + , m_player(new SpritePlayer) { setAlignment(Qt::AlignCenter); - m_frame_timer.setTimerType(Qt::PreciseTimer); - m_frame_timer.setSingleShot(true); - - m_repaint_timer.setInterval(200); - m_repaint_timer.setSingleShot(true); - - connect(&m_frame_timer, SIGNAL(timeout()), this, SLOT(_p_paint_next_frame())); - connect(&m_repaint_timer, SIGNAL(timeout()), this, SLOT(_p_paint_frame())); + connect(m_player.get(), SIGNAL(current_frame_changed()), this, SLOT(paint_frame())); + connect(m_player.get(), SIGNAL(file_name_changed(QString)), this, SIGNAL(file_name_changed(QString))); + connect(m_player.get(), SIGNAL(reader_changed()), this, SIGNAL(reader_changed())); + connect(m_player.get(), SIGNAL(started()), this, SIGNAL(started())); + connect(m_player.get(), SIGNAL(finished()), this, SIGNAL(finished())); } SpriteViewer::~SpriteViewer() {} -QString SpriteViewer::get_file_name() const +SpritePlayer::ScalingMode SpriteViewer::get_scaling_mode() const { - return m_reader->get_file_name(); + return m_player->get_scaling_mode(); } -QIODevice *SpriteViewer::get_device() const +void SpriteViewer::set_scaling_mode(SpritePlayer::ScalingMode p_scaling_mode) { - return nullptr; + m_player->set_scaling_mode(p_scaling_mode); } -SpriteViewer::ScalingMode SpriteViewer::get_scaling_mode() const +void SpriteViewer::set_play_once(bool p_on) { - return m_scaling_mode; + m_player->set_play_once(p_on); } -void SpriteViewer::set_file_name(QString p_file_name) +void SpriteViewer::set_mirror(bool p_on) { - if (!p_file_name.isEmpty() && p_file_name == get_file_name()) - { - return; - } - stop(); - m_reader->set_file_name(p_file_name); - m_frame_count = m_reader->get_frame_count(); - emit file_name_changed(p_file_name); + m_player->set_mirror(p_on); } -void SpriteViewer::set_device(QIODevice *p_device) +QString SpriteViewer::get_file_name() const { - const QString l_prev_file_name = get_file_name(); - stop(); - m_reader->set_device(p_device); - m_frame_count = m_reader->get_frame_count(); - const QString l_file_name = get_file_name(); - if (l_file_name != l_prev_file_name) - { - emit file_name_changed(l_file_name); - } + return m_player->get_file_name(); } -void SpriteViewer::set_play_once(bool p_on) +void SpriteViewer::set_file_name(QString p_file_name) { - m_play_once = p_on; + m_player->set_file_name(p_file_name); } -void SpriteViewer::set_mirror(bool p_on) +QIODevice *SpriteViewer::get_device() const { - m_mirror = p_on; + return m_player->get_device(); } -void SpriteViewer::set_scaling_mode(ScalingMode scaling_mode) +void SpriteViewer::set_device(QIODevice *p_device) { - if (m_scaling_mode == scaling_mode) - { - return; - } - m_scaling_mode = scaling_mode; - _p_resolve_scaling_mode(); - _p_paint_frame(); + m_player->set_device(p_device); } SpriteReader::ptr SpriteViewer::get_reader() const { - return m_reader; + return m_player->get_reader(); } void SpriteViewer::set_reader(SpriteReader::ptr p_reader) { - const QString l_prev_file_name = get_file_name(); - stop(); - if (p_reader == nullptr) - { - p_reader = SpriteReader::ptr(new SpriteDynamicReader); - } - m_reader = p_reader; - m_frame_count = m_reader->get_frame_count(); - const QString l_file_name = get_file_name(); - if (l_file_name != l_prev_file_name) - { - emit file_name_changed(l_file_name); - } + m_player->set_reader(p_reader); +} + +SpritePlayer *SpriteViewer::get_player() const +{ + return m_player.get(); } bool SpriteViewer::is_valid() const { - return m_reader->is_valid(); + return m_player->is_valid(); } bool SpriteViewer::is_running() const { - return m_running; + return m_player->is_running(); } void SpriteViewer::start() { - m_running = true; - m_elapsed_timer.start(); - emit started(); - _p_resolve_scaling_mode(); - _p_paint_next_frame(); + m_player->start(); } void SpriteViewer::restart() @@ -163,143 +120,17 @@ void SpriteViewer::restart() void SpriteViewer::stop() { - m_running = false; - m_frame_timer.stop(); - m_frame_number = 0; + m_player->stop(); } void SpriteViewer::resizeEvent(QResizeEvent *p_event) { QLabel::resizeEvent(p_event); - _p_resolve_scaling_mode(); - m_repaint_timer.start(); + m_player->set_size(p_event->size()); + paint_frame(); } -void SpriteViewer::_p_resolve_scaling_mode() +void SpriteViewer::paint_frame() { - m_resolved_scaling_mode = m_scaling_mode; - - const QSize p_size = size(); - const QSize p_image_size = m_reader->get_sprite_size(); - if (p_size == p_image_size || !p_image_size.isValid()) - { - m_resolved_scaling_mode = NoScaling; - } - else if (m_resolved_scaling_mode == DynamicScaling) - { - const qreal l_width_factor = (qreal)qMax(p_image_size.width(), 1) / qMax(p_size.width(), 1); - const qreal l_height_factor = (qreal)qMax(p_image_size.height(), 1) / qMax(p_size.height(), 1); - - const QSize l_by_width_size{ - int((qreal)p_image_size.width() / l_width_factor), - int((qreal)p_image_size.height() / l_width_factor), - }; - const QSize l_by_height_size{ - int((qreal)p_image_size.width() / l_height_factor), - int((qreal)p_image_size.height() / l_height_factor), - }; - - if (l_by_width_size.height() >= p_size.height()) - { - m_resolved_scaling_mode = WidthScaling; - } - else if (l_by_height_size.width() >= p_size.width()) - { - m_resolved_scaling_mode = HeightScaling; - } - else - { - m_resolved_scaling_mode = StretchScaling; - } - } - - m_transform = Qt::SmoothTransformation; - if (p_image_size.width() < p_size.width() || p_image_size.height() < p_size.height()) - { - m_transform = Qt::FastTransformation; - } -} - -void SpriteViewer::_p_paint_next_frame() -{ - QElapsedTimer l_timer; - l_timer.start(); - - if (!is_valid()) - { - if (m_running && m_play_once) - { - m_running = false; - emit finished(); - } - - return; - } - - if (!m_running) - { - return; - } - - if (m_frame_number >= m_frame_count) - { - if (m_play_once) - { - m_running = false; - emit finished(); - return; - } - - if (m_frame_count > 1) - { - m_frame_number = 0; - } - else - { - return; - } - } - - const int l_current_frame_number = m_frame_number; - m_current_frame = m_reader->get_frame(m_frame_number++); - _p_paint_frame(); - - const int l_next_delay = m_current_frame.delay - l_timer.elapsed(); - m_frame_timer.start(qMax(0, l_next_delay)); -} - -void SpriteViewer::_p_paint_frame() -{ - QPixmap l_image = m_current_frame.image; - - if (!l_image.isNull()) - { - switch (m_resolved_scaling_mode) - { - case NoScaling: - [[fallthrough]]; - default: - break; - - case StretchScaling: - l_image = l_image.scaled(size(), Qt::IgnoreAspectRatio, m_transform); - break; - - case WidthScaling: - l_image = l_image.scaledToWidth(width(), m_transform); - break; - - case HeightScaling: - l_image = l_image.scaledToHeight(height(), m_transform); - break; - } - } - - // slow operation... - if (m_mirror) - { - l_image = QPixmap::fromImage(l_image.toImage().mirrored(true, false)); - } - - setPixmap(l_image); + setPixmap(QPixmap::fromImage(m_player->get_current_frame())); } diff --git a/src/mk2/spriteviewer.h b/src/mk2/spriteviewer.h index 2cdb9a871..c13642710 100644 --- a/src/mk2/spriteviewer.h +++ b/src/mk2/spriteviewer.h @@ -20,13 +20,11 @@ #pragma once +#include "spriteplayer.h" #include "spritereader.h" #include -#include -#include - -#include +#include namespace mk2 { @@ -35,43 +33,37 @@ class SpriteViewer : public QLabel Q_OBJECT public: - enum ScalingMode - { - NoScaling, - WidthScaling, - HeightScaling, - StretchScaling, - DynamicScaling, - }; - Q_ENUM(ScalingMode) + using ScalingMode = SpritePlayer::ScalingMode; SpriteViewer(QWidget *parent = nullptr); ~SpriteViewer(); + SpritePlayer::ScalingMode get_scaling_mode() const; + QString get_file_name() const; QIODevice *get_device() const; - SpriteViewer::ScalingMode get_scaling_mode() const; - SpriteReader::ptr get_reader() const; - void set_reader(SpriteReader::ptr reader); + SpritePlayer *get_player() const; bool is_valid() const; bool is_running() const; public slots: - void set_file_name(QString file_name); - - void set_device(QIODevice *device); + void set_scaling_mode(SpritePlayer::ScalingMode scaling_mode); void set_play_once(bool on); void set_mirror(bool on); - void set_scaling_mode(SpriteViewer::ScalingMode scaling_mode); + void set_file_name(QString file_name); + + void set_device(QIODevice *device); + + void set_reader(SpriteReader::ptr reader); void start(); void restart(); @@ -89,24 +81,9 @@ public slots: void resizeEvent(QResizeEvent *event) final; private: - SpriteReader::ptr m_reader; - SpriteFrame m_current_frame; - SpriteViewer::ScalingMode m_scaling_mode; - SpriteViewer::ScalingMode m_resolved_scaling_mode; - Qt::TransformationMode m_transform; - bool m_running; - bool m_mirror; - bool m_play_once; - int m_frame_count; - int m_frame_number; - QElapsedTimer m_elapsed_timer; - QTimer m_frame_timer; - QTimer m_repaint_timer; - - void _p_resolve_scaling_mode(); + QScopedPointer m_player; private slots: - void _p_paint_next_frame(); - void _p_paint_frame(); + void paint_frame(); }; } // namespace mk2 diff --git a/src/theme.cpp b/src/theme.cpp index 00cbfba36..ea5061c73 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -11,7 +11,7 @@ // src #include "aoapplication.h" #include "datatypes.h" -#include "drstickermovie.h" +#include "drstickerviewer.h" #include "drtextedit.h" void set_size_and_pos(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) @@ -136,11 +136,11 @@ void center_widget_to_screen(QWidget *p_widget) p_widget->move(x, y); } -void set_sticker_play_once(DRStickerMovie *p_sticker, QString p_identifier, QString p_ini_file, AOApplication *ao_app) +void set_sticker_play_once(DRStickerViewer *p_sticker, QString p_identifier, QString p_ini_file, AOApplication *ao_app) { const bool l_play_once = ao_app->read_theme_ini_bool(p_identifier + "_play_once", p_ini_file); p_sticker->set_play_once(l_play_once); - if (l_play_once && !p_sticker->is_running()) + if (!p_sticker->is_running()) { p_sticker->start(); } diff --git a/src/theme.h b/src/theme.h index 95a0770ea..fa892134e 100644 --- a/src/theme.h +++ b/src/theme.h @@ -2,7 +2,7 @@ // src class AOApplication; -class DRStickerMovie; +class DRStickerViewer; class DRTextEdit; // qt @@ -16,4 +16,4 @@ void set_font(QWidget *widget, QString identifier, QString ini_file, AOApplicati void set_drtextedit_font(DRTextEdit *widget, QString identifier, QString ini_file, AOApplication *ao_app); bool set_stylesheet(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); void center_widget_to_screen(QWidget *widget); -void set_sticker_play_once(DRStickerMovie *sticker, QString identifier, QString ini_file, AOApplication *ao_app); +void set_sticker_play_once(DRStickerViewer *sticker, QString identifier, QString ini_file, AOApplication *ao_app); diff --git a/src/version.cpp b/src/version.cpp index a1fc54358..2f2066501 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -30,7 +30,7 @@ VersionNumber get_version_number() QString get_post_version() { - return "rc.1"; + return "rc.2"; } QString get_version_string() From b6d97b0efcd4cc448b4855d5af9d966394c44ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 20 Jul 2022 02:13:11 +0200 Subject: [PATCH 790/842] Resolve flawed Linux pathing --- src/aoapplication.cpp | 66 ++++++++++++++++++++++++++++-------------- src/aoconfig.cpp | 18 ++++++------ src/aoconfig.h | 1 + src/main.cpp | 30 ++++++++++++++----- src/path_functions.cpp | 63 ++++++++++++---------------------------- 5 files changed, 97 insertions(+), 81 deletions(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 9ebb45d52..5ffb928e5 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -21,8 +21,11 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) { ao_config = new AOConfig(this); + ao_config_panel = new AOConfigPanel(this); + dr_discord = new DRDiscord(this); + m_server_socket = new DRServerSocket(this); connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(handle_theme_modification())); @@ -198,45 +201,66 @@ QString AOApplication::get_sfx_noext_path(QString p_file) QString AOApplication::get_character_sprite_path(QString p_character, QString p_emote, QString p_prefix, bool p_use_placeholder) { - QStringList l_filelist; - QStringList l_blacklist; - for (const QString &i_character_name : get_char_include_tree(p_character)) + bool l_valid = true; + const QStringList l_blacklist{ + "char_icon.png", + "showname.png", + "emotions", + }; + for (const QString &i_black : l_blacklist) { - l_blacklist.append({ - get_character_path(i_character_name, "char_icon.png"), - get_character_path(i_character_name, "showname.png"), - get_character_path(i_character_name, "emotions"), - }); - - if (!p_prefix.isEmpty()) + if (p_emote.startsWith(i_black, Qt::CaseInsensitive)) { - l_filelist.append(get_character_path(i_character_name, QString("%1%2").arg(p_prefix, p_emote))); + l_valid = false; + break; } - l_filelist.append(get_character_path(i_character_name, p_emote)); } - QString l_file = find_asset_path(l_filelist, animated_or_static_extensions()); - if (l_file.isEmpty() && p_use_placeholder) + QStringList l_file_name_list; + for (const QString &i_extension : animated_or_static_extensions()) { - l_file = find_theme_asset_path("placeholder", animated_extensions()); + if (!p_prefix.isEmpty()) + { + l_file_name_list.append(p_prefix + p_emote + i_extension); + } + l_file_name_list.append(p_emote + i_extension); } - for (const QString &i_blackened : qAsConst(l_blacklist)) + QString l_file_path; + if (l_valid) { - if (l_file == i_blackened) + QStringList l_file_path_list; + for (const QString &i_chr_name : get_char_include_tree(p_character)) { - l_file.clear(); - break; + for (const QString &i_file_name : qAsConst(l_file_name_list)) + { + l_file_path_list.append(get_character_path(i_chr_name, i_file_name)); + } + } + + for (const QString &i_file_path : qAsConst(l_file_path_list)) + { + const QString l_resolved_file_path = find_asset_path(i_file_path); + if (!l_resolved_file_path.isEmpty()) + { + l_file_path = l_resolved_file_path; + break; + } } } - if (l_file.isEmpty()) + if (l_file_path.isEmpty() && p_use_placeholder) + { + l_file_path = find_theme_asset_path("placeholder", animated_extensions()); + } + + if (l_file_path.isEmpty()) { qWarning() << "error: character animation not found" << "character:" << p_character << "emote:" << p_emote << "prefix:" << p_prefix; } - return l_file; + return l_file_path; } QString AOApplication::get_character_sprite_pre_path(QString character, QString emote) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index f523a23d6..546665b9b 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -29,7 +29,7 @@ class AOConfigPrivate : public QObject // setters public slots: - void read_file(); + void load_file(); void save_file(); private: @@ -115,18 +115,13 @@ AOConfigPrivate::AOConfigPrivate() connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(on_application_state_changed(Qt::ApplicationState))); - read_file(); + load_file(); } AOConfigPrivate::~AOConfigPrivate() -{ - if (autosave) - { - save_file(); - } -} +{} -void AOConfigPrivate::read_file() +void AOConfigPrivate::load_file() { autosave = cfg.value("autosave", true).toBool(); @@ -694,6 +689,11 @@ bool AOConfig::blank_blips_enabled() const return d->blank_blips; } +void AOConfig::load_file() +{ + d->load_file(); +} + void AOConfig::save_file() { d->save_file(); diff --git a/src/aoconfig.h b/src/aoconfig.h index e35e8c78d..33056bded 100644 --- a/src/aoconfig.h +++ b/src/aoconfig.h @@ -80,6 +80,7 @@ class AOConfig : public QObject // io public slots: + void load_file(); void save_file(); // setters diff --git a/src/main.cpp b/src/main.cpp index 588899a19..c1852c3bc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,14 @@ #include "aoapplication.h" +#include "aoconfig.h" #include "drmediatester.h" #include "lobby.h" #include "logger.h" #include +#include "drcharactermovie.h" +#include "drscenemovie.h" + int main(int argc, char *argv[]) { #if defined(Q_OS_WINDOWS) @@ -42,15 +46,27 @@ int main(int argc, char *argv[]) } AOApplication app(argc, argv); - DRMediaTester tester; - app.load_fonts(); - app.construct_lobby(); - app.get_lobby()->show(); + int l_exit_code = 0; + { + AOConfig l_config; + l_config.load_file(); + + DRMediaTester tester; + + app.load_fonts(); + app.construct_lobby(); + app.get_lobby()->show(); - const int code = app.exec(); + l_exit_code = app.exec(); - logger::shutdown(); + logger::shutdown(); + + if (l_config.autosave()) + { + l_config.save_file(); + } + } - return code; + return l_exit_code; } diff --git a/src/path_functions.cpp b/src/path_functions.cpp index da9ead958..689d0abd3 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -33,14 +33,12 @@ QString AOApplication::get_base_file_path(QString p_file) QString AOApplication::get_character_folder_path(QString p_chr) { - QString r_path = get_base_path() + "characters/" + p_chr; - return get_case_sensitive_path(r_path); + return get_base_path() + "characters/" + p_chr; } QString AOApplication::get_character_path(QString p_chr, QString p_file) { - const QString r_path = get_character_folder_path(p_chr) + "/" + p_file; - return get_case_sensitive_path(r_path); + return get_character_folder_path(p_chr) + "/" + p_file; } QString AOApplication::get_music_folder_path() @@ -130,34 +128,17 @@ QString AOApplication::get_case_sensitive_path(QString p_file) } #else { - // First, check to see if the file already exists as it is. - if (QFile(p_file).exists()) + if (QFile::exists(p_file) || p_file.isEmpty()) + { return p_file; + } - QFileInfo file(p_file); - - QString file_basename = file.fileName(); - QString file_parent_dir = get_case_sensitive_path(file.absolutePath()); - - // Second, does it exist in the new parent directory? - if (QFile(file_parent_dir + "/" + file_basename).exists()) - return file_parent_dir + "/" + file_basename; - - // In case it doesn't, look through the entries in the parent directory, and - // try and find it based on a case-insensitive regex search. - // Note also the fixed string search here. This is so that, for example, music - // files with parentheses don't get interpreted as grouping for a regex - // search. - QRegExp file_rx = QRegExp(file_basename, Qt::CaseInsensitive, QRegExp::FixedString); - QStringList files = QDir(file_parent_dir).entryList(); - - int result = files.indexOf(file_rx); - - if (result != -1) - return file_parent_dir + "/" + files.at(result); - - // If nothing is found, we let the caller handle that case. - return file_parent_dir + "/" + file_basename; + QFileInfo l_file_info(p_file); + const QDir l_dir(l_file_info.absolutePath()); + const QString l_file_name = l_file_info.fileName(); + const QStringList l_file_list = l_dir.entryList(); + const QRegExp l_regex = QRegExp(l_file_name, Qt::CaseInsensitive, QRegExp::FixedString); + return l_file_list.value(l_file_list.indexOf(l_regex), l_file_name); } #endif @@ -178,23 +159,17 @@ QString AOApplication::get_case_sensitive_path(QString p_file) */ QString AOApplication::find_asset_path(QStringList p_file_list, QStringList p_extension_list) { - for (QString &i_root : p_file_list) + for (const QString &i_file : qAsConst(p_file_list)) { - // We can assume that possible_exts will only be populated with hardcoded strings. - // Therefore, the only place where sanitize_path could catch something bad is in the root. - // So, check that now, so we don't need to check later. - if (!is_safe_path(i_root)) - continue; - - // Check if parent folder actually exists. If it does not, none of the following files would exist - if (!dir_exists(QFileInfo(i_root).absolutePath())) - continue; + const QDir l_dir(get_case_sensitive_path(QFileInfo(i_file).absolutePath())); - for (QString &i_ext : p_extension_list) + for (const QString &i_extension : qAsConst(p_extension_list)) { - QString full_path = get_case_sensitive_path(i_root + i_ext); - if (file_exists(full_path)) - return full_path; + const QString l_file_path = get_case_sensitive_path(l_dir.filePath(i_file + i_extension)); + if (file_exists(l_file_path)) + { + return l_file_path; + } } } From 2cde526d39d90966504a26c4c1d55f876d57a03d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 20 Jul 2022 04:01:59 +0200 Subject: [PATCH 791/842] Updated Linux instructions --- data/README-LINUX.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/data/README-LINUX.md b/data/README-LINUX.md index 499cfb2c9..a9c98c4ba 100644 --- a/data/README-LINUX.md +++ b/data/README-LINUX.md @@ -1,10 +1,7 @@ # Installation instructions 1. Open your terminal and run the following commands in order. ``` -sudo apt-get update -sudo apt-get upgrade -sudo apt-get install qt5-default -sudo apt-get install libxvidcore-dev +sudo apt-get install qt5-default libqt5multimedia5 libqt5multimedia5-plugins libqt5multimediawidgets5 gstreamer1.0-libav ``` 2. Update dro-client and dro-client.sh permissions ``` From b6059923084ea702c4fe59ec0f2222dc1906e26a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 20 Jul 2022 05:57:06 +0200 Subject: [PATCH 792/842] Fix sprites not being centered when drawn --- src/drgraphicscene.cpp | 4 +++- src/drgraphicscene.h | 3 +++ src/emotes.cpp | 6 ++++++ src/mk2/graphicsspriteitem.cpp | 14 ++++++++++++-- src/mk2/spriteplayer.cpp | 5 +++++ src/mk2/spriteplayer.h | 2 ++ 6 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/drgraphicscene.cpp b/src/drgraphicscene.cpp index 08a6def9b..6d1e8b3be 100644 --- a/src/drgraphicscene.cpp +++ b/src/drgraphicscene.cpp @@ -27,6 +27,7 @@ DRGraphicsView::DRGraphicsView(QWidget *parent) : QGraphicsView(parent) + , m_scene(new QGraphicsScene(this)) { setInteractive(false); @@ -36,7 +37,7 @@ DRGraphicsView::DRGraphicsView(QWidget *parent) setFrameShape(QFrame::NoFrame); setFrameStyle(0); - setScene(new QGraphicsScene(this)); + setScene(m_scene); setBackgroundBrush(Qt::black); } @@ -55,5 +56,6 @@ void DRGraphicsView::resizeEvent(QResizeEvent *event) l_object->setProperty("size", event->size()); } } + m_scene->setSceneRect(rect()); setSceneRect(rect()); } diff --git a/src/drgraphicscene.h b/src/drgraphicscene.h index 9d7bf6db1..b399061d2 100644 --- a/src/drgraphicscene.h +++ b/src/drgraphicscene.h @@ -32,4 +32,7 @@ class DRGraphicsView : public QGraphicsView protected: void resizeEvent(QResizeEvent *event) final; + +private: + QGraphicsScene *m_scene; }; diff --git a/src/emotes.cpp b/src/emotes.cpp index c2ed55203..a839a2f82 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -227,7 +227,9 @@ void Courtroom::show_emote_tooltip(int p_id, QPoint p_global_pos) QScreen *screen = QApplication::screenAt(p_global_pos); if (screen == nullptr) + { return; + } QRect l_screen_geometry = screen->geometry(); // position below cursor @@ -235,10 +237,14 @@ void Courtroom::show_emote_tooltip(int p_id, QPoint p_global_pos) QPoint l_final_global_pos(p_global_pos.x(), p_global_pos.y() + l_vertical_spacing); if (l_screen_geometry.width() < ui_emote_preview->width() + l_final_global_pos.x()) + { l_final_global_pos.setX(p_global_pos.x() - ui_emote_preview->width()); + } if (l_screen_geometry.height() < ui_emote_preview->height() + l_final_global_pos.y()) + { l_final_global_pos.setY(p_global_pos.y() - ui_emote_preview->height() - l_vertical_spacing); + } ui_emote_preview->move(l_final_global_pos); ui_emote_preview->show(); diff --git a/src/mk2/graphicsspriteitem.cpp b/src/mk2/graphicsspriteitem.cpp index e25045596..3742e43e6 100644 --- a/src/mk2/graphicsspriteitem.cpp +++ b/src/mk2/graphicsspriteitem.cpp @@ -19,6 +19,7 @@ **************************************************************************/ #include "graphicsspriteitem.h" +#include #include #include @@ -29,7 +30,7 @@ GraphicsSpriteItem::GraphicsSpriteItem(QGraphicsItem *parent) : QGraphicsObject(parent) , m_player(new SpritePlayer) { - connect(m_player.get(), SIGNAL(finished()), this, SLOT(notify_size())); + connect(m_player.get(), SIGNAL(size_changed(QSize)), this, SLOT(notify_size())); connect(m_player.get(), SIGNAL(current_frame_changed()), this, SLOT(notify_update())); connect(m_player.get(), SIGNAL(file_name_changed(QString)), this, SIGNAL(file_name_changed(QString))); connect(m_player.get(), SIGNAL(reader_changed()), this, SIGNAL(reader_changed())); @@ -146,7 +147,16 @@ void GraphicsSpriteItem::paint(QPainter *painter, const QStyleOptionGraphicsItem { painter->save(); painter->setCompositionMode(QPainter::CompositionMode_SourceOver); - painter->drawImage(0, 0, m_player->get_current_frame()); + + // calculate center position + QPointF l_horizontal_center; + if (auto *l_scene = scene()) + { + const QPointF l_center = l_scene->sceneRect().center() - m_player->get_scaled_bounding_rect().center(); + l_horizontal_center.setX(l_center.x()); + } + + painter->drawImage(l_horizontal_center, m_player->get_current_frame()); painter->restore(); } } diff --git a/src/mk2/spriteplayer.cpp b/src/mk2/spriteplayer.cpp index 1d5fe0bfd..788b80193 100644 --- a/src/mk2/spriteplayer.cpp +++ b/src/mk2/spriteplayer.cpp @@ -56,6 +56,11 @@ QImage SpritePlayer::get_current_frame() const return m_scaled_current_frame; } +QRectF SpritePlayer::get_scaled_bounding_rect() const +{ + return m_scaled_current_frame.rect(); +} + QString SpritePlayer::get_file_name() const { return m_reader->get_file_name(); diff --git a/src/mk2/spriteplayer.h b/src/mk2/spriteplayer.h index 95dffaf13..e257a00df 100644 --- a/src/mk2/spriteplayer.h +++ b/src/mk2/spriteplayer.h @@ -50,6 +50,8 @@ class SpritePlayer : public QObject QImage get_current_frame() const; + QRectF get_scaled_bounding_rect() const; + SpritePlayer::ScalingMode get_scaling_mode() const; QSize get_size() const; From 6ca6f0b3a45079ce48e3cfa17b0acc9bea9c9b20 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 20 Jul 2022 06:00:32 +0200 Subject: [PATCH 793/842] Fix-attempt for clock not showing --- src/courtroom.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f76723576..32715cdaa 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -585,6 +585,7 @@ void Courtroom::handle_clock(QString time) return; } ui_vp_clock->set_theme_image(clock_filename); + ui_vp_clock->start(); ui_vp_clock->show(); } From b407101be445fc1495177ebf882294686fa7f55e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 20 Jul 2022 06:13:56 +0200 Subject: [PATCH 794/842] Updated Ubuntu packages --- data/README-LINUX.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data/README-LINUX.md b/data/README-LINUX.md index a9c98c4ba..ff34fb6e4 100644 --- a/data/README-LINUX.md +++ b/data/README-LINUX.md @@ -1,8 +1,15 @@ # Installation instructions 1. Open your terminal and run the following commands in order. + +For Ubuntu 20.04 and lower. ``` sudo apt-get install qt5-default libqt5multimedia5 libqt5multimedia5-plugins libqt5multimediawidgets5 gstreamer1.0-libav ``` +For Ubuntu 21.10 and higher. +``` +sudo apt-get install libqt5core5a libqt5concurrent5 libqt5multimedia5 libqt5multimedia5-plugins libqt5widgets5 libqt5x11extras5 gstreamer1.0-libav +``` + 2. Update dro-client and dro-client.sh permissions ``` chmod +x dro-client.sh From 19c2d6b132a5afe74c889c1abc6e0eba4fdba8e1 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 20 Jul 2022 16:14:01 +0200 Subject: [PATCH 795/842] Very small refactor --- src/aoconfig.cpp | 16 ++++++++-------- src/aoconfig.h | 2 +- src/drmovie.cpp | 8 ++++---- src/drmovie.h | 4 ++-- src/drstickerviewer.cpp | 6 +++--- src/drstickerviewer.h | 2 +- src/mk2/graphicsspriteitem.cpp | 8 ++++---- src/mk2/graphicsspriteitem.h | 4 ++-- src/mk2/spriteplayer.cpp | 8 ++++---- src/mk2/spriteplayer.h | 4 ++-- src/mk2/spriteviewer.cpp | 8 ++++---- src/mk2/spriteviewer.h | 4 ++-- 12 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 546665b9b..cace5deb8 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -871,12 +871,12 @@ void AOConfig::set_manual_timeofday_selection_enabled(bool p_enabled) d->invoke_signal("manual_timeofday_selection_changed", Q_ARG(bool, p_enabled)); } -void AOConfig::set_searchable_iniswap(bool p_on) +void AOConfig::set_searchable_iniswap(bool p_enabled) { - if (d->searchable_iniswap == p_on) + if (d->searchable_iniswap == p_enabled) return; - d->searchable_iniswap = p_on; - d->invoke_signal("searchable_iniswap_changed", Q_ARG(bool, p_on)); + d->searchable_iniswap = p_enabled; + d->invoke_signal("searchable_iniswap_changed", Q_ARG(bool, p_enabled)); } void AOConfig::set_always_pre(bool p_enabled) @@ -1018,12 +1018,12 @@ void AOConfig::set_system_memory_threshold(int p_percent) d->invoke_signal("system_memory_threshold_changed", Q_ARG(int, p_percent)); } -void AOConfig::set_sprite_caching(int p_type, bool p_on) +void AOConfig::set_sprite_caching(int p_type, bool p_enabled) { - if (d->sprite_caching[p_type] == p_on) + if (d->sprite_caching[p_type] == p_enabled) return; - d->sprite_caching[p_type] = p_on; - d->invoke_signal("sprite_caching_toggled", Q_ARG(int, p_type), Q_ARG(bool, p_on)); + d->sprite_caching[p_type] = p_enabled; + d->invoke_signal("sprite_caching_toggled", Q_ARG(int, p_type), Q_ARG(bool, p_enabled)); } void AOConfig::set_loading_bar_delay(int p_delay) diff --git a/src/aoconfig.h b/src/aoconfig.h index 33056bded..eefaed074 100644 --- a/src/aoconfig.h +++ b/src/aoconfig.h @@ -124,7 +124,7 @@ public slots: void set_suppress_background_audio(bool p_enabled); // performance - void set_sprite_caching(int type, bool on); + void set_sprite_caching(int type, bool enabled); void set_system_memory_threshold(int percent); void set_loading_bar_delay(int delay); void set_caching_threshold(int percent); diff --git a/src/drmovie.cpp b/src/drmovie.cpp index 2a25059c4..4ff55117b 100644 --- a/src/drmovie.cpp +++ b/src/drmovie.cpp @@ -18,14 +18,14 @@ QString DRMovie::file_name() return get_file_name(); } -void DRMovie::set_hide_on_done(bool p_on) +void DRMovie::set_hide_on_done(bool p_enabled) { - m_hide_when_done = p_on; + m_hide_when_done = p_enabled; } -void DRMovie::set_mirrored(bool p_on) +void DRMovie::set_mirrored(bool p_enabled) { - set_mirror(p_on); + set_mirror(p_enabled); } void DRMovie::update_visibility() diff --git a/src/drmovie.h b/src/drmovie.h index 60bc3b816..06e21a284 100644 --- a/src/drmovie.h +++ b/src/drmovie.h @@ -11,8 +11,8 @@ class DRMovie : public mk2::GraphicsSpriteItem ~DRMovie(); QString file_name(); - void set_hide_on_done(bool on); - void set_mirrored(bool on); + void set_hide_on_done(bool enabled); + void set_mirrored(bool enabled); signals: void done(); diff --git a/src/drstickerviewer.cpp b/src/drstickerviewer.cpp index 238030222..d866712fa 100644 --- a/src/drstickerviewer.cpp +++ b/src/drstickerviewer.cpp @@ -14,13 +14,13 @@ DRStickerViewer::DRStickerViewer(AOApplication *ao_app, QWidget *parent) DRStickerViewer::~DRStickerViewer() {} -void DRStickerViewer::set_hide_when_done(bool p_on) +void DRStickerViewer::set_hide_when_done(bool p_enabled) { - if (m_hide_when_done == p_on) + if (m_hide_when_done == p_enabled) { return; } - m_hide_when_done = p_on; + m_hide_when_done = p_enabled; if (!is_running()) { maybe_hide(); diff --git a/src/drstickerviewer.h b/src/drstickerviewer.h index bcbdd98de..72f10c7e4 100644 --- a/src/drstickerviewer.h +++ b/src/drstickerviewer.h @@ -12,7 +12,7 @@ class DRStickerViewer : public mk2::SpriteViewer explicit DRStickerViewer(AOApplication *ao_app, QWidget *parent = nullptr); ~DRStickerViewer(); - void set_hide_when_done(bool on); + void set_hide_when_done(bool enabled); void set_theme_image(QString p_file_name, QString p_character); diff --git a/src/mk2/graphicsspriteitem.cpp b/src/mk2/graphicsspriteitem.cpp index 3742e43e6..fa00fe4b6 100644 --- a/src/mk2/graphicsspriteitem.cpp +++ b/src/mk2/graphicsspriteitem.cpp @@ -61,14 +61,14 @@ void GraphicsSpriteItem::set_size(QSizeF p_size) m_player->set_size(p_size.toSize()); } -void GraphicsSpriteItem::set_play_once(bool p_on) +void GraphicsSpriteItem::set_play_once(bool p_enabled) { - m_player->set_play_once(p_on); + m_player->set_play_once(p_enabled); } -void GraphicsSpriteItem::set_mirror(bool p_on) +void GraphicsSpriteItem::set_mirror(bool p_enabled) { - m_player->set_mirror(p_on); + m_player->set_mirror(p_enabled); } QString GraphicsSpriteItem::get_file_name() const diff --git a/src/mk2/graphicsspriteitem.h b/src/mk2/graphicsspriteitem.h index 023b5d925..1ecafaefe 100644 --- a/src/mk2/graphicsspriteitem.h +++ b/src/mk2/graphicsspriteitem.h @@ -64,9 +64,9 @@ public slots: void set_size(QSizeF size); - void set_play_once(bool on); + void set_play_once(bool enabled); - void set_mirror(bool on); + void set_mirror(bool enabled); void set_file_name(QString file_name); diff --git a/src/mk2/spriteplayer.cpp b/src/mk2/spriteplayer.cpp index 788b80193..db3bff2bb 100644 --- a/src/mk2/spriteplayer.cpp +++ b/src/mk2/spriteplayer.cpp @@ -106,14 +106,14 @@ void SpritePlayer::set_device(QIODevice *p_device) } } -void SpritePlayer::set_play_once(bool p_on) +void SpritePlayer::set_play_once(bool p_enabled) { - m_play_once = p_on; + m_play_once = p_enabled; } -void SpritePlayer::set_mirror(bool p_on) +void SpritePlayer::set_mirror(bool p_enabled) { - m_mirror = p_on; + m_mirror = p_enabled; } void SpritePlayer::set_scaling_mode(ScalingMode scaling_mode) diff --git a/src/mk2/spriteplayer.h b/src/mk2/spriteplayer.h index e257a00df..9d9dfa6c6 100644 --- a/src/mk2/spriteplayer.h +++ b/src/mk2/spriteplayer.h @@ -67,9 +67,9 @@ class SpritePlayer : public QObject bool is_running() const; public slots: - void set_play_once(bool on); + void set_play_once(bool enabled); - void set_mirror(bool on); + void set_mirror(bool enabled); void set_scaling_mode(SpritePlayer::ScalingMode scaling_mode); diff --git a/src/mk2/spriteviewer.cpp b/src/mk2/spriteviewer.cpp index 9cd2ac5b1..27deab8f4 100644 --- a/src/mk2/spriteviewer.cpp +++ b/src/mk2/spriteviewer.cpp @@ -52,14 +52,14 @@ void SpriteViewer::set_scaling_mode(SpritePlayer::ScalingMode p_scaling_mode) m_player->set_scaling_mode(p_scaling_mode); } -void SpriteViewer::set_play_once(bool p_on) +void SpriteViewer::set_play_once(bool p_enabled) { - m_player->set_play_once(p_on); + m_player->set_play_once(p_enabled); } -void SpriteViewer::set_mirror(bool p_on) +void SpriteViewer::set_mirror(bool p_enabled) { - m_player->set_mirror(p_on); + m_player->set_mirror(p_enabled); } QString SpriteViewer::get_file_name() const diff --git a/src/mk2/spriteviewer.h b/src/mk2/spriteviewer.h index c13642710..e176160f5 100644 --- a/src/mk2/spriteviewer.h +++ b/src/mk2/spriteviewer.h @@ -55,9 +55,9 @@ class SpriteViewer : public QLabel public slots: void set_scaling_mode(SpritePlayer::ScalingMode scaling_mode); - void set_play_once(bool on); + void set_play_once(bool enabled); - void set_mirror(bool on); + void set_mirror(bool enabled); void set_file_name(QString file_name); From a4714e41f6d16d9dd387a9ca11c2f7be91010f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Wed, 20 Jul 2022 16:19:08 +0200 Subject: [PATCH 796/842] Update README-LINUX.md --- data/README-LINUX.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/README-LINUX.md b/data/README-LINUX.md index ff34fb6e4..7af0b109a 100644 --- a/data/README-LINUX.md +++ b/data/README-LINUX.md @@ -1,11 +1,11 @@ # Installation instructions 1. Open your terminal and run the following commands in order. -For Ubuntu 20.04 and lower. +For Ubuntu 20.04 and lower: ``` sudo apt-get install qt5-default libqt5multimedia5 libqt5multimedia5-plugins libqt5multimediawidgets5 gstreamer1.0-libav ``` -For Ubuntu 21.10 and higher. +For Ubuntu 21.10 and higher: ``` sudo apt-get install libqt5core5a libqt5concurrent5 libqt5multimedia5 libqt5multimedia5-plugins libqt5widgets5 libqt5x11extras5 gstreamer1.0-libav ``` From ae77cfb513b6cc8b2a1d8902d19382d57838892c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 21 Jul 2022 14:18:38 +0200 Subject: [PATCH 797/842] Lowered MacOS deployment target to expected version of Qt5 LTS --- dronline-client.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dronline-client.pro b/dronline-client.pro index 6fc1f9e85..0b4930d80 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -178,5 +178,5 @@ FORMS += \ res/ui/config_panel.ui # Mac stuff -QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14 +QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.13 ICON = icon.icns From d156ae6f6c73b11804420589bf7df262ab25c9c4 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 22 Jul 2022 13:00:57 +0200 Subject: [PATCH 798/842] Resolve backgrounds not updating on TOD change --- src/aoapplication.h | 3 +-- src/courtroom.cpp | 15 ++++++++++++-- src/courtroom.h | 2 ++ src/path_functions.cpp | 45 ++---------------------------------------- 4 files changed, 18 insertions(+), 47 deletions(-) diff --git a/src/aoapplication.h b/src/aoapplication.h index 0a7f45166..ffa022644 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -60,8 +60,7 @@ class AOApplication : public QApplication QString get_music_path(QString p_song); QString get_background_path(QString p_background_name); - QStringList get_available_background_identifier_list(); - QString get_current_background(); + QString get_background_dir_path(QString p_identifier); bool is_safe_path(QString p_file); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 32715cdaa..4f8784a43 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -409,11 +409,21 @@ void Courtroom::play_ambient() m_effects_player->play_ambient(l_filepath); } +QString Courtroom::get_current_background() const +{ + QString l_tod = ao_config->timeofday(); + if (ao_config->is_manual_timeofday_selection_enabled()) + { + l_tod = ao_config->manual_timeofday(); + } + + return m_background.background_tod_map.value(l_tod, m_background.background); +} + void Courtroom::update_background_scene() { const QString l_prev_background_name = m_background_name; - // TOD background is resolved in the method below - m_background_name = ao_app->get_current_background(); + m_background_name = get_current_background(); if (l_prev_background_name.isEmpty() || l_prev_background_name != m_background_name) { @@ -2472,6 +2482,7 @@ void Courtroom::load_theme() } setup_courtroom(); + update_background_scene(); } void Courtroom::load_character() diff --git a/src/courtroom.h b/src/courtroom.h index 20fcb16cc..fc8983298 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -102,6 +102,8 @@ class Courtroom : public QWidget void set_ambient(QString ambient_sfx); void play_ambient(); + QString get_current_background() const; + // updates background based on the position given from the chatmessage; will reset preloading if active void update_background_scene(); diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 689d0abd3..9f1a4a953 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -58,50 +58,9 @@ QString AOApplication::get_background_path(QString p_identifier) return get_base_path() + "background/" + p_identifier; } -QStringList AOApplication::get_available_background_identifier_list() +QString AOApplication::get_background_dir_path(QString p_identifier) { - QStringList l_bg_list; - - if (is_courtroom_constructed) - { - const DRAreaBackground l_area_bg = m_courtroom->get_background(); - - const QMap &l_bg_map = l_area_bg.background_tod_map; - if (ao_config->is_manual_timeofday_selection_enabled()) - { - const QString l_manual_tod = ao_config->manual_timeofday(); - if (!l_manual_tod.isEmpty() && l_bg_map.contains(l_manual_tod)) - l_bg_list.append(l_bg_map.value(l_manual_tod)); - } - - const QString l_tod = ao_config->timeofday(); - if (!l_tod.isEmpty() && l_bg_map.contains(l_tod)) - l_bg_list.append(l_bg_map.value(l_tod)); - - if (!l_area_bg.background.isEmpty()) - l_bg_list.append(l_area_bg.background); - } - - return l_bg_list; -} - -QString AOApplication::get_current_background() -{ - QString l_background_name; - - if (is_courtroom_constructed) - { - const QStringList l_background_name_list = get_available_background_identifier_list(); - for (const QString &i_background_name : l_background_name_list) - { - if (!dir_exists(get_case_sensitive_path(get_background_path(i_background_name)))) - continue; - l_background_name = i_background_name; - break; - } - } - - return l_background_name; + return get_case_sensitive_path(get_background_path(p_identifier)); } /** From 9786f65859f17c946b690f09f3b27b95a103ee5a Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 22 Jul 2022 13:05:22 +0200 Subject: [PATCH 799/842] Bumped RC version --- src/version.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.cpp b/src/version.cpp index 2f2066501..5c763c1a9 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -30,7 +30,7 @@ VersionNumber get_version_number() QString get_post_version() { - return "rc.2"; + return "rc.3"; } QString get_version_string() From ab262a508b16c79bc04c1b066dd94c348520cb60 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 22 Jul 2022 19:01:34 +0200 Subject: [PATCH 800/842] Reformat initializer list, braces and switches --- src/aoblipplayer.cpp | 3 +- src/aobutton.cpp | 3 +- src/aocharbutton.cpp | 3 +- src/aoconfig.cpp | 4 +- src/aoconfigpanel.cpp | 4 +- src/aoemotebutton.cpp | 3 +- src/aoguiloader.cpp | 7 +- src/aoimagedisplay.cpp | 3 +- src/aolabel.cpp | 3 +- src/aolineedit.cpp | 3 +- src/aonotearea.cpp | 3 +- src/aonotepad.cpp | 4 +- src/aonotepicker.cpp | 3 +- src/aonotepicker.h | 1 + src/aoobject.cpp | 4 +- src/aopixmap.cpp | 6 +- src/aosfxplayer.cpp | 3 +- src/aoshoutplayer.cpp | 3 +- src/aosystemplayer.cpp | 3 +- src/aotimer.cpp | 3 +- src/courtroom.cpp | 376 ++++++++++++++++----------- src/courtroom_widgets.cpp | 45 ++-- src/datatypes.cpp | 4 +- src/datatypes.h | 68 ++++- src/draudiodevice.cpp | 4 +- src/draudioengine.cpp | 3 +- src/draudioengine_p.cpp | 4 +- src/draudioerror.cpp | 3 +- src/draudiotrackmetadata.cpp | 3 +- src/drchatlog.cpp | 12 +- src/drdiscord.cpp | 3 +- src/drmasterclient.cpp | 4 +- src/drmediatester.cpp | 3 +- src/drpacket.cpp | 3 +- src/drposition.cpp | 7 +- src/drserversocket.cpp | 38 +-- src/drtextedit.cpp | 3 +- src/drthememovie.cpp | 3 +- src/lobby.cpp | 26 +- src/mk2/graphicsvideoscreen.cpp | 64 ++--- src/mk2/spriteplayer.cpp | 32 +-- src/mk2/spritereadersynchronizer.cpp | 5 +- src/utils.cpp | 3 +- 43 files changed, 479 insertions(+), 306 deletions(-) diff --git a/src/aoblipplayer.cpp b/src/aoblipplayer.cpp index 2171971bc..397ccf12c 100644 --- a/src/aoblipplayer.cpp +++ b/src/aoblipplayer.cpp @@ -4,7 +4,8 @@ const int AOBlipPlayer::BLIP_COUNT = 5; -AOBlipPlayer::AOBlipPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) +AOBlipPlayer::AOBlipPlayer(AOApplication *p_ao_app, QObject *p_parent) + : AOObject(p_ao_app, p_parent) { m_family = DRAudioEngine::get_family(DRAudio::Family::FBlip); m_family->set_capacity(BLIP_COUNT); diff --git a/src/aobutton.cpp b/src/aobutton.cpp index 137775918..d50d3f65a 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -6,7 +6,8 @@ #include -AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) : QPushButton(parent) +AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) + : QPushButton(parent) { ao_app = p_ao_app; } diff --git a/src/aocharbutton.cpp b/src/aocharbutton.cpp index 9c6483af9..4d0e83698 100644 --- a/src/aocharbutton.cpp +++ b/src/aocharbutton.cpp @@ -7,7 +7,8 @@ #include #include -AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos) : QPushButton(parent) +AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos) + : QPushButton(parent) { ao_app = p_ao_app; diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index cace5deb8..22c68b4c4 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -109,7 +109,9 @@ private slots: }; AOConfigPrivate::AOConfigPrivate() - : QObject(nullptr), cfg(DRPather::get_application_path() + BASE_CONFIG_INI, QSettings::IniFormat), audio_engine(new DRAudioEngine(this)) + : QObject(nullptr) + , cfg(DRPather::get_application_path() + BASE_CONFIG_INI, QSettings::IniFormat) + , audio_engine(new DRAudioEngine(this)) { Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 408b211cc..843a41c2f 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -22,7 +22,9 @@ #include AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) - : QWidget(p_parent), m_config(new AOConfig(this)), m_engine(new DRAudioEngine(this)) + : QWidget(p_parent) + , m_config(new AOConfig(this)) + , m_engine(new DRAudioEngine(this)) { ao_app = p_ao_app; diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index aec660a1b..249b64a6a 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -9,7 +9,8 @@ #include #include -AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) +AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) + : QPushButton(p_parent) { ao_app = p_ao_app; diff --git a/src/aoguiloader.cpp b/src/aoguiloader.cpp index 14de2dac6..cd5799dc6 100644 --- a/src/aoguiloader.cpp +++ b/src/aoguiloader.cpp @@ -4,10 +4,9 @@ #include #include -AOGuiLoader::AOGuiLoader(QObject *p_parent) : QUiLoader(p_parent) -{ - // padding -} +AOGuiLoader::AOGuiLoader(QObject *p_parent) + : QUiLoader(p_parent) +{} QWidget *AOGuiLoader::load_from_file(QString p_file_path, QWidget *p_parent) { diff --git a/src/aoimagedisplay.cpp b/src/aoimagedisplay.cpp index 9c5f16e1e..bab734d7b 100644 --- a/src/aoimagedisplay.cpp +++ b/src/aoimagedisplay.cpp @@ -10,7 +10,8 @@ * @class AOImageDisplay * @brief Represents a static theme-dependent image. */ -AOImageDisplay::AOImageDisplay(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) +AOImageDisplay::AOImageDisplay(QWidget *parent, AOApplication *p_ao_app) + : QLabel(parent) { ao_app = p_ao_app; } diff --git a/src/aolabel.cpp b/src/aolabel.cpp index c258dba32..8d01a2569 100644 --- a/src/aolabel.cpp +++ b/src/aolabel.cpp @@ -2,7 +2,8 @@ #include "aoapplication.h" -AOLabel::AOLabel(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) +AOLabel::AOLabel(QWidget *parent, AOApplication *p_ao_app) + : QLabel(parent) { ao_app = p_ao_app; } diff --git a/src/aolineedit.cpp b/src/aolineedit.cpp index 9f477d994..cbc8efba7 100644 --- a/src/aolineedit.cpp +++ b/src/aolineedit.cpp @@ -1,6 +1,7 @@ #include "aolineedit.h" -AOLineEdit::AOLineEdit(QWidget *parent) : QLineEdit(parent) +AOLineEdit::AOLineEdit(QWidget *parent) + : QLineEdit(parent) { setReadOnly(true); setFrame(false); diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp index 725c0b0e1..253e9f59d 100644 --- a/src/aonotearea.cpp +++ b/src/aonotearea.cpp @@ -14,7 +14,8 @@ #include #include -AONoteArea::AONoteArea(QWidget *p_parent, AOApplication *p_ao_app) : AOImageDisplay(p_parent, p_ao_app) +AONoteArea::AONoteArea(QWidget *p_parent, AOApplication *p_ao_app) + : AOImageDisplay(p_parent, p_ao_app) { ao_app = p_ao_app; } diff --git a/src/aonotepad.cpp b/src/aonotepad.cpp index d21fcf69d..63c0d5a7d 100644 --- a/src/aonotepad.cpp +++ b/src/aonotepad.cpp @@ -1,5 +1,7 @@ #include "aonotepad.h" -AONotepad::AONotepad(QWidget *p_parent, AOApplication *p_ao_app) : DRTextEdit(p_parent), ao_app(p_ao_app) +AONotepad::AONotepad(QWidget *p_parent, AOApplication *p_ao_app) + : DRTextEdit(p_parent) + , ao_app(p_ao_app) { } diff --git a/src/aonotepicker.cpp b/src/aonotepicker.cpp index e7f209a25..4098072f2 100644 --- a/src/aonotepicker.cpp +++ b/src/aonotepicker.cpp @@ -10,7 +10,8 @@ #include #include -AONotePicker::AONotePicker(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) +AONotePicker::AONotePicker(QWidget *p_parent, AOApplication *p_ao_app) + : QLabel(p_parent) { ao_app = p_ao_app; } diff --git a/src/aonotepicker.h b/src/aonotepicker.h index b27983b60..9f550303d 100644 --- a/src/aonotepicker.h +++ b/src/aonotepicker.h @@ -26,6 +26,7 @@ class AONotePicker : public QLabel void set_active(bool p_active); bool is_active(); + private: AOApplication *ao_app = nullptr; diff --git a/src/aoobject.cpp b/src/aoobject.cpp index f7a8389a2..2de19f263 100644 --- a/src/aoobject.cpp +++ b/src/aoobject.cpp @@ -1,4 +1,6 @@ #include "aoobject.h" -AOObject::AOObject(AOApplication *p_ao_app, QObject *p_parent) : QObject(p_parent), ao_app(p_ao_app) +AOObject::AOObject(AOApplication *p_ao_app, QObject *p_parent) + : QObject(p_parent) + , ao_app(p_ao_app) {} diff --git a/src/aopixmap.cpp b/src/aopixmap.cpp index 9da66c643..26c029c86 100644 --- a/src/aopixmap.cpp +++ b/src/aopixmap.cpp @@ -1,6 +1,7 @@ #include "aopixmap.h" -AOPixmap::AOPixmap(QPixmap p_pixmap) : m_pixmap(p_pixmap) +AOPixmap::AOPixmap(QPixmap p_pixmap) + : m_pixmap(p_pixmap) { if (m_pixmap.isNull()) { @@ -8,7 +9,8 @@ AOPixmap::AOPixmap(QPixmap p_pixmap) : m_pixmap(p_pixmap) } } -AOPixmap::AOPixmap(QString p_file_path) : AOPixmap(QPixmap(p_file_path)) +AOPixmap::AOPixmap(QString p_file_path) + : AOPixmap(QPixmap(p_file_path)) {} void AOPixmap::clear() diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index 05d6eecd9..618f348d1 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -9,7 +9,8 @@ #include AOSfxPlayer::AOSfxPlayer(AOApplication *p_ao_app, QObject *p_parent) - : AOObject(p_ao_app, p_parent), m_player(DRAudioEngine::get_family(DRAudio::Family::FEffect)) + : AOObject(p_ao_app, p_parent) + , m_player(DRAudioEngine::get_family(DRAudio::Family::FEffect)) {} void AOSfxPlayer::play(QString p_filename) diff --git a/src/aoshoutplayer.cpp b/src/aoshoutplayer.cpp index 28e119492..bdf3c7622 100644 --- a/src/aoshoutplayer.cpp +++ b/src/aoshoutplayer.cpp @@ -4,7 +4,8 @@ #include "draudioengine.h" #include "file_functions.h" -AOShoutPlayer::AOShoutPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) +AOShoutPlayer::AOShoutPlayer(AOApplication *p_ao_app, QObject *p_parent) + : AOObject(p_ao_app, p_parent) {} void AOShoutPlayer::play(QString p_chr, QString p_shout) diff --git a/src/aosystemplayer.cpp b/src/aosystemplayer.cpp index 7ec54b86b..b65761a9b 100644 --- a/src/aosystemplayer.cpp +++ b/src/aosystemplayer.cpp @@ -4,7 +4,8 @@ #include "draudioengine.h" #include "file_functions.h" -AOSystemPlayer::AOSystemPlayer(AOApplication *p_ao_app, QObject *p_parent) : AOObject(p_ao_app, p_parent) +AOSystemPlayer::AOSystemPlayer(AOApplication *p_ao_app, QObject *p_parent) + : AOObject(p_ao_app, p_parent) {} void AOSystemPlayer::play(QString p_name) diff --git a/src/aotimer.cpp b/src/aotimer.cpp index 31ca10eab..44511729c 100644 --- a/src/aotimer.cpp +++ b/src/aotimer.cpp @@ -3,7 +3,8 @@ #include #include -AOTimer::AOTimer(QWidget *p_parent) : DRTextEdit(p_parent) +AOTimer::AOTimer(QWidget *p_parent) + : DRTextEdit(p_parent) { // Adapted from: // https://stackoverflow.com/questions/36679708/how-to-make-a-chronometer-in-qt-c diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 4f8784a43..a70d005d9 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -126,17 +126,20 @@ void Courtroom::setup_courtroom() m_shout_state = 0; m_shout_current = 0; check_shouts(); - if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) cycle_shout(1); + if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) + cycle_shout(1); m_effect_state = 0; m_effect_current = 0; check_effects(); - if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) cycle_effect(1); + if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) + cycle_effect(1); m_wtce_current = 0; reset_wtce_buttons(); check_wtce(); - if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) cycle_wtce(1); + if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) + cycle_wtce(1); ui_flip->show(); @@ -307,10 +310,12 @@ void Courtroom::enter_courtroom(int p_cid) const bool l_changed_chr_id = (m_chr_id != p_cid); m_chr_id = p_cid; - if (l_changed_chr_id) update_default_iniswap_item(); + if (l_changed_chr_id) + update_default_iniswap_item(); QLineEdit *l_current_field = ui_ic_chat_message_field; - if (ui_ooc_chat_message->hasFocus()) l_current_field = ui_ooc_chat_message; + if (ui_ooc_chat_message->hasFocus()) + l_current_field = ui_ooc_chat_message; const int l_current_cursor_pos = l_current_field->cursorPosition(); const QString l_chr_name = get_character_ini(); @@ -331,7 +336,8 @@ void Courtroom::enter_courtroom(int p_cid) ao_app->send_server_packet(DRPacket("chrini", l_content)); } const bool l_changed_chr = l_chr_name != l_prev_chr_name; - if (l_changed_chr) set_character_position(ao_app->get_char_side(l_chr_name)); + if (l_changed_chr) + set_character_position(ao_app->get_char_side(l_chr_name)); select_base_character_iniswap(); refresh_character_content_url(); @@ -366,7 +372,8 @@ void Courtroom::enter_courtroom(int p_cid) l_current_field->setCursorPosition(l_current_cursor_pos); const QString l_showname = ao_config->showname(); - if (!l_showname.isEmpty() && !is_first_showname_sent) send_showname_packet(l_showname); + if (!l_showname.isEmpty() && !is_first_showname_sent) + send_showname_packet(l_showname); ui_char_select_background->hide(); } @@ -575,7 +582,8 @@ void Courtroom::update_music_text_anim() void Courtroom::handle_clock(QString time) { m_current_clock = time.toInt(); - if (m_current_clock < 0) m_current_clock = -1; + if (m_current_clock < 0) + m_current_clock = -1; qInfo() << QString("Clock time changed to %1").arg(m_current_clock); ui_vp_clock->hide(); @@ -624,7 +632,8 @@ void Courtroom::list_music() DRAudiotrackMetadata l_track(i_song); QListWidgetItem *l_item = new QListWidgetItem(l_track.title(), ui_music_list); l_item->setData(Qt::UserRole, l_track.filename()); - if (l_track.title() != l_track.filename()) l_item->setToolTip(l_track.filename()); + if (l_track.title() != l_track.filename()) + l_item->setToolTip(l_track.filename()); const QString l_song_path = ao_app->find_asset_path({ao_app->get_music_path(i_song)}, audio_extensions()); l_item->setBackground(l_song_path.isEmpty() ? l_missing_song_brush : l_song_brush); } @@ -665,12 +674,14 @@ void Courtroom::list_note_files() QString line = in.readLine().trimmed(); QStringList f_contents = line.split("="); - if (f_contents.size() < 2) continue; + if (f_contents.size() < 2) + continue; int f_index = f_contents.at(0).toInt(); f_filestring = f_filename = f_contents.at(1).trimmed(); - if (f_contents.size() > 2) f_filename = f_contents.at(2).trimmed(); + if (f_contents.size() > 2) + f_filename = f_contents.at(2).trimmed(); while (f_index >= f_layout->count()) on_add_button_clicked(); @@ -687,7 +698,8 @@ void Courtroom::load_note() // that notepad text is kept in client if the user has decided not to choose a // file to save to. Of course, this is ephimeral storage, it will be erased // after the client closes or when the user decides to load a file. - if (current_file.isEmpty()) return; + if (current_file.isEmpty()) + return; QString f_text = ao_app->read_note(current_file); ui_vp_notepad->setText(f_text); } @@ -709,7 +721,8 @@ void Courtroom::save_textlog(QString p_text) void Courtroom::append_server_chatmessage(QString p_name, QString p_message) { ui_ooc_chatlog->append_chatmessage(p_name, p_message); - if (ao_config->log_is_recording_enabled()) save_textlog("(OOC)" + p_name + ": " + p_message); + if (ao_config->log_is_recording_enabled()) + save_textlog("(OOC)" + p_name + ": " + p_message); } void Courtroom::ignore_next_showname() @@ -760,9 +773,11 @@ void Courtroom::on_character_ini_changed() void Courtroom::on_ic_message_return_pressed() { - if (ui_ic_chat_message_field->text() == "") return; + if (ui_ic_chat_message_field->text() == "") + return; - if ((anim_state < 3 || text_state < 2) && m_shout_state == 0) return; + if ((anim_state < 3 || text_state < 2) && m_shout_state == 0) + return; // MS // deskmod @@ -1057,19 +1072,19 @@ void Courtroom::handle_chatmessage() const int l_emote_mod = m_chatmessage[CMEmoteModifier].toInt(); switch (l_emote_mod) { - case IdleEmoteMod: - default: - break; - case PreEmoteMod: - m_play_pre = true; - break; - case ZoomEmoteMod: - m_play_zoom = true; - break; - case PreZoomEmoteMod: - m_play_pre = true; - m_play_zoom = true; - break; + case IdleEmoteMod: + default: + break; + case PreEmoteMod: + m_play_pre = true; + break; + case ZoomEmoteMod: + m_play_zoom = true; + break; + case PreZoomEmoteMod: + m_play_pre = true; + m_play_zoom = true; + break; } m_speaker_chr_id = m_chatmessage[CMChrId].toInt(); @@ -1255,7 +1270,8 @@ void Courtroom::handle_chatmessage_3() // idle f_anim_state = 3; - if (f_anim_state <= anim_state) return; + if (f_anim_state <= anim_state) + return; ui_vp_player_char->stop(); const QString f_char = m_chatmessage[CMChrName]; @@ -1296,25 +1312,25 @@ void Courtroom::handle_chatmessage_3() } switch (f_anim_state) { - case 2: - if (!m_hide_character && !m_msg_is_first_person) - { - swap_viewport_reader(ui_vp_player_char, ViewportCharacterTalk); - ui_vp_player_char->start(); - } - anim_state = 2; - break; - default: - qDebug() << "W: invalid anim_state: " << f_anim_state; - [[fallthrough]]; - case 3: - if (!m_hide_character && !m_msg_is_first_person) - { - swap_viewport_reader(ui_vp_player_char, ViewportCharacterIdle); - ui_vp_player_char->start(); - } - anim_state = 3; - break; + case 2: + if (!m_hide_character && !m_msg_is_first_person) + { + swap_viewport_reader(ui_vp_player_char, ViewportCharacterTalk); + ui_vp_player_char->start(); + } + anim_state = 2; + break; + default: + qDebug() << "W: invalid anim_state: " << f_anim_state; + [[fallthrough]]; + case 3: + if (!m_hide_character && !m_msg_is_first_person) + { + swap_viewport_reader(ui_vp_player_char, ViewportCharacterIdle); + ui_vp_player_char->start(); + } + anim_state = 3; + break; } { @@ -1337,10 +1353,12 @@ void Courtroom::handle_chatmessage_3() QString overlay_name = overlay.at(0); QString overlay_sfx = overlay.at(1); - if (overlay_sfx == "") overlay_sfx = ao_app->get_sfx(s_eff); + if (overlay_sfx == "") + overlay_sfx = ao_app->get_sfx(s_eff); m_effects_player->play_effect(overlay_sfx); - if (overlay_name == "") overlay_name = s_eff; + if (overlay_name == "") + overlay_name = s_eff; ui_vp_effect->set_play_once(once); swap_viewport_reader(ui_vp_effect, ViewportEffect); ui_vp_effect->start(); @@ -1361,7 +1379,8 @@ void Courtroom::handle_chatmessage_3() const QString message = ui_vp_showname->toPlainText() + " has called you via your callword \"" + word + "\": \"" + f_message + "\""; ui_ooc_chatlog->append_chatmessage(name, message); - if (ao_config->log_is_recording_enabled()) save_textlog("(OOC)" + name + ": " + message); + if (ao_config->log_is_recording_enabled()) + save_textlog("(OOC)" + name + ": " + message); break; } } @@ -1396,7 +1415,8 @@ void Courtroom::load_ic_text_format() set_format_color("showname", m_ic_log_format.name); m_ic_log_format.selfname = m_ic_log_format.name; - if (ao_config->log_display_self_highlight_enabled()) set_format_color("selfname", m_ic_log_format.selfname); + if (ao_config->log_display_self_highlight_enabled()) + set_format_color("selfname", m_ic_log_format.selfname); m_ic_log_format.message = m_ic_log_format.base; set_format_color("message", m_ic_log_format.message); @@ -1447,9 +1467,11 @@ void Courtroom::update_ic_log(bool p_reset_log) const DRChatRecord l_record = m_ic_record_queue.takeFirst(); m_ic_record_list.append(l_record); - if (!ao_config->log_display_empty_messages_enabled() && l_record.get_message().trimmed().isEmpty()) continue; + if (!ao_config->log_display_empty_messages_enabled() && l_record.get_message().trimmed().isEmpty()) + continue; - if (!ao_config->log_display_music_switch_enabled() && l_record.is_music()) continue; + if (!ao_config->log_display_music_switch_enabled() && l_record.is_music()) + continue; l_cursor.movePosition(move_type); @@ -1458,7 +1480,8 @@ void Courtroom::update_ic_log(bool p_reset_log) l_cursor.insertText(l_linefeed + QString(l_use_newline ? l_linefeed : nullptr), l_message_format); l_log_is_empty = false; - if (!l_topdown_orientation) l_cursor.movePosition(move_type); + if (!l_topdown_orientation) + l_cursor.movePosition(move_type); // self-highlight check const QTextCharFormat &l_target_name_format = @@ -1550,9 +1573,11 @@ void Courtroom::on_ic_chatlog_scroll_bottomup_clicked() void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, int p_client_id, bool p_self) { - if (p_name.trimmed().isEmpty()) p_name = "Anonymous"; + if (p_name.trimmed().isEmpty()) + p_name = "Anonymous"; - if (p_line.trimmed().isEmpty()) p_line = p_line.trimmed(); + if (p_line.trimmed().isEmpty()) + p_line = p_line.trimmed(); DRChatRecord new_record(p_name, p_line); new_record.set_system(p_system); @@ -1622,7 +1647,8 @@ void Courtroom::setup_chat() m_rainbow_step = 0; // we need to ensure that the text isn't already ticking because this function // can be called by two logic paths - if (text_state != 0) return; + if (text_state != 0) + return; if (chatmessage_is_empty) { @@ -1668,7 +1694,8 @@ void Courtroom::stop_chat_timer() void Courtroom::calculate_chat_tick_interval() { double l_tick_rate = ao_config->chat_tick_interval(); - if (m_server_tick_rate.has_value()) l_tick_rate = qMax(m_server_tick_rate.value(), 0); + if (m_server_tick_rate.has_value()) + l_tick_rate = qMax(m_server_tick_rate.value(), 0); l_tick_rate = qBound(0.0, l_tick_rate * (1.0 - qBound(-1.0, 0.4 * m_tick_speed, 1.0)), l_tick_rate * 2.0); m_tick_timer->setInterval(l_tick_rate); } @@ -1717,21 +1744,21 @@ void Courtroom::next_chat_letter() switch (m_rainbow_step) { - case 0: - html_color = "#BA1518"; - break; - case 1: - html_color = "#D55900"; - break; - case 2: - html_color = "#E7CE4E"; - break; - case 3: - html_color = "#65C856"; - break; - default: - html_color = "#1596C8"; - m_rainbow_step = -1; + case 0: + html_color = "#BA1518"; + break; + case 1: + html_color = "#D55900"; + break; + case 2: + html_color = "#E7CE4E"; + break; + case 3: + html_color = "#65C856"; + break; + default: + html_color = "#1596C8"; + m_rainbow_step = -1; } ++m_rainbow_step; @@ -1749,7 +1776,8 @@ void Courtroom::next_chat_letter() // render_character should only be false if the character is a highlight // character specifically marked as a character that should not be // rendered. - if (m_message_color_stack.isEmpty()) m_message_color_stack.push(""); + if (m_message_color_stack.isEmpty()) + m_message_color_stack.push(""); if (!is_ignore_next_letter) { @@ -1784,7 +1812,8 @@ void Courtroom::next_chat_letter() { if (f_character == col[0][1]) { - if (m_message_color_stack.size() > 1) m_message_color_stack.pop(); + if (m_message_color_stack.size() > 1) + m_message_color_stack.pop(); m_future_string_color = m_message_color_stack.top(); render_character = (col[2] != "0"); break; @@ -1792,7 +1821,8 @@ void Courtroom::next_chat_letter() } } - if (render_character) ui_vp_message->textCursor().insertText(f_character, vp_message_format); + if (render_character) + ui_vp_message->textCursor().insertText(f_character, vp_message_format); m_message_color_name = m_future_string_color; } @@ -1848,7 +1878,8 @@ void Courtroom::post_chatmessage() void Courtroom::play_sfx() { const QString l_effect = m_chatmessage[CMSoundName]; - if (l_effect.isEmpty() || l_effect == "0" || l_effect == "1") return; + if (l_effect.isEmpty() || l_effect == "0" || l_effect == "1") + return; const QString l_chr = m_chatmessage[CMChrName]; m_effects_player->play_character_effect(l_chr, l_effect); } @@ -1869,7 +1900,8 @@ void Courtroom::set_text_color() void Courtroom::set_ban(int p_cid) { - if (p_cid != m_chr_id && p_cid != SpectatorId) return; + if (p_cid != m_chr_id && p_cid != SpectatorId) + return; call_notice("You have been banned."); @@ -1879,7 +1911,8 @@ void Courtroom::set_ban(int p_cid) void Courtroom::handle_song(QStringList p_contents) { - if (p_contents.size() < 4) return; + if (p_contents.size() < 4) + return; QString l_song = p_contents.at(0); for (auto &i_extension : audio_extensions()) @@ -1899,7 +1932,8 @@ void Courtroom::handle_song(QStringList p_contents) { const bool l_restart = p_contents.at(3).toInt(); - if (m_current_song == l_song && !l_restart) return; + if (m_current_song == l_song && !l_restart) + return; } m_current_song = l_song; @@ -1941,7 +1975,8 @@ void Courtroom::handle_wtce(QString p_wtce) void Courtroom::set_hp_bar(int p_bar, int p_state) { - if (p_state < 0 || p_state > 10) return; + if (p_state < 0 || p_state > 10) + return; if (p_bar == 1) { @@ -1958,7 +1993,8 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) void Courtroom::set_character_position(QString p_pos) { int index = ui_pos_dropdown->findData(p_pos); - if (index != -1) ui_pos_dropdown->setCurrentIndex(index); + if (index != -1) + ui_pos_dropdown->setCurrentIndex(index); // enable judge mechanics if appropriate set_judge_enabled(p_pos == "jud"); @@ -2002,7 +2038,8 @@ void Courtroom::mod_called(QString p_ip) { m_system_player->play(ao_app->get_sfx("mod_call")); ao_app->alert(this); - if (ao_config->log_is_recording_enabled()) save_textlog("(OOC)(MOD CALL)" + p_ip); + if (ao_config->log_is_recording_enabled()) + save_textlog("(OOC)(MOD CALL)" + p_ip); } } @@ -2074,7 +2111,8 @@ void Courtroom::on_ooc_message_return_pressed() // Note arguments[0] == "/ts", so every index (and thus length) is off by // one. - if (size > 5) return; + if (size > 5) + return; int timer_id = (size > 1 ? arguments[1].toInt() : 0); int new_time = (size > 2 ? arguments[2].toInt() : 300) * 1000; @@ -2107,35 +2145,37 @@ void Courtroom::on_pos_dropdown_changed(int p_index) { ui_ic_chat_message_field->setFocus(); - if (p_index < 0 || p_index > 5) return; + if (p_index < 0 || p_index > 5) + return; QString f_pos; switch (p_index) { - case 0: - f_pos = "wit"; - break; - case 1: - f_pos = "def"; - break; - case 2: - f_pos = "pro"; - break; - case 3: - f_pos = "jud"; - break; - case 4: - f_pos = "hld"; - break; - case 5: - f_pos = "hlp"; - break; - default: - f_pos = ""; - } - - if (f_pos == "" || ao_config->username() == "") return; + case 0: + f_pos = "wit"; + break; + case 1: + f_pos = "def"; + break; + case 2: + f_pos = "pro"; + break; + case 3: + f_pos = "jud"; + break; + case 4: + f_pos = "hld"; + break; + case 5: + f_pos = "hlp"; + break; + default: + f_pos = ""; + } + + if (f_pos == "" || ao_config->username() == "") + return; set_judge_enabled(f_pos == "jud"); @@ -2219,7 +2259,8 @@ void Courtroom::on_music_search_edited() void Courtroom::send_mc_packet(QString p_song) { - if (is_client_muted) return; + if (is_client_muted) + return; ao_app->send_server_packet(DRPacket("MC", {p_song, QString::number(m_chr_id)})); } @@ -2240,16 +2281,19 @@ void Courtroom::reset_shout_buttons() void Courtroom::on_shout_button_clicked(const bool p_checked) { AOButton *l_button = dynamic_cast(sender()); - if (l_button == nullptr) return; + if (l_button == nullptr) + return; bool l_ok = false; const int l_id = l_button->property("shout_id").toInt(&l_ok); - if (!l_ok) return; + if (!l_ok) + return; // disable all other buttons for (AOButton *i_button : qAsConst(ui_shouts)) { - if (i_button == l_button) continue; + if (i_button == l_button) + continue; i_button->setChecked(false); } m_shout_state = p_checked ? l_id : 0; @@ -2260,14 +2304,17 @@ void Courtroom::on_shout_button_clicked(const bool p_checked) void Courtroom::on_shout_button_toggled(const bool p_checked) { AOButton *l_button = dynamic_cast(sender()); - if (l_button == nullptr) return; + if (l_button == nullptr) + return; const QString l_name = l_button->property("shout_name").toString(); - if (l_name.isEmpty()) return; + if (l_name.isEmpty()) + return; const QString l_image_name(QString("%1%2.png").arg(l_name, QString(p_checked ? "_selected" : nullptr))); l_button->set_image(l_image_name); - if (!l_button->has_image()) l_button->setText(l_name); + if (!l_button->has_image()) + l_button->setText(l_name); } void Courtroom::on_cycle_clicked() @@ -2277,26 +2324,26 @@ void Courtroom::on_cycle_clicked() switch (f_cycle_id) { - case 5: - cycle_wtce(-1); - break; - case 4: - cycle_wtce(1); - break; - case 3: - cycle_effect(-1); - break; - case 2: - cycle_effect(1); - break; - case 1: - cycle_shout(-1); - break; - case 0: - cycle_shout(1); - break; - default: - break; + case 5: + cycle_wtce(-1); + break; + case 4: + cycle_wtce(1); + break; + case 3: + cycle_effect(-1); + break; + case 2: + cycle_effect(1); + break; + case 1: + cycle_shout(-1); + break; + case 0: + cycle_shout(1); + break; + default: + break; } if (ao_app->read_theme_ini_bool("enable_cycle_ding", COURTROOM_CONFIG_INI)) @@ -2359,16 +2406,19 @@ void Courtroom::reset_effect_buttons() void Courtroom::on_effect_button_clicked(const bool p_checked) { AOButton *l_button = dynamic_cast(sender()); - if (l_button == nullptr) return; + if (l_button == nullptr) + return; bool l_ok = false; const int l_id = l_button->property("effect_id").toInt(&l_ok); - if (!l_ok) return; + if (!l_ok) + return; // disable all other buttons for (AOButton *i_button : qAsConst(ui_effects)) { - if (i_button == l_button) continue; + if (i_button == l_button) + continue; i_button->setChecked(false); } @@ -2379,42 +2429,49 @@ void Courtroom::on_effect_button_clicked(const bool p_checked) void Courtroom::on_effect_button_toggled(const bool p_checked) { AOButton *l_button = dynamic_cast(sender()); - if (l_button == nullptr) return; + if (l_button == nullptr) + return; const QString l_name = l_button->property("effect_name").toString(); - if (l_name.isEmpty()) return; + if (l_name.isEmpty()) + return; const QString l_image_name(QString("%1%2.png").arg(l_name, QString(p_checked ? "_pressed" : nullptr))); l_button->set_image(l_image_name); - if (!l_button->has_image()) l_button->setText(l_name); + if (!l_button->has_image()) + l_button->setText(l_name); } void Courtroom::on_defense_minus_clicked() { int f_state = defense_bar_state - 1; - if (f_state >= 0) ao_app->send_server_packet(DRPacket("HP", {QString::number(1), QString::number(f_state)})); + if (f_state >= 0) + ao_app->send_server_packet(DRPacket("HP", {QString::number(1), QString::number(f_state)})); } void Courtroom::on_defense_plus_clicked() { int f_state = defense_bar_state + 1; - if (f_state <= 10) ao_app->send_server_packet(DRPacket("HP", {QString::number(1), QString::number(f_state)})); + if (f_state <= 10) + ao_app->send_server_packet(DRPacket("HP", {QString::number(1), QString::number(f_state)})); } void Courtroom::on_prosecution_minus_clicked() { int f_state = prosecution_bar_state - 1; - if (f_state >= 0) ao_app->send_server_packet(DRPacket("HP", {QString::number(2), QString::number(f_state)})); + if (f_state >= 0) + ao_app->send_server_packet(DRPacket("HP", {QString::number(2), QString::number(f_state)})); } void Courtroom::on_prosecution_plus_clicked() { int f_state = prosecution_bar_state + 1; - if (f_state <= 10) ao_app->send_server_packet(DRPacket("HP", {QString::number(2), QString::number(f_state)})); + if (f_state <= 10) + ao_app->send_server_packet(DRPacket("HP", {QString::number(2), QString::number(f_state)})); } void Courtroom::on_text_color_changed(int p_color) @@ -2451,7 +2508,8 @@ void Courtroom::reset_wtce_buttons() void Courtroom::on_wtce_clicked() { // qDebug() << "AA: wtce clicked!"; - if (is_client_muted) return; + if (is_client_muted) + return; AOButton *f_sig = static_cast(sender()); QString id = f_sig->property("wtce_id").toString(); @@ -2499,7 +2557,8 @@ void Courtroom::load_audiotracks() void Courtroom::on_back_to_lobby_clicked() { - if (m_back_to_lobby_clicked) return; + if (m_back_to_lobby_clicked) + return; m_back_to_lobby_clicked = true; // hide so we don't get the 'disconnected from server' prompt @@ -2534,7 +2593,8 @@ void Courtroom::on_call_mod_clicked() void Courtroom::on_switch_area_music_clicked() { - if (is_area_music_list_separated()) return; + if (is_area_music_list_separated()) + return; if (ui_area_list->isHidden()) { @@ -2609,7 +2669,8 @@ void Courtroom::changeEvent(QEvent *event) if (event->type() == QEvent::WindowStateChange) { m_is_maximized = windowState().testFlag(Qt::WindowMaximized); - if (!m_is_maximized) resize(m_default_size); + if (!m_is_maximized) + resize(m_default_size); } } @@ -2629,35 +2690,40 @@ void Courtroom::on_set_notes_clicked() void Courtroom::resume_timer(int p_id) { - if (p_id < 0 || p_id >= ui_timers.length()) return; + if (p_id < 0 || p_id >= ui_timers.length()) + return; AOTimer *l_timer = ui_timers.at(p_id); l_timer->resume(); } void Courtroom::set_timer_time(int p_id, int new_time) { - if (p_id < 0 || p_id >= ui_timers.length()) return; + if (p_id < 0 || p_id >= ui_timers.length()) + return; AOTimer *l_timer = ui_timers.at(p_id); l_timer->set_time(QTime(0, 0).addMSecs(new_time)); } void Courtroom::set_timer_timestep(int p_id, int timestep_length) { - if (p_id < 0 || p_id >= ui_timers.length()) return; + if (p_id < 0 || p_id >= ui_timers.length()) + return; AOTimer *l_timer = ui_timers.at(p_id); l_timer->set_timestep_length(timestep_length); } void Courtroom::set_timer_firing(int p_id, int firing_interval) { - if (p_id < 0 || p_id >= ui_timers.length()) return; + if (p_id < 0 || p_id >= ui_timers.length()) + return; AOTimer *l_timer = ui_timers.at(p_id); l_timer->set_firing_interval(firing_interval); } void Courtroom::pause_timer(int p_id) { - if (p_id < 0 || p_id >= ui_timers.length()) return; + if (p_id < 0 || p_id >= ui_timers.length()) + return; AOTimer *l_timer = ui_timers.at(p_id); l_timer->pause(); } diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 2501efdad..334904bd2 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -497,7 +497,8 @@ void Courtroom::insert_widget_name(QString p_widget_name, QWidget *p_widget) void Courtroom::insert_widget_names(QVector &p_name_list, QVector &p_widget_list) { - if (p_name_list.length() != p_widget_list.length()) qFatal("[WARNING] Length of names and widgets differs!"); + if (p_name_list.length() != p_widget_list.length()) + qFatal("[WARNING] Length of names and widgets differs!"); for (int i = 0; i < p_widget_list.length(); ++i) insert_widget_name(p_name_list[i], p_widget_list[i]); } @@ -714,7 +715,8 @@ void Courtroom::set_widgets() ui_vp_music_display_b->show(); set_size_and_pos(ui_vp_clock, "clock", COURTROOM_DESIGN_INI, ao_app); - if (m_current_clock == -1) ui_vp_clock->hide(); + if (m_current_clock == -1) + ui_vp_clock->hide(); set_sticker_play_once(ui_vp_clock, "clock", COURTROOM_CONFIG_INI, ao_app); ui_vp_chatbox->set_theme_image("chatmed.png"); @@ -899,19 +901,24 @@ void Courtroom::set_widgets() // set_image first tries the gamemode-timeofday folder, then the theme // folder, then falls back to the default theme ui_change_character->set_image("changecharacter.png"); - if (ui_change_character->get_image().isEmpty()) ui_change_character->setText("Change Character"); + if (ui_change_character->get_image().isEmpty()) + ui_change_character->setText("Change Character"); ui_call_mod->set_image("callmod.png"); - if (ui_call_mod->get_image().isEmpty()) ui_call_mod->setText("Call Mod"); + if (ui_call_mod->get_image().isEmpty()) + ui_call_mod->setText("Call Mod"); ui_switch_area_music->set_image("switch_area_music.png"); - if (ui_switch_area_music->get_image().isEmpty()) ui_switch_area_music->setText("A/M"); + if (ui_switch_area_music->get_image().isEmpty()) + ui_switch_area_music->setText("A/M"); ui_config_panel->set_image("config_panel.png"); - if (ui_config_panel->get_image().isEmpty()) ui_config_panel->setText("Config"); + if (ui_config_panel->get_image().isEmpty()) + ui_config_panel->setText("Config"); ui_note_button->set_image("notebutton.png"); - if (ui_note_button->get_image().isEmpty()) ui_note_button->setText("Notes"); + if (ui_note_button->get_image().isEmpty()) + ui_note_button->setText("Notes"); } // The config panel has a special property. If it is displayed beyond the right or lower limit of the window, it will @@ -925,7 +932,8 @@ void Courtroom::set_widgets() ui_config_panel->move(0, 0); // Moreover, if the width or height is invalid, change it to some fixed // values - if (ui_config_panel->width() <= 0 || ui_config_panel->height() <= 0) ui_config_panel->resize(64, 64); + if (ui_config_panel->width() <= 0 || ui_config_panel->height() <= 0) + ui_config_panel->resize(64, 64); } set_size_and_pos(ui_pre, "pre", COURTROOM_DESIGN_INI, ao_app); @@ -1134,7 +1142,8 @@ void Courtroom::check_effects() { QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), effect_names.at(i))}, animated_extensions()); - if (path.isEmpty()) path = ao_app->find_theme_asset_path(effect_names.at(i), animated_extensions()); + if (path.isEmpty()) + path = ao_app->find_theme_asset_path(effect_names.at(i), animated_extensions()); effects_enabled[i] = (!path.isEmpty()); } } @@ -1153,7 +1162,8 @@ void Courtroom::check_shouts() QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), shout_names.at(i))}, animated_extensions()); - if (path.isEmpty()) path = ao_app->find_theme_asset_path(shout_names.at(i), animated_extensions()); + if (path.isEmpty()) + path = ao_app->find_theme_asset_path(shout_names.at(i), animated_extensions()); shouts_enabled[i] = (!path.isEmpty()); } @@ -1172,7 +1182,8 @@ void Courtroom::check_wtce() { QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), wtce_names.at(i))}, animated_extensions()); - if (path.isEmpty()) path = ao_app->find_theme_asset_path(wtce_names.at(i), animated_extensions()); + if (path.isEmpty()) + path = ao_app->find_theme_asset_path(wtce_names.at(i), animated_extensions()); wtce_enabled[i] = (!path.isEmpty()); } } @@ -1185,7 +1196,8 @@ void Courtroom::delete_widget(QWidget *p_widget) // transfer the children to our grandparent since our parent is about to be deleted QWidget *grand_parent = p_widget->parentWidget(); // if we don't have a grand parent, attach ourselves to courtroom - if (!grand_parent) grand_parent = this; + if (!grand_parent) + grand_parent = this; // set new parent for (QWidget *child : p_widget->findChildren(nullptr, Qt::FindDirectChildrenOnly)) @@ -1340,7 +1352,8 @@ void Courtroom::set_shouts() { for (auto &shout : ui_shouts) shout->hide(); - if (ui_shouts.size() > 0) ui_shouts[m_shout_current]->show(); // check to prevent crashing + if (ui_shouts.size() > 0) + ui_shouts[m_shout_current]->show(); // check to prevent crashing } /** @@ -1353,7 +1366,8 @@ void Courtroom::set_effects() effect->hide(); // check to prevent crashing - if (ui_effects.size() > 0) ui_effects[m_effect_current]->show(); + if (ui_effects.size() > 0) + ui_effects[m_effect_current]->show(); } void Courtroom::set_judge_enabled(bool p_enabled) @@ -1387,7 +1401,8 @@ void Courtroom::set_judge_wtce() ui_wtce_down->setVisible(is_judge && is_single_wtce); // prevent going ahead if we have no wtce - if (!is_judge || ui_wtce.length() == 0) return; + if (!is_judge || ui_wtce.length() == 0) + return; // set visibility based off parameter if (is_single_wtce == true) diff --git a/src/datatypes.cpp b/src/datatypes.cpp index 9b366a8a6..1fdaf61f2 100644 --- a/src/datatypes.cpp +++ b/src/datatypes.cpp @@ -53,7 +53,9 @@ VersionNumber::VersionNumber() {} VersionNumber::VersionNumber(int p_release, int p_major, int p_minor) - : release(p_release), major(p_major), minor(p_minor) + : release(p_release) + , major(p_major) + , minor(p_minor) {} QString VersionNumber::to_string() const diff --git a/src/datatypes.h b/src/datatypes.h index c8a4172e0..2e2f68632 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -36,21 +36,56 @@ class DRChatRecord { public: DRChatRecord(QString p_name, QString p_message) - : name(p_name), message(p_message) {} + : name(p_name) + , message(p_message) + {} - QDateTime get_timestamp() const { return timestamp; } - QString get_name() const { return name; } - QString get_message() const { return message; } - int get_client_id() const { return client_id; } - bool is_self() const { return self; } - bool is_system() const { return system; } - bool is_music() const { return music; } + QDateTime get_timestamp() const + { + return timestamp; + } + QString get_name() const + { + return name; + } + QString get_message() const + { + return message; + } + int get_client_id() const + { + return client_id; + } + bool is_self() const + { + return self; + } + bool is_system() const + { + return system; + } + bool is_music() const + { + return music; + } // set - void set_client_id(const int p_client_id) { client_id = p_client_id; } - void set_self(const bool p_enabled) { self = p_enabled; } - void set_system(bool p_enabled) { system = p_enabled; } - void set_music(bool p_enabled) { music = p_enabled; } + void set_client_id(const int p_client_id) + { + client_id = p_client_id; + } + void set_self(const bool p_enabled) + { + self = p_enabled; + } + void set_system(bool p_enabled) + { + system = p_enabled; + } + void set_music(bool p_enabled) + { + music = p_enabled; + } private: QDateTime timestamp = QDateTime::currentDateTime(); @@ -67,7 +102,9 @@ struct DRSfx public: DRSfx() = default; DRSfx(QString p_name, QString p_file, bool p_is_found = false) - : name(p_name.trimmed()), file(p_file.trimmed()), is_found(p_is_found) + : name(p_name.trimmed()) + , file(p_file.trimmed()) + , is_found(p_is_found) {} QString name; @@ -234,7 +271,10 @@ struct ColorInfo public: ColorInfo() = default; ColorInfo(QString p_showname, QString p_code) - : name(p_showname.toLower()), showname(p_showname), code(p_code) {} + : name(p_showname.toLower()) + , showname(p_showname) + , code(p_code) + {} QString name; QString showname; diff --git a/src/draudiodevice.cpp b/src/draudiodevice.cpp index ab3cbbaad..dc0c468c7 100644 --- a/src/draudiodevice.cpp +++ b/src/draudiodevice.cpp @@ -22,7 +22,9 @@ QVector DRAudioDevice::get_device_list() return r_device_list; } -DRAudioDevice::DRAudioDevice() : m_id(0), m_name("") +DRAudioDevice::DRAudioDevice() + : m_id(0) + , m_name("") {} DRAudioDevice::~DRAudioDevice() diff --git a/src/draudioengine.cpp b/src/draudioengine.cpp index 0061baf36..9c50dfbf1 100644 --- a/src/draudioengine.cpp +++ b/src/draudioengine.cpp @@ -51,7 +51,8 @@ static class DRAudioEngineData ptr d; } d; -DRAudioEngine::DRAudioEngine(QObject *p_parent) : QObject(p_parent) +DRAudioEngine::DRAudioEngine(QObject *p_parent) + : QObject(p_parent) { d->children.append(this); } diff --git a/src/draudioengine_p.cpp b/src/draudioengine_p.cpp index 5c8679621..1a63c15a1 100644 --- a/src/draudioengine_p.cpp +++ b/src/draudioengine_p.cpp @@ -13,7 +13,9 @@ #include #include -DRAudioEnginePrivate::DRAudioEnginePrivate() : QObject(nullptr), update_timer(new QTimer(this)) +DRAudioEnginePrivate::DRAudioEnginePrivate() + : QObject(nullptr) + , update_timer(new QTimer(this)) { BASS_SetConfig(BASS_CONFIG_DEV_DEFAULT, FALSE); } diff --git a/src/draudioerror.cpp b/src/draudioerror.cpp index b6ed24f6f..9e116f1a7 100644 --- a/src/draudioerror.cpp +++ b/src/draudioerror.cpp @@ -3,7 +3,8 @@ DRAudioError::DRAudioError() {} -DRAudioError::DRAudioError(QString p_error) : m_error(QString("[bass] %1").arg(p_error)) +DRAudioError::DRAudioError(QString p_error) + : m_error(QString("[bass] %1").arg(p_error)) {} QString DRAudioError::what() diff --git a/src/draudiotrackmetadata.cpp b/src/draudiotrackmetadata.cpp index e85bbe46e..b49b8bcf5 100644 --- a/src/draudiotrackmetadata.cpp +++ b/src/draudiotrackmetadata.cpp @@ -88,7 +88,8 @@ void DRAudiotrackMetadata::update_cache() DRAudiotrackMetadata::DRAudiotrackMetadata() {} -DRAudiotrackMetadata::DRAudiotrackMetadata(QString p_file_name) : m_filename(p_file_name) +DRAudiotrackMetadata::DRAudiotrackMetadata(QString p_file_name) + : m_filename(p_file_name) { const QString l_lower_file_name = p_file_name.toLower(); if (s_audiotrack_cache.contains(l_lower_file_name)) diff --git a/src/drchatlog.cpp b/src/drchatlog.cpp index fe46afef0..9106d3ffb 100644 --- a/src/drchatlog.cpp +++ b/src/drchatlog.cpp @@ -8,7 +8,9 @@ #include #include -DRChatLog::DRChatLog(QWidget *parent) : QTextBrowser(parent), dr_config(new AOConfig(this)) +DRChatLog::DRChatLog(QWidget *parent) + : QTextBrowser(parent) + , dr_config(new AOConfig(this)) { connect(this, SIGNAL(message_queued()), this, SLOT(_p_write_message_queue())); } @@ -93,8 +95,12 @@ void DRChatLog::_p_write_message_queue() QString text; bool is_href = false; - TextPiece() {} - TextPiece(QString p_text, bool p_is_href = false) : text(p_text), is_href(p_is_href) {} + TextPiece() + {} + TextPiece(QString p_text, bool p_is_href = false) + : text(p_text) + , is_href(p_is_href) + {} }; QVector l_piece_list; diff --git a/src/drdiscord.cpp b/src/drdiscord.cpp index b3b726881..766af5c04 100644 --- a/src/drdiscord.cpp +++ b/src/drdiscord.cpp @@ -27,7 +27,8 @@ QByteArray resize_buf(const QString &f_str_message, const int f_max_size) return l_message; } -DRDiscord::DRDiscord(QObject *f_parent) : QObject(f_parent) +DRDiscord::DRDiscord(QObject *f_parent) + : QObject(f_parent) { m_waiter = new QTimer(this); m_waiter->setInterval(std::chrono::seconds(1)); diff --git a/src/drmasterclient.cpp b/src/drmasterclient.cpp index 06bf590d9..a88c9600e 100644 --- a/src/drmasterclient.cpp +++ b/src/drmasterclient.cpp @@ -8,7 +8,9 @@ #include #include -DRMasterClient::DRMasterClient(QObject *parent) : QObject(parent), m_network(new QNetworkAccessManager(this)) +DRMasterClient::DRMasterClient(QObject *parent) + : QObject(parent) + , m_network(new QNetworkAccessManager(this)) {} DRMasterClient::~DRMasterClient() diff --git a/src/drmediatester.cpp b/src/drmediatester.cpp index 2a479cd57..945adc99c 100644 --- a/src/drmediatester.cpp +++ b/src/drmediatester.cpp @@ -4,7 +4,8 @@ #include "debug_functions.h" -DRMediaTester::DRMediaTester(QObject *parent) : QObject(parent) +DRMediaTester::DRMediaTester(QObject *parent) + : QObject(parent) { m_player.setMuted(true); connect(&m_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, diff --git a/src/drpacket.cpp b/src/drpacket.cpp index 3aeddcf17..2969a6bff 100644 --- a/src/drpacket.cpp +++ b/src/drpacket.cpp @@ -12,7 +12,8 @@ QString DRPacket::decode(QString p_data) return p_data.replace("", "#").replace("", "%").replace("", "$").replace("", "&"); } -DRPacket::DRPacket(QString p_header) : DRPacket(p_header, {}) +DRPacket::DRPacket(QString p_header) + : DRPacket(p_header, {}) {} DRPacket::DRPacket(QString p_header, QStringList p_content) diff --git a/src/drposition.cpp b/src/drposition.cpp index 6096fffbe..8566800f0 100644 --- a/src/drposition.cpp +++ b/src/drposition.cpp @@ -34,11 +34,14 @@ DRPosition::DRPosition() {} DRPosition::DRPosition(QString p_back, QString p_front) - : m_back(p_back), m_front(p_front) + : m_back(p_back) + , m_front(p_front) {} DRPosition::DRPosition(QString p_back, QString p_front, QString p_ambient_sfx) - : m_back(p_back), m_front(p_front), m_ambient_sfx(p_ambient_sfx) + : m_back(p_back) + , m_front(p_front) + , m_ambient_sfx(p_ambient_sfx) {} DRPosition::~DRPosition() diff --git a/src/drserversocket.cpp b/src/drserversocket.cpp index b4b95d3c2..3aaa71540 100644 --- a/src/drserversocket.cpp +++ b/src/drserversocket.cpp @@ -68,25 +68,25 @@ void DRServerSocket::_p_update_state(QAbstractSocket::SocketState p_state) { switch (p_state) { - case QAbstractSocket::ConnectingState: - m_connecting_timer->start(); - Q_EMIT connecting_to_server(); - break; - - case QAbstractSocket::ConnectedState: - m_connecting_timer->stop(); - m_connected = true; - Q_EMIT connected_to_server(); - break; - - case QAbstractSocket::UnconnectedState: - m_connecting_timer->stop(); - m_connected = false; - Q_EMIT disconnected_from_server(); - break; - - default: - break; + case QAbstractSocket::ConnectingState: + m_connecting_timer->start(); + Q_EMIT connecting_to_server(); + break; + + case QAbstractSocket::ConnectedState: + m_connecting_timer->stop(); + m_connected = true; + Q_EMIT connected_to_server(); + break; + + case QAbstractSocket::UnconnectedState: + m_connecting_timer->stop(); + m_connected = false; + Q_EMIT disconnected_from_server(); + break; + + default: + break; } } diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp index cbc1b7eb8..aa9729e11 100644 --- a/src/drtextedit.cpp +++ b/src/drtextedit.cpp @@ -4,7 +4,8 @@ #include #include -DRTextEdit::DRTextEdit(QWidget *parent) : QTextEdit(parent) +DRTextEdit::DRTextEdit(QWidget *parent) + : QTextEdit(parent) { connect(this, SIGNAL(textChanged()), this, SLOT(on_text_changed())); connect(this, SIGNAL(text_alignment_changed(Qt::Alignment)), this, SLOT(on_text_changed())); diff --git a/src/drthememovie.cpp b/src/drthememovie.cpp index 691635529..afedef80f 100644 --- a/src/drthememovie.cpp +++ b/src/drthememovie.cpp @@ -3,7 +3,8 @@ #include "aoapplication.h" DRThemeMovie::DRThemeMovie(AOApplication *app, QGraphicsItem *parent) - : DRMovie(parent), ao_app(app) + : DRMovie(parent) + , ao_app(app) {} DRThemeMovie::~DRThemeMovie() diff --git a/src/lobby.cpp b/src/lobby.cpp index dcdf42524..e490de105 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -519,19 +519,19 @@ void Lobby::on_connect_released() QString l_reason; switch (l_status) { - case VersionStatus::NotCompatible: - l_reason = "The server did not report any client version."; - break; - case VersionStatus::ServerOutdated: - l_reason = QString("The server is outdated.
    (Server version: %1, expected version: %2)") - .arg(ao_app->get_server_client_version().to_string(), get_version_number().to_string()); - break; - case VersionStatus::ClientOutdated: - l_reason = QString("Your client is outdated.
    (Client version: %1, expected version: %2)") - .arg(get_version_number().to_string(), ao_app->get_server_client_version().to_string()); - break; - default: - break; + case VersionStatus::NotCompatible: + l_reason = "The server did not report any client version."; + break; + case VersionStatus::ServerOutdated: + l_reason = QString("The server is outdated.
    (Server version: %1, expected version: %2)") + .arg(ao_app->get_server_client_version().to_string(), get_version_number().to_string()); + break; + case VersionStatus::ClientOutdated: + l_reason = QString("Your client is outdated.
    (Client version: %1, expected version: %2)") + .arg(get_version_number().to_string(), ao_app->get_server_client_version().to_string()); + break; + default: + break; } call_warning("You are connecting to an incompatible DRO server.

    Reason: " + l_reason + diff --git a/src/mk2/graphicsvideoscreen.cpp b/src/mk2/graphicsvideoscreen.cpp index 3863fa0c4..1b89bc4f9 100644 --- a/src/mk2/graphicsvideoscreen.cpp +++ b/src/mk2/graphicsvideoscreen.cpp @@ -134,30 +134,30 @@ void DRVideoScreen::check_status(QMediaPlayer::MediaStatus p_status) { switch (p_status) { - case QMediaPlayer::InvalidMedia: - m_scanned = true; - qWarning() << "error: media file is invalid:" << m_file_name; - finish_playback(); - break; + case QMediaPlayer::InvalidMedia: + m_scanned = true; + qWarning() << "error: media file is invalid:" << m_file_name; + finish_playback(); + break; + + case QMediaPlayer::NoMedia: + m_scanned = true; + finish_playback(); - case QMediaPlayer::NoMedia: - m_scanned = true; + case QMediaPlayer::LoadedMedia: + m_scanned = true; + if (m_video_available) + { + start_playback(); + } + else + { finish_playback(); + } + break; - case QMediaPlayer::LoadedMedia: - m_scanned = true; - if (m_video_available) - { - start_playback(); - } - else - { - finish_playback(); - } - break; - - default: - break; + default: + break; } } } @@ -166,19 +166,19 @@ void DRVideoScreen::check_state(QMediaPlayer::State p_state) { switch (p_state) { - case QMediaPlayer::PlayingState: - emit started(); - break; + case QMediaPlayer::PlayingState: + emit started(); + break; - case QMediaPlayer::StoppedState: - if (m_running) - { - finish_playback(); - } - break; + case QMediaPlayer::StoppedState: + if (m_running) + { + finish_playback(); + } + break; - default: - break; + default: + break; } } diff --git a/src/mk2/spriteplayer.cpp b/src/mk2/spriteplayer.cpp index db3bff2bb..fe94c6e66 100644 --- a/src/mk2/spriteplayer.cpp +++ b/src/mk2/spriteplayer.cpp @@ -295,22 +295,22 @@ void SpritePlayer::scale_current_frame() { switch (m_resolved_scaling_mode) { - case NoScaling: - [[fallthrough]]; - default: - break; - - case StretchScaling: - l_image = l_image.scaled(m_size, Qt::IgnoreAspectRatio, m_transform); - break; - - case WidthScaling: - l_image = l_image.scaledToWidth(m_size.width(), m_transform); - break; - - case HeightScaling: - l_image = l_image.scaledToHeight(m_size.height(), m_transform); - break; + case NoScaling: + [[fallthrough]]; + default: + break; + + case StretchScaling: + l_image = l_image.scaled(m_size, Qt::IgnoreAspectRatio, m_transform); + break; + + case WidthScaling: + l_image = l_image.scaledToWidth(m_size.width(), m_transform); + break; + + case HeightScaling: + l_image = l_image.scaledToHeight(m_size.height(), m_transform); + break; } } diff --git a/src/mk2/spritereadersynchronizer.cpp b/src/mk2/spritereadersynchronizer.cpp index c253e74c0..0e05a766c 100644 --- a/src/mk2/spritereadersynchronizer.cpp +++ b/src/mk2/spritereadersynchronizer.cpp @@ -23,7 +23,10 @@ using namespace mk2; SpriteReaderSynchronizer::SpriteReaderSynchronizer(QObject *parent) - : QObject{parent}, m_waiting{false}, m_finished{false}, m_threshold{50} + : QObject{parent} + , m_waiting{false} + , m_finished{false} + , m_threshold{50} {} SpriteReaderSynchronizer::~SpriteReaderSynchronizer() diff --git a/src/utils.cpp b/src/utils.cpp index 9eeb28f3c..096e8063c 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -13,7 +13,8 @@ static QString lookupKey(const QStringList &p_key_list, const QString &p_target_ return p_target_key; } -QSettingsKeyFetcher::QSettingsKeyFetcher(QSettings &settings) : m_settings(settings) +QSettingsKeyFetcher::QSettingsKeyFetcher(QSettings &settings) + : m_settings(settings) {} QString QSettingsKeyFetcher::lookup_group(QString p_name) From f716da5a2c9edec55466ff367c0d4a1250ba9d74 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 22 Jul 2022 20:57:18 +0200 Subject: [PATCH 801/842] Added extra checks and fallbacks for missing themes --- res/ui/config_panel.ui | 109 ++++++++++++++++++++++++----------------- src/aoapplication.cpp | 50 +++++++++++++++++++ src/aoapplication.h | 1 + src/aoconfig.cpp | 19 ++++++- src/aoconfig.h | 2 + src/aoconfigpanel.cpp | 92 +++++++++++++++++----------------- src/aoconfigpanel.h | 3 ++ src/lobby.cpp | 17 +------ src/main.cpp | 1 - 9 files changed, 185 insertions(+), 109 deletions(-) diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index e368ce128..1178eafd8 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -32,7 +32,7 @@ - 4 + 3 @@ -892,10 +892,10 @@ Theme - + - + @@ -908,20 +908,7 @@ - - - - - 0 - 0 - - - - Gamemode: - - - - + @@ -934,24 +921,21 @@ - - + + - + 0 0 - - <html><head/><body><p>Allows you to manually switch to a gamemode interface.</p></body></html> - - Manual + Folder: - - + + 0 @@ -959,14 +943,14 @@ - Folder: + Gamemode: - - + + - + false @@ -976,7 +960,7 @@
    - <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> gamemode.</p></body></html> + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> time of day.</p></body></html> <default> @@ -990,21 +974,18 @@ - - - true - + - <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> gamemode.</p></body></html> + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> time of day.</p></body></html> - - + + - + false @@ -1014,7 +995,7 @@ - <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> time of day.</p></body></html> + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> gamemode.</p></body></html> <default> @@ -1028,30 +1009,66 @@ - + + + true + - <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> time of day.</p></body></html> + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> gamemode.</p></body></html> - + - - + + 0 0 + + <html><head/><body><p>Allows you to manually switch to a gamemode interface.</p></body></html> + - Reload + Manual + + + + + + + 0 + 0 + + + + Switch + + + + + + + + 0 + 0 + + + + Reload + + + + + diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 8988aea5a..b6276e472 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -7,6 +7,7 @@ #include "drdiscord.h" #include "drmasterclient.h" #include "drpacket.h" +#include "drpather.h" #include "drserversocket.h" #include "file_functions.h" #include "lobby.h" @@ -14,6 +15,7 @@ #include "version.h" #include +#include #include #include @@ -52,6 +54,8 @@ AOApplication::AOApplication(int &argc, char **argv) connect(m_server_socket, SIGNAL(disconnected_from_server()), this, SLOT(_p_handle_server_disconnection())); connect(m_server_socket, SIGNAL(disconnected_from_server()), this, SIGNAL(disconnected_from_server())); connect(m_server_socket, SIGNAL(packet_received(DRPacket)), this, SLOT(_p_handle_server_packet(DRPacket))); + + resolve_current_theme(); } AOApplication::~AOApplication() @@ -422,6 +426,52 @@ void AOApplication::on_courtroom_destroyed() ao_config_panel->hide(); } +void AOApplication::resolve_current_theme() +{ + const QString l_theme_dir = get_case_sensitive_path(get_base_file_path("themes")); + if (l_theme_dir.isEmpty()) + { + call_warning("It doesn't look like your client is set up correctly. This can be " + "due to the following reasons: \n" + "1. Check you downloaded and extracted the resources correctly from " + "the DRO Discord including the large 'base' folder.\n" + "2. If you did, check that the base folder is in the same folder " + "where you launched Danganronpa Online from: " + + DRPather::get_application_path() + + "\n" + "3. If it is there, check that your current theme folder exists in " + "base/themes. "); + } + + const QString l_current_theme = ao_config->theme(); + std::optional l_target_theme; + const QList l_info_list = QDir(l_theme_dir).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QFileInfo &i_info : l_info_list) + { + const QString l_theme = i_info.fileName(); + if (l_theme == l_current_theme) + { + l_target_theme.reset(); + break; + } + + // target theme is always the first + if (!l_target_theme.has_value()) + { + l_target_theme = l_theme; + } + } + + if (l_target_theme.has_value()) + { + if (!ao_config->first_launch()) + { + call_warning(tr("Your previous theme [%1] is missing; the current theme has been reset.\n\nTo select a different theme, go to the config panel.").arg(l_current_theme)); + } + ao_config->set_theme(l_target_theme.value()); + } +} + bool AOApplication::notify(QObject *receiver, QEvent *event) { bool l_done = true; diff --git a/src/aoapplication.h b/src/aoapplication.h index ffa022644..ccdc8f461 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -228,6 +228,7 @@ private slots: void _p_handle_server_packet(DRPacket); void on_courtroom_closing(); void on_courtroom_destroyed(); + void resolve_current_theme(); void handle_theme_modification(); void handle_character_reloading(); void handle_audiotracks_reloading(); diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp index 22c68b4c4..a2f8b85b1 100644 --- a/src/aoconfig.cpp +++ b/src/aoconfig.cpp @@ -9,6 +9,8 @@ // qt #include #include +#include +#include #include #include #include @@ -47,6 +49,7 @@ private slots: QVector children; // data + bool first_launch; bool autosave; QStringList notification_filter; QString username; @@ -114,8 +117,7 @@ AOConfigPrivate::AOConfigPrivate() , audio_engine(new DRAudioEngine(this)) { Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); - connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, - SLOT(on_application_state_changed(Qt::ApplicationState))); + connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(on_application_state_changed(Qt::ApplicationState))); load_file(); } @@ -125,6 +127,14 @@ AOConfigPrivate::~AOConfigPrivate() void AOConfigPrivate::load_file() { + first_launch = cfg.value("first_launch", true).toBool(); + + if (first_launch) + { + cfg.setValue("first_launch", false); + cfg.sync(); + } + autosave = cfg.value("autosave", true).toBool(); { // notifications @@ -394,6 +404,11 @@ int AOConfig::get_number(QString p_name, int p_default) const return d->cfg.value(p_name, p_default).toInt(); } +bool AOConfig::first_launch() const +{ + return d->first_launch; +} + bool AOConfig::autosave() const { return d->autosave; diff --git a/src/aoconfig.h b/src/aoconfig.h index eefaed074..591e9ab39 100644 --- a/src/aoconfig.h +++ b/src/aoconfig.h @@ -20,6 +20,8 @@ class AOConfig : public QObject bool get_bool(QString p_name, bool p_default = false) const; int get_number(QString p_name, int p_default = 0) const; + bool first_launch() const; + // getters bool autosave() const; bool display_notification(QString message) const; diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp index 843a41c2f..99945b520 100644 --- a/src/aoconfigpanel.cpp +++ b/src/aoconfigpanel.cpp @@ -58,7 +58,8 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) // game ui_theme = AO_GUI_WIDGET(QComboBox, "theme"); - ui_reload_theme = AO_GUI_WIDGET(QPushButton, "theme_reload"); + ui_switch_theme = AO_GUI_WIDGET(QPushButton, "switch_theme"); + ui_reload_theme = AO_GUI_WIDGET(QPushButton, "reload_theme"); ui_gamemode = AO_GUI_WIDGET(QLineEdit, "gamemode"); ui_manual_gamemode = AO_GUI_WIDGET(QComboBox, "manual_gamemode"); ui_manual_gamemode_selection = AO_GUI_WIDGET(QCheckBox, "manual_gamemode_selection"); @@ -162,15 +163,12 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(theme_changed(QString)), this, SLOT(on_theme_changed(QString))); connect(m_config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_gamemode_changed(QString))); connect(m_config, SIGNAL(manual_gamemode_changed(QString)), this, SLOT(on_manual_gamemode_changed(QString))); - connect(m_config, SIGNAL(manual_gamemode_selection_changed(bool)), this, - SLOT(on_manual_gamemode_selection_changed(bool))); + connect(m_config, SIGNAL(manual_gamemode_selection_changed(bool)), this, SLOT(on_manual_gamemode_selection_changed(bool))); connect(m_config, SIGNAL(timeofday_changed(QString)), this, SLOT(on_timeofday_changed(QString))); connect(m_config, SIGNAL(manual_timeofday_changed(QString)), this, SLOT(on_manual_timeofday_changed(QString))); - connect(m_config, SIGNAL(manual_timeofday_selection_changed(bool)), this, - SLOT(on_manual_timeofday_selection_changed(bool))); + connect(m_config, SIGNAL(manual_timeofday_selection_changed(bool)), this, SLOT(on_manual_timeofday_selection_changed(bool))); connect(m_config, SIGNAL(showname_changed(QString)), ui_showname, SLOT(setText(QString))); - connect(m_config, SIGNAL(showname_placeholder_changed(QString)), this, - SLOT(on_showname_placeholder_changed(QString))); + connect(m_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); connect(m_config, SIGNAL(searchable_iniswap_changed(bool)), ui_searchable_iniswap, SLOT(setChecked(bool))); connect(m_config, SIGNAL(always_pre_changed(bool)), ui_always_pre, SLOT(setChecked(bool))); connect(m_config, SIGNAL(chat_tick_interval_changed(int)), ui_chat_tick_interval, SLOT(setValue(int))); @@ -181,40 +179,31 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(m_config, SIGNAL(log_max_lines_changed(int)), ui_log_max_lines, SLOT(setValue(int))); connect(m_config, SIGNAL(log_display_timestamp_changed(bool)), ui_log_display_timestamp, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_display_client_id_changed(bool)), ui_log_display_client_id, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_display_self_highlight_changed(bool)), ui_log_display_self_highlight, - SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_self_highlight_changed(bool)), ui_log_display_self_highlight, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_format_use_newline_changed(bool)), ui_log_format_use_newline, SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_display_empty_messages_changed(bool)), ui_log_display_empty_messages, - SLOT(setChecked(bool))); - connect(m_config, SIGNAL(log_display_music_switch_changed(bool)), ui_log_display_music_switch, - SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_empty_messages_changed(bool)), ui_log_display_empty_messages, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_music_switch_changed(bool)), ui_log_display_music_switch, SLOT(setChecked(bool))); connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); connect(m_config, SIGNAL(log_is_recording_changed(bool)), ui_log_is_recording, SLOT(setChecked(bool))); // audio connect(m_config, SIGNAL(master_volume_changed(int)), ui_master, SLOT(setValue(int))); - connect(m_config, SIGNAL(suppress_background_audio_changed(bool)), ui_suppress_background_audio, - SLOT(setChecked(bool))); + connect(m_config, SIGNAL(suppress_background_audio_changed(bool)), ui_suppress_background_audio, SLOT(setChecked(bool))); connect(m_config, SIGNAL(system_volume_changed(int)), ui_system, SLOT(setValue(int))); connect(m_config, SIGNAL(effect_volume_changed(int)), ui_effect, SLOT(setValue(int))); - connect(m_config, SIGNAL(effect_ignore_suppression_changed(bool)), ui_effect_ignore_suppression, - SLOT(setChecked(bool))); + connect(m_config, SIGNAL(effect_ignore_suppression_changed(bool)), ui_effect_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(music_volume_changed(int)), ui_music, SLOT(setValue(int))); - connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), ui_music_ignore_suppression, - SLOT(setChecked(bool))); + connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), ui_music_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(video_volume_changed(int)), ui_video, SLOT(setValue(int))); - connect(m_config, SIGNAL(video_ignore_suppression_changed(bool)), ui_video_ignore_suppression, - SLOT(setChecked(bool))); + connect(m_config, SIGNAL(video_ignore_suppression_changed(bool)), ui_video_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(blip_volume_changed(int)), ui_blip, SLOT(setValue(int))); connect(m_config, SIGNAL(blip_ignore_suppression_changed(bool)), ui_blip_ignore_suppression, SLOT(setChecked(bool))); connect(m_config, SIGNAL(blip_rate_changed(int)), ui_blip_rate, SLOT(setValue(int))); connect(m_config, SIGNAL(blank_blips_changed(bool)), ui_blank_blips, SLOT(setChecked(bool))); connect(m_engine, SIGNAL(current_device_changed(DRAudioDevice)), this, SLOT(on_audio_device_changed(DRAudioDevice))); - connect(m_engine, SIGNAL(device_list_changed(QVector)), this, - SLOT(on_audio_device_list_changed(QVector))); - connect(m_engine, SIGNAL(favorite_device_changed(DRAudioDevice)), this, - SLOT(on_favorite_audio_device_changed(DRAudioDevice))); + connect(m_engine, SIGNAL(device_list_changed(QVector)), this, SLOT(on_audio_device_list_changed(QVector))); + connect(m_engine, SIGNAL(favorite_device_changed(DRAudioDevice)), this, SLOT(on_favorite_audio_device_changed(DRAudioDevice))); // meta connect(ui_close, SIGNAL(clicked()), this, SLOT(close())); @@ -231,18 +220,15 @@ AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) connect(ui_discord_hide_character, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_character(const bool))); // game - connect(ui_theme, SIGNAL(currentIndexChanged(QString)), m_config, SLOT(set_theme(QString))); + connect(ui_theme, SIGNAL(currentTextChanged(QString)), this, SLOT(update_theme_controls())); + connect(ui_switch_theme, SIGNAL(clicked()), this, SLOT(on_switch_theme_clicked())); connect(ui_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); connect(ui_reload_character, SIGNAL(clicked()), this, SLOT(on_reload_character_clicked())); connect(ui_reload_audiotracks, SIGNAL(clicked()), this, SLOT(on_reload_audiotracks_clicked())); - connect(ui_manual_gamemode, SIGNAL(currentIndexChanged(QString)), this, - SLOT(on_manual_gamemode_index_changed(QString))); - connect(ui_manual_gamemode_selection, SIGNAL(toggled(bool)), m_config, - SLOT(set_manual_gamemode_selection_enabled(bool))); - connect(ui_manual_timeofday, SIGNAL(currentIndexChanged(QString)), this, - SLOT(on_manual_timeofday_index_changed(QString))); - connect(ui_manual_timeofday_selection, SIGNAL(toggled(bool)), m_config, - SLOT(set_manual_timeofday_selection_enabled(bool))); + connect(ui_manual_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_manual_gamemode_index_changed(QString))); + connect(ui_manual_gamemode_selection, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_gamemode_selection_enabled(bool))); + connect(ui_manual_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_manual_timeofday_index_changed(QString))); + connect(ui_manual_timeofday_selection, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_timeofday_selection_enabled(bool))); connect(ui_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); connect(ui_searchable_iniswap, SIGNAL(toggled(bool)), m_config, SLOT(set_searchable_iniswap(bool))); connect(ui_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); @@ -393,21 +379,27 @@ void AOConfigPanel::showEvent(QShowEvent *event) void AOConfigPanel::refresh_theme_list() { - ui_theme->blockSignals(true); + const QString l_current_theme = ui_theme->currentText(); + ui_theme->clear(); + std::optional l_theme_index; + const QString l_theme_dir = DRPather::get_application_path() + "/base/themes"; + for (const QFileInfo &i_info : QDir(ao_app->get_case_sensitive_path(l_theme_dir)).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) + { + const QString l_theme = i_info.fileName(); + if (l_theme == l_current_theme) + { + l_theme_index = ui_theme->count(); + } + ui_theme->addItem(l_theme); + } - // themes - const QString path = DRPather::get_application_path() + "/base/themes"; - for (const QString &i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + if (l_theme_index.has_value()) { - if (i_folder == "." || i_folder == "..") - continue; - ui_theme->addItem(i_folder); + ui_theme->setCurrentIndex(l_theme_index.value()); } - // restore previous selection - ui_theme->setCurrentText(m_config->theme()); - ui_theme->blockSignals(false); + update_theme_controls(); } void AOConfigPanel::refresh_gamemode_list() @@ -500,6 +492,18 @@ void AOConfigPanel::update_audio_device_list() ui_device->setCurrentIndex(l_prev_driver_index.value_or(l_current_driver_index.value_or(0))); } +void AOConfigPanel::update_theme_controls() +{ + const bool l_different_theme = ui_theme->currentText() != m_config->theme(); + ui_switch_theme->setVisible(l_different_theme); + ui_reload_theme->setHidden(l_different_theme); +} + +void AOConfigPanel::on_switch_theme_clicked() +{ + m_config->set_theme(ui_theme->currentText()); +} + void AOConfigPanel::on_reload_theme_clicked() { Q_EMIT reload_theme(); diff --git a/src/aoconfigpanel.h b/src/aoconfigpanel.h index 433a95c66..15aba5f82 100644 --- a/src/aoconfigpanel.h +++ b/src/aoconfigpanel.h @@ -46,6 +46,8 @@ public slots: void update_audio_device_list(); private slots: + void update_theme_controls(); + void on_switch_theme_clicked(); void on_reload_theme_clicked(); void on_reload_character_clicked(); void on_reload_audiotracks_clicked(); @@ -121,6 +123,7 @@ private slots: // game QComboBox *ui_theme = nullptr; + QPushButton *ui_switch_theme = nullptr; QPushButton *ui_reload_theme = nullptr; QLineEdit *ui_gamemode = nullptr; QComboBox *ui_manual_gamemode = nullptr; diff --git a/src/lobby.cpp b/src/lobby.cpp index e490de105..0ab976263 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -10,7 +10,6 @@ #include "drchatlog.h" #include "drmasterclient.h" #include "drpacket.h" -#include "drpather.h" #include "drtextedit.h" #include "theme.h" #include "version.h" @@ -73,6 +72,7 @@ Lobby::Lobby(AOApplication *p_ao_app) ui_cancel = new AOButton(ui_loading_background, ao_app); connect(ao_app, SIGNAL(reload_theme()), this, SLOT(update_widgets())); + connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(update_widgets())); connect(ao_config, SIGNAL(server_advertiser_changed(QString)), m_master_client, SLOT(set_address(QString))); connect(m_master_client, SIGNAL(address_changed()), this, SLOT(request_advertiser_update())); connect(m_master_client, SIGNAL(motd_changed()), this, SLOT(update_motd())); @@ -117,26 +117,11 @@ DRServerInfoList Lobby::get_combined_server_list() void Lobby::update_widgets() { pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", LOBBY_DESIGN_INI); - if (f_lobby.width < 0 || f_lobby.height < 0) { qWarning() << "W: did not find lobby width or height in " << LOBBY_DESIGN_INI; f_lobby.width = 517; f_lobby.height = 666; - - // Most common symptom of bad config files, missing assets, or misnamed - // theme folder - call_notice("It doesn't look like your client is set up correctly. This can be " - "due to the following reasons: \n" - "1. Check you downloaded and extracted the resources correctly from " - "the DRO Discord including the large 'base' folder.\n" - "2. If you did, check that the base folder is in the same folder " - "where you launched Danganronpa Online from: " + - DRPather::get_application_path() + - "\n" - "3. If it is there, check that your current theme folder exists in " - "base/themes. According to base/config.ini, your current theme is " + - ao_config->theme()); } setWindowState(Qt::WindowNoState); resize(f_lobby.width, f_lobby.height); diff --git a/src/main.cpp b/src/main.cpp index c1852c3bc..53527d0fb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -50,7 +50,6 @@ int main(int argc, char *argv[]) int l_exit_code = 0; { AOConfig l_config; - l_config.load_file(); DRMediaTester tester; From aa74d6a202ed3f29f84ad868a258bc43a503faf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifa=E2=99=A5?= <26681464+TrickyLeifa@users.noreply.github.com> Date: Sat, 23 Jul 2022 20:26:55 +0200 Subject: [PATCH 802/842] Properly resolve Linux pathing * Bumped RC version --- src/path_functions.cpp | 26 ++++++++++++++++++++------ src/version.cpp | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 9f1a4a953..3aa10ff94 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -10,6 +10,7 @@ #include #include #include +#include // Copied over from Vanilla. // As said in the comments there, this is a *super broad* definition. @@ -92,12 +93,25 @@ QString AOApplication::get_case_sensitive_path(QString p_file) return p_file; } - QFileInfo l_file_info(p_file); - const QDir l_dir(l_file_info.absolutePath()); - const QString l_file_name = l_file_info.fileName(); - const QStringList l_file_list = l_dir.entryList(); - const QRegExp l_regex = QRegExp(l_file_name, Qt::CaseInsensitive, QRegExp::FixedString); - return l_file_list.value(l_file_list.indexOf(l_regex), l_file_name); + const auto l_dir_path = get_case_sensitive_path(QFileInfo(p_file).absolutePath()); + if (l_dir_path.isEmpty()) + { + return p_file; + } + + const QDir l_dir(l_dir_path); + const auto l_file_list = l_dir.entryList(QDir::Files); + const auto l_regex = QRegExp(p_file, Qt::CaseInsensitive, QRegExp::FixedString); + for (auto &i_file : l_file_list) + { + const QString l_file_path = l_dir.absoluteFilePath(i_file); + if (l_regex.exactMatch(l_file_path)) + { + p_file = l_file_path; + break; + } + } + return p_file; } #endif diff --git a/src/version.cpp b/src/version.cpp index 5c763c1a9..12614cbc8 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -30,7 +30,7 @@ VersionNumber get_version_number() QString get_post_version() { - return "rc.3"; + return "rc.4"; } QString get_version_string() From 91c47142d5cf7156d8ca751b8e8fe0b6a0a7fbb2 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 24 Jul 2022 00:33:33 +0200 Subject: [PATCH 803/842] Added add/edit/remove context menu prompt for server list --- dronline-client.pro | 5 +- res.qrc | 1 + res/ui/config_panel.ui | 2 +- src/datatypes.h | 1 - src/debug_functions.cpp | 31 ++++++-- src/debug_functions.h | 1 + src/drserverinfoeditor.cpp | 47 +++++++++++++ src/drserverinfoeditor.h | 32 +++++++++ src/drserverinfoeditor.ui | 111 +++++++++++++++++++++++++++++ src/lobby.cpp | 140 ++++++++++++++++++++++++++++--------- src/lobby.h | 19 +++-- src/main.cpp | 3 - src/server_socket.cpp | 25 ++----- 13 files changed, 349 insertions(+), 69 deletions(-) create mode 100644 src/drserverinfoeditor.cpp create mode 100644 src/drserverinfoeditor.h create mode 100644 src/drserverinfoeditor.ui diff --git a/dronline-client.pro b/dronline-client.pro index 0b4930d80..0ee344c4d 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -57,6 +57,7 @@ HEADERS += \ src/drpather.h \ src/drposition.h \ src/drscenemovie.h \ + src/drserverinfoeditor.h \ src/drserversocket.h \ src/drshoutmovie.h \ src/drsplashmovie.h \ @@ -131,6 +132,7 @@ SOURCES += \ src/drpather.cpp \ src/drposition.cpp \ src/drscenemovie.cpp \ + src/drserverinfoeditor.cpp \ src/drserversocket.cpp \ src/drshoutmovie.cpp \ src/drsplashmovie.cpp \ @@ -175,7 +177,8 @@ RESOURCES += \ DISTFILES += FORMS += \ - res/ui/config_panel.ui + res/ui/config_panel.ui \ + src/drserverinfoeditor.ui # Mac stuff QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.13 diff --git a/res.qrc b/res.qrc index bc2bedb90..5adca3668 100644 --- a/res.qrc +++ b/res.qrc @@ -5,5 +5,6 @@ res/git/git_branch.txt res/git/git_hash.txt res/tests/mp4_media_test.mp4 + src/drserverinfoeditor.ui diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui index 1178eafd8..b78bf2e23 100644 --- a/res/ui/config_panel.ui +++ b/res/ui/config_panel.ui @@ -32,7 +32,7 @@ - 3 + 1 diff --git a/src/datatypes.h b/src/datatypes.h index 2e2f68632..76118331a 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -119,7 +119,6 @@ class DRServerInfo QString description; QString address; int port; - bool favorite = false; QString to_info() const; QString to_address() const; diff --git a/src/debug_functions.cpp b/src/debug_functions.cpp index c4f7acc1b..74b7b8b13 100644 --- a/src/debug_functions.cpp +++ b/src/debug_functions.cpp @@ -6,16 +6,28 @@ #include "aoconfig.h" -void drMessageBox(QString p_message, bool p_is_warning) +enum class MessageType +{ + Notice, + Warning, + Prompt, +}; + +bool drMessageBox(QString p_message, MessageType p_message_type) { AOConfig config; if (!config.display_notification(p_message)) - return; + return QMessageBox::Accepted; QMessageBox message; - message.setWindowTitle(p_is_warning ? "Warning" : "Notice"); - message.setIcon(p_is_warning ? QMessageBox::Warning : QMessageBox::Information); + const bool l_is_notice = p_message_type == MessageType::Notice; + message.setWindowTitle(l_is_notice ? "Notice" : "Warning"); + message.setIcon(l_is_notice ? QMessageBox::Information : QMessageBox::Warning); message.setText(p_message); + if (p_message_type == MessageType::Prompt) + { + message.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + } QCheckBox *show_again = new QCheckBox(&message); show_again->setText(QObject::tr("&Show this message again")); show_again->setChecked(true); @@ -24,16 +36,23 @@ void drMessageBox(QString p_message, bool p_is_warning) if (!show_again->isChecked()) config.filter_notification(p_message); + return message.result() == QMessageBox::Ok; } void call_notice(QString p_message) { qInfo() << p_message; - drMessageBox(p_message, false); + drMessageBox(p_message, MessageType::Notice); } void call_warning(QString p_message) { qWarning() << "error:" << p_message; - drMessageBox(p_message, true); + drMessageBox(p_message, MessageType::Warning); +} + +bool prompt_warning(QString p_message) +{ + qDebug() << "prompt:" << p_message; + return drMessageBox(p_message, MessageType::Prompt); } diff --git a/src/debug_functions.h b/src/debug_functions.h index 84bfb3707..24cf65280 100644 --- a/src/debug_functions.h +++ b/src/debug_functions.h @@ -5,5 +5,6 @@ class QString; void call_notice(QString message); void call_warning(QString message); +bool prompt_warning(QString message); #endif // DEBUG_FUNCTIONS_H diff --git a/src/drserverinfoeditor.cpp b/src/drserverinfoeditor.cpp new file mode 100644 index 000000000..307fc3e16 --- /dev/null +++ b/src/drserverinfoeditor.cpp @@ -0,0 +1,47 @@ +#include "drserverinfoeditor.h" + +#include "aoguiloader.h" + +DRServerInfoEditor::DRServerInfoEditor(QWidget *parent) + : QDialog(parent) +{ + setModal(true); + + AOGuiLoader l_loader; + l_loader.load_from_file(":/src/drserverinfoeditor.ui", this); + + ui_name = AO_GUI_WIDGET(QLineEdit, "name"); + ui_description = AO_GUI_WIDGET(QPlainTextEdit, "description"); + ui_address = AO_GUI_WIDGET(QLineEdit, "address"); + ui_port = AO_GUI_WIDGET(QSpinBox, "port"); + ui_button_box = AO_GUI_WIDGET(QDialogButtonBox, "button_box"); + + connect(ui_button_box, SIGNAL(accepted()), this, SLOT(accept())); + connect(ui_button_box, SIGNAL(rejected()), this, SLOT(reject())); +} + +DRServerInfoEditor::~DRServerInfoEditor() +{} + +DRServerInfo DRServerInfoEditor::get_server_info() +{ + DRServerInfo l_server_info = m_server_info; + + l_server_info.name = ui_name->text(); + if (l_server_info.name.isEmpty()) + l_server_info.name = ui_name->placeholderText(); + l_server_info.description = ui_description->toPlainText(); + l_server_info.address = ui_address->text(); + l_server_info.port = ui_port->value(); + + return l_server_info; +} + +void DRServerInfoEditor::set_server_info(DRServerInfo p_server_info) +{ + m_server_info = p_server_info; + ui_name->setText(m_server_info.name); + ui_description->setPlainText(m_server_info.description); + ui_address->setText(m_server_info.address); + ui_port->setValue(m_server_info.port); +} diff --git a/src/drserverinfoeditor.h b/src/drserverinfoeditor.h new file mode 100644 index 000000000..e5ebf3abd --- /dev/null +++ b/src/drserverinfoeditor.h @@ -0,0 +1,32 @@ +#pragma once + +#include "datatypes.h" + +#include +#include +#include +#include +#include + +class DRServerInfoEditor : public QDialog +{ + Q_OBJECT + +public: + DRServerInfoEditor(QWidget *parent = nullptr); + ~DRServerInfoEditor(); + + DRServerInfo get_server_info(); + +public slots: + void set_server_info(DRServerInfo p_server_info); + +private: + DRServerInfo m_server_info; + + QLineEdit *ui_name; + QPlainTextEdit *ui_description; + QLineEdit *ui_address; + QSpinBox *ui_port; + QDialogButtonBox *ui_button_box; +}; diff --git a/src/drserverinfoeditor.ui b/src/drserverinfoeditor.ui new file mode 100644 index 000000000..04e5efa6b --- /dev/null +++ b/src/drserverinfoeditor.ui @@ -0,0 +1,111 @@ + + + Form + + + + 0 + 0 + 320 + 209 + + + + + 0 + 0 + + + + + 320 + 0 + + + + Form + + + + + + Name: + + + + + + + The name of the server. + + + Undefined + + + + + + + Description: + + + + + + + The description of the server. + + + + + + + Address: + + + + + + + The address of the server. May be IPv4 or IPv6. + + + 127.0.0.1 + + + + + + + Port: + + + + + + + The port to connect to the server. May range from 1024 to 65535. + + + 1024 + + + 65535 + + + 50000 + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/src/lobby.cpp b/src/lobby.cpp index 0ab976263..5b495fb70 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -10,10 +10,12 @@ #include "drchatlog.h" #include "drmasterclient.h" #include "drpacket.h" +#include "drserverinfoeditor.h" #include "drtextedit.h" #include "theme.h" #include "version.h" +#include #include #include #include @@ -22,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +54,14 @@ Lobby::Lobby(AOApplication *p_ao_app) ui_version->setReadOnly(true); ui_config_panel = new AOButton(this, ao_app); ui_server_list = new QListWidget(this); + ui_server_list->setContextMenuPolicy(Qt::CustomContextMenu); + + ui_server_menu = new QMenu(this); + ui_server_menu->addSection(tr("Server")); + ui_create_server = ui_server_menu->addAction(tr("Add")); + ui_modify_server = ui_server_menu->addAction(tr("Edit")); + ui_delete_server = ui_server_menu->addAction(tr("Remove")); + ui_player_count = new DRTextEdit(this); ui_player_count->setFrameStyle(QFrame::NoFrame); ui_player_count->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -72,28 +83,41 @@ Lobby::Lobby(AOApplication *p_ao_app) ui_cancel = new AOButton(ui_loading_background, ao_app); connect(ao_app, SIGNAL(reload_theme()), this, SLOT(update_widgets())); + connect(ao_app, SIGNAL(connecting_to_server()), this, SLOT(_p_on_connecting_to_server())); + connect(ao_app, SIGNAL(connected_to_server()), this, SLOT(_p_on_connected_to_server())); + connect(ao_app, SIGNAL(closed_connection_to_server()), this, SLOT(_p_on_closed_connection_to_server())); + connect(ao_app, SIGNAL(disconnected_from_server()), this, SLOT(_p_on_disconnected_from_server())); + connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(update_widgets())); connect(ao_config, SIGNAL(server_advertiser_changed(QString)), m_master_client, SLOT(set_address(QString))); + connect(m_master_client, SIGNAL(address_changed()), this, SLOT(request_advertiser_update())); connect(m_master_client, SIGNAL(motd_changed()), this, SLOT(update_motd())); connect(m_master_client, SIGNAL(server_list_changed()), this, SLOT(update_server_list())); - connect(ao_app, SIGNAL(connecting_to_server()), this, SLOT(_p_on_connecting_to_server())); - connect(ao_app, SIGNAL(connected_to_server()), this, SLOT(_p_on_connected_to_server())); - connect(ao_app, SIGNAL(closed_connection_to_server()), this, SLOT(_p_on_closed_connection_to_server())); - connect(ao_app, SIGNAL(disconnected_from_server()), this, SLOT(_p_on_disconnected_from_server())); - connect(ui_public_server_filter, SIGNAL(clicked()), this, SLOT(toggle_public_server_filter())); + connect(ui_favorite_server_filter, SIGNAL(clicked()), this, SLOT(toggle_favorite_server_filter())); + connect(ui_refresh, SIGNAL(pressed()), this, SLOT(on_refresh_pressed())); connect(ui_refresh, SIGNAL(released()), this, SLOT(on_refresh_released())); + connect(ui_toggle_favorite, SIGNAL(pressed()), this, SLOT(on_add_to_fav_pressed())); connect(ui_toggle_favorite, SIGNAL(released()), this, SLOT(on_add_to_fav_released())); + connect(ui_connect, SIGNAL(pressed()), this, SLOT(on_connect_pressed())); connect(ui_connect, SIGNAL(released()), this, SLOT(on_connect_released())); + connect(ui_config_panel, SIGNAL(pressed()), this, SLOT(on_config_pressed())); connect(ui_config_panel, SIGNAL(released()), this, SLOT(on_config_released())); + connect(ui_server_list, SIGNAL(currentRowChanged(int)), this, SLOT(connect_to_server(int))); + connect(ui_server_list, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(show_server_context_menu(QPoint))); + + connect(ui_create_server, SIGNAL(triggered(bool)), this, SLOT(prompt_server_info_editor())); + connect(ui_modify_server, SIGNAL(triggered(bool)), this, SLOT(prompt_server_info_editor())); + connect(ui_delete_server, SIGNAL(triggered(bool)), this, SLOT(prompt_delete_server())); + connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); load_settings(); @@ -131,8 +155,7 @@ void Lobby::update_widgets() ui_background->set_theme_image("lobbybackground.png"); set_size_and_pos(ui_public_server_filter, "public_servers", LOBBY_DESIGN_INI, ao_app); - ui_public_server_filter->set_image(m_server_filter == PublicOnly ? "publicservers_selected.png" - : "publicservers.png"); + ui_public_server_filter->set_image(m_server_filter == PublicOnly ? "publicservers_selected.png" : "publicservers.png"); set_size_and_pos(ui_favorite_server_filter, "favorites", LOBBY_DESIGN_INI, ao_app); ui_favorite_server_filter->set_image(m_server_filter == FavoriteOnly ? "favorites_selected.png" : "favorites.png"); @@ -300,9 +323,9 @@ void Lobby::load_favorite_server_list() l_ini.beginGroup(i_group); DRServerInfo l_server; l_server.name = l_ini.value("name").toString(); + l_server.description = l_ini.value("description").toString(); l_server.address = l_ini.value("address").toString(); l_server.port = l_ini.value("port").toInt(); - l_server.favorite = true; l_server_list.append(std::move(l_server)); l_ini.endGroup(); } @@ -325,9 +348,9 @@ void Lobby::load_legacy_favorite_server_list() f_server.address = l_contents.at(0); f_server.port = l_contents.at(1).toInt(); f_server.name = l_contents.at(2); - f_server.favorite = true; l_server_list.append(std::move(f_server)); } + l_file.remove(); } set_favorite_server_list(l_server_list); } @@ -343,6 +366,7 @@ void Lobby::save_favorite_server_list() const DRServerInfo &i_server = m_favorite_server_list.at(i); l_ini.beginGroup(QString::number(i)); l_ini.setValue("name", i_server.name); + l_ini.setValue("description", i_server.description); l_ini.setValue("address", i_server.address); l_ini.setValue("port", i_server.port); l_ini.endGroup(); @@ -377,6 +401,7 @@ void Lobby::update_server_list() void Lobby::set_favorite_server_list(DRServerInfoList p_server_list) { m_favorite_server_list = p_server_list; + save_favorite_server_list(); update_combined_server_list(); emit favorite_server_list_changed(); } @@ -392,15 +417,18 @@ void Lobby::update_server_listing() ui_server_list->clear(); const QIcon l_favorite_icon = QPixmap(ao_app->find_theme_asset_path("favorite_server.png")); const QBrush l_favorite_color = ao_app->get_color("favorite_server_color", LOBBY_DESIGN_INI); - for (const DRServerInfo &l_server : qAsConst(m_combined_server_list)) + for (int i = 0; i < m_combined_server_list.length(); ++i) { + const DRServerInfo &l_server = m_combined_server_list.at(i); QListWidgetItem *l_server_item = new QListWidgetItem; ui_server_list->addItem(l_server_item); l_server_item->setText(l_server.name); - if (l_server.favorite) + l_server_item->setData(Qt::UserRole, false); + if (i < m_favorite_server_list.length()) { l_server_item->setIcon(l_favorite_icon); l_server_item->setBackground(l_favorite_color); + l_server_item->setData(Qt::UserRole, true); } } filter_server_listing(); @@ -411,7 +439,7 @@ void Lobby::filter_server_listing() for (int i = 0; i < ui_server_list->count(); ++i) { QListWidgetItem *l_server_item = ui_server_list->item(i); - l_server_item->setHidden(m_server_filter == (m_combined_server_list.at(i).favorite ? PublicOnly : FavoriteOnly)); + l_server_item->setHidden(m_server_filter == (l_server_item->data(Qt::UserRole).toBool() ? PublicOnly : FavoriteOnly)); } select_current_server(); } @@ -445,8 +473,7 @@ void Lobby::toggle_favorite_server_filter() void Lobby::update_server_filter_buttons() { - ui_public_server_filter->set_image(m_server_filter == PublicOnly ? "publicservers_selected.png" - : "publicservers.png"); + ui_public_server_filter->set_image(m_server_filter == PublicOnly ? "publicservers_selected.png" : "publicservers.png"); ui_favorite_server_filter->set_image(m_server_filter == FavoriteOnly ? "favorites_selected.png" : "favorites.png"); filter_server_listing(); } @@ -471,24 +498,16 @@ void Lobby::on_add_to_fav_pressed() void Lobby::on_add_to_fav_released() { ui_toggle_favorite->set_image("addtofav.png"); - DRServerInfoList l_new_list = m_favorite_server_list; - if (m_current_server.favorite) + DRServerInfoList l_server_info_list = m_favorite_server_list; + if (m_favorite_server_list.contains(m_current_server)) { - l_new_list.removeAll(m_current_server); + l_server_info_list.removeAll(m_current_server); } - else if (!m_favorite_server_list.contains(m_current_server)) + else { - m_current_server.favorite = true; - - const QString l_new_name = - QInputDialog::getText(this, windowTitle(), "Name", QLineEdit::Normal, m_current_server.name); - if (!l_new_name.isEmpty()) - m_current_server.name = l_new_name; - - l_new_list.append(m_current_server); + l_server_info_list.append(m_current_server); } - set_favorite_server_list(l_new_list); - save_favorite_server_list(); + set_favorite_server_list(l_server_info_list); } void Lobby::on_connect_pressed() @@ -508,12 +527,10 @@ void Lobby::on_connect_released() l_reason = "The server did not report any client version."; break; case VersionStatus::ServerOutdated: - l_reason = QString("The server is outdated.
    (Server version: %1, expected version: %2)") - .arg(ao_app->get_server_client_version().to_string(), get_version_number().to_string()); + l_reason = QString("The server is outdated.
    (Server version: %1, expected version: %2)").arg(ao_app->get_server_client_version().to_string(), get_version_number().to_string()); break; case VersionStatus::ClientOutdated: - l_reason = QString("Your client is outdated.
    (Client version: %1, expected version: %2)") - .arg(get_version_number().to_string(), ao_app->get_server_client_version().to_string()); + l_reason = QString("Your client is outdated.
    (Client version: %1, expected version: %2)").arg(get_version_number().to_string(), ao_app->get_server_client_version().to_string()); break; default: break; @@ -553,6 +570,65 @@ void Lobby::connect_to_server(int p_row) } } +void Lobby::show_server_context_menu(QPoint p_point) +{ + const QPoint l_global_point = ui_server_list->viewport()->mapToGlobal(p_point); + + m_server_list_index.reset(); + const auto l_item = ui_server_list->indexAt(p_point); + ui_create_server->setEnabled(true); + ui_modify_server->setDisabled(true); + ui_delete_server->setDisabled(true); + if (l_item.isValid()) + { + m_server_list_index = l_item.row(); + + if (l_item.row() < m_favorite_server_list.length()) + { + ui_create_server->setDisabled(true); + ui_modify_server->setEnabled(true); + ui_delete_server->setEnabled(true); + } + } + ui_server_menu->popup(l_global_point); +} + +void Lobby::prompt_server_info_editor() +{ + DRServerInfoEditor l_editor(this); + l_editor.setWindowTitle(tr("Add server")); + if (m_server_list_index.has_value()) + { + l_editor.set_server_info(m_combined_server_list.at(m_server_list_index.value())); + } + if (l_editor.exec()) + { + auto l_server_info = l_editor.get_server_info(); + auto l_server_info_list = m_favorite_server_list; + if (m_server_list_index.has_value() && m_server_list_index.value() < l_server_info_list.length()) + { + l_server_info_list.replace(m_server_list_index.value(), l_server_info); + } + else + { + l_server_info_list.append(l_server_info); + } + set_favorite_server_list(l_server_info_list); + } +} + +void Lobby::prompt_delete_server() +{ + const auto l_server_index = m_server_list_index.value(); + const auto l_server = m_combined_server_list.at(l_server_index); + if (prompt_warning(tr("Are you sure you wish to remove the server %1?").arg(l_server.name))) + { + auto l_server_list = m_favorite_server_list; + l_server_list.remove(l_server_index); + set_favorite_server_list(l_server_list); + } +} + void Lobby::_p_on_connecting_to_server() { set_connection_state(ConnectingState); diff --git a/src/lobby.h b/src/lobby.h index f63c8aae8..f8a5f1821 100644 --- a/src/lobby.h +++ b/src/lobby.h @@ -3,6 +3,11 @@ #include "datatypes.h" +#include +#include + +#include + class AOApplication; class AOButton; class AOConfig; @@ -11,14 +16,10 @@ class DRChatLog; class DRMasterClient; class DRTextEdit; -#include -#include - class QListWidget; class QLineEdit; class QProgressBar; class QTextBrowser; - class Lobby : public QMainWindow { Q_OBJECT @@ -89,6 +90,12 @@ class Lobby : public QMainWindow QProgressBar *ui_progress_bar = nullptr; AOButton *ui_cancel = nullptr; + QMenu *ui_server_menu; + std::optional m_server_list_index; + QAction *ui_create_server; + QAction *ui_modify_server; + QAction *ui_delete_server; + void load_settings(); void save_settings(); @@ -123,6 +130,10 @@ private slots: void on_config_released(); void connect_to_server(int row); + void show_server_context_menu(QPoint); + void prompt_server_info_editor(); + void prompt_delete_server(); + void _p_on_connecting_to_server(); void _p_on_connected_to_server(); void _p_on_closed_connection_to_server(); diff --git a/src/main.cpp b/src/main.cpp index 53527d0fb..192c131b0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,9 +6,6 @@ #include -#include "drcharactermovie.h" -#include "drscenemovie.h" - int main(int argc, char *argv[]) { #if defined(Q_OS_WINDOWS) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index f9aba016a..9b0ce8bf8 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -127,19 +127,6 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) is_courtroom_loaded = false; DRServerInfo l_current_server = m_lobby->get_selected_server(); - if (l_current_server.favorite) - { - const QString l_current_server_address = l_current_server.to_address(); - const DRServerInfoList l_server_list = m_lobby->get_combined_server_list(); - for (const DRServerInfo &i_server : qAsConst(l_server_list)) - { - if (l_current_server_address != i_server.to_address()) - continue; - l_current_server.name = i_server.name; - break; - } - } - QString l_window_title = "Danganronpa Online (" + get_version_string() + ")"; if (!l_current_server.name.isEmpty()) l_window_title = l_window_title + ": " + l_current_server.to_info(); @@ -162,8 +149,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) QVector l_chr_list = m_courtroom->get_character_list(); if (l_content.length() != l_chr_list.length()) { - qWarning() << "Server sent a character list of length " << l_content.length() - << "which is different from the expected length " << l_chr_list.length() << "so ignoring it."; + qWarning() << "Server sent a character list of length " << l_content.length() << "which is different from the expected length " << l_chr_list.length() << "so ignoring it."; return; } @@ -182,8 +168,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) char_type l_chr; l_chr.name = i_chr_name; l_chr_list.append(std::move(l_chr)); - m_lobby->set_loading_text("Loading chars:\n" + QString::number(++m_loaded_characters) + "/" + - QString::number(m_character_count)); + m_lobby->set_loading_text("Loading chars:\n" + QString::number(++m_loaded_characters) + "/" + QString::number(m_character_count)); } m_courtroom->set_character_list(l_chr_list); @@ -226,11 +211,9 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) m_courtroom->set_music_list(l_music_list); m_loaded_music = m_music_count; - m_lobby->set_loading_text("Loading music:\n" + QString::number(m_loaded_music) + "/" + - QString::number(m_music_count)); + m_lobby->set_loading_text("Loading music:\n" + QString::number(m_loaded_music) + "/" + QString::number(m_music_count)); int total_loading_size = m_character_count + m_evidence_count + m_music_count; - int loading_value = - ((m_loaded_characters + m_loaded_evidence + m_loaded_music) / static_cast(total_loading_size)) * 100; + int loading_value = ((m_loaded_characters + m_loaded_evidence + m_loaded_music) / static_cast(total_loading_size)) * 100; m_lobby->set_loading_value(loading_value); send_server_packet(DRPacket("RD")); } From de322159aa849196d9b5190f5a90cc197e155158 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 24 Jul 2022 03:25:33 +0200 Subject: [PATCH 804/842] Added move up/down server for server menu --- src/lobby.cpp | 28 +++++++++++++++++++++++++--- src/lobby.h | 5 +++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 5b495fb70..b95ba9e8a 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -60,6 +60,8 @@ Lobby::Lobby(AOApplication *p_ao_app) ui_server_menu->addSection(tr("Server")); ui_create_server = ui_server_menu->addAction(tr("Add")); ui_modify_server = ui_server_menu->addAction(tr("Edit")); + ui_move_up_server = ui_server_menu->addAction(tr("Move up")); + ui_move_down_server = ui_server_menu->addAction(tr("Move down")); ui_delete_server = ui_server_menu->addAction(tr("Remove")); ui_player_count = new DRTextEdit(this); @@ -117,6 +119,8 @@ Lobby::Lobby(AOApplication *p_ao_app) connect(ui_create_server, SIGNAL(triggered(bool)), this, SLOT(prompt_server_info_editor())); connect(ui_modify_server, SIGNAL(triggered(bool)), this, SLOT(prompt_server_info_editor())); connect(ui_delete_server, SIGNAL(triggered(bool)), this, SLOT(prompt_delete_server())); + connect(ui_move_up_server, SIGNAL(triggered(bool)), this, SLOT(move_up_server())); + connect(ui_move_down_server, SIGNAL(triggered(bool)), this, SLOT(move_down_server())); connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); @@ -579,15 +583,19 @@ void Lobby::show_server_context_menu(QPoint p_point) ui_create_server->setEnabled(true); ui_modify_server->setDisabled(true); ui_delete_server->setDisabled(true); + ui_move_up_server->setDisabled(true); + ui_move_down_server->setDisabled(true); if (l_item.isValid()) { - m_server_list_index = l_item.row(); - - if (l_item.row() < m_favorite_server_list.length()) + const int l_item_row = l_item.row(); + m_server_list_index = l_item_row; + if (l_item_row < m_favorite_server_list.length()) { ui_create_server->setDisabled(true); ui_modify_server->setEnabled(true); ui_delete_server->setEnabled(true); + ui_move_up_server->setEnabled(l_item_row - 1 >= 0); + ui_move_down_server->setEnabled(l_item_row + 1 < m_favorite_server_list.length()); } } ui_server_menu->popup(l_global_point); @@ -629,6 +637,20 @@ void Lobby::prompt_delete_server() } } +void Lobby::move_up_server() +{ + auto l_server_list = m_favorite_server_list; + l_server_list.swapItemsAt(m_server_list_index.value(), m_server_list_index.value() - 1); + set_favorite_server_list(l_server_list); +} + +void Lobby::move_down_server() +{ + auto l_server_list = m_favorite_server_list; + l_server_list.swapItemsAt(m_server_list_index.value(), m_server_list_index.value() + 1); + set_favorite_server_list(l_server_list); +} + void Lobby::_p_on_connecting_to_server() { set_connection_state(ConnectingState); diff --git a/src/lobby.h b/src/lobby.h index f8a5f1821..5a89edc56 100644 --- a/src/lobby.h +++ b/src/lobby.h @@ -95,6 +95,9 @@ class Lobby : public QMainWindow QAction *ui_create_server; QAction *ui_modify_server; QAction *ui_delete_server; + QMenu *ui_swap_with; + QAction *ui_move_up_server; + QAction *ui_move_down_server; void load_settings(); void save_settings(); @@ -133,6 +136,8 @@ private slots: void show_server_context_menu(QPoint); void prompt_server_info_editor(); void prompt_delete_server(); + void move_up_server(); + void move_down_server(); void _p_on_connecting_to_server(); void _p_on_connected_to_server(); From 9efc39e254ea3dcfd027265d587d9f91efc1784e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 24 Jul 2022 04:43:45 +0200 Subject: [PATCH 805/842] Changed swap methodology --- src/lobby.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index b95ba9e8a..af2466977 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -31,6 +31,8 @@ #include #include +#include + Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() , m_connection_state(NotConnectedState) @@ -640,14 +642,16 @@ void Lobby::prompt_delete_server() void Lobby::move_up_server() { auto l_server_list = m_favorite_server_list; - l_server_list.swapItemsAt(m_server_list_index.value(), m_server_list_index.value() - 1); + const int l_server_index = m_server_list_index.value(); + std::swap(l_server_list.begin()[l_server_index], l_server_list.begin()[l_server_index - 1]); set_favorite_server_list(l_server_list); } void Lobby::move_down_server() { auto l_server_list = m_favorite_server_list; - l_server_list.swapItemsAt(m_server_list_index.value(), m_server_list_index.value() + 1); + const int l_server_index = m_server_list_index.value(); + std::swap(l_server_list.begin()[l_server_index], l_server_list.begin()[l_server_index + 1]); set_favorite_server_list(l_server_list); } From d38b12671d6e4828a779357019d608ff1c51dda2 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 24 Jul 2022 17:54:56 +0200 Subject: [PATCH 806/842] Added default position, deprecated /pos client-side --- src/courtroom.cpp | 114 +++++++++++--------------------------- src/courtroom.h | 8 ++- src/courtroom_widgets.cpp | 29 ++++------ src/emotes.cpp | 19 +++---- 4 files changed, 57 insertions(+), 113 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a70d005d9..e568b76e3 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -434,8 +434,7 @@ void Courtroom::update_background_scene() if (l_prev_background_name.isEmpty() || l_prev_background_name != m_background_name) { - const QString l_positions_ini = - ao_app->find_asset_path(ao_app->get_background_path(m_background_name) + "/" + "positions.ini"); + const QString l_positions_ini = ao_app->find_asset_path(ao_app->get_background_path(m_background_name) + "/" + "positions.ini"); if (l_positions_ini.isEmpty() || !m_position_map.load_file(l_positions_ini)) { m_position_map = get_legacy_background(m_background_name); @@ -535,8 +534,7 @@ void Courtroom::set_background(DRAreaBackground p_background) if (!l_missing_backgrounds.isEmpty()) { - ui_ooc_chatlog->append_chatmessage("[WARNING]", - "Missing background(s) detected: " + l_missing_backgrounds.join(", ")); + ui_ooc_chatlog->append_chatmessage("[WARNING]", "Missing background(s) detected: " + l_missing_backgrounds.join(", ")); } update_background_scene(); @@ -692,6 +690,15 @@ void Courtroom::list_note_files() } } +QString Courtroom::get_current_position() +{ + if (ui_pos_dropdown->currentIndex() == DefaultPositionIndex) + { + return ao_app->get_char_side(get_character_ini()); + } + return ui_pos_dropdown->currentData(Qt::UserRole).toString(); +} + void Courtroom::load_note() { // Do not attempt to load anything if no file was chosen. This makes it so @@ -802,8 +809,7 @@ void Courtroom::on_ic_message_return_pressed() const DREmote &l_emote = get_emote(m_emote_id); - const QString l_desk_modifier = - l_emote.desk_modifier == -1 ? QString("chat") : QString::number(l_emote.desk_modifier); + const QString l_desk_modifier = l_emote.desk_modifier == -1 ? QString("chat") : QString::number(l_emote.desk_modifier); packet_contents.append(l_desk_modifier); packet_contents.append(l_emote.anim); @@ -817,8 +823,7 @@ void Courtroom::on_ic_message_return_pressed() packet_contents.append(ui_ic_chat_message_field->text()); - const QString l_side = ao_app->get_char_side(get_character_ini()); - packet_contents.append(l_side); + packet_contents.append(get_current_position()); // sfx file const QString l_sound_file = current_sfx_file(); @@ -940,9 +945,7 @@ void Courtroom::next_chatmessage(QStringList p_chatmessage) } } - const QString l_message = QString(p_chatmessage[CMMessage]) - .remove(QRegularExpression("(?get_background_sprite_path(m_background_name, l_position.get_back())); - l_file_list.insert(ViewportStageFront, - ao_app->get_background_sprite_path(m_background_name, l_position.get_front())); + l_file_list.insert(ViewportStageBack, ao_app->get_background_sprite_path(m_background_name, l_position.get_back())); + l_file_list.insert(ViewportStageFront, ao_app->get_background_sprite_path(m_background_name, l_position.get_front())); } // characters @@ -1096,9 +1097,7 @@ void Courtroom::handle_chatmessage() return; } - const QString l_message = QString(m_chatmessage[CMMessage]) - .remove(QRegularExpression("(?find_theme_asset_path("characters/" + f_char + "/showname", static_extensions()); if (l_showname_image.isEmpty()) - l_showname_image = - ao_app->find_asset_path({ao_app->get_character_path(f_char, "showname")}, static_extensions()); + l_showname_image = ao_app->find_asset_path({ao_app->get_character_path(f_char, "showname")}, static_extensions()); ui_vp_showname_image->set_image(l_showname_image); ui_vp_showname_image->show(); } @@ -1376,8 +1374,7 @@ void Courtroom::handle_chatmessage_3() m_system_player->play(ao_app->get_sfx("word_call")); ao_app->alert(this); const QString name = "CLIENT"; - const QString message = - ui_vp_showname->toPlainText() + " has called you via your callword \"" + word + "\": \"" + f_message + "\""; + const QString message = ui_vp_showname->toPlainText() + " has called you via your callword \"" + word + "\": \"" + f_message + "\""; ui_ooc_chatlog->append_chatmessage(name, message); if (ao_config->log_is_recording_enabled()) save_textlog("(OOC)" + name + ": " + message); @@ -1402,9 +1399,7 @@ void Courtroom::load_ic_text_format() m_ic_log_format.base.setForeground(ui_ic_chatlog->palette().color(ui_ic_chatlog->foregroundRole())); auto set_format_color = [this](const QString &f_identifier, QTextCharFormat &f_format) { - if (const std::optional l_color = - ao_app->maybe_color(QString("ic_chatlog_%1_color").arg(f_identifier), COURTROOM_FONTS_INI); - l_color.has_value()) + if (const std::optional l_color = ao_app->maybe_color(QString("ic_chatlog_%1_color").arg(f_identifier), COURTROOM_FONTS_INI); l_color.has_value()) f_format.setForeground(l_color.value()); if (ao_app->get_font_property(QString("ic_chatlog_%1_bold").arg(f_identifier), COURTROOM_FONTS_INI)) @@ -1427,8 +1422,7 @@ void Courtroom::load_ic_text_format() void Courtroom::update_ic_log(bool p_reset_log) { - if (const int l_record_count = m_ic_record_list.length() + m_ic_record_queue.length(); - l_record_count > ao_config->log_max_lines()) + if (const int l_record_count = m_ic_record_list.length() + m_ic_record_queue.length(); l_record_count > ao_config->log_max_lines()) m_ic_record_list = m_ic_record_list.mid(l_record_count - ao_config->log_max_lines()); if (p_reset_log || ao_config->log_max_lines() == 1) @@ -1459,8 +1453,7 @@ void Courtroom::update_ic_log(bool p_reset_log) QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); const int l_scroll_pos = l_scrollbar->value(); - const bool l_is_end_scroll_pos = p_reset_log || (l_topdown_orientation ? l_scroll_pos == l_scrollbar->maximum() - : l_scroll_pos == l_scrollbar->minimum()); + const bool l_is_end_scroll_pos = p_reset_log || (l_topdown_orientation ? l_scroll_pos == l_scrollbar->maximum() : l_scroll_pos == l_scrollbar->minimum()); while (!m_ic_record_queue.isEmpty()) { @@ -1484,8 +1477,7 @@ void Courtroom::update_ic_log(bool p_reset_log) l_cursor.movePosition(move_type); // self-highlight check - const QTextCharFormat &l_target_name_format = - (l_record.is_self() && ao_config->log_display_self_highlight_enabled()) ? l_selfname_format : l_name_format; + const QTextCharFormat &l_target_name_format = (l_record.is_self() && ao_config->log_display_self_highlight_enabled()) ? l_selfname_format : l_name_format; if (ao_config->log_display_timestamp_enabled()) l_cursor.insertText(QString("[%1] ").arg(l_record.get_timestamp().toString("hh:mm")), l_target_name_format); @@ -1514,11 +1506,9 @@ void Courtroom::update_ic_log(bool p_reset_log) } { // remove unneeded blocks - const int l_max_block_count = m_ic_record_list.length() * (1 + l_use_newline) + - (l_use_newline * (m_ic_record_list.length() - 1)) + !l_topdown_orientation; + const int l_max_block_count = m_ic_record_list.length() * (1 + l_use_newline) + (l_use_newline * (m_ic_record_list.length() - 1)) + !l_topdown_orientation; const QTextCursor::MoveOperation l_orientation = l_topdown_orientation ? QTextCursor::Start : QTextCursor::End; - const QTextCursor::MoveOperation l_block_orientation = - l_topdown_orientation ? QTextCursor::NextBlock : QTextCursor::PreviousBlock; + const QTextCursor::MoveOperation l_block_orientation = l_topdown_orientation ? QTextCursor::NextBlock : QTextCursor::PreviousBlock; const int l_remove_block_count = ui_ic_chatlog->document()->blockCount() - l_max_block_count; if (l_remove_block_count > 0) @@ -1541,8 +1531,7 @@ void Courtroom::on_ic_chatlog_scroll_changed() QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); const int l_scroll_pos = l_scrollbar->value(); const bool l_topdown_orientation = ao_config->log_is_topdown_enabled(); - const bool l_is_end_scroll_pos = - (l_topdown_orientation ? l_scroll_pos == l_scrollbar->maximum() : l_scroll_pos == l_scrollbar->minimum()); + const bool l_is_end_scroll_pos = (l_topdown_orientation ? l_scroll_pos == l_scrollbar->maximum() : l_scroll_pos == l_scrollbar->minimum()); if (l_is_end_scroll_pos) { @@ -1570,8 +1559,7 @@ void Courtroom::on_ic_chatlog_scroll_bottomup_clicked() l_scrollbar->setValue(l_scrollbar->minimum()); } -void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, int p_client_id, - bool p_self) +void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, int p_client_id, bool p_self) { if (p_name.trimmed().isEmpty()) p_name = "Anonymous"; @@ -1618,8 +1606,7 @@ void Courtroom::play_preanim() const QString l_chr_name = m_chatmessage[CMChrName]; const QString l_anim_name = m_chatmessage[CMPreAnim]; - qDebug() << "[viewport] Playing character animation; character:" << l_chr_name << "animation: " << l_anim_name - << "file:" << ui_vp_player_char->file_name(); + qDebug() << "[viewport] Playing character animation; character:" << l_chr_name << "animation: " << l_anim_name << "file:" << ui_vp_player_char->file_name(); ui_vp_player_char->set_play_once(true); swap_viewport_reader(ui_vp_player_char, ViewportCharacterPre); ui_vp_player_char->start(); @@ -1947,8 +1934,7 @@ void Courtroom::handle_song(QStringList p_contents) l_showname = ao_app->get_showname(m_chr_list.at(l_chr_id).name); } - append_ic_text(l_showname, "has played a song: " + l_song_meta.title(), false, true, NoClientId, - l_chr_id == m_chr_id); + append_ic_text(l_showname, "has played a song: " + l_song_meta.title(), false, true, NoClientId, l_chr_id == m_chr_id); if (ao_config->log_is_recording_enabled()) { @@ -2141,48 +2127,10 @@ void Courtroom::on_ooc_message_return_pressed() ui_ooc_chat_message->setFocus(); } -void Courtroom::on_pos_dropdown_changed(int p_index) +void Courtroom::on_pos_dropdown_changed() { + set_judge_enabled(get_current_position() == "jud"); ui_ic_chat_message_field->setFocus(); - - if (p_index < 0 || p_index > 5) - return; - - QString f_pos; - - switch (p_index) - { - case 0: - f_pos = "wit"; - break; - case 1: - f_pos = "def"; - break; - case 2: - f_pos = "pro"; - break; - case 3: - f_pos = "jud"; - break; - case 4: - f_pos = "hld"; - break; - case 5: - f_pos = "hlp"; - break; - default: - f_pos = ""; - } - - if (f_pos == "" || ao_config->username() == "") - return; - - set_judge_enabled(f_pos == "jud"); - - send_ooc_packet("/pos " + f_pos); - // Uncomment later and remove above - // Will only work in TSDR 4.3+ servers - // ao_app->send_server_packet(DRPacket("SP", {f_pos})); } void Courtroom::on_area_list_clicked() diff --git a/src/courtroom.h b/src/courtroom.h index fc8983298..1e3b2f415 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -452,6 +452,11 @@ class Courtroom : public QWidget QComboBox *ui_emote_dropdown = nullptr; QComboBox *ui_iniswap_dropdown = nullptr; + + enum PositionIndex + { + DefaultPositionIndex, + }; QComboBox *ui_pos_dropdown = nullptr; AOImageDisplay *ui_defense_bar = nullptr; @@ -561,6 +566,7 @@ class Courtroom : public QWidget void fill_emote_dropdown(); DREmote get_emote(const int id); DREmote get_current_emote(); + QString get_current_position(); void load_note(); void save_note(); @@ -635,7 +641,7 @@ private slots: void on_emote_dropdown_changed(int p_index); void on_iniswap_dropdown_changed(int p_index); void update_iniswap_dropdown_searchable(); - void on_pos_dropdown_changed(int p_index); + void on_pos_dropdown_changed(); void on_cycle_clicked(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 334904bd2..e88720185 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -313,11 +313,10 @@ void Courtroom::connect_widgets() connect(ui_emote_dropdown, SIGNAL(activated(int)), this, SLOT(on_emote_dropdown_changed(int))); connect(ui_iniswap_dropdown, SIGNAL(activated(int)), this, SLOT(on_iniswap_dropdown_changed(int))); - connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); + connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed())); connect(ao_config, SIGNAL(showname_changed(QString)), this, SLOT(on_showname_changed(QString))); - connect(ao_config, SIGNAL(showname_placeholder_changed(QString)), this, - SLOT(on_showname_placeholder_changed(QString))); + connect(ao_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(on_character_ini_changed())); connect(ui_ic_chat_showname, SIGNAL(editingFinished()), this, SLOT(on_ic_showname_editing_finished())); connect(ui_ic_chat_message_field, SIGNAL(returnPressed()), this, SLOT(on_ic_message_return_pressed())); @@ -332,8 +331,7 @@ void Courtroom::connect_widgets() connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_music_list_clicked())); connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_music_list_double_clicked(QModelIndex))); - connect(ui_music_list, SIGNAL(customContextMenuRequested(QPoint)), this, - SLOT(on_music_list_context_menu_requested(QPoint))); + connect(ui_music_list, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(on_music_list_context_menu_requested(QPoint))); connect(ui_music_menu_play, SIGNAL(triggered()), this, SLOT(on_music_menu_play_triggered())); connect(ui_music_menu_insert_ooc, SIGNAL(triggered()), this, SLOT(on_music_menu_insert_ooc_triggered())); @@ -387,10 +385,8 @@ void Courtroom::connect_widgets() connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); connect(ui_hide_character, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); - connect(ui_sfx_list, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, - SLOT(on_sfx_list_current_item_changed(QListWidgetItem *, QListWidgetItem *))); - connect(ui_sfx_list, SIGNAL(customContextMenuRequested(QPoint)), this, - SLOT(on_sfx_list_context_menu_requested(QPoint))); + connect(ui_sfx_list, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(on_sfx_list_current_item_changed(QListWidgetItem *, QListWidgetItem *))); + connect(ui_sfx_list, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(on_sfx_list_context_menu_requested(QPoint))); connect(ui_sfx_menu_preview, SIGNAL(triggered()), this, SLOT(on_sfx_menu_preview_triggered())); connect(ui_sfx_menu_insert_ooc, SIGNAL(triggered()), this, SLOT(on_sfx_menu_insert_ooc_triggered())); @@ -833,8 +829,7 @@ void Courtroom::set_widgets() ui_effect_down->set_image("effectdown.png"); ui_effect_down->hide(); - if (ao_app->read_theme_ini_bool("enable_single_effect", COURTROOM_CONFIG_INI) && - ui_effects.size() > 0) // check to prevent crashing + if (ao_app->read_theme_ini_bool("enable_single_effect", COURTROOM_CONFIG_INI) && ui_effects.size() > 0) // check to prevent crashing { for (auto &effect : ui_effects) move_widget(effect, "effect"); @@ -925,8 +920,7 @@ void Courtroom::set_widgets() // be moved to 0, 0 A similar behavior will occur if the button is resized to 0, 0 due to 'config_panel' not being // found in courtroom_design.ini This is to assist with people who switch to incompatible and/or smaller themes and // have the button disappear - if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || ui_config_panel->width() <= 0 || - ui_config_panel->height() <= 0) + if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || ui_config_panel->width() <= 0 || ui_config_panel->height() <= 0) { ui_config_panel->setVisible(true); ui_config_panel->move(0, 0); @@ -1140,8 +1134,7 @@ void Courtroom::check_effects() for (int i = 0; i < ui_effects.size(); ++i) { - QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), effect_names.at(i))}, - animated_extensions()); + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), effect_names.at(i))}, animated_extensions()); if (path.isEmpty()) path = ao_app->find_theme_asset_path(effect_names.at(i), animated_extensions()); effects_enabled[i] = (!path.isEmpty()); @@ -1159,8 +1152,7 @@ void Courtroom::check_shouts() for (int i = 0; i < ui_shouts.size(); ++i) { - QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), shout_names.at(i))}, - animated_extensions()); + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), shout_names.at(i))}, animated_extensions()); if (path.isEmpty()) path = ao_app->find_theme_asset_path(shout_names.at(i), animated_extensions()); @@ -1180,8 +1172,7 @@ void Courtroom::check_wtce() for (int i = 0; i < ui_wtce.size(); ++i) { - QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), wtce_names.at(i))}, - animated_extensions()); + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), wtce_names.at(i))}, animated_extensions()); if (path.isEmpty()) path = ao_app->find_theme_asset_path(wtce_names.at(i), animated_extensions()); wtce_enabled[i] = (!path.isEmpty()); diff --git a/src/emotes.cpp b/src/emotes.cpp index a839a2f82..16a56e31d 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -39,12 +39,13 @@ void Courtroom::construct_emotes() ui_emote_dropdown = new QComboBox(this); ui_pos_dropdown = new QComboBox(this); - ui_pos_dropdown->addItem("wit", "wit"); - ui_pos_dropdown->addItem("def", "def"); - ui_pos_dropdown->addItem("pro", "pro"); - ui_pos_dropdown->addItem("jud", "jud"); - ui_pos_dropdown->addItem("hld", "hld"); - ui_pos_dropdown->addItem("hlp", "hlp"); + ui_pos_dropdown->addItem("Default"); + ui_pos_dropdown->addItem("Witness", "wit"); + ui_pos_dropdown->addItem("Defense", "def"); + ui_pos_dropdown->addItem("Prosecutor", "pro"); + ui_pos_dropdown->addItem("Judge", "jud"); + ui_pos_dropdown->addItem("Defense Assistant", "hld"); + ui_pos_dropdown->addItem("Prosecutor Assistant", "hlp"); construct_emote_page_layout(); } @@ -121,15 +122,13 @@ void Courtroom::refresh_emote_page(const bool p_scroll_to_current_emote) return; const int l_emote_count = m_emote_list.length(); - const int l_page_count = - qFloor(l_emote_count / m_page_max_emote_count) + bool(l_emote_count % m_page_max_emote_count); + const int l_page_count = qFloor(l_emote_count / m_page_max_emote_count) + bool(l_emote_count % m_page_max_emote_count); if (p_scroll_to_current_emote) m_current_emote_page = m_emote_id / m_page_max_emote_count; m_current_emote_page = qBound(0, m_current_emote_page, l_page_count - 1); - const int l_current_page_emote_count = - qBound(0, l_emote_count - m_current_emote_page * m_page_max_emote_count, m_page_max_emote_count); + const int l_current_page_emote_count = qBound(0, l_emote_count - m_current_emote_page * m_page_max_emote_count, m_page_max_emote_count); if (m_current_emote_page + 1 < l_page_count) ui_emote_right->show(); From 355553ce0ee25464ca82247651206f297750f28e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 24 Jul 2022 19:23:43 +0200 Subject: [PATCH 807/842] Readded /pos because broken features otherwise. --- src/courtroom.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index e568b76e3..6bb3bf880 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2129,7 +2129,9 @@ void Courtroom::on_ooc_message_return_pressed() void Courtroom::on_pos_dropdown_changed() { - set_judge_enabled(get_current_position() == "jud"); + const QString l_pos = get_current_position(); + set_judge_enabled(l_pos == "jud"); + send_ooc_packet("/pos " + l_pos); ui_ic_chat_message_field->setFocus(); } From 182c72deb037fe87eac3cebe974fd4fa0634d904 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Mon, 25 Jul 2022 22:24:45 +0200 Subject: [PATCH 808/842] Allow blank servers and editor reset --- src/drserverinfoeditor.cpp | 22 ++++++++++++++------ src/drserverinfoeditor.h | 3 +-- src/drserverinfoeditor.ui | 5 +---- src/lobby.cpp | 41 ++++++++++++++++++++++++++------------ src/lobby.h | 10 +++++++++- 5 files changed, 55 insertions(+), 26 deletions(-) diff --git a/src/drserverinfoeditor.cpp b/src/drserverinfoeditor.cpp index 307fc3e16..503919b67 100644 --- a/src/drserverinfoeditor.cpp +++ b/src/drserverinfoeditor.cpp @@ -2,6 +2,8 @@ #include "aoguiloader.h" +#include + DRServerInfoEditor::DRServerInfoEditor(QWidget *parent) : QDialog(parent) { @@ -16,6 +18,7 @@ DRServerInfoEditor::DRServerInfoEditor(QWidget *parent) ui_port = AO_GUI_WIDGET(QSpinBox, "port"); ui_button_box = AO_GUI_WIDGET(QDialogButtonBox, "button_box"); + connect(ui_button_box->button(QDialogButtonBox::Reset), SIGNAL(clicked()), this, SLOT(clear_server_info())); connect(ui_button_box, SIGNAL(accepted()), this, SLOT(accept())); connect(ui_button_box, SIGNAL(rejected()), this, SLOT(reject())); } @@ -25,7 +28,7 @@ DRServerInfoEditor::~DRServerInfoEditor() DRServerInfo DRServerInfoEditor::get_server_info() { - DRServerInfo l_server_info = m_server_info; + DRServerInfo l_server_info; l_server_info.name = ui_name->text(); if (l_server_info.name.isEmpty()) @@ -39,9 +42,16 @@ DRServerInfo DRServerInfoEditor::get_server_info() void DRServerInfoEditor::set_server_info(DRServerInfo p_server_info) { - m_server_info = p_server_info; - ui_name->setText(m_server_info.name); - ui_description->setPlainText(m_server_info.description); - ui_address->setText(m_server_info.address); - ui_port->setValue(m_server_info.port); + ui_name->setText(p_server_info.name); + ui_description->setPlainText(p_server_info.description); + ui_address->setText(p_server_info.address); + ui_port->setValue(p_server_info.port); +} + +void DRServerInfoEditor::clear_server_info() +{ + ui_name->setText(nullptr); + ui_description->setPlainText(nullptr); + ui_address->setText(nullptr); + ui_port->setValue(ui_port->minimum()); } diff --git a/src/drserverinfoeditor.h b/src/drserverinfoeditor.h index e5ebf3abd..e84870da1 100644 --- a/src/drserverinfoeditor.h +++ b/src/drserverinfoeditor.h @@ -20,10 +20,9 @@ class DRServerInfoEditor : public QDialog public slots: void set_server_info(DRServerInfo p_server_info); + void clear_server_info(); private: - DRServerInfo m_server_info; - QLineEdit *ui_name; QPlainTextEdit *ui_description; QLineEdit *ui_address; diff --git a/src/drserverinfoeditor.ui b/src/drserverinfoeditor.ui index 04e5efa6b..90e44d8f2 100644 --- a/src/drserverinfoeditor.ui +++ b/src/drserverinfoeditor.ui @@ -92,15 +92,12 @@ 65535 - - 50000 -
    - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset diff --git a/src/lobby.cpp b/src/lobby.cpp index af2466977..77db3865f 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -118,8 +118,8 @@ Lobby::Lobby(AOApplication *p_ao_app) connect(ui_server_list, SIGNAL(currentRowChanged(int)), this, SLOT(connect_to_server(int))); connect(ui_server_list, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(show_server_context_menu(QPoint))); - connect(ui_create_server, SIGNAL(triggered(bool)), this, SLOT(prompt_server_info_editor())); - connect(ui_modify_server, SIGNAL(triggered(bool)), this, SLOT(prompt_server_info_editor())); + connect(ui_create_server, SIGNAL(triggered(bool)), this, SLOT(create_server_info())); + connect(ui_modify_server, SIGNAL(triggered(bool)), this, SLOT(modify_server_info())); connect(ui_delete_server, SIGNAL(triggered(bool)), this, SLOT(prompt_delete_server())); connect(ui_move_up_server, SIGNAL(triggered(bool)), this, SLOT(move_up_server())); connect(ui_move_down_server, SIGNAL(triggered(bool)), this, SLOT(move_down_server())); @@ -580,7 +580,8 @@ void Lobby::show_server_context_menu(QPoint p_point) { const QPoint l_global_point = ui_server_list->viewport()->mapToGlobal(p_point); - m_server_list_index.reset(); + m_server_index.reset(); + m_server_index_type = NoServerType; const auto l_item = ui_server_list->indexAt(p_point); ui_create_server->setEnabled(true); ui_modify_server->setDisabled(true); @@ -590,10 +591,10 @@ void Lobby::show_server_context_menu(QPoint p_point) if (l_item.isValid()) { const int l_item_row = l_item.row(); - m_server_list_index = l_item_row; + m_server_index = l_item_row; if (l_item_row < m_favorite_server_list.length()) { - ui_create_server->setDisabled(true); + m_server_index_type = FavoriteServer; ui_modify_server->setEnabled(true); ui_delete_server->setEnabled(true); ui_move_up_server->setEnabled(l_item_row - 1 >= 0); @@ -606,18 +607,18 @@ void Lobby::show_server_context_menu(QPoint p_point) void Lobby::prompt_server_info_editor() { DRServerInfoEditor l_editor(this); - l_editor.setWindowTitle(tr("Add server")); - if (m_server_list_index.has_value()) + l_editor.setWindowTitle(tr("Server Info Editor")); + if (m_server_index.has_value()) { - l_editor.set_server_info(m_combined_server_list.at(m_server_list_index.value())); + l_editor.set_server_info(m_combined_server_list.at(m_server_index.value())); } if (l_editor.exec()) { auto l_server_info = l_editor.get_server_info(); auto l_server_info_list = m_favorite_server_list; - if (m_server_list_index.has_value() && m_server_list_index.value() < l_server_info_list.length()) + if (m_server_index.has_value() && m_server_index.value() < l_server_info_list.length()) { - l_server_info_list.replace(m_server_list_index.value(), l_server_info); + l_server_info_list.replace(m_server_index.value(), l_server_info); } else { @@ -627,9 +628,23 @@ void Lobby::prompt_server_info_editor() } } +void Lobby::create_server_info() +{ + if (m_server_index_type == FavoriteServer) + { + m_server_index.reset(); + } + prompt_server_info_editor(); +} + +void Lobby::modify_server_info() +{ + prompt_server_info_editor(); +} + void Lobby::prompt_delete_server() { - const auto l_server_index = m_server_list_index.value(); + const auto l_server_index = m_server_index.value(); const auto l_server = m_combined_server_list.at(l_server_index); if (prompt_warning(tr("Are you sure you wish to remove the server %1?").arg(l_server.name))) { @@ -642,7 +657,7 @@ void Lobby::prompt_delete_server() void Lobby::move_up_server() { auto l_server_list = m_favorite_server_list; - const int l_server_index = m_server_list_index.value(); + const int l_server_index = m_server_index.value(); std::swap(l_server_list.begin()[l_server_index], l_server_list.begin()[l_server_index - 1]); set_favorite_server_list(l_server_list); } @@ -650,7 +665,7 @@ void Lobby::move_up_server() void Lobby::move_down_server() { auto l_server_list = m_favorite_server_list; - const int l_server_index = m_server_list_index.value(); + const int l_server_index = m_server_index.value(); std::swap(l_server_list.begin()[l_server_index], l_server_list.begin()[l_server_index + 1]); set_favorite_server_list(l_server_list); } diff --git a/src/lobby.h b/src/lobby.h index 5a89edc56..902a70162 100644 --- a/src/lobby.h +++ b/src/lobby.h @@ -91,7 +91,13 @@ class Lobby : public QMainWindow AOButton *ui_cancel = nullptr; QMenu *ui_server_menu; - std::optional m_server_list_index; + std::optional m_server_index; + enum ServerType + { + NoServerType, + FavoriteServer, + }; + ServerType m_server_index_type = NoServerType; QAction *ui_create_server; QAction *ui_modify_server; QAction *ui_delete_server; @@ -135,6 +141,8 @@ private slots: void show_server_context_menu(QPoint); void prompt_server_info_editor(); + void create_server_info(); + void modify_server_info(); void prompt_delete_server(); void move_up_server(); void move_down_server(); From b31838153ff1b041e431579956c5056bc56c35e0 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 26 Jul 2022 00:58:30 +0200 Subject: [PATCH 809/842] Fix viewport shout pathing * Fixed viewport shout pathing Shouts in the viewport were only looking for the shouts within the theme folder, ignoring the character folder. --- src/aoapplication.cpp | 20 ++++++++++---------- src/courtroom.cpp | 2 +- src/drshoutmovie.cpp | 1 - 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index b6276e472..12c76e020 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -5,7 +5,6 @@ #include "courtroom.h" #include "debug_functions.h" #include "drdiscord.h" -#include "drmasterclient.h" #include "drpacket.h" #include "drpather.h" #include "drserversocket.h" @@ -284,8 +283,7 @@ QString AOApplication::get_character_sprite_talk_path(QString character, QString QString AOApplication::get_background_sprite_path(QString p_background_name, QString p_image) { - const QString l_target_filename = - find_asset_path(get_background_path(p_background_name) + "/" + p_image); + const QString l_target_filename = find_asset_path(get_background_path(p_background_name) + "/" + p_image); return l_target_filename; } @@ -306,22 +304,24 @@ QString AOApplication::get_background_sfx_path(QString p_background, QString p_a QString AOApplication::get_shout_sprite_path(QString p_character, QString p_shout) { - QString l_file_name = find_asset_path( - {get_character_path(p_character, p_shout), get_character_path(p_character, p_shout + "_bubble")}, - animated_extensions()); + QStringList l_filepath_list{ + get_character_path(p_character, p_shout), + get_character_path(p_character, p_shout + "_bubble"), + }; - if (l_file_name.isEmpty()) + QString l_filename = find_asset_path(l_filepath_list, animated_extensions()); + if (l_filename.isEmpty()) { - l_file_name = find_theme_asset_path(p_shout, animated_extensions()); + l_filename = find_theme_asset_path(p_shout, animated_extensions()); } - if (l_file_name.isEmpty()) + if (l_filename.isEmpty()) { qWarning() << "error: shout not found" << "character:" << p_character << "shout:" << p_shout; } - return l_file_name; + return l_filename; } QString AOApplication::get_theme_sprite_path(QString p_file_name, QString p_character) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6bb3bf880..a49151b0f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1015,7 +1015,7 @@ void Courtroom::preload_chatmessage(QStringList p_contents) l_file_list.insert(ViewportCharacterTalk, ao_app->get_character_sprite_talk_path(l_character, l_emote)); // shouts - l_file_list.insert(ViewportShout, ao_app->get_theme_sprite_path(get_shout_name(l_shout_id), l_character)); + l_file_list.insert(ViewportShout, ao_app->get_shout_sprite_path(l_character, get_shout_name(l_shout_id))); // effects l_file_list.insert(ViewportEffect, ao_app->get_theme_sprite_path(get_effect_name(l_effect_id))); diff --git a/src/drshoutmovie.cpp b/src/drshoutmovie.cpp index 95d5a737d..fe194717e 100644 --- a/src/drshoutmovie.cpp +++ b/src/drshoutmovie.cpp @@ -1,7 +1,6 @@ #include "drshoutmovie.h" #include "aoapplication.h" -#include "file_functions.h" #include From f497460145da4fbca52aed00592dcf86117c5069 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 26 Jul 2022 02:08:02 +0200 Subject: [PATCH 810/842] Bumped rc version --- src/version.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.cpp b/src/version.cpp index 12614cbc8..8c3d77501 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -30,7 +30,7 @@ VersionNumber get_version_number() QString get_post_version() { - return "rc.4"; + return "rc.5"; } QString get_version_string() From 98380a7da546d341292499a0d93177101afe1bee Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 26 Jul 2022 05:09:31 +0200 Subject: [PATCH 811/842] Chatboxes are now hidden immediately before a video begins to play --- src/courtroom.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index a49151b0f..c1d61718a 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1135,6 +1135,12 @@ void Courtroom::handle_chatmessage() ui_vp_effect->stop(); ui_vp_effect->hide(); + ui_vp_message->clear(); + ui_vp_chatbox->hide(); + ui_vp_showname->hide(); + ui_vp_showname_image->hide(); + ui_vp_showname->setText(m_speaker_showname); + /** * WARNING No check prior to changing will cause an unrecoverable * exception. You have been warned! @@ -1219,12 +1225,6 @@ void Courtroom::handle_chatmessage_2() // handles IC load_theme(); } - ui_vp_message->clear(); - ui_vp_chatbox->hide(); - ui_vp_showname->hide(); - ui_vp_showname_image->hide(); - ui_vp_showname->setText(m_speaker_showname); - const QString l_chatbox_name = ao_app->get_chat(m_chatmessage[CMChrName]); const bool l_is_self = (ao_config->log_display_self_highlight_enabled() && m_speaker_chr_id == m_chr_id); ui_vp_chatbox->set_chatbox_image(l_chatbox_name, l_is_self); From 210d4aaaeeba1a67b916823db59b4de3656091f1 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 26 Jul 2022 05:49:50 +0200 Subject: [PATCH 812/842] Potential fix for character suddenly showing up while hidden --- src/courtroom.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index c1d61718a..8bf1f0070 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1595,7 +1595,6 @@ void Courtroom::play_preanim() { // set state anim_state = 1; - ui_vp_player_char->show(); if (m_msg_is_first_person) { From d1b0f050b8026742281c06c3e0b10cd36c53fdd0 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 26 Jul 2022 11:35:15 +0200 Subject: [PATCH 813/842] Reworked media tester --- data/sample.avi | Bin 0 -> 12382 bytes res.qrc | 2 +- res/tests/mp4_media_test.mp4 | Bin 3219 -> 0 bytes src/drmediatester.cpp | 16 +++++++++++----- src/drmediatester.h | 3 ++- src/main.cpp | 3 ++- 6 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 data/sample.avi delete mode 100644 res/tests/mp4_media_test.mp4 diff --git a/data/sample.avi b/data/sample.avi new file mode 100644 index 0000000000000000000000000000000000000000..3e4ab0eab34ae7a8dbcce925cd7689b0f79bdfec GIT binary patch literal 12382 zcmeHNPiz!b7=KgR1`jk_6m1OA2YN8r?#^sWDNTlSi>(x~5W&V6lj+-;*`1-YJJXqI zyY)gfm3TnI!Dv9-UQmd^#CXs~L%@p%h@6a(1o7aXSV^>cFoZ+v_wCLj(BsVfd_YCbnP}xj~W%v%8w5=XO$jIx2%|MwOo_75$wu#cg5ot)(|33)Ge615%4lJ9@8f+ zVvfR7V8Az@Pa(9EE{?vL_nc{P(Uak6c7ps1%v`Z0il3U zKqw#-5DEwd?r{ZhZ(=sFH^C;10Jbt#w6%byBI73LKLYRg-UJ5Y-b5SPVMk=(P;430bulp-km*oM=J;p@2|8C?FIN3J3*+0zv_yfKWgvAQTV^+&>E7{y-z_ zF}Oy_ftPjt{waM`rMNHiC|HMv2lo!`A3V?k{XJ~5uyuP{=}9ZHQrk_rzWTt&s!YNl z6c7sBdkWw$gx5?+$(^+ER2QBWgC-RXvhK{~Th3s7T0SCw_a7TYk9FG+0FjcwI*(+BRrIVIS)iNtIn8-tK8}))| z7z&@7Z>UOIhC%Q_OLoOHG|Z^L2+e2ZqNys~RJVM$#55SH(9ky>#}1&^Q|U4E5IXvJ zNtJ;DGhTJerkYjS6@?a95Ncj9W_uV>PcZIjZlMsEq1ut9q2mX6+GrJ5aAkb$RQ}9{I z3_z^B=1oK2Hq0GRTlQc91AlzHWsrI#aKyI{a|fGja6&}#5{~7YBgCy zr*qb^J!^~0OVt!vQ@m4l_#vo|tR5a{ZGRfhlh&(VnV%~oRX?n=PI9;n>r-{s$vRiC zJ_D>BV681L9r={3Id~`5oKLQLVPHf1AtIf<+YwTA;nunXpLLNIQuW%w0r;cpLTz1R zO025#A+Fx>#i)XxZoEXKw-Rh(tC*W>0md$A$RuJ`<-W;gt|LS?7n5w_jOQ}{m+|{b z#Q5B;7_Ve=*T^<1iVV*hywdT?!jr}L>_LoDrJ#@ZAIDn&<65E?7~cmN?>EHoMMeQ* z? z-(!H07mx2w_@4nB=l3%ANBJy{qkNNz_#+AbalmnY^V}ch`-bBv-*res/ui/config_panel.ui res/git/git_branch.txt res/git/git_hash.txt - res/tests/mp4_media_test.mp4 src/drserverinfoeditor.ui + data/sample.avi diff --git a/res/tests/mp4_media_test.mp4 b/res/tests/mp4_media_test.mp4 deleted file mode 100644 index ce21536697fdb3b8afed0acc51117c0918d1207e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3219 zcmcgtd2AF_7=ODxC~bjOs1&L35x^_$va>CvEg5XBXjP0vBBBtN*_qky(wUv@%=A(S zl|%4~N&roXS3Cd}W165SB8bGGA(5aU7)2$PNQ}1;f}!j0n>`q|)%b_;WqaeF)t$u1f)(`(Bm}%VAW30Y>Oq9Jeg$jUQZUQZ*Oes&zEsETcYt57w|b|g-v zsklxoZ;h@*CFr=WWUsx$&)D^$+xI!1RS`uqQ!!X)R8iAB(fODgEmBO;RI>4WrAK37 zFylIPC#bvZDN0gTp_nS!&>($rhpF)-?|H_uLmK!eEZa&S2-&QmLj$GZj+qoRV%aTV z2{eXSB*m_8c_M+CfT+zLg^fyl!^&DgU+wp<{GBtljI&GUcuuUo(Zlj0-yi6&Ja^^{ zd6!$Zv#-Qcl8YG2IhB-KwBIzm0O>{n6EQ0PHN0a;@miGg&qXF=2>@XMy zSs755qSt^n0BJet2n#G*Hb7}0)`)qG72-?_ZvsNVeLx@Z7H|mog|VVipc04!4*^GT zG8Y52z#V{zwLbn2y-VxH@Xk+C=j)*bJF9jHv*(R&9b^7f?_TNZU5Z_nz)1S0ayY@g z=l;HrclI(CXT$ZeWpu>cY3E_CobO}rmQ){?Sjj$4+y09l_TtJpJ-j5Vhws9pBc%d4 zr8~Trl@O&;F=|yyaT+P7x3_mHdMOuld?TGH^K{`ea$6oGd8LodA7J_5JhV00ZCSxs zE_obuwI5}ntZ9%Rf#&oCrRC(9mgl+T`7Zegm%P9wFLcR^T=HUf%DOUGCmRn?ZT}atu2$g$T%TFMBA^3U4{QSt00X#2*u$dB0la2KtAUSj zm2eitR|7W#c$cR=>G}NKk66jr4Thy@u z|KE;+u&Jty<+bRFgufE+^8CC+;@&T2pW1cuSj(FmE06MHlYjiFJ5XE0tGH~KDi@~L zb2Ct1FWka~88d{?^y$#F_+T;ZmbtTUsH)*j^P6F+s4^7IM!ZK;!!{52{nb?gzfcch z#J1xN)24NGb@}KY$Izsh&oEo3Q5s*w)-~7|aXV_ntOhPiAxRbmj(-)gnk#BZGvqc= zXz(}qxfGMM9!nMd-PH~LZb7K#x++H0ZfV&=u*5p98Ah8F!6cZL3Ea}6GHOW+{5)nlIpnA=*;Gd~ zW~-(q!3gq@)?rFLLD|sbl8w2HEyAN6jUfaKO^Iy6rlhMDxGpu?8nNSIfVZhVP>VvE z3*rGpY)e*Ss@!3VHGaxtO0*}YYDM5|%E3&%yG6f(IU!p2Ztp(yN8VRc;%C&o53 z+F{;K#fg4jE!a4vaj4WS*49H{*=k&@;n6rY9R~~#q?X!oo^(2_7u|i0x^~sX#NZDf ze?712g;CzGK0UGdQaSNW!Oe$@$FYj~U3;g5c6jsO{bVsK-_P3iJuuOE;qb^gzlo0@ z_p&+o=bv8Zs~3C%f?mrD-Ycj7)WW8|nWekGZDd6|CY9taez+p}g=Tb9->n78N7BRu zY2V7o$29es-OV4a-}8v0$aaHzolAXzU%#VxH1Qi}IXkn`!SB#eHR^!k*P)6`IVDY- zE^^fOCkE>p*{GJRxTQ^-0*-#gWrLenF?PH+<66_S>9(Y5`Nm26PK=xy+4Fw;ve}_Il6a^2XauU_H%FmLacTokrs)tc*GE4nH- zx9HH@sTwn(YH>;>a$?Kw)B4si3s17`iTxXv9X|W?%AMP4
    files. The video " + call_warning("Your operating system appears to not currently support video playback. The video " "playback will not work properly.

    In order for the feature to function properly, you " - "will need to install the appropriate codecs.

    For more information visit " + "will need to install additional codecs.

    For more information visit " "https://www.danganronpaonline.com"); + emit done(); + break; + + case QMediaPlayer::LoadedMedia: + emit done(); break; default: diff --git a/src/drmediatester.h b/src/drmediatester.h index 8d16d6de3..c8ee9bc3c 100644 --- a/src/drmediatester.h +++ b/src/drmediatester.h @@ -11,7 +11,8 @@ class DRMediaTester : public QObject DRMediaTester(QObject *parent = nullptr); ~DRMediaTester(); -private slots: +signals: + void done(); private: QMediaPlayer m_player; diff --git a/src/main.cpp b/src/main.cpp index 192c131b0..d14ee620f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,7 +48,8 @@ int main(int argc, char *argv[]) { AOConfig l_config; - DRMediaTester tester; + DRMediaTester *l_media_tester = new DRMediaTester(&app); + QObject::connect(l_media_tester, SIGNAL(done()), l_media_tester, SLOT(deleteLater())); app.load_fonts(); app.construct_lobby(); From 35ceaabc24ee87fcc081a22144de415ad59606d3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 26 Jul 2022 22:59:32 +0200 Subject: [PATCH 814/842] Mark version as release --- src/version.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.cpp b/src/version.cpp index 8c3d77501..0ddd89de5 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -30,7 +30,7 @@ VersionNumber get_version_number() QString get_post_version() { - return "rc.5"; + return ""; } QString get_version_string() From c49c81204fe405eff9be2476233ca097a79dab50 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 27 Jul 2022 01:43:45 +0200 Subject: [PATCH 815/842] Fix wrong showname being fetched --- src/courtroom.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 8bf1f0070..6dd9f09e0 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -935,14 +935,7 @@ void Courtroom::next_chatmessage(QStringList p_chatmessage) QString l_showname = p_chatmessage[CMShowName]; if (l_showname.isEmpty() && !l_system_speaking) { - if (!m_chr_list.isEmpty()) - { - l_showname = ao_app->get_showname(m_chr_list.at(l_message_chr_id).name); - } - else - { - l_showname = tr("???"); - } + l_showname = ao_app->get_showname(p_chatmessage[CMChrName]); } const QString l_message = QString(p_chatmessage[CMMessage]).remove(QRegularExpression("(?get_showname(m_chr_list.at(m_speaker_chr_id).name); + f_showname = ao_app->get_showname(m_chatmessage[CMChrName]); } m_speaker_showname = f_showname; From 6170b97f4a66ae8e13554c1af07d95f18efe6504 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 27 Jul 2022 06:08:45 +0200 Subject: [PATCH 816/842] Small optimization for positions --- src/courtroom.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 6dd9f09e0..04f54c55f 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1970,9 +1970,18 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) void Courtroom::set_character_position(QString p_pos) { - int index = ui_pos_dropdown->findData(p_pos); - if (index != -1) - ui_pos_dropdown->setCurrentIndex(index); + const bool l_is_default_pos = p_pos == ao_app->get_char_side(get_character_ini()); + + int l_pos_index = ui_pos_dropdown->currentIndex(); + if (!l_is_default_pos) + { + const int l_new_pos_index = ui_pos_dropdown->findData(p_pos); + if (l_new_pos_index != -1) + { + l_pos_index = l_new_pos_index; + } + } + ui_pos_dropdown->setCurrentIndex(l_pos_index); // enable judge mechanics if appropriate set_judge_enabled(p_pos == "jud"); @@ -2122,7 +2131,6 @@ void Courtroom::on_ooc_message_return_pressed() void Courtroom::on_pos_dropdown_changed() { const QString l_pos = get_current_position(); - set_judge_enabled(l_pos == "jud"); send_ooc_packet("/pos " + l_pos); ui_ic_chat_message_field->setFocus(); } From 1adae398bb5439e3602cf8ec8c9b3a2529d44f18 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 27 Jul 2022 06:20:43 +0200 Subject: [PATCH 817/842] Character position immediately reset after switching character --- src/courtroom.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 04f54c55f..d4d4db57d 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -366,6 +366,7 @@ void Courtroom::enter_courtroom(int p_cid) ui_emote_dropdown->setDisabled(is_spectating()); ui_iniswap_dropdown->setDisabled(is_spectating()); ui_ic_chat_message_field->setDisabled(is_spectating()); + set_character_position(ao_app->get_char_side(get_character_ini())); // restore line field focus l_current_field->setFocus(); From 1cf7ac61a863ebdf95e6430af1b8a19517557964 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 29 Jul 2022 16:41:13 +0200 Subject: [PATCH 818/842] Deprecated background sounds look-up and added ambient folder for later --- src/aoapplication.cpp | 15 +++++---------- src/aoapplication.h | 2 +- src/courtroom.cpp | 8 ++++---- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 12c76e020..aa12f3202 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -202,6 +202,11 @@ QString AOApplication::get_sfx_noext_path(QString p_file) return find_asset_path(get_sfx_dir_path() + "/" + p_file, audio_extensions()); } +QString AOApplication::get_ambient_sfx_path(QString p_file) +{ + return find_asset_path(get_sfx_dir_path() + "/ambient/" + p_file); +} + QString AOApplication::get_character_sprite_path(QString p_character, QString p_emote, QString p_prefix, bool p_use_placeholder) { bool l_valid = true; @@ -292,16 +297,6 @@ QString AOApplication::get_background_sprite_noext_path(QString background, QStr return find_asset_path(get_background_path(background) + "/" + image, animated_or_static_extensions()); } -QString AOApplication::get_background_sfx_path(QString p_background, QString p_ambient) -{ - const QStringList l_filelist{ - get_background_path(p_background) + "/" + p_ambient, - get_sfx_path(p_ambient), - }; - - return find_asset_path(l_filelist); -} - QString AOApplication::get_shout_sprite_path(QString p_character, QString p_shout) { QStringList l_filepath_list{ diff --git a/src/aoapplication.h b/src/aoapplication.h index ccdc8f461..918843aa9 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -237,13 +237,13 @@ private slots: QString get_sfx_dir_path(); QString get_sfx_path(QString sfx); QString get_sfx_noext_path(QString p_file); + QString get_ambient_sfx_path(QString p_file); QString get_character_sprite_path(QString character, QString emote, QString prefix, bool use_placeholder); QString get_character_sprite_pre_path(QString character, QString emote); QString get_character_sprite_idle_path(QString character, QString emote); QString get_character_sprite_talk_path(QString character, QString emote); QString get_background_sprite_path(QString background, QString image); QString get_background_sprite_noext_path(QString background, QString image); - QString get_background_sfx_path(QString background, QString ambient); QString get_shout_sprite_path(QString character, QString shout); QString get_theme_sprite_path(QString file_name, QString character); QString get_theme_sprite_path(QString file_name); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index d4d4db57d..b6d3050d9 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -406,14 +406,14 @@ void Courtroom::set_ambient(QString p_ambient_sfx) void Courtroom::play_ambient() { - QString l_filepath = ao_app->get_sfx_path(m_ambient_sfx); - - if (l_filepath.isEmpty()) + QString l_ambient = m_ambient_sfx; + if (l_ambient.isDetached()) { DRPosition l_position = m_position_map.get_position(m_chatmessage[CMPosition]); - l_filepath = ao_app->get_background_sfx_path(m_background_name, l_position.get_ambient_sfx()); + l_ambient = l_position.get_ambient_sfx(); } + QString l_filepath = ao_app->get_ambient_sfx_path(l_ambient); m_effects_player->play_ambient(l_filepath); } From 703fd4bb4cefe9f3ce2a006f9f650bed020a2a92 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 4 Aug 2022 01:53:22 +0200 Subject: [PATCH 819/842] Slight tweak to ensure chat arrow restarts --- src/courtroom.cpp | 2 +- src/mk2/spriteplayer.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index b6d3050d9..17f578606 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1850,7 +1850,7 @@ void Courtroom::post_chatmessage() if (ui_vp_chatbox->isVisible()) { - ui_vp_chat_arrow->start(); + ui_vp_chat_arrow->restart(); ui_vp_chat_arrow->show(); } } diff --git a/src/mk2/spriteplayer.cpp b/src/mk2/spriteplayer.cpp index fe94c6e66..35d3ada6b 100644 --- a/src/mk2/spriteplayer.cpp +++ b/src/mk2/spriteplayer.cpp @@ -244,10 +244,14 @@ void SpritePlayer::fetch_next_frame() if (!is_valid()) { - if (m_running && m_play_once) + if (m_running) { m_running = false; - emit finished(); + + if (m_play_once) + { + emit finished(); + } } return; From a0f5859edf9f1143d97204ec746e804ba6c7a526 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 4 Aug 2022 19:35:27 +0200 Subject: [PATCH 820/842] Lobby no longer reselect servers with empty names --- src/lobby.cpp | 5 +++++ src/mk2/spriteplayer.cpp | 12 ++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 77db3865f..7367425d1 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -452,6 +452,11 @@ void Lobby::filter_server_listing() void Lobby::select_current_server() { + if (m_current_server.name.isEmpty()) + { + return; + } + for (int i = 0; i < ui_server_list->count(); ++i) { QListWidgetItem *l_item = ui_server_list->item(i); diff --git a/src/mk2/spriteplayer.cpp b/src/mk2/spriteplayer.cpp index 35d3ada6b..649b5cf30 100644 --- a/src/mk2/spriteplayer.cpp +++ b/src/mk2/spriteplayer.cpp @@ -244,14 +244,10 @@ void SpritePlayer::fetch_next_frame() if (!is_valid()) { - if (m_running) + if (m_play_once && m_running) { m_running = false; - - if (m_play_once) - { - emit finished(); - } + emit finished(); } return; @@ -285,8 +281,8 @@ void SpritePlayer::fetch_next_frame() m_current_frame = m_reader->get_frame(l_current_frame_number); m_frame_number++; - const int l_next_delay = m_current_frame.delay - l_timer.elapsed(); - m_frame_timer.start(qMax(0, l_next_delay)); + const int l_next_delay = qMax(0, int(m_current_frame.delay - l_timer.elapsed())); + m_frame_timer.start(l_next_delay); scale_current_frame(); } From 04ee79b6b21c3eca098a8aa3b74332b6c5c325b3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Thu, 4 Aug 2022 19:49:00 +0200 Subject: [PATCH 821/842] Fixed add to favorite adding an invalid server to favorite --- src/lobby.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index 7367425d1..0700f9a79 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -509,16 +509,22 @@ void Lobby::on_add_to_fav_pressed() void Lobby::on_add_to_fav_released() { ui_toggle_favorite->set_image("addtofav.png"); - DRServerInfoList l_server_info_list = m_favorite_server_list; - if (m_favorite_server_list.contains(m_current_server)) + const auto l_index = ui_server_list->currentIndex(); + if (!l_index.isValid() || l_index.row() < m_favorite_server_list.length()) { - l_server_info_list.removeAll(m_current_server); + return; + } + const auto l_selected_server = m_combined_server_list.at(l_index.row()); + DRServerInfoList l_server_list = m_favorite_server_list; + if (m_favorite_server_list.contains(l_selected_server)) + { + l_server_list.removeAll(m_current_server); } else { - l_server_info_list.append(m_current_server); + l_server_list.append(m_current_server); } - set_favorite_server_list(l_server_info_list); + set_favorite_server_list(l_server_list); } void Lobby::on_connect_pressed() From ad4ccf6b80b4218c5acb3082993a2025c32d3bb3 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 5 Aug 2022 15:34:04 +0200 Subject: [PATCH 822/842] Loading/reloading themes no longer reset the reader cache --- src/courtroom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 17f578606..124f71ba3 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -74,6 +74,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app, QWidget *parent) m_chatmessage[CMPosition] = "wit"; setup_courtroom(); + map_viewport_viewers(); + map_viewport_readers(); set_char_select(); load_audiotracks(); reset_viewport(); @@ -119,8 +121,6 @@ void Courtroom::setup_courtroom() load_sfx_list_theme(); map_viewers(); - map_viewport_viewers(); - map_viewport_readers(); assign_readers_for_all_viewers(); m_shout_state = 0; From dbf72ae49fdc439997a639eea965fe686d251a62 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 5 Aug 2022 16:10:53 +0200 Subject: [PATCH 823/842] Potential fix for one-frame pre-animation issues --- src/mk2/spriteplayer.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/mk2/spriteplayer.cpp b/src/mk2/spriteplayer.cpp index 649b5cf30..a36f1b864 100644 --- a/src/mk2/spriteplayer.cpp +++ b/src/mk2/spriteplayer.cpp @@ -281,10 +281,16 @@ void SpritePlayer::fetch_next_frame() m_current_frame = m_reader->get_frame(l_current_frame_number); m_frame_number++; + scale_current_frame(); + const int l_next_delay = qMax(0, int(m_current_frame.delay - l_timer.elapsed())); + if (l_next_delay == 0 && m_frame_count == 1) + { + m_running = false; + emit finished(); + return; + } m_frame_timer.start(l_next_delay); - - scale_current_frame(); } void SpritePlayer::scale_current_frame() From 83b76b29e7fdff444650216ec8a2a3fe0a329c1b Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 5 Aug 2022 16:29:28 +0200 Subject: [PATCH 824/842] Resolve sprite crashing --- src/mk2/spriteplayer.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/mk2/spriteplayer.cpp b/src/mk2/spriteplayer.cpp index a36f1b864..f0dd78295 100644 --- a/src/mk2/spriteplayer.cpp +++ b/src/mk2/spriteplayer.cpp @@ -287,10 +287,16 @@ void SpritePlayer::fetch_next_frame() if (l_next_delay == 0 && m_frame_count == 1) { m_running = false; - emit finished(); - return; + + if (m_play_once) + { + emit finished(); + } + } + else + { + m_frame_timer.start(l_next_delay); } - m_frame_timer.start(l_next_delay); } void SpritePlayer::scale_current_frame() From a5e981560bdc103ff81d0fc4e58a7e68daebc303 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 5 Aug 2022 17:47:20 +0200 Subject: [PATCH 825/842] Delaying theme reloading during preloading of a message --- src/courtroom.cpp | 39 +++++++++++++++++++++++++-------------- src/courtroom.h | 8 ++++++++ src/courtroom_widgets.cpp | 4 ++++ src/drtextedit.h | 4 ++-- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 124f71ba3..415475e99 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -60,7 +60,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app, QWidget *parent) m_preloader_sync = new mk2::SpriteReaderSynchronizer(this); m_preloader_sync->set_threshold(ao_config->caching_threshold()); - connect(ao_app, SIGNAL(reload_theme()), this, SLOT(load_theme())); + connect(ao_app, SIGNAL(reload_theme()), this, SLOT(reload_theme())); connect(ao_app, SIGNAL(reload_character()), this, SLOT(load_character())); connect(ao_app, SIGNAL(reload_audiotracks()), this, SLOT(load_audiotracks())); @@ -955,6 +955,15 @@ void Courtroom::next_chatmessage(QStringList p_chatmessage) } } + { // clear interface if required + bool l_ok; + const int l_client_id = p_chatmessage[CMClientId].toInt(&l_ok); + if (l_ok && l_client_id == ao_app->get_client_id()) + { + handle_acknowledged_ms(); + } + } + preload_chatmessage(p_chatmessage); } @@ -979,15 +988,7 @@ void Courtroom::preload_chatmessage(QStringList p_contents) cleanup_preload_readers(); m_loading_timer->stop(); m_pre_chatmessage = p_contents; - - { - bool l_ok; - const int l_client_id = m_pre_chatmessage[CMClientId].toInt(&l_ok); - if (l_ok && l_client_id == ao_app->get_client_id()) - { - handle_acknowledged_ms(); - } - } + m_game_state = PreloadingState; QMap l_file_list; const QString l_position_id = m_pre_chatmessage[CMPosition]; @@ -1054,6 +1055,7 @@ void Courtroom::start_chatmessage() m_tick_timer->stop(); m_chatmessage = m_pre_chatmessage; + m_game_state = ProcessingState; handle_chatmessage(); } @@ -1215,9 +1217,9 @@ void Courtroom::handle_chatmessage_2() // handles IC if (m_shout_reload_theme) { - m_shout_reload_theme = false; load_theme(); } + m_shout_reload_theme = false; const QString l_chatbox_name = ao_app->get_chat(m_chatmessage[CMChrName]); const bool l_is_self = (ao_config->log_display_self_highlight_enabled() && m_speaker_chr_id == m_chr_id); @@ -1836,6 +1838,10 @@ void Courtroom::next_chat_letter() void Courtroom::post_chatmessage() { m_tick_timer->stop(); + if (m_game_state != PreloadingState) + { + m_game_state = FinishedState; + } text_state = 2; anim_state = 3; @@ -2484,14 +2490,19 @@ void Courtroom::on_change_character_clicked() void Courtroom::load_theme() { - if (ui_vp_objection->is_running()) + setup_courtroom(); + update_background_scene(); +} + +void Courtroom::reload_theme() +{ + if (m_game_state == PreloadingState || ui_vp_objection->is_running()) { m_shout_reload_theme = true; return; } - setup_courtroom(); - update_background_scene(); + load_theme(); } void Courtroom::load_character() diff --git a/src/courtroom.h b/src/courtroom.h index 1e3b2f415..ca51af960 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -286,6 +286,13 @@ class Courtroom : public QWidget static const int MINIMUM_MESSAGE_SIZE = 15; static const int OPTIMAL_MESSAGE_SIZE = 19; QStringList m_pre_chatmessage; + enum GameState + { + PreloadingState, + ProcessingState, + FinishedState, + }; + GameState m_game_state = FinishedState; QTimer *m_loading_timer; mk2::SpriteReaderSynchronizer *m_preloader_sync; QStringList m_chatmessage; @@ -681,6 +688,7 @@ private slots: void on_change_character_clicked(); void load_theme(); + void reload_theme(); void load_character(); void load_audiotracks(); void on_call_mod_clicked(); diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index e88720185..08ac4dbb3 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -1410,7 +1410,9 @@ void Courtroom::set_judge_wtce() void Courtroom::set_fonts() { set_drtextedit_font(ui_vp_showname, "showname", COURTROOM_FONTS_INI, ao_app); + ui_vp_showname->setPlainText(ui_vp_showname->toPlainText()); set_drtextedit_font(ui_vp_message, "message", COURTROOM_FONTS_INI, ao_app); + ui_vp_message->setPlainText(ui_vp_message->toPlainText()); set_drtextedit_font(ui_ic_chatlog, "ic_chatlog", COURTROOM_FONTS_INI, ao_app); // Chatlog does not support drtextedit because html @@ -1421,7 +1423,9 @@ void Courtroom::set_fonts() set_font(ui_area_list, "area_list", COURTROOM_FONTS_INI, ao_app); set_font(ui_sfx_list, "sfx_list", COURTROOM_FONTS_INI, ao_app); set_drtextedit_font(ui_vp_music_name, "music_name", COURTROOM_FONTS_INI, ao_app); + ui_vp_music_name->setPlainText(ui_vp_music_name->toPlainText()); set_drtextedit_font(ui_vp_notepad, "notepad", COURTROOM_FONTS_INI, ao_app); + ui_vp_notepad->setPlainText(ui_vp_notepad->toPlainText()); for (int i = 0; i < ui_timers.length(); ++i) { diff --git a/src/drtextedit.h b/src/drtextedit.h index b27579632..7f5d9f0cc 100644 --- a/src/drtextedit.h +++ b/src/drtextedit.h @@ -6,8 +6,8 @@ class DRTextEdit : public QTextEdit { Q_OBJECT - Q_PROPERTY( - Qt::Alignment text_alignment READ get_text_alignment WRITE set_text_alignment NOTIFY text_alignment_changed) + + Q_PROPERTY(Qt::Alignment text_alignment READ get_text_alignment WRITE set_text_alignment NOTIFY text_alignment_changed) public: DRTextEdit(QWidget *p_parent = nullptr); From 80cd7dd136426176238a2fa46da3b62fd09bb5c4 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 5 Aug 2022 17:53:13 +0200 Subject: [PATCH 826/842] Improved readability --- src/courtroom.cpp | 10 +++++----- src/courtroom.h | 11 ++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 415475e99..f399997cd 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -988,7 +988,7 @@ void Courtroom::preload_chatmessage(QStringList p_contents) cleanup_preload_readers(); m_loading_timer->stop(); m_pre_chatmessage = p_contents; - m_game_state = PreloadingState; + m_game_state = GameState::Preloading; QMap l_file_list; const QString l_position_id = m_pre_chatmessage[CMPosition]; @@ -1055,7 +1055,7 @@ void Courtroom::start_chatmessage() m_tick_timer->stop(); m_chatmessage = m_pre_chatmessage; - m_game_state = ProcessingState; + m_game_state = GameState::Processing; handle_chatmessage(); } @@ -1838,9 +1838,9 @@ void Courtroom::next_chat_letter() void Courtroom::post_chatmessage() { m_tick_timer->stop(); - if (m_game_state != PreloadingState) + if (m_game_state != GameState::Preloading) { - m_game_state = FinishedState; + m_game_state = GameState::Finished; } text_state = 2; anim_state = 3; @@ -2496,7 +2496,7 @@ void Courtroom::load_theme() void Courtroom::reload_theme() { - if (m_game_state == PreloadingState || ui_vp_objection->is_running()) + if (m_game_state == GameState::Preloading || ui_vp_objection->is_running()) { m_shout_reload_theme = true; return; diff --git a/src/courtroom.h b/src/courtroom.h index ca51af960..da8d5d68e 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -286,13 +286,14 @@ class Courtroom : public QWidget static const int MINIMUM_MESSAGE_SIZE = 15; static const int OPTIMAL_MESSAGE_SIZE = 19; QStringList m_pre_chatmessage; - enum GameState + enum class GameState { - PreloadingState, - ProcessingState, - FinishedState, + Preloading, + Processing, + Finished, }; - GameState m_game_state = FinishedState; + Q_ENUM(GameState) + GameState m_game_state = GameState::Finished; QTimer *m_loading_timer; mk2::SpriteReaderSynchronizer *m_preloader_sync; QStringList m_chatmessage; From 64f8c539db2bc234e478af85151ed809cfd05915 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 5 Aug 2022 18:10:11 +0200 Subject: [PATCH 827/842] Fix visibility for Q_ENUM --- src/courtroom.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/courtroom.h b/src/courtroom.h index da8d5d68e..981330d56 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -63,6 +63,14 @@ class Courtroom : public QWidget Q_OBJECT public: + enum class GameState + { + Preloading, + Processing, + Finished, + }; + Q_ENUM(GameState) + static const int DEFAULT_WIDTH; static const int DEFAULT_HEIGHT; @@ -286,13 +294,6 @@ class Courtroom : public QWidget static const int MINIMUM_MESSAGE_SIZE = 15; static const int OPTIMAL_MESSAGE_SIZE = 19; QStringList m_pre_chatmessage; - enum class GameState - { - Preloading, - Processing, - Finished, - }; - Q_ENUM(GameState) GameState m_game_state = GameState::Finished; QTimer *m_loading_timer; mk2::SpriteReaderSynchronizer *m_preloader_sync; From 3c6bc011db53fb150596fd871e9c9ed3a154863c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 6 Aug 2022 01:28:51 +0200 Subject: [PATCH 828/842] Added insert caption action for sound effects --- src/courtroom.h | 6 ++++-- src/courtroom_sfx.cpp | 19 ++++++++++++++++++- src/courtroom_widgets.cpp | 6 ++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/courtroom.h b/src/courtroom.h index 981330d56..293a3f1f6 100644 --- a/src/courtroom.h +++ b/src/courtroom.h @@ -439,7 +439,8 @@ class Courtroom : public QWidget QColor m_sfx_color_missing; QMenu *ui_sfx_menu = nullptr; QAction *ui_sfx_menu_preview = nullptr; - QAction *ui_sfx_menu_insert_ooc = nullptr; + QAction *ui_sfx_menu_insert_file_name = nullptr; + QAction *ui_sfx_menu_insert_caption = nullptr; QLineEdit *ui_ic_chat_showname = nullptr; QWidget *ui_ic_chat_message = nullptr; @@ -766,7 +767,8 @@ private slots: void on_sfx_list_current_item_changed(QListWidgetItem *current_item, QListWidgetItem *previous_item); void on_sfx_list_context_menu_requested(QPoint point); void on_sfx_menu_preview_triggered(); - void on_sfx_menu_insert_ooc_triggered(); + void on_sfx_menu_insert_file_name_triggered(); + void on_sfx_menu_insert_caption_triggered(); /*! * ============================================================================= diff --git a/src/courtroom_sfx.cpp b/src/courtroom_sfx.cpp index ff71b6ab4..a78523ac5 100644 --- a/src/courtroom_sfx.cpp +++ b/src/courtroom_sfx.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include @@ -143,8 +145,23 @@ void Courtroom::on_sfx_menu_preview_triggered() m_effects_player->play_effect(current_sfx_file()); } -void Courtroom::on_sfx_menu_insert_ooc_triggered() +void Courtroom::on_sfx_menu_insert_file_name_triggered() { ui_ooc_chat_message->insert(current_sfx_file()); ui_ooc_chat_message->setFocus(); } + +void Courtroom::on_sfx_menu_insert_caption_triggered() +{ + const std::optional l_sfx = current_sfx(); + if (l_sfx.has_value()) + { + QString l_caption = l_sfx->name; + static const QRegularExpression l_regex("\"(.+)\""); + if (const auto l_match = l_regex.match(l_caption); l_match.hasMatch()) + { + l_caption = l_match.captured(1); + } + ui_ic_chat_message_field->insert(l_caption); + } +} diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 08ac4dbb3..0e11c4d17 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -174,7 +174,8 @@ void Courtroom::create_widgets() ui_sfx_search->setFrame(false); ui_sfx_menu = new QMenu(this); ui_sfx_menu_preview = ui_sfx_menu->addAction(tr("Preview")); - ui_sfx_menu_insert_ooc = ui_sfx_menu->addAction(tr("Insert to OOC")); + ui_sfx_menu_insert_file_name = ui_sfx_menu->addAction(tr("Insert filename")); + ui_sfx_menu_insert_caption = ui_sfx_menu->addAction("Insert caption"); ui_ic_chat_message = new QWidget(this); @@ -389,7 +390,8 @@ void Courtroom::connect_widgets() connect(ui_sfx_list, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(on_sfx_list_context_menu_requested(QPoint))); connect(ui_sfx_menu_preview, SIGNAL(triggered()), this, SLOT(on_sfx_menu_preview_triggered())); - connect(ui_sfx_menu_insert_ooc, SIGNAL(triggered()), this, SLOT(on_sfx_menu_insert_ooc_triggered())); + connect(ui_sfx_menu_insert_file_name, SIGNAL(triggered()), this, SLOT(on_sfx_menu_insert_file_name_triggered())); + connect(ui_sfx_menu_insert_caption, SIGNAL(triggered()), this, SLOT(on_sfx_menu_insert_caption_triggered())); connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, SLOT(on_add_button_clicked())); connect(ui_set_notes, SIGNAL(clicked(bool)), this, SLOT(on_set_notes_clicked())); From 906f07e0cdcf3d3c21bf97ce1a22545ee4c8995e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 6 Aug 2022 21:22:52 +0200 Subject: [PATCH 829/842] Resolved ambient pathing issue --- src/aoapplication.cpp | 2 +- src/draudiostream.cpp | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index aa12f3202..97011d3b3 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -204,7 +204,7 @@ QString AOApplication::get_sfx_noext_path(QString p_file) QString AOApplication::get_ambient_sfx_path(QString p_file) { - return find_asset_path(get_sfx_dir_path() + "/ambient/" + p_file); + return find_asset_path(get_base_path() + "sounds/ambient/" + p_file); } QString AOApplication::get_character_sprite_path(QString p_character, QString p_emote, QString p_prefix, bool p_use_placeholder) diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp index e03bb9423..4821cbd91 100644 --- a/src/draudiostream.cpp +++ b/src/draudiostream.cpp @@ -60,8 +60,7 @@ void DRAudioStream::play() return; if (!BASS_ChannelPlay(m_hstream, FALSE)) { - qWarning() - << QString("error: failed to play file: %1 (file: \"%1\")").arg(DRAudio::get_last_bass_error(), m_filename); + qWarning() << QString("error: failed to play file: %1 (file: \"%1\")").arg(DRAudio::get_last_bass_error(), m_filename); Q_EMIT finished(); } } @@ -80,7 +79,7 @@ std::optional DRAudioStream::set_file_name(QString p_file_name) m_init_state = InitNotDone; if (!ensure_init()) { - return DRAudioError("failed to set file"); + return DRAudioError("failed to set file: " + p_file_name); } emit file_name_changed(m_filename); return std::nullopt; @@ -202,8 +201,7 @@ bool DRAudioStream::ensure_init() if (m_filename.endsWith("opus", Qt::CaseInsensitive)) l_hstream = BASS_OPUS_StreamCreateFile(FALSE, m_filename.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); else - l_hstream = - BASS_StreamCreateFile(FALSE, m_filename.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE | BASS_STREAM_PRESCAN); + l_hstream = BASS_StreamCreateFile(FALSE, m_filename.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE | BASS_STREAM_PRESCAN); if (!l_hstream) { From 5110b2263b00530644d7b2e6756876a03a498340 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sat, 6 Aug 2022 21:23:31 +0200 Subject: [PATCH 830/842] Bump version --- dronline-client.pro | 2 +- src/version.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index 0ee344c4d..d2363ad76 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -3,7 +3,7 @@ QT += core gui widgets uitools network multimedia multimediawidgets CONFIG += c++17 TEMPLATE = app -VERSION = 1.1.0.0 +VERSION = 1.2.1.0 TARGET = dro-client RC_ICONS = icon.ico diff --git a/src/version.cpp b/src/version.cpp index 0ddd89de5..3e6077300 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -20,7 +20,7 @@ int get_major_version() int get_minor_version() { - return 0; + return 1; } VersionNumber get_version_number() From fbdafe6fba3296f07e0d755a15ff624bf9d7308e Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 7 Aug 2022 01:56:49 +0200 Subject: [PATCH 831/842] Chat arrow now once again displays --- src/courtroom_widgets.cpp | 1 + src/mk2/spriteplayer.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 0e11c4d17..1496da60c 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -668,6 +668,7 @@ void Courtroom::set_widgets() set_size_and_pos(ui_vp_loading, "loading", COURTROOM_DESIGN_INI, ao_app); ui_vp_loading->set_theme_image("loading"); ui_vp_loading->setVisible(l_visible); + ui_vp_loading->start(); } set_size_and_pos(ui_ic_chatlog, "ic_chatlog", COURTROOM_DESIGN_INI, ao_app); diff --git a/src/mk2/spriteplayer.cpp b/src/mk2/spriteplayer.cpp index f0dd78295..38cf99f46 100644 --- a/src/mk2/spriteplayer.cpp +++ b/src/mk2/spriteplayer.cpp @@ -244,7 +244,7 @@ void SpritePlayer::fetch_next_frame() if (!is_valid()) { - if (m_play_once && m_running) + if (m_running && m_play_once) { m_running = false; emit finished(); From e5bdf4b883d88b2316f44e0091de7a6dae9b7966 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 7 Aug 2022 01:58:35 +0200 Subject: [PATCH 832/842] Bumped version --- dronline-client.pro | 2 +- src/version.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index d2363ad76..0ff5b971a 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -3,7 +3,7 @@ QT += core gui widgets uitools network multimedia multimediawidgets CONFIG += c++17 TEMPLATE = app -VERSION = 1.2.1.0 +VERSION = 1.2.2 TARGET = dro-client RC_ICONS = icon.ico diff --git a/src/version.cpp b/src/version.cpp index 3e6077300..a716a3a64 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -20,7 +20,7 @@ int get_major_version() int get_minor_version() { - return 1; + return 2; } VersionNumber get_version_number() From da6c63c57770594948acd873c54b197e10f44e1c Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 7 Aug 2022 01:59:29 +0200 Subject: [PATCH 833/842] Under bump version --- dronline-client.pro | 2 +- src/version.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index 0ff5b971a..15a347343 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -3,7 +3,7 @@ QT += core gui widgets uitools network multimedia multimediawidgets CONFIG += c++17 TEMPLATE = app -VERSION = 1.2.2 +VERSION = 1.2.1 TARGET = dro-client RC_ICONS = icon.ico diff --git a/src/version.cpp b/src/version.cpp index a716a3a64..3e6077300 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -20,7 +20,7 @@ int get_major_version() int get_minor_version() { - return 2; + return 1; } VersionNumber get_version_number() From d713635539dada7858b60893c4ebc70ceeb00ad5 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Sun, 7 Aug 2022 03:34:14 +0200 Subject: [PATCH 834/842] Loading appearing regardless of anything else --- src/courtroom_widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 1496da60c..5b8ef1b19 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -667,8 +667,8 @@ void Courtroom::set_widgets() const bool l_visible = ui_vp_loading->isVisible(); set_size_and_pos(ui_vp_loading, "loading", COURTROOM_DESIGN_INI, ao_app); ui_vp_loading->set_theme_image("loading"); - ui_vp_loading->setVisible(l_visible); ui_vp_loading->start(); + ui_vp_loading->setVisible(l_visible); } set_size_and_pos(ui_ic_chatlog, "ic_chatlog", COURTROOM_DESIGN_INI, ao_app); From a1e168221eaacd27f8e09a6189383d09c415b962 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Tue, 9 Aug 2022 18:10:40 +0200 Subject: [PATCH 835/842] Fixed ambient not allowing background ambient, bumped version --- src/courtroom.cpp | 2 +- src/version.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/courtroom.cpp b/src/courtroom.cpp index f399997cd..2398acfb3 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -407,7 +407,7 @@ void Courtroom::set_ambient(QString p_ambient_sfx) void Courtroom::play_ambient() { QString l_ambient = m_ambient_sfx; - if (l_ambient.isDetached()) + if (l_ambient.isEmpty()) { DRPosition l_position = m_position_map.get_position(m_chatmessage[CMPosition]); l_ambient = l_position.get_ambient_sfx(); diff --git a/src/version.cpp b/src/version.cpp index 3e6077300..a716a3a64 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -20,7 +20,7 @@ int get_major_version() int get_minor_version() { - return 1; + return 2; } VersionNumber get_version_number() From ce1618d12378190058370cd7378dd20c27f714c9 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 12 Aug 2022 02:13:47 +0200 Subject: [PATCH 836/842] Update the device properly for video playback --- src/mk2/graphicsvideoscreen.cpp | 45 ++++++++++++++++++++------------- src/mk2/graphicsvideoscreen.h | 5 ---- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/mk2/graphicsvideoscreen.cpp b/src/mk2/graphicsvideoscreen.cpp index 1b89bc4f9..54d74840d 100644 --- a/src/mk2/graphicsvideoscreen.cpp +++ b/src/mk2/graphicsvideoscreen.cpp @@ -44,12 +44,14 @@ DRVideoScreen::DRVideoScreen(AOApplication *ao_app, QGraphicsItem *parent) connect(m_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(check_status(QMediaPlayer::MediaStatus))); connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(check_state(QMediaPlayer::State))); - connect(m_engine, SIGNAL(current_device_changed(DRAudioDevice)), this, SLOT(update_audio_device(DRAudioDevice))); + connect(m_engine, SIGNAL(current_device_changed(DRAudioDevice)), this, SLOT(update_audio_output())); connect(m_config, SIGNAL(video_volume_changed(int)), this, SLOT(update_volume())); connect(m_engine, SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); connect(m_engine, SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); connect(m_family.get(), SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); connect(m_family.get(), SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); + + update_audio_output(); } DRVideoScreen::~DRVideoScreen() @@ -186,6 +188,8 @@ void DRVideoScreen::start_playback() { if (m_player->state() == QMediaPlayer::StoppedState) { + update_audio_output(); + m_player->play(); } } @@ -196,38 +200,45 @@ void DRVideoScreen::finish_playback() emit finished(); } -void DRVideoScreen::update_audio_device(DRAudioDevice p_device) +void DRVideoScreen::update_audio_output() { - if (m_device == p_device) + const auto l_target_device = m_engine->get_current_device(); + if (!l_target_device.has_value()) { + qWarning() << "error: no device to switch to"; return; } - m_device = p_device; - update_audio_output(); -} -void DRVideoScreen::update_audio_output() -{ - const QString l_new_device_name = m_device.get_name(); QMediaService *l_service = m_player->service(); + if (!l_service) + { + qWarning() << "error: missing media service, device unchanged"; + return; + } + QAudioOutputSelectorControl *l_control = l_service->requestControl(); if (!l_control) { qWarning() << "error: missing audio output control, device unchanged"; - return; } - - const QStringList l_device_id_list = l_control->availableOutputs(); - for (const QString &i_device_id : l_device_id_list) + else { - if (l_control->outputDescription(i_device_id) == l_new_device_name) + const QStringList l_device_name_list = l_control->availableOutputs(); + for (const QString &i_device_name : l_device_name_list) { - qDebug() << "Media player changed audio device;" << l_new_device_name; - l_control->setActiveOutput(i_device_id); - break; + const QString l_device_description = l_control->outputDescription(i_device_name); + if (l_device_description == l_target_device->get_name() || l_device_description == l_target_device->get_driver()) + { + qDebug() << "Media player changed audio device to" << l_target_device->get_name(); + l_control->setActiveOutput(i_device_name); + break; + } } + return; } l_service->releaseControl(l_control); + + update_volume(); } void DRVideoScreen::update_volume() diff --git a/src/mk2/graphicsvideoscreen.h b/src/mk2/graphicsvideoscreen.h index 1101d9f68..af9f7ff13 100644 --- a/src/mk2/graphicsvideoscreen.h +++ b/src/mk2/graphicsvideoscreen.h @@ -21,7 +21,6 @@ #include "aoapplication.h" #include "aoconfig.h" -#include "draudiodevice.h" #include "draudioengine.h" #include "draudiostreamfamily.h" @@ -59,8 +58,6 @@ public slots: DRAudioEngine *m_engine; - DRAudioDevice m_device; - DRAudioStreamFamily::ptr m_family; QString m_file_name; @@ -84,8 +81,6 @@ private slots: void check_state(QMediaPlayer::State); - void update_audio_device(DRAudioDevice); - void update_audio_output(); void update_volume(); From 54b82e936e272b79ef5e8b1ae0d33d637412c271 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Fri, 12 Aug 2022 02:27:47 +0200 Subject: [PATCH 837/842] Another attempt --- src/mk2/graphicsvideoscreen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mk2/graphicsvideoscreen.cpp b/src/mk2/graphicsvideoscreen.cpp index 54d74840d..4a4b6d781 100644 --- a/src/mk2/graphicsvideoscreen.cpp +++ b/src/mk2/graphicsvideoscreen.cpp @@ -227,7 +227,7 @@ void DRVideoScreen::update_audio_output() for (const QString &i_device_name : l_device_name_list) { const QString l_device_description = l_control->outputDescription(i_device_name); - if (l_device_description == l_target_device->get_name() || l_device_description == l_target_device->get_driver()) + if (i_device_name == l_target_device->get_name() || i_device_name == l_target_device->get_driver() || l_device_description == l_target_device->get_name() || l_device_description == l_target_device->get_driver()) { qDebug() << "Media player changed audio device to" << l_target_device->get_name(); l_control->setActiveOutput(i_device_name); From 2547b58e654c5fd963f6d13a50c66b4f2fe6b7c6 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 20 Aug 2022 12:12:54 -0400 Subject: [PATCH 838/842] Allow character list reload --- src/server_socket.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/server_socket.cpp b/src/server_socket.cpp index 9b0ce8bf8..a3aa5b9f2 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -168,15 +168,19 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) char_type l_chr; l_chr.name = i_chr_name; l_chr_list.append(std::move(l_chr)); - m_lobby->set_loading_text("Loading chars:\n" + QString::number(++m_loaded_characters) + "/" + QString::number(m_character_count)); } m_courtroom->set_character_list(l_chr_list); + m_loaded_characters = m_character_count; - int total_loading_size = m_character_count + m_evidence_count + m_music_count; - int loading_value = (m_loaded_characters / static_cast(total_loading_size)) * 100; - m_lobby->set_loading_value(loading_value); + if (is_lobby_constructed) + { + m_lobby->set_loading_text("Loading chars:\n" + QString::number(m_loaded_characters) + "/" + QString::number(m_character_count)); + int total_loading_size = m_character_count + m_evidence_count + m_music_count; + int loading_value = (m_loaded_characters / static_cast(total_loading_size)) * 100; + m_lobby->set_loading_value(loading_value); - send_server_packet(DRPacket("RM")); + send_server_packet(DRPacket("RM")); + } } else if (l_header == "SM") // TODO remove block for 1.2.0+ { From 304813aa3945218d2cb6c056db34172d82f20e40 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Tue, 23 Aug 2022 15:33:31 -0400 Subject: [PATCH 839/842] Increase OOC chatbox character limit to 8000 --- src/courtroom_widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 5b8ef1b19..6182c0f14 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -187,7 +187,7 @@ void Courtroom::create_widgets() ui_ooc_chat_message = new QLineEdit(this); ui_ooc_chat_message->setFrame(false); ui_ooc_chat_message->setPlaceholderText(tr("Say something out-of-character.")); - ui_ooc_chat_message->setMaxLength(255); + ui_ooc_chat_message->setMaxLength(8000); ui_ic_chat_message_counter = new QLabel(ui_ic_chat_message); ui_ic_chat_message_counter->setAlignment(Qt::AlignVCenter | Qt::AlignRight); From 4ea3e0e4dfaa4cc8e8080569d9d8009820c2df48 Mon Sep 17 00:00:00 2001 From: TrickyLeifa Date: Wed, 24 Aug 2022 02:26:42 +0200 Subject: [PATCH 840/842] Last time I change this crap. --- src/aoapplication.cpp | 18 ++++----- src/aoapplication.h | 25 +++++++++---- src/courtroom.cpp | 1 + src/drserversocket.cpp | 35 +++++++++-------- src/drserversocket.h | 15 +++++--- src/lobby.cpp | 85 ++++++++++++++---------------------------- src/lobby.h | 16 -------- src/server_socket.cpp | 71 ++++++++++++++++++++++++++++------- 8 files changed, 138 insertions(+), 128 deletions(-) diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 97011d3b3..1c2b322a1 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -48,10 +48,7 @@ AOApplication::AOApplication(int &argc, char **argv) connect(ao_config, SIGNAL(discord_hide_server_changed(bool)), dr_discord, SLOT(set_hide_server(bool))); connect(ao_config, SIGNAL(discord_hide_character_changed(bool)), dr_discord, SLOT(set_hide_character(bool))); - connect(m_server_socket, SIGNAL(connecting_to_server()), this, SIGNAL(connecting_to_server())); - connect(m_server_socket, SIGNAL(connected_to_server()), this, SIGNAL(connected_to_server())); - connect(m_server_socket, SIGNAL(disconnected_from_server()), this, SLOT(_p_handle_server_disconnection())); - connect(m_server_socket, SIGNAL(disconnected_from_server()), this, SIGNAL(disconnected_from_server())); + connect(m_server_socket, &DRServerSocket::connection_state_changed, this, &AOApplication::_p_handle_server_state_update); connect(m_server_socket, SIGNAL(packet_received(DRPacket)), this, SLOT(_p_handle_server_packet(DRPacket))); resolve_current_theme(); @@ -74,6 +71,12 @@ void AOApplication::set_client_id(int id) m_client_id = id; } +void AOApplication::leave_server() +{ + m_server_status = NotConnected; + m_server_socket->disconnect_from_server(); +} + Lobby *AOApplication::get_lobby() const { return m_lobby; @@ -142,13 +145,6 @@ void AOApplication::destruct_courtroom() { qDebug() << "W: courtroom was attempted destructed when it did not exist"; } - - // gracefully close our connection to the current server - if (m_server_socket->is_connected()) - { - m_server_socket->disconnect_from_server(); - emit closed_connection_to_server(); - } } DRDiscord *AOApplication::get_discord() const diff --git a/src/aoapplication.h b/src/aoapplication.h index 918843aa9..25eb4c4c2 100644 --- a/src/aoapplication.h +++ b/src/aoapplication.h @@ -3,13 +3,13 @@ #include "datatypes.h" #include "drpacket.h" +#include "drserversocket.h" class AOConfig; class AOConfigPanel; class Courtroom; class DRDiscord; class DRMasterClient; -class DRServerSocket; class Lobby; #include @@ -22,14 +22,27 @@ class AOApplication : public QApplication Q_OBJECT public: + enum ServerStatus + { + NotConnected, + Connecting, + Connected, + Joined, + TimedOut, + Disconnected, + }; + AOApplication(int &argc, char **argv); ~AOApplication(); int get_client_id() const; void set_client_id(int id); + void leave_server(); void connect_to_server(DRServerInfo server); void send_server_packet(DRPacket packet); + ServerStatus last_server_status(); + bool joined_server(); Lobby *get_lobby() const; void construct_lobby(); @@ -183,11 +196,7 @@ public slots: void reload_theme(); void reload_character(); void reload_audiotracks(); - - void connecting_to_server(); - void connected_to_server(); - void closed_connection_to_server(); - void disconnected_from_server(); + void server_status_changed(ServerStatus); private: AOConfig *ao_config = nullptr; @@ -195,13 +204,13 @@ public slots: DRDiscord *dr_discord = nullptr; DRServerSocket *m_server_socket = nullptr; + ServerStatus m_server_status = NotConnected; Lobby *m_lobby = nullptr; bool is_lobby_constructed = false; Courtroom *m_courtroom = nullptr; bool is_courtroom_constructed = false; - bool is_courtroom_loaded = false; ///////////////server metadata//////////////// @@ -224,7 +233,7 @@ public slots: bool m_loaded_area_list = false; private slots: - void _p_handle_server_disconnection(); + void _p_handle_server_state_update(DRServerSocket::ConnectionState); void _p_handle_server_packet(DRPacket); void on_courtroom_closing(); void on_courtroom_destroyed(); diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 2398acfb3..16bf06909 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -2525,6 +2525,7 @@ void Courtroom::on_back_to_lobby_clicked() // hide so we don't get the 'disconnected from server' prompt hide(); + ao_app->leave_server(); ao_app->construct_lobby(); ao_app->destruct_courtroom(); } diff --git a/src/drserversocket.cpp b/src/drserversocket.cpp index 3aaa71540..3d1190908 100644 --- a/src/drserversocket.cpp +++ b/src/drserversocket.cpp @@ -19,17 +19,16 @@ DRServerSocket::DRServerSocket(QObject *p_parent) : QObject(p_parent) { m_socket = new QTcpSocket(this); - m_connecting_timer = new QTimer(this); + m_connecting_timeout = new QTimer(this); - m_connecting_timer->setSingleShot(true); - m_connecting_timer->setInterval(CONNECTING_DELAY); + m_connecting_timeout->setSingleShot(true); + m_connecting_timeout->setInterval(CONNECTING_DELAY); connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(_p_check_socket_error())); - connect(m_socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, - SLOT(_p_update_state(QAbstractSocket::SocketState))); + connect(m_socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(_p_update_state(QAbstractSocket::SocketState))); connect(m_socket, SIGNAL(readyRead()), this, SLOT(_p_read_socket())); - connect(m_connecting_timer, SIGNAL(timeout()), this, SLOT(disconnect_from_server())); + connect(m_connecting_timeout, SIGNAL(timeout()), this, SLOT(disconnect_from_server())); m_socket->close(); } @@ -57,8 +56,7 @@ void DRServerSocket::send_packet(DRPacket p_packet) if (!is_connected()) { const QString l_server_info = m_server.to_info(); - qWarning().noquote() - << QString("Failed to send packet; not connected to server%1").arg(drFormatServerInfo(m_server)); + qWarning().noquote() << QString("Failed to send packet; not connected to server%1").arg(drFormatServerInfo(m_server)); return; } m_socket->write(p_packet.to_string(true).toUtf8()); @@ -66,28 +64,33 @@ void DRServerSocket::send_packet(DRPacket p_packet) void DRServerSocket::_p_update_state(QAbstractSocket::SocketState p_state) { + bool l_state_changed = true; switch (p_state) { case QAbstractSocket::ConnectingState: - m_connecting_timer->start(); - Q_EMIT connecting_to_server(); + m_connecting_timeout->start(); + m_state = Connecting; break; case QAbstractSocket::ConnectedState: - m_connecting_timer->stop(); - m_connected = true; - Q_EMIT connected_to_server(); + m_connecting_timeout->stop(); + m_state = Connected; break; case QAbstractSocket::UnconnectedState: - m_connecting_timer->stop(); - m_connected = false; - Q_EMIT disconnected_from_server(); + m_connecting_timeout->stop(); + m_state = NotConnected; break; default: + l_state_changed = false; break; } + + if (l_state_changed) + { + emit connection_state_changed(m_state); + } } void DRServerSocket::_p_check_socket_error() diff --git a/src/drserversocket.h b/src/drserversocket.h index 1c28c380a..cb2193b00 100644 --- a/src/drserversocket.h +++ b/src/drserversocket.h @@ -14,6 +14,13 @@ class DRServerSocket : public QObject Q_OBJECT public: + enum ConnectionState + { + NotConnected, + Connecting, + Connected, + }; + DRServerSocket(QObject *parent = nullptr); bool is_connected() const; @@ -26,9 +33,7 @@ public slots: void send_packet(DRPacket packet); signals: - void connected_to_server(); - void connecting_to_server(); - void disconnected_from_server(); + void connection_state_changed(ConnectionState); void packet_received(DRPacket); void socket_error(QString); @@ -37,8 +42,8 @@ public slots: DRServerInfo m_server; QTcpSocket *m_socket = nullptr; - QTimer *m_connecting_timer = nullptr; - bool m_connected = false; + QTimer *m_connecting_timeout = nullptr; + ConnectionState m_state = NotConnected; QString m_buffer; private slots: diff --git a/src/lobby.cpp b/src/lobby.cpp index 0700f9a79..3cc9b9867 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -35,7 +35,6 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() - , m_connection_state(NotConnectedState) { ao_app = p_ao_app; ao_config = new AOConfig(this); @@ -87,10 +86,7 @@ Lobby::Lobby(AOApplication *p_ao_app) ui_cancel = new AOButton(ui_loading_background, ao_app); connect(ao_app, SIGNAL(reload_theme()), this, SLOT(update_widgets())); - connect(ao_app, SIGNAL(connecting_to_server()), this, SLOT(_p_on_connecting_to_server())); - connect(ao_app, SIGNAL(connected_to_server()), this, SLOT(_p_on_connected_to_server())); - connect(ao_app, SIGNAL(closed_connection_to_server()), this, SLOT(_p_on_closed_connection_to_server())); - connect(ao_app, SIGNAL(disconnected_from_server()), this, SLOT(_p_on_disconnected_from_server())); + connect(ao_app, &AOApplication::server_status_changed, this, &Lobby::_p_update_description); connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(update_widgets())); connect(ao_config, SIGNAL(server_advertiser_changed(QString)), m_master_client, SLOT(set_address(QString))); @@ -380,12 +376,6 @@ void Lobby::save_favorite_server_list() l_ini.sync(); } -void Lobby::set_connection_state(ConnectionState p_connection_state) -{ - m_connection_state = p_connection_state; - _p_update_description(); -} - void Lobby::request_advertiser_update() { m_master_client->request_motd(); @@ -681,64 +671,43 @@ void Lobby::move_down_server() set_favorite_server_list(l_server_list); } -void Lobby::_p_on_connecting_to_server() -{ - set_connection_state(ConnectingState); -} - -void Lobby::_p_on_connected_to_server() -{ - set_connection_state(ConnectedState); -} - -void Lobby::_p_on_closed_connection_to_server() +void Lobby::_p_update_description() { - set_connection_state(NotConnectedState); -} + QMap l_report_map{ + {AOApplication::NotConnected, tr("Choose a server.")}, + {AOApplication::Connecting, tr("Connecting to server.")}, + {AOApplication::Connected, tr("Connected to server.")}, + {AOApplication::Joined, tr("Joined server.")}, + {AOApplication::TimedOut, tr("Failed to connect to server.")}, + {AOApplication::Disconnected, tr("Choose a server.")}, + }; -void Lobby::_p_on_disconnected_from_server() -{ - set_connection_state(LostConnectionState); -} + QString l_message = l_report_map[ao_app->last_server_status()]; -void Lobby::_p_update_description() -{ - QString l_message; + if (!m_current_server.name.isEmpty()) { - const QString l_format = QMap{ - { - NotConnectedState, - tr("Choose a server."), - }, - { - ConnectingState, - tr("Connecting to server (%1)..."), - }, - { - ConnectedState, - tr("Connected to server (%1)."), - }, - { - LostConnectionState, - tr("Failed to connect to server (%1)."), - }, - }[m_connection_state]; - l_message = QString(l_format).arg(m_current_server.name.toHtmlEscaped()); + l_message = QString("%1\n\n" + "==== STATUS ====\n" + "%2") + .arg(m_current_server.name.toHtmlEscaped()) + .arg(l_message); } - if (m_connection_state == ConnectedState) + if (!m_current_server.description.isEmpty()) { - l_message += "\n\n" + m_current_server.description.toHtmlEscaped(); - + QString l_description = m_current_server.description.toHtmlEscaped(); const QRegExp l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); - if (l_message.contains(l_regex)) + if (l_description.contains(l_regex)) { - l_message.replace(l_regex, "\\1"); + l_description.replace(l_regex, "\\1"); } - - l_message.replace("\n", "
    "); + l_message = QString("%1\n\n" + "==== DESCRIPTION ====\n" + "%2") + .arg(l_description); } - ui_description->setHtml(l_message); + + ui_description->setHtml(l_message.replace("\n", "
    ")); } void Lobby::set_choose_a_server() diff --git a/src/lobby.h b/src/lobby.h index 902a70162..903c4860f 100644 --- a/src/lobby.h +++ b/src/lobby.h @@ -56,15 +56,6 @@ class Lobby : public QMainWindow DRServerInfoList m_combined_server_list; DRServerInfo m_current_server; - enum ConnectionState - { - NotConnectedState, - ConnectingState, - ConnectedState, - LostConnectionState, - }; - ConnectionState m_connection_state; - // ui AOImageDisplay *ui_background = nullptr; AOButton *ui_public_server_filter = nullptr; @@ -112,8 +103,6 @@ class Lobby : public QMainWindow void load_legacy_favorite_server_list(); void save_favorite_server_list(); - void set_connection_state(ConnectionState state); - private slots: void update_widgets(); @@ -147,11 +136,6 @@ private slots: void move_up_server(); void move_down_server(); - void _p_on_connecting_to_server(); - void _p_on_connected_to_server(); - void _p_on_closed_connection_to_server(); - void _p_on_disconnected_from_server(); - void _p_update_description(); }; diff --git a/src/server_socket.cpp b/src/server_socket.cpp index a3aa5b9f2..50ed0be1c 100644 --- a/src/server_socket.cpp +++ b/src/server_socket.cpp @@ -29,14 +29,58 @@ void AOApplication::send_server_packet(DRPacket p_packet) m_server_socket->send_packet(p_packet); } -void AOApplication::_p_handle_server_disconnection() +AOApplication::ServerStatus AOApplication::last_server_status() { - if (!is_courtroom_constructed) - return; - m_courtroom->stop_all_audio(); - call_notice("Disconnected from server."); - construct_lobby(); - destruct_courtroom(); + return m_server_status; +} + +bool AOApplication::joined_server() +{ + return m_server_status == Joined; +} + +void AOApplication::_p_handle_server_state_update(DRServerSocket::ConnectionState p_state) +{ + const ServerStatus l_previous_status = m_server_status; + switch (p_state) + { + case DRServerSocket::NotConnected: + switch (l_previous_status) + { + case Connecting: + m_server_status = TimedOut; + break; + + case Joined: + m_server_status = Disconnected; + if (is_courtroom_constructed) + { + m_courtroom->stop_all_audio(); + call_notice("Disconnected from server."); + construct_lobby(); + destruct_courtroom(); + } + break; + + default: + m_server_status = NotConnected; + break; + } + break; + + case DRServerSocket::Connecting: + m_server_status = Connecting; + break; + + case DRServerSocket::Connected: + m_server_status = Connected; + break; + } + + if (m_server_status != l_previous_status) + { + emit server_status_changed(m_server_status); + } } void AOApplication::_p_handle_server_packet(DRPacket p_packet) @@ -124,8 +168,6 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) construct_courtroom(); - is_courtroom_loaded = false; - DRServerInfo l_current_server = m_lobby->get_selected_server(); QString l_window_title = "Danganronpa Online (" + get_version_string() + ")"; if (!l_current_server.name.isEmpty()) @@ -252,8 +294,8 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) return; m_courtroom->done_received(); - - is_courtroom_loaded = true; + m_server_status = Joined; + emit server_status_changed(m_server_status); destruct_lobby(); } @@ -316,17 +358,17 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) } else if (l_header == "MS") { - if (is_courtroom_constructed && is_courtroom_loaded) + if (is_courtroom_constructed && joined_server()) m_courtroom->next_chatmessage(l_content); } else if (l_header == "ackMS") { - if (is_courtroom_constructed && is_courtroom_loaded) + if (is_courtroom_constructed && joined_server()) m_courtroom->handle_acknowledged_ms(); } else if (l_header == "MC") { - if (is_courtroom_constructed && is_courtroom_loaded) + if (is_courtroom_constructed && joined_server()) m_courtroom->handle_song(l_content); } else if (l_header == "RT") @@ -352,6 +394,7 @@ void AOApplication::_p_handle_server_packet(DRPacket p_packet) return; call_notice("You have been kicked."); + leave_server(); construct_lobby(); destruct_courtroom(); } From 7398d66441b60eb2c0ff365b1a69a4e2053dba03 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Fri, 26 Aug 2022 17:20:14 -0400 Subject: [PATCH 841/842] Increase OOC chatbox character limit to 1023 --- src/courtroom_widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp index 6182c0f14..456d3e9fe 100644 --- a/src/courtroom_widgets.cpp +++ b/src/courtroom_widgets.cpp @@ -187,7 +187,7 @@ void Courtroom::create_widgets() ui_ooc_chat_message = new QLineEdit(this); ui_ooc_chat_message->setFrame(false); ui_ooc_chat_message->setPlaceholderText(tr("Say something out-of-character.")); - ui_ooc_chat_message->setMaxLength(8000); + ui_ooc_chat_message->setMaxLength(1023); ui_ic_chat_message_counter = new QLabel(ui_ic_chat_message); ui_ic_chat_message_counter->setAlignment(Qt::AlignVCenter | Qt::AlignRight); From e07bbee48dacb9de2171d45d6d1ec585bddb77db Mon Sep 17 00:00:00 2001 From: Chrezm Date: Sat, 27 Aug 2022 21:26:42 -0400 Subject: [PATCH 842/842] Bump to 1.2.3 --- dronline-client.pro | 2 +- src/version.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dronline-client.pro b/dronline-client.pro index 15a347343..ee06906ab 100644 --- a/dronline-client.pro +++ b/dronline-client.pro @@ -3,7 +3,7 @@ QT += core gui widgets uitools network multimedia multimediawidgets CONFIG += c++17 TEMPLATE = app -VERSION = 1.2.1 +VERSION = 1.2.3 TARGET = dro-client RC_ICONS = icon.ico diff --git a/src/version.cpp b/src/version.cpp index a716a3a64..ccebff63a 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -20,7 +20,7 @@ int get_major_version() int get_minor_version() { - return 2; + return 3; } VersionNumber get_version_number()