Lightly reworked NetworkManager, ...

* Lightly reworked `NetworkManager`
* Added new modules to handle various connection types.
  * TCP
  * WebSocket
* Added general string splitter alias based on Qt version.
* Replaced `lobby_constructed` and `courtroom_constructed`
* Refactored and partially reimplemented the following classes:
  * `AOBlipPlayer`
  * `AOEmotePreview`
  * `AOMusicPlayer`
  * `AOSfxPlayer`
  * `AOTextArea`
This commit is contained in:
TrickyLeifa 2024-05-17 16:39:30 +02:00
parent 39e4354b1d
commit 1ef96383c8
34 changed files with 945 additions and 666 deletions

View File

@ -77,6 +77,12 @@ add_executable(Attorney_Online
src/lobby.cpp
src/lobby.h
src/main.cpp
src/net/netconnection.cpp
src/net/netconnection.h
src/net/nettcpconnection.cpp
src/net/nettcpconnection.h
src/net/netwebsocketconnection.cpp
src/net/netwebsocketconnection.h
src/networkmanager.cpp
src/networkmanager.h
src/options.cpp

View File

@ -40,16 +40,20 @@ AOApplication::~AOApplication()
qInstallMessageHandler(original_message_handler);
}
bool AOApplication::is_lobby_constructed()
{
return w_lobby;
}
void AOApplication::construct_lobby()
{
if (lobby_constructed)
if (is_lobby_constructed())
{
qWarning() << "lobby was attempted constructed when it already exists";
return;
}
w_lobby = new Lobby(this, net_manager);
lobby_constructed = true;
QRect geometry = QGuiApplication::primaryScreen()->geometry();
int x = (geometry.width() - w_lobby->width()) / 2;
@ -71,7 +75,7 @@ void AOApplication::construct_lobby()
void AOApplication::destruct_lobby()
{
if (!lobby_constructed)
if (!is_lobby_constructed())
{
qWarning() << "lobby was attempted destructed when it did not exist";
return;
@ -79,19 +83,22 @@ void AOApplication::destruct_lobby()
delete w_lobby;
w_lobby = nullptr;
lobby_constructed = false;
}
bool AOApplication::is_courtroom_constructed()
{
return w_courtroom;
}
void AOApplication::construct_courtroom()
{
if (courtroom_constructed)
if (is_courtroom_constructed())
{
qWarning() << "courtroom was attempted constructed when it already exists";
return;
}
w_courtroom = new Courtroom(this);
courtroom_constructed = true;
QRect geometry = QGuiApplication::primaryScreen()->geometry();
int x = (geometry.width() - w_courtroom->width()) / 2;
@ -110,7 +117,7 @@ void AOApplication::construct_courtroom()
void AOApplication::destruct_courtroom()
{
if (!courtroom_constructed)
if (!is_courtroom_constructed())
{
qWarning() << "courtroom was attempted destructed when it did not exist";
return;
@ -118,7 +125,6 @@ void AOApplication::destruct_courtroom()
delete w_courtroom;
w_courtroom = nullptr;
courtroom_constructed = false;
}
QString AOApplication::get_version_string()
@ -128,9 +134,12 @@ QString AOApplication::get_version_string()
void AOApplication::server_disconnected()
{
if (courtroom_constructed)
if (is_courtroom_constructed())
{
if (w_courtroom->isVisible())
{
call_notice(tr("Disconnected from server."));
}
construct_lobby();
destruct_courtroom();
}
@ -145,12 +154,12 @@ void AOApplication::loading_cancelled()
void AOApplication::call_settings_menu()
{
AOOptionsDialog *l_dialog = new AOOptionsDialog(this);
if (courtroom_constructed)
if (is_courtroom_constructed())
{
connect(l_dialog, &AOOptionsDialog::reloadThemeRequest, w_courtroom, &Courtroom::on_reload_theme_clicked);
}
if (lobby_constructed)
if (is_lobby_constructed())
{}
l_dialog->exec();
delete l_dialog;

View File

@ -61,18 +61,17 @@ public:
~AOApplication();
NetworkManager *net_manager;
Lobby *w_lobby;
Courtroom *w_courtroom;
Lobby *w_lobby = nullptr;
Courtroom *w_courtroom = nullptr;
AttorneyOnline::Discord *discord;
QFont default_font;
bool lobby_constructed = false;
bool courtroom_constructed = false;
bool is_lobby_constructed();
void construct_lobby();
void destruct_lobby();
bool is_courtroom_constructed();
void construct_courtroom();
void destruct_courtroom();

View File

@ -1,64 +1,54 @@
#include "aoblipplayer.h"
AOBlipPlayer::AOBlipPlayer(AOApplication *p_ao_app)
: ao_app(p_ao_app)
AOBlipPlayer::AOBlipPlayer(AOApplication *ao_app)
: ao_app(ao_app)
{}
void AOBlipPlayer::set_blips(QString p_sfx)
void AOBlipPlayer::setVolume(int value)
{
QString f_path = ao_app->get_sfx_suffix(ao_app->get_sounds_path(p_sfx));
m_volume = value;
updateInternalVolume();
}
for (int n_stream = 0; n_stream < 5; ++n_stream)
{
BASS_StreamFree(m_stream_list[n_stream]);
void AOBlipPlayer::setMuted(bool enabled)
{
m_muted = enabled;
updateInternalVolume();
}
if (f_path.endsWith(".opus"))
void AOBlipPlayer::setBlip(QString blip)
{
QString path = ao_app->get_sfx_suffix(ao_app->get_sounds_path(blip));
for (int i = 0; i < STREAM_COUNT; ++i)
{
m_stream_list[n_stream] = BASS_OPUS_StreamCreateFile(FALSE, f_path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE);
BASS_StreamFree(m_stream[i]);
if (path.endsWith(".opus"))
{
m_stream[i] = BASS_OPUS_StreamCreateFile(FALSE, path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE);
}
else
{
m_stream_list[n_stream] = BASS_StreamCreateFile(FALSE, f_path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE);
m_stream[i] = BASS_StreamCreateFile(FALSE, path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE);
}
}
set_volume_internal(m_volume);
updateInternalVolume();
}
void AOBlipPlayer::blip_tick()
void AOBlipPlayer::playBlip()
{
int f_cycle = m_cycle++;
HSTREAM stream = m_stream[m_cycle];
BASS_ChannelSetDevice(stream, BASS_GetDevice());
BASS_ChannelPlay(stream, false);
m_cycle = ++m_cycle % STREAM_COUNT;
}
if (m_cycle == 5)
void AOBlipPlayer::updateInternalVolume()
{
float volume = m_muted ? 0.0f : (m_volume * 0.01);
for (int i = 0; i < STREAM_COUNT; ++i)
{
m_cycle = 0;
}
HSTREAM f_stream = m_stream_list[f_cycle];
BASS_ChannelSetDevice(f_stream, BASS_GetDevice());
BASS_ChannelPlay(f_stream, false);
}
void AOBlipPlayer::set_muted(bool toggle)
{
m_muted = toggle;
set_volume_internal(m_volume);
}
void AOBlipPlayer::set_volume(int p_value)
{
m_volume = static_cast<qreal>(p_value) / 100;
set_volume_internal(m_volume);
}
void AOBlipPlayer::set_volume_internal(qreal p_value)
{
// If muted, volume will always be 0
float volume = static_cast<float>(p_value) * !m_muted;
for (int n_stream = 0; n_stream < 5; ++n_stream)
{
BASS_ChannelSetAttribute(m_stream_list[n_stream], BASS_ATTRIB_VOL, volume);
BASS_ChannelSetAttribute(m_stream[i], BASS_ATTRIB_VOL, volume);
}
}

View File

@ -13,19 +13,24 @@
class AOBlipPlayer
{
public:
AOBlipPlayer(AOApplication *p_ao_app);
static constexpr int STREAM_COUNT = 5;
void set_blips(QString p_sfx);
void blip_tick();
void set_volume(int p_volume);
void set_muted(bool toggle);
AOBlipPlayer(AOApplication *ao_app);
void setVolume(int value);
void setMuted(bool enabled);
void setBlip(QString blip);
void playBlip();
private:
AOApplication *ao_app;
qreal m_volume = 0.0;
int m_cycle = 0;
bool m_muted = false;
HSTREAM m_stream_list[5];
void set_volume_internal(qreal p_volume);
int m_volume = 0;
bool m_muted = false;
HSTREAM m_stream[STREAM_COUNT]{};
int m_cycle = 0;
void updateInternalVolume();
};

View File

@ -1,44 +1,45 @@
#include "aoemotepreview.h"
AOEmotePreview::AOEmotePreview(AOApplication *p_ao_app, QWidget *parent)
AOEmotePreview::AOEmotePreview(AOApplication *ao_app, QWidget *parent)
: QWidget(parent)
, ao_app(p_ao_app)
, ao_app(ao_app)
{
setWindowFlag(Qt::Tool);
setWindowFlag(Qt::WindowMinMaxButtonsHint, false);
ui_viewport = new QWidget(this);
ui_vp_player_char = new CharLayer(ao_app, ui_viewport);
ui_vp_player_char->setObjectName("ui_vp_player_char");
ui_vp_player_char->masked = false;
ui_size_label = new QLabel(this);
ui_size_label->setObjectName("ui_size_label");
setWindowFlag(Qt::WindowMinMaxButtonsHint, false);
setWindowFlag(Qt::Tool);
this->resize(256, 192);
}
void AOEmotePreview::set_widgets()
void AOEmotePreview::updateViewportGeometry()
{
ui_viewport->resize(this->width(), this->height());
ui_viewport->resize(size());
ui_vp_player_char->move_and_center(0, 0);
ui_vp_player_char->combo_resize(ui_viewport->width(), ui_viewport->height());
ui_size_label->setText(QString::number(this->width()) + "x" + QString::number(this->height()));
ui_size_label->setText(QString::number(width()) + "x" + QString::number(height()));
}
void AOEmotePreview::play(QString emote, QString char_name, bool flipped, int self_offset, int self_offset_v)
void AOEmotePreview::display(QString character, QString emote, bool flipped, int xOffset, int yOffset)
{
m_character = character;
m_emote = emote;
ui_vp_player_char->stop();
ui_vp_player_char->set_flipped(flipped);
ui_vp_player_char->move_and_center(ui_viewport->width() * self_offset / 100, ui_viewport->height() * self_offset_v / 100);
ui_vp_player_char->load_image(emote, char_name, 0, false);
ui_vp_player_char->move_and_center(ui_viewport->width() * xOffset / 100, ui_viewport->height() * yOffset / 100);
ui_vp_player_char->load_image(emote, character, 0, false);
ui_vp_player_char->set_play_once(false);
m_emote = emote;
m_char = char_name;
setWindowTitle(char_name + ": " + emote);
setWindowTitle(character + ": " + emote);
}
void AOEmotePreview::resizeEvent(QResizeEvent *)
void AOEmotePreview::resizeEvent(QResizeEvent *event)
{
set_widgets();
ui_vp_player_char->load_image(m_emote, m_char, 0, false);
QWidget::resizeEvent(event);
updateViewportGeometry();
ui_vp_player_char->load_image(m_emote, m_character, 0, false);
}

View File

@ -8,19 +8,20 @@ class AOEmotePreview : public QWidget
Q_OBJECT
public:
AOEmotePreview(AOApplication *p_ao_app, QWidget *parent = nullptr);
AOEmotePreview(AOApplication *ao_app, QWidget *parent = nullptr);
void set_widgets();
void play(QString emote, QString char_name, bool flipped = false, int self_offset = 0, int self_offset_v = 0);
void display(QString character, QString emote, bool flipped = false, int xOffset = 0, int yOffset = 0);
void updateViewportGeometry();
protected:
void resizeEvent(QResizeEvent *);
void resizeEvent(QResizeEvent *event);
private:
AOApplication *ao_app;
QString m_character;
QString m_emote;
QString m_char;
QWidget *ui_viewport;
BackgroundLayer *ui_vp_background;

View File

@ -24,7 +24,7 @@ void AOEvidenceDisplay::show_evidence(int p_index, QString p_evidence_image, boo
m_last_evidence_index = p_index;
m_sfx_player->set_volume(p_volume);
m_sfx_player->setVolume(p_volume);
QString gif_name;
QString icon_identifier;
@ -56,7 +56,7 @@ void AOEvidenceDisplay::show_evidence(int p_index, QString p_evidence_image, boo
m_evidence_movie->max_duration = 1000;
m_evidence_movie->set_play_once(true);
m_evidence_movie->load_image(gif_name, "");
m_sfx_player->play(ao_app->get_court_sfx("evidence_present"));
m_sfx_player->findAndPlaySfx(ao_app->get_court_sfx("evidence_present"));
}
void AOEvidenceDisplay::reset()

View File

@ -9,48 +9,52 @@
#include <QFuture>
#include <QWidget>
AOMusicPlayer::AOMusicPlayer(AOApplication *p_ao_app)
: ao_app(p_ao_app)
AOMusicPlayer::AOMusicPlayer(AOApplication *ao_app)
: ao_app(ao_app)
{}
AOMusicPlayer::~AOMusicPlayer()
{
for (int n_stream = 0; n_stream < CHANNEL_COUNT; ++n_stream)
for (int n_stream = 0; n_stream < STREAM_COUNT; ++n_stream)
{
BASS_ChannelStop(m_stream_list[n_stream]);
}
}
QString AOMusicPlayer::play(QString p_song, int channel, bool loop, int effect_flags)
QString AOMusicPlayer::playStream(QString song, int streamId, bool loopEnabled, int effectFlags)
{
channel = channel % CHANNEL_COUNT;
if (channel < 0) // wtf?
if (!ensureValidStreamId(streamId))
{
return "[ERROR] Invalid Channel";
}
unsigned int flags = BASS_STREAM_PRESCAN | BASS_STREAM_AUTOFREE | BASS_UNICODE | BASS_ASYNCFILE;
unsigned int streaming_flags = BASS_STREAM_AUTOFREE;
if (loop)
quint32 flags = BASS_STREAM_AUTOFREE;
if (loopEnabled)
{
flags |= BASS_SAMPLE_LOOP;
streaming_flags |= BASS_SAMPLE_LOOP;
}
QString f_path = p_song;
QString f_path = song;
DWORD newstream;
if (f_path.startsWith("http"))
{
if (!Options::getInstance().streamingEnabled())
{
BASS_ChannelStop(m_stream_list[channel]);
BASS_ChannelStop(m_stream_list[streamId]);
return QObject::tr("[MISSING] Streaming disabled.");
}
QUrl l_url = QUrl(f_path);
newstream = BASS_StreamCreateURL(l_url.toEncoded().toStdString().c_str(), 0, streaming_flags, nullptr, 0);
newstream = BASS_StreamCreateURL(l_url.toEncoded().toStdString().c_str(), 0, flags, nullptr, 0);
}
else
{
f_path = ao_app->get_real_path(ao_app->get_music_path(p_song));
if (f_path.endsWith(".mo3") || f_path.endsWith(".xm") || f_path.endsWith(".mod") || f_path.endsWith(".s3m") || f_path.endsWith(".it") || f_path.endsWith(".mtm") || f_path.endsWith(".umx"))
flags |= BASS_STREAM_PRESCAN | BASS_UNICODE | BASS_ASYNCFILE;
f_path = ao_app->get_real_path(ao_app->get_music_path(song));
QString extension = f_path.split('.').last();
static const QStringList VALID_EXTENSION_LIST{"mo3", "xm", "mod", "s3m", "it", "mtm", "umx"};
if (VALID_EXTENSION_LIST.contains(extension, Qt::CaseInsensitive))
{
newstream = BASS_MusicLoad(FALSE, f_path.utf16(), 0, 0, flags, 1);
}
@ -60,18 +64,17 @@ QString AOMusicPlayer::play(QString p_song, int channel, bool loop, int effect_f
}
}
int error_code = BASS_ErrorGetCode();
int error = BASS_ErrorGetCode();
if (Options::getInstance().audioOutputDevice() != "default")
{
BASS_ChannelSetDevice(m_stream_list[channel], BASS_GetDevice());
BASS_ChannelSetDevice(m_stream_list[streamId], BASS_GetDevice());
}
QString d_path = f_path + ".txt";
m_loop_start[streamId] = 0;
m_loop_end[streamId] = 0;
m_loop_start[channel] = 0;
m_loop_end[channel] = 0;
if (loop && file_exists(d_path)) // Contains loop/etc. information file
QString d_path = f_path + ".txt";
if (loopEnabled && file_exists(d_path)) // Contains loop/etc. information file
{
QStringList lines = ao_app->read_file(d_path).split("\n");
bool seconds_mode = false;
@ -90,6 +93,7 @@ QString AOMusicPlayer::play(QString p_song, int channel, bool loop, int effect_f
seconds_mode = true; // Use new epic behavior
continue;
}
continue;
}
@ -114,25 +118,25 @@ QString AOMusicPlayer::play(QString p_song, int channel, bool loop, int effect_f
}
if (arg == "loop_start")
{
m_loop_start[channel] = bytes;
m_loop_start[streamId] = bytes;
}
else if (arg == "loop_length")
{
m_loop_end[channel] = m_loop_start[channel] + bytes;
m_loop_end[streamId] = m_loop_start[streamId] + bytes;
}
else if (arg == "loop_end")
{
m_loop_end[channel] = bytes;
m_loop_end[streamId] = bytes;
}
}
qDebug() << "Found data file for song" << p_song << "length" << BASS_ChannelGetLength(newstream, BASS_POS_BYTE) << "loop start" << m_loop_start[channel] << "loop end" << m_loop_end[channel];
qDebug() << "Found data file for song" << song << "length" << BASS_ChannelGetLength(newstream, BASS_POS_BYTE) << "loop start" << m_loop_start[streamId] << "loop end" << m_loop_end[streamId];
}
if (BASS_ChannelIsActive(m_stream_list[channel]) == BASS_ACTIVE_PLAYING)
if (BASS_ChannelIsActive(m_stream_list[streamId]) == BASS_ACTIVE_PLAYING)
{
DWORD oldstream = m_stream_list[channel];
DWORD oldstream = m_stream_list[streamId];
if (effect_flags & SYNC_POS)
if (effectFlags & SYNC_POS)
{
BASS_ChannelLock(oldstream, true);
// Sync it with the new sample
@ -140,7 +144,7 @@ QString AOMusicPlayer::play(QString p_song, int channel, bool loop, int effect_f
BASS_ChannelLock(oldstream, false);
}
if ((effect_flags & FADE_OUT) && m_volume[channel] > 0)
if ((effectFlags & FADE_OUT) && m_volume[streamId] > 0)
{
// Fade out the other sample and stop it (due to -1)
BASS_ChannelSlideAttribute(oldstream, BASS_ATTRIB_VOL | BASS_SLIDE_LOG, -1, 4000);
@ -152,47 +156,47 @@ QString AOMusicPlayer::play(QString p_song, int channel, bool loop, int effect_f
}
else
{
BASS_ChannelStop(m_stream_list[channel]);
BASS_ChannelStop(m_stream_list[streamId]);
}
m_stream_list[channel] = newstream;
m_stream_list[streamId] = newstream;
BASS_ChannelPlay(newstream, false);
if (effect_flags & FADE_IN)
if (effectFlags & FADE_IN)
{
// Fade in our sample
BASS_ChannelSetAttribute(newstream, BASS_ATTRIB_VOL, 0);
BASS_ChannelSlideAttribute(newstream, BASS_ATTRIB_VOL, static_cast<float>(m_volume[channel] / 100.0f), 1000);
BASS_ChannelSlideAttribute(newstream, BASS_ATTRIB_VOL, static_cast<float>(m_volume[streamId] / 100.0f), 1000);
}
else
{
this->set_volume(m_volume[channel], channel);
this->setStreamVolume(m_volume[streamId], streamId);
}
BASS_ChannelSetSync(newstream, BASS_SYNC_DEV_FAIL, 0, ao_app->BASSreset, 0);
this->set_looping(loop, channel); // Have to do this here due to any
this->setStreamLooping(loopEnabled, streamId); // Have to do this here due to any
// crossfading-related changes, etc.
bool is_stop = (p_song == "~stop.mp3");
QString p_song_clear = QUrl(p_song).fileName();
bool is_stop = (song == "~stop.mp3");
QString p_song_clear = QUrl(song).fileName();
p_song_clear = p_song_clear.left(p_song_clear.lastIndexOf('.'));
if (is_stop && channel == 0)
if (is_stop && streamId == 0)
{ // don't send text on channels besides 0
return QObject::tr("None");
}
if (error_code == BASS_ERROR_HANDLE)
if (error == BASS_ERROR_HANDLE)
{ // Cheap hack to see if file missing
return QObject::tr("[MISSING] %1").arg(p_song_clear);
}
if (p_song.startsWith("http") && channel == 0)
if (song.startsWith("http") && streamId == 0)
{
return QObject::tr("[STREAM] %1").arg(p_song_clear);
}
if (channel == 0)
if (streamId == 0)
{
return p_song_clear;
}
@ -200,36 +204,37 @@ QString AOMusicPlayer::play(QString p_song, int channel, bool loop, int effect_f
return "";
}
void AOMusicPlayer::stop(int channel)
void AOMusicPlayer::setMuted(bool enabled)
{
BASS_ChannelStop(m_stream_list[channel]);
}
void AOMusicPlayer::set_muted(bool toggle)
{
m_muted = toggle;
m_muted = enabled;
// Update all volume based on the mute setting
for (int n_stream = 0; n_stream < CHANNEL_COUNT; ++n_stream)
for (int n_stream = 0; n_stream < STREAM_COUNT; ++n_stream)
{
set_volume(m_volume[n_stream], n_stream);
setStreamVolume(m_volume[n_stream], n_stream);
}
}
void AOMusicPlayer::set_volume(int p_value, int channel)
void AOMusicPlayer::setStreamVolume(int value, int streamId)
{
m_volume[channel] = p_value;
// If muted, volume will always be 0
float volume = (m_volume[channel] / 100.0f) * !m_muted;
if (channel < 0)
if (!ensureValidStreamId(streamId))
{
for (int n_stream = 0; n_stream < CHANNEL_COUNT; ++n_stream)
qWarning().noquote() << QObject::tr("Invalid stream ID '%2'").arg(streamId);
return;
}
m_volume[streamId] = value;
// If muted, volume will always be 0
float volume = (m_volume[streamId] / 100.0f) * !m_muted;
if (streamId < 0)
{
for (int n_stream = 0; n_stream < STREAM_COUNT; ++n_stream)
{
BASS_ChannelSetAttribute(m_stream_list[n_stream], BASS_ATTRIB_VOL, volume);
}
}
else
{
BASS_ChannelSetAttribute(m_stream_list[channel], BASS_ATTRIB_VOL, volume);
BASS_ChannelSetAttribute(m_stream_list[streamId], BASS_ATTRIB_VOL, volume);
}
}
@ -243,37 +248,48 @@ void CALLBACK loopProc(HSYNC handle, DWORD channel, DWORD data, void *user)
BASS_ChannelLock(channel, false);
}
void AOMusicPlayer::set_looping(bool loop_song, int channel)
void AOMusicPlayer::setStreamLooping(bool enabled, int streamId)
{
if (!loop_song)
if (!ensureValidStreamId(streamId))
{
if (BASS_ChannelFlags(m_stream_list[channel], 0, 0) & BASS_SAMPLE_LOOP)
{
BASS_ChannelFlags(m_stream_list[channel], 0,
BASS_SAMPLE_LOOP); // remove the LOOP flag
}
BASS_ChannelRemoveSync(m_stream_list[channel], m_loop_sync[channel]);
m_loop_sync[channel] = 0;
qWarning().noquote() << QObject::tr("Invalid stream ID '%2'").arg(streamId);
return;
}
BASS_ChannelFlags(m_stream_list[channel], BASS_SAMPLE_LOOP,
BASS_SAMPLE_LOOP); // set the LOOP flag
if (m_loop_sync[channel] != 0)
if (!enabled)
{
BASS_ChannelRemoveSync(m_stream_list[channel],
m_loop_sync[channel]); // remove the sync
m_loop_sync[channel] = 0;
if (BASS_ChannelFlags(m_stream_list[streamId], 0, 0) & BASS_SAMPLE_LOOP)
{
BASS_ChannelFlags(m_stream_list[streamId], 0,
BASS_SAMPLE_LOOP); // remove the LOOP flag
}
BASS_ChannelRemoveSync(m_stream_list[streamId], m_loop_sync[streamId]);
m_loop_sync[streamId] = 0;
return;
}
if (m_loop_start[channel] < m_loop_end[channel])
BASS_ChannelFlags(m_stream_list[streamId], BASS_SAMPLE_LOOP,
BASS_SAMPLE_LOOP); // set the LOOP flag
if (m_loop_sync[streamId] != 0)
{
BASS_ChannelRemoveSync(m_stream_list[streamId],
m_loop_sync[streamId]); // remove the sync
m_loop_sync[streamId] = 0;
}
if (m_loop_start[streamId] < m_loop_end[streamId])
{
// Loop when the endpoint is reached.
m_loop_sync[channel] = BASS_ChannelSetSync(m_stream_list[channel], BASS_SYNC_POS | BASS_SYNC_MIXTIME, m_loop_end[channel], loopProc, &m_loop_start[channel]);
m_loop_sync[streamId] = BASS_ChannelSetSync(m_stream_list[streamId], BASS_SYNC_POS | BASS_SYNC_MIXTIME, m_loop_end[streamId], loopProc, &m_loop_start[streamId]);
}
else
{
// Loop when the end of the file is reached.
m_loop_sync[channel] = BASS_ChannelSetSync(m_stream_list[channel], BASS_SYNC_END | BASS_SYNC_MIXTIME, 0, loopProc, &m_loop_start[channel]);
m_loop_sync[streamId] = BASS_ChannelSetSync(m_stream_list[streamId], BASS_SYNC_END | BASS_SYNC_MIXTIME, 0, loopProc, &m_loop_start[streamId]);
}
}
bool AOMusicPlayer::ensureValidStreamId(int streamId)
{
return (streamId >= 0 && streamId < STREAM_COUNT);
}

View File

@ -7,38 +7,32 @@
class AOMusicPlayer
{
public:
// Channel 0 = music
// Channel 1 = ambience
static constexpr int CHANNEL_COUNT = 2;
// 0 = music
// 1 = ambience
static constexpr int STREAM_COUNT = 2;
AOMusicPlayer(AOApplication *p_ao_app);
explicit AOMusicPlayer(AOApplication *ao_app);
virtual ~AOMusicPlayer();
void set_volume(int p_value, int channel = -1);
void set_looping(bool loop_song, int channel = 0);
void set_muted(bool toggle);
void setMuted(bool enabled);
QFutureWatcher<QString> music_watcher;
QString playStream(QString song, int streamId, bool loopEnabled, int effectFlags);
public Q_SLOTS:
QString play(QString p_song, int channel = 0, bool loop = false, int effect_flags = 0);
void stop(int channel = 0);
void setStreamVolume(int value, int streamId);
void setStreamLooping(bool enabled, int streamId);
QFutureWatcher<QString> m_watcher;
private:
AOApplication *ao_app;
bool m_muted = false;
int m_volume[CHANNEL_COUNT] = {0, 0};
HSTREAM m_stream_list[CHANNEL_COUNT];
HSYNC m_loop_sync[CHANNEL_COUNT];
/**
* @brief The starting sample of the AB-Loop.
*/
unsigned int m_loop_start[CHANNEL_COUNT] = {0, 0};
int m_volume[STREAM_COUNT]{};
HSTREAM m_stream_list[STREAM_COUNT]{};
HSYNC m_loop_sync[STREAM_COUNT]{};
quint32 m_loop_start[STREAM_COUNT]{};
quint32 m_loop_end[STREAM_COUNT]{};
/**
* @brief The end sample of the AB-Loop.
*/
unsigned int m_loop_end[CHANNEL_COUNT] = {0, 0};
bool ensureValidStreamId(int streamId);
};

View File

@ -10,6 +10,9 @@ QString AOPacket::decode(QString data)
return data.replace("<num>", "#").replace("<percent>", "%").replace("<dollar>", "$").replace("<and>", "&");
}
AOPacket::AOPacket()
{}
AOPacket::AOPacket(QString header)
: m_header(header)
{}

View File

@ -1,5 +1,6 @@
#pragma once
#include <QMetaType>
#include <QString>
#include <QStringList>
@ -9,6 +10,7 @@ public:
static QString encode(QString data);
static QString decode(QString data);
AOPacket();
AOPacket(QString header);
AOPacket(QString header, QStringList content);
@ -21,3 +23,4 @@ private:
QString m_header;
QStringList m_content;
};
Q_DECLARE_METATYPE(AOPacket)

View File

@ -2,111 +2,135 @@
#include "file_functions.h"
AOSfxPlayer::AOSfxPlayer(AOApplication *p_ao_app)
: ao_app(p_ao_app)
AOSfxPlayer::AOSfxPlayer(AOApplication *ao_app)
: ao_app(ao_app)
{}
void AOSfxPlayer::clear()
{
for (int n_stream = 0; n_stream < CHANNEL_COUNT; ++n_stream)
{
BASS_ChannelStop(m_stream_list[n_stream]);
}
set_volume_internal(m_volume);
}
void AOSfxPlayer::loop_clear()
{
for (int n_stream = 0; n_stream < CHANNEL_COUNT; ++n_stream)
{
if ((BASS_ChannelFlags(m_stream_list[n_stream], 0, 0) & BASS_SAMPLE_LOOP))
{
BASS_ChannelStop(m_stream_list[n_stream]);
}
}
set_volume_internal(m_volume);
}
void AOSfxPlayer::play(QString p_sfx, QString p_character, QString p_misc)
{
for (int i = 0; i < CHANNEL_COUNT; ++i)
{
if (BASS_ChannelIsActive(m_stream_list[i]) == BASS_ACTIVE_PLAYING)
{
m_channel = (i + 1) % CHANNEL_COUNT;
}
else
{
m_channel = i;
break;
}
}
QString path = ao_app->get_sfx(p_sfx, p_misc, p_character);
if (path.endsWith(".opus"))
{
m_stream_list[m_channel] = BASS_OPUS_StreamCreateFile(FALSE, path.utf16(), 0, 0, BASS_STREAM_AUTOFREE | BASS_UNICODE | BASS_ASYNCFILE);
}
else
{
m_stream_list[m_channel] = BASS_StreamCreateFile(FALSE, path.utf16(), 0, 0, BASS_STREAM_AUTOFREE | BASS_UNICODE | BASS_ASYNCFILE);
}
set_volume_internal(m_volume);
BASS_ChannelSetDevice(m_stream_list[m_channel], BASS_GetDevice());
BASS_ChannelPlay(m_stream_list[m_channel], false);
BASS_ChannelSetSync(m_stream_list[m_channel], BASS_SYNC_DEV_FAIL, 0, ao_app->BASSreset, 0);
}
void AOSfxPlayer::stop(int channel)
{
if (channel == -1)
{
channel = m_channel;
}
BASS_ChannelStop(m_stream_list[channel]);
}
void AOSfxPlayer::set_muted(bool toggle)
{
m_muted = toggle;
// Update the audio volume
set_volume_internal(m_volume);
}
int AOSfxPlayer::get_volume()
int AOSfxPlayer::volume()
{
return m_volume * 100;
}
void AOSfxPlayer::set_volume(qreal p_value)
void AOSfxPlayer::setVolume(int value)
{
m_volume = p_value * 0.01;
set_volume_internal(m_volume);
m_volume = value;
updateInternalVolume();
}
void AOSfxPlayer::set_volume_internal(qreal p_value)
void AOSfxPlayer::play(QString path)
{
// If muted, volume will always be 0
float volume = static_cast<float>(p_value) * !m_muted;
for (int n_stream = 0; n_stream < CHANNEL_COUNT; ++n_stream)
for (int i = 0; i < STREAM_COUNT; ++i)
{
BASS_ChannelSetAttribute(m_stream_list[n_stream], BASS_ATTRIB_VOL, volume);
if (BASS_ChannelIsActive(m_stream[i]) == BASS_ACTIVE_PLAYING)
{
m_current_stream_id = (i + 1) % STREAM_COUNT;
}
else
{
m_current_stream_id = i;
break;
}
}
if (path.endsWith(".opus"))
{
m_stream[m_current_stream_id] = BASS_OPUS_StreamCreateFile(FALSE, path.utf16(), 0, 0, BASS_STREAM_AUTOFREE | BASS_UNICODE | BASS_ASYNCFILE);
}
else
{
m_stream[m_current_stream_id] = BASS_StreamCreateFile(FALSE, path.utf16(), 0, 0, BASS_STREAM_AUTOFREE | BASS_UNICODE | BASS_ASYNCFILE);
}
updateInternalVolume();
BASS_ChannelSetDevice(m_stream[m_current_stream_id], BASS_GetDevice());
BASS_ChannelPlay(m_stream[m_current_stream_id], false);
BASS_ChannelSetSync(m_stream[m_current_stream_id], BASS_SYNC_DEV_FAIL, 0, ao_app->BASSreset, 0);
}
void AOSfxPlayer::findAndPlaySfx(QString sfx)
{
// TODO replace this with proper pathing tools
findAndPlayCharacterShout(sfx, QString(), QString());
}
void AOSfxPlayer::findAndPlayCharacterSfx(QString sfx, QString character)
{
// TODO replace this with proper pathing tools
findAndPlayCharacterShout(sfx, character, QString());
}
void AOSfxPlayer::findAndPlayCharacterShout(QString shout, QString character, QString group)
{
QString file_path = ao_app->get_sfx(shout, group, character);
if (file_exists(file_path))
{
play(file_path);
}
}
void AOSfxPlayer::set_looping(bool toggle, int channel)
void AOSfxPlayer::stopAll()
{
if (channel == -1)
for (int i = 0; i < STREAM_COUNT; ++i)
{
channel = m_channel;
stop(i);
}
}
void AOSfxPlayer::stopAllLoopingStream()
{
for (int i = 0; i < STREAM_COUNT; ++i)
{
if (BASS_ChannelFlags(m_stream[i], 0, 0) & BASS_SAMPLE_LOOP)
{
stop(i);
}
}
}
void AOSfxPlayer::stop(int streamId)
{
streamId = maybeFetchCurrentStreamId(streamId);
if (!ensureValidStreamId(streamId))
{
qWarning().noquote() << QObject::tr("Failed to stop stream; invalid stream ID '%1'").arg(streamId);
return;
}
BASS_ChannelStop(m_stream[streamId]);
}
void AOSfxPlayer::setMuted(bool toggle)
{
m_muted = toggle;
// Update the audio volume
updateInternalVolume();
}
void AOSfxPlayer::updateInternalVolume()
{
float volume = m_muted ? 0.0f : m_volume;
for (int i = 0; i < STREAM_COUNT; ++i)
{
BASS_ChannelSetAttribute(m_stream[i], BASS_ATTRIB_VOL, volume);
}
}
void AOSfxPlayer::setLooping(bool toggle, int streamId)
{
streamId = maybeFetchCurrentStreamId(streamId);
if (!ensureValidStreamId(streamId))
{
qWarning().noquote() << QObject::tr("Failed to setup stream loop; invalid stream ID '%1'").arg(streamId);
return;
}
m_looping = toggle;
if (BASS_ChannelFlags(m_stream_list[channel], 0, 0) & BASS_SAMPLE_LOOP)
if (BASS_ChannelFlags(m_stream[streamId], 0, 0) & BASS_SAMPLE_LOOP)
{
if (m_looping == false)
{
BASS_ChannelFlags(m_stream_list[channel], 0,
BASS_ChannelFlags(m_stream[streamId], 0,
BASS_SAMPLE_LOOP); // remove the LOOP flag
}
}
@ -114,8 +138,18 @@ void AOSfxPlayer::set_looping(bool toggle, int channel)
{
if (m_looping == true)
{
BASS_ChannelFlags(m_stream_list[channel], BASS_SAMPLE_LOOP,
BASS_ChannelFlags(m_stream[streamId], BASS_SAMPLE_LOOP,
BASS_SAMPLE_LOOP); // set the LOOP flag
}
}
}
int AOSfxPlayer::maybeFetchCurrentStreamId(int streamId)
{
return streamId == -1 ? m_current_stream_id : streamId;
}
bool AOSfxPlayer::ensureValidStreamId(int streamId)
{
return streamId >= 0 && streamId < STREAM_COUNT;
}

View File

@ -11,28 +11,35 @@
class AOSfxPlayer
{
public:
static constexpr int CHANNEL_COUNT = 5;
static constexpr int STREAM_COUNT = 5;
AOSfxPlayer(AOApplication *p_ao_app);
AOSfxPlayer(AOApplication *ao_app);
int get_volume();
int volume();
void setVolume(int value);
void clear();
void loop_clear();
void play(QString p_sfx, QString p_char = QString(), QString shout = QString());
void stop(int channel = -1);
void set_volume(qreal p_volume);
void set_looping(bool toggle, int channel = -1);
void set_muted(bool toggle);
void play(QString path);
void stop(int streamId = -1);
void stopAll();
void stopAllLoopingStream();
void findAndPlaySfx(QString sfx);
void findAndPlayCharacterSfx(QString sfx, QString character);
void findAndPlayCharacterShout(QString shout, QString character, QString group);
void setMuted(bool toggle);
void setLooping(bool toggle, int streamId = -1);
private:
AOApplication *ao_app;
qreal m_volume = 0.0;
bool m_looping = true;
int m_volume = 0;
bool m_muted = false;
int m_channel = 0;
HSTREAM m_stream_list[CHANNEL_COUNT]{};
bool m_looping = true;
HSTREAM m_stream[STREAM_COUNT]{};
int m_current_stream_id = 0;
void set_volume_internal(qreal p_volume);
int maybeFetchCurrentStreamId(int streamId);
bool ensureValidStreamId(int streamId);
void updateInternalVolume();
};

View File

@ -1,16 +1,16 @@
#include "aotextarea.h"
AOTextArea::AOTextArea(QWidget *p_parent)
: AOTextArea(5000, p_parent)
AOTextArea::AOTextArea(QWidget *parent)
: AOTextArea(5000, parent)
{}
AOTextArea::AOTextArea(int p_log_length, QWidget *p_parent)
: QTextBrowser(p_parent)
AOTextArea::AOTextArea(int maximumLogLenth, QWidget *parent)
: QTextBrowser(parent)
{
this->document()->setMaximumBlockCount(p_log_length);
document()->setMaximumBlockCount(maximumLogLenth);
}
void AOTextArea::append_chatmessage(QString p_name, QString p_message, QString p_name_colour, QString p_color)
void AOTextArea::addMessage(QString name, QString message, QString nameColor, QString messageColor)
{
const QTextCursor old_cursor = this->textCursor();
const int old_scrollbar_value = this->verticalScrollBar()->value();
@ -19,19 +19,19 @@ void AOTextArea::append_chatmessage(QString p_name, QString p_message, QString p
this->moveCursor(QTextCursor::End);
this->append("");
if (!p_name.isEmpty())
if (!name.isEmpty())
{
this->insertHtml("<b><font color=" + p_name_colour + ">" + p_name.toHtmlEscaped() + "</font></b>:&nbsp;");
this->insertHtml("<b><font color=" + nameColor + ">" + name.toHtmlEscaped() + "</font></b>:&nbsp;");
// cheap workarounds ahoy
p_message += " ";
message += " ";
}
QString result = p_message.toHtmlEscaped().replace("\n", "<br>").replace(url_parser_regex, "<a href='\\1'>\\1</a>");
QString result = message.toHtmlEscaped().replace("\n", "<br>").replace(url_parser_regex, "<a href='\\1'>\\1</a>");
if (!p_color.isEmpty())
if (!messageColor.isEmpty())
{
result = "<font color=" + p_color + ">" + result + "</font>";
result = "<font color=" + messageColor + ">" + result + "</font>";
}
this->insertHtml(result);

View File

@ -11,10 +11,10 @@ class AOTextArea : public QTextBrowser
Q_OBJECT
public:
AOTextArea(QWidget *p_parent = nullptr);
AOTextArea(int p_log_length, QWidget *p_parent = nullptr);
AOTextArea(QWidget *parent = nullptr);
AOTextArea(int maximumLogLenth, QWidget *parent = nullptr);
void append_chatmessage(QString p_name, QString p_message, QString p_name_colour, QString p_color = QString());
void addMessage(QString name, QString message, QString nameColor, QString messageColor = QString());
private:
const QRegularExpression url_parser_regex = QRegularExpression("\\b(https?://\\S+\\.\\S+)\\b");

View File

@ -338,7 +338,7 @@ void Courtroom::character_loading_finished()
// This part here serves as a way of showing to the player that the game is
// still running, it is just loading the pictures of the characters.
if (ao_app->lobby_constructed)
if (ao_app->is_lobby_constructed())
{
ao_app->generated_chars++;
}

View File

@ -28,20 +28,20 @@ Courtroom::Courtroom(AOApplication *p_ao_app)
sfx_delay_timer->setSingleShot(true);
music_player = new AOMusicPlayer(ao_app);
music_player->set_muted(true);
connect(&music_player->music_watcher, &QFutureWatcher<QString>::finished, this, &Courtroom::update_ui_music_name, Qt::QueuedConnection);
music_player->setMuted(true);
connect(&music_player->m_watcher, &QFutureWatcher<QString>::finished, this, &Courtroom::update_ui_music_name, Qt::QueuedConnection);
sfx_player = new AOSfxPlayer(ao_app);
sfx_player->set_muted(true);
sfx_player->setMuted(true);
objection_player = new AOSfxPlayer(ao_app);
objection_player->set_muted(true);
objection_player->setMuted(true);
blip_player = new AOBlipPlayer(ao_app);
blip_player->set_muted(true);
blip_player->setMuted(true);
modcall_player = new AOSfxPlayer(ao_app);
modcall_player->set_volume(50);
modcall_player->setVolume(50);
ui_background = new AOImage(ao_app, this);
ui_background->setObjectName("ui_background");
@ -552,15 +552,15 @@ void Courtroom::update_audio_volume()
remaining_percent = 0;
}
music_player->set_volume(ui_music_slider->value() * remaining_percent, 0); // set music
music_player->setStreamVolume(ui_music_slider->value() * remaining_percent, 0); // set music
// Set the ambience and other misc. music layers
for (int i = 1; i < music_player->CHANNEL_COUNT; ++i)
for (int i = 1; i < music_player->STREAM_COUNT; ++i)
{
music_player->set_volume(ui_sfx_slider->value() * remaining_percent, i);
music_player->setStreamVolume(ui_sfx_slider->value() * remaining_percent, i);
}
sfx_player->set_volume(ui_sfx_slider->value() * remaining_percent);
objection_player->set_volume(ui_sfx_slider->value() * remaining_percent);
blip_player->set_volume(ui_blip_slider->value() * remaining_percent);
sfx_player->setVolume(ui_sfx_slider->value() * remaining_percent);
objection_player->setVolume(ui_sfx_slider->value() * remaining_percent);
blip_player->setVolume(ui_blip_slider->value() * remaining_percent);
}
void Courtroom::append_char(CharacterSlot p_char)
@ -1688,10 +1688,10 @@ void Courtroom::enter_courtroom()
}
// Unmute everything
music_player->set_muted(false);
objection_player->set_muted(false);
sfx_player->set_muted(false);
blip_player->set_muted(false);
music_player->setMuted(false);
objection_player->setMuted(false);
sfx_player->setMuted(false);
blip_player->setMuted(false);
// Update the audio sliders
update_audio_volume();
@ -1866,7 +1866,7 @@ void Courtroom::debug_message_handler(QtMsgType type, const QMessageLogContext &
Q_UNUSED(context);
const QMap<QtMsgType, QString> colors = {{QtDebugMsg, "debug"}, {QtInfoMsg, "info"}, {QtWarningMsg, "warn"}, {QtCriticalMsg, "critical"}, {QtFatalMsg, "fatal"}};
const QString color_id = QString("debug_log_%1_color").arg(colors.value(type, "info"));
ui_debug_log->append_chatmessage(colors.value(type, "info"), msg, QString(), ao_app->get_color(color_id, "courtroom_fonts.ini").name());
ui_debug_log->addMessage(colors.value(type, "info"), msg, QString(), ao_app->get_color(color_id, "courtroom_fonts.ini").name());
}
void Courtroom::append_server_chatmessage(QString p_name, QString p_message, QString p_color)
@ -1887,7 +1887,7 @@ void Courtroom::append_server_chatmessage(QString p_name, QString p_message, QSt
on_authentication_state_received(1);
}
ui_server_chatlog->append_chatmessage(p_name, p_message, color);
ui_server_chatlog->addMessage(p_name, p_message, color);
if (Options::getInstance().logToTextFileEnabled() && !ao_app->log_filename.isEmpty())
{
@ -2666,33 +2666,33 @@ bool Courtroom::handle_objection()
{
case 1:
filename = "holdit_bubble";
objection_player->play("holdit", m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
objection_player->findAndPlayCharacterShout("holdit", m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
break;
case 2:
filename = "objection_bubble";
objection_player->play("objection", m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
objection_player->findAndPlayCharacterShout("objection", m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
break;
case 3:
filename = "takethat_bubble";
objection_player->play("takethat", m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
objection_player->findAndPlayCharacterShout("takethat", m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
break;
// case 4 is AO2 only
case 4:
if (custom_objection != "")
{
filename = "custom_objections/" + custom_objection.left(custom_objection.lastIndexOf("."));
objection_player->play(filename, m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
objection_player->findAndPlayCharacterShout(filename, m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
}
else
{
filename = "custom";
objection_player->play("custom", m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
objection_player->findAndPlayCharacterShout("custom", m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
}
break;
m_chatmessage[EMOTE_MOD] = QChar(PREANIM);
}
ui_vp_objection->load_image(filename, m_chatmessage[CHAR_NAME], ao_app->get_chat(m_chatmessage[CHAR_NAME]));
sfx_player->clear(); // Objection played! Cut all sfx.
sfx_player->stopAll(); // Objection played! Cut all sfx.
ui_vp_player_char->set_play_once(true);
return true;
}
@ -2710,7 +2710,7 @@ void Courtroom::display_character()
ui_vp_player_char->stop();
ui_vp_effect->stop();
// Clear all looping sfx to prevent obnoxiousness
sfx_player->loop_clear();
sfx_player->stopAllLoopingStream();
// Hide the message and chatbox and handle the emotes
ui_vp_message->hide();
ui_vp_chatbox->setVisible(chatbox_always_show);
@ -2991,7 +2991,7 @@ void Courtroom::do_effect(QString fx_path, QString fx_sound, QString p_char, QSt
if (fx_sound != "")
{
sfx_player->play(fx_sound);
sfx_player->findAndPlaySfx(fx_sound);
}
// Only check if effects are disabled after playing the sound if it exists
@ -3073,7 +3073,7 @@ void Courtroom::do_effect(QString fx_path, QString fx_sound, QString p_char, QSt
void Courtroom::play_char_sfx(QString sfx_name)
{
sfx_player->play(sfx_name);
sfx_player->findAndPlaySfx(sfx_name);
}
void Courtroom::initialize_chatbox()
@ -3216,7 +3216,7 @@ void Courtroom::handle_callwords()
if (f_message.contains(word, Qt::CaseInsensitive))
{
// Play the call word sfx on the modcall_player sound container
modcall_player->play(ao_app->get_court_sfx("word_call"));
modcall_player->findAndPlaySfx(ao_app->get_court_sfx("word_call"));
// Make the window flash
ao_app->alert(this);
// Break the loop so we don't spam sound effects
@ -3236,7 +3236,7 @@ void Courtroom::display_evidence_image()
// QString f_evi_name = local_evidence_list.at(f_evi_id - 1).name;
// def jud and hlp should display the evidence icon on the RIGHT side
bool is_left_side = !(side == "def" || side == "hlp" || side == "jud" || side == "jur");
ui_vp_evidence_display->show_evidence(f_evi_id, f_image, is_left_side, sfx_player->get_volume());
ui_vp_evidence_display->show_evidence(f_evi_id, f_image, is_left_side, sfx_player->volume());
}
}
@ -3930,7 +3930,7 @@ void Courtroom::start_chat_ticking()
else if (m_chatmessage[REALIZATION] == "1")
{
this->do_flash();
sfx_player->play(ao_app->get_custom_realization(m_chatmessage[CHAR_NAME]));
sfx_player->findAndPlaySfx(ao_app->get_custom_realization(m_chatmessage[CHAR_NAME]));
}
int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); // text meme bonanza
if ((emote_mod == IDLE || emote_mod == ZOOM) && m_chatmessage[SCREENSHAKE] == "1")
@ -4009,7 +4009,7 @@ void Courtroom::start_chat_ticking()
{
f_blips = ao_app->get_blips(m_chatmessage[BLIPNAME]);
}
blip_player->set_blips(f_blips);
blip_player->setBlip(f_blips);
// means text is currently ticking
text_state = 1;
@ -4276,7 +4276,7 @@ void Courtroom::chat_tick()
// ignoring white space unless blank_blip is enabled.
if (!formatting_char && (f_character != ' ' || blank_blip))
{
blip_player->blip_tick();
blip_player->playBlip();
++blip_ticker;
}
}
@ -4335,10 +4335,10 @@ void Courtroom::play_sfx()
return;
}
sfx_player->play(sfx_name);
sfx_player->findAndPlaySfx(sfx_name);
if (Options::getInstance().loopingSfx())
{
sfx_player->set_looping(ao_app->get_sfx_looping(current_char, current_emote) == "1");
sfx_player->setLooping(ao_app->get_sfx_looping(current_char, current_emote) == "1");
}
}
@ -4500,22 +4500,19 @@ void Courtroom::handle_song(QStringList *p_contents)
{
// Current song UI only displays the song playing, not other channels.
// Any other music playing is irrelevant.
if (music_player->music_watcher.isRunning())
if (music_player->m_watcher.isRunning())
{
music_player->music_watcher.cancel();
music_player->m_watcher.cancel();
}
ui_music_name->setText(tr("[LOADING] %1").arg(f_song_clear));
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
music_player->music_watcher.setFuture(QtConcurrent::run(music_player, &AOMusicPlayer::play, f_song, channel, looping, effect_flags));
#else
music_player->music_watcher.setFuture(QtConcurrent::run(&AOMusicPlayer::play, music_player, f_song, channel, looping, effect_flags));
#endif
music_player->m_watcher.setFuture(QtConcurrent::run([=, this]() -> QString { return music_player->playStream(f_song, channel, looping, effect_flags); }));
}
void Courtroom::update_ui_music_name()
{
QString result = music_player->music_watcher.result();
QString result = music_player->m_watcher.result();
if (result.isEmpty())
{
return;
@ -4590,7 +4587,7 @@ void Courtroom::handle_wtce(QString p_wtce, int variant)
filename = p_wtce;
}
}
sfx_player->play(sfx_name);
sfx_player->findAndPlaySfx(sfx_name);
ui_vp_wtce->load_image(filename, "", bg_misc);
ui_vp_wtce->set_play_once(true);
}
@ -4648,7 +4645,7 @@ void Courtroom::set_hp_bar(int p_bar, int p_state)
if (!sfx_name.isEmpty())
{
sfx_player->play(sfx_name);
sfx_player->findAndPlaySfx(sfx_name);
}
}
@ -4678,7 +4675,7 @@ void Courtroom::mod_called(QString p_ip)
ui_server_chatlog->append(p_ip);
if (!ui_guard->isChecked())
{
modcall_player->play(ao_app->get_court_sfx("mod_call"));
modcall_player->findAndPlaySfx(ao_app->get_court_sfx("mod_call"));
ao_app->alert(this);
}
}
@ -4703,11 +4700,7 @@ void Courtroom::on_ooc_return_pressed()
if (ooc_message.startsWith("/load_case"))
{
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
QStringList command = ooc_message.split(" ", QString::SkipEmptyParts);
#else
QStringList command = ooc_message.split(" ", Qt::SkipEmptyParts);
#endif
QStringList command = ooc_message.split(" ", AOSplitBehaviorFlags::SkipEmptyParts);
QDir casefolder(get_base_path() + "/cases");
if (!casefolder.exists())
{
@ -4803,11 +4796,7 @@ void Courtroom::on_ooc_return_pressed()
}
else if (ooc_message.startsWith("/save_case"))
{
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
QStringList command = ooc_message.split(" ", QString::SkipEmptyParts);
#else
QStringList command = ooc_message.split(" ", Qt::SkipEmptyParts);
#endif
QStringList command = ooc_message.split(" ", AOSplitBehaviorFlags::SkipEmptyParts);
QDir casefolder(get_base_path() + "cases");
if (!casefolder.exists())
{
@ -5265,7 +5254,7 @@ void Courtroom::on_sfx_context_menu_requested(const QPoint &pos)
void Courtroom::on_sfx_play_clicked()
{
sfx_player->play(get_char_sfx(), get_current_char());
sfx_player->findAndPlayCharacterSfx(get_char_sfx(), get_current_char());
}
void Courtroom::on_sfx_edit_requested()
@ -6063,25 +6052,25 @@ void Courtroom::on_text_color_changed(int p_color)
void Courtroom::on_music_slider_moved(int p_value)
{
music_player->set_volume(p_value, 0); // Set volume on music layer
music_player->setStreamVolume(p_value, 0); // Set volume on music layer
ui_ic_chat_message->setFocus();
}
void Courtroom::on_sfx_slider_moved(int p_value)
{
sfx_player->set_volume(p_value);
sfx_player->setVolume(p_value);
// Set the ambience and other misc. music layers
for (int i = 1; i < music_player->CHANNEL_COUNT; ++i)
for (int i = 1; i < music_player->STREAM_COUNT; ++i)
{
music_player->set_volume(p_value, i);
music_player->setStreamVolume(p_value, i);
}
objection_player->set_volume(p_value);
objection_player->setVolume(p_value);
ui_ic_chat_message->setFocus();
}
void Courtroom::on_blip_slider_moved(int p_value)
{
blip_player->set_volume(p_value);
blip_player->setVolume(p_value);
ui_ic_chat_message->setFocus();
}
@ -6150,8 +6139,8 @@ void Courtroom::on_guilty_clicked()
void Courtroom::on_change_character_clicked()
{
sfx_player->set_muted(true);
blip_player->set_muted(true);
sfx_player->setMuted(true);
blip_player->setMuted(true);
set_char_select();

View File

@ -3,6 +3,12 @@
#include <QMap>
#include <QString>
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
using AOSplitBehaviorFlags = QString::SplitBehaviorFlags;
#else
using AOSplitBehaviorFlags = Qt::SplitBehaviorFlags;
#endif
enum ServerConnectionType
{
TcpServerConnection,
@ -18,6 +24,8 @@ struct ServerInfo
QString ip;
int port;
ServerConnectionType socket_type;
inline QString toString() { return QString("%1 (<%2>%3:%4)").arg(name, SERVER_CONNECTION_TYPE_STRING_MAP.key(socket_type), ip, QString::number(port)); }
};
struct CharacterSlot

View File

@ -1,5 +1,7 @@
#include "demoserver.h"
#include "datatypes.h"
DemoServer::DemoServer(QObject *parent)
: QObject(parent)
{
@ -93,12 +95,7 @@ void DemoServer::recv_data()
{
QString in_data = QString::fromUtf8(client_sock->readAll());
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
const QStringList packet_list = in_data.split("%", QString::SkipEmptyParts);
#else
const QStringList packet_list = in_data.split("%", Qt::SkipEmptyParts);
#endif
const QStringList packet_list = in_data.split("%", AOSplitBehaviorFlags::SkipEmptyParts);
for (const QString &packet : packet_list)
{
QStringList f_contents;

View File

@ -22,6 +22,7 @@ void Courtroom::initialize_emotes()
emote_preview = new AOEmotePreview(ao_app, this);
emote_preview->setObjectName("ui_emote_preview");
emote_preview->resize(256, 192);
connect(ui_emote_left, &AOButton::clicked, this, &Courtroom::on_emote_left_clicked);
connect(ui_emote_right, &AOButton::clicked, this, &Courtroom::on_emote_right_clicked);
@ -262,7 +263,7 @@ void Courtroom::show_emote_menu(const QPoint &pos)
emote_menu->setDefaultAction(emote_menu->addAction("Preview Selected", this, [this] {
emote_preview->show();
emote_preview->raise();
emote_preview->set_widgets();
emote_preview->updateViewportGeometry();
update_emote_preview();
}));
QString prefix;
@ -291,8 +292,8 @@ void Courtroom::preview_emote(QString f_emote)
{
emote_preview->show();
emote_preview->raise();
emote_preview->set_widgets();
emote_preview->play(f_emote, current_char, ui_flip->isChecked(), ui_pair_offset_spinbox->value(), ui_pair_vert_offset_spinbox->value());
emote_preview->updateViewportGeometry();
emote_preview->display(current_char, f_emote, ui_flip->isChecked(), ui_pair_offset_spinbox->value(), ui_pair_vert_offset_spinbox->value());
}
void Courtroom::on_emote_left_clicked()

View File

@ -17,6 +17,8 @@ int main(int argc, char *argv[])
{
qSetMessagePattern("%{type}: %{if-category}%{category}: %{endif}%{message}");
qRegisterMetaType<AOPacket>();
AOApplication main_app(argc, argv);
#ifdef ANDROID

View File

@ -0,0 +1,5 @@
#include "netconnection.h"
NetConnection::NetConnection(QObject *parent)
: QObject(parent)
{}

28
src/net/netconnection.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include "aopacket.h"
#include "datatypes.h"
#include <QObject>
class NetConnection : public QObject
{
Q_OBJECT
public:
explicit NetConnection(QObject *parent = nullptr);
virtual bool isConnected() = 0;
virtual void connectToServer(ServerInfo &server) = 0;
virtual void disconnectFromServer() = 0;
virtual void sendPacket(AOPacket packet) = 0;
Q_SIGNALS:
void connectedToServer();
void disconnectedFromServer();
void errorOccurred(QString error);
void receivedPacket(AOPacket packet);
};

View File

@ -0,0 +1,102 @@
#include "nettcpconnection.h"
NetTcpConnection::NetTcpConnection(QObject *parent)
: NetConnection(parent)
, m_socket(new QTcpSocket(this))
, m_last_state(QAbstractSocket::UnconnectedState)
{
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this, &NetTcpConnection::onErrorOccurred);
#else
connect(m_socket, &QTcpSocket::errorOccurred, this, &NetTcpConnection::onErrorOccurred);
#endif
connect(m_socket, &QTcpSocket::stateChanged, this, &NetTcpConnection::onStateChanged);
connect(m_socket, &QTcpSocket::readyRead, this, &NetTcpConnection::onReadyRead);
}
NetTcpConnection::~NetTcpConnection()
{
m_socket->disconnect(this);
disconnectFromServer();
}
bool NetTcpConnection::isConnected()
{
return m_last_state == QAbstractSocket::ConnectedState;
}
void NetTcpConnection::connectToServer(ServerInfo &server)
{
disconnectedFromServer();
m_socket->connectToHost(server.ip, server.port);
}
void NetTcpConnection::disconnectFromServer()
{
m_socket->abort();
}
void NetTcpConnection::sendPacket(AOPacket packet)
{
if (!isConnected())
{
qWarning().noquote() << QObject::tr("Cannot send packet, not connected to server");
return;
}
m_socket->write(packet.toString(true).toUtf8());
}
void NetTcpConnection::onErrorOccurred()
{
Q_EMIT errorOccurred(m_socket->errorString());
}
void NetTcpConnection::onStateChanged(QAbstractSocket::SocketState state)
{
m_last_state = state;
switch (state)
{
default:
break;
case QAbstractSocket::ConnectedState:
m_cached_data.clear();
Q_EMIT connectedToServer();
break;
case QAbstractSocket::UnconnectedState:
Q_EMIT disconnectFromServer();
break;
}
}
void NetTcpConnection::onReadyRead()
{
m_cached_data += QString::fromUtf8(m_socket->readAll());
if (!m_cached_data.endsWith('%'))
{
return;
}
QStringList raw_packet_list = m_cached_data.split('%', AOSplitBehaviorFlags::SkipEmptyParts);
m_cached_data.clear();
for (QString raw_packet : raw_packet_list)
{
if (!raw_packet.endsWith('#'))
{
Q_EMIT errorOccurred(QObject::tr("Malformed packet received %1").arg(raw_packet));
continue;
}
raw_packet.chop(1);
QStringList raw_content = raw_packet.split('#');
const QString header = raw_content.takeFirst();
for (QString &data : raw_content)
{
data = AOPacket::decode(data);
}
Q_EMIT receivedPacket(AOPacket(header, raw_content));
}
}

View File

@ -0,0 +1,31 @@
#pragma once
#include "aopacket.h"
#include "datatypes.h"
#include "netconnection.h"
#include <QTcpSocket>
class NetTcpConnection : public NetConnection
{
public:
NetTcpConnection(QObject *parent = nullptr);
virtual ~NetTcpConnection();
bool isConnected() override;
void connectToServer(ServerInfo &server) override;
void disconnectFromServer() override;
void sendPacket(AOPacket packet) override;
private:
QTcpSocket *m_socket;
QAbstractSocket::SocketState m_last_state;
QString m_cached_data;
private Q_SLOTS:
void onErrorOccurred();
void onStateChanged(QAbstractSocket::SocketState state);
void onReadyRead();
};

View File

@ -0,0 +1,97 @@
#include "netwebsocketconnection.h"
#include "networkmanager.h"
#include <QNetworkRequest>
#include <QUrl>
NetWebSocketConnection::NetWebSocketConnection(NetworkManager *networkManager)
: NetConnection(networkManager)
, m_network_manager(networkManager)
, m_socket(new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this))
, m_last_state(QAbstractSocket::UnconnectedState)
{
connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, &NetWebSocketConnection::onError);
connect(m_socket, &QWebSocket::stateChanged, this, &NetWebSocketConnection::onStateChanged);
connect(m_socket, &QWebSocket::textMessageReceived, this, &NetWebSocketConnection::onTextMessageReceived);
}
NetWebSocketConnection::~NetWebSocketConnection()
{
m_socket->disconnect(this);
disconnectFromServer();
}
bool NetWebSocketConnection::isConnected()
{
return m_last_state == QAbstractSocket::ConnectedState;
}
void NetWebSocketConnection::connectToServer(ServerInfo &server)
{
disconnectFromServer();
QUrl url;
url.setScheme("ws");
url.setHost(server.ip);
url.setPort(server.port);
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, m_network_manager->get_user_agent());
m_socket->open(req);
}
void NetWebSocketConnection::disconnectFromServer()
{
if (isConnected())
{
m_socket->close(QWebSocketProtocol::CloseCodeGoingAway);
}
}
void NetWebSocketConnection::sendPacket(AOPacket packet)
{
m_socket->sendTextMessage(packet.toString(true));
}
void NetWebSocketConnection::onError()
{
Q_EMIT errorOccurred(m_socket->errorString());
}
void NetWebSocketConnection::onStateChanged(QAbstractSocket::SocketState state)
{
m_last_state = state;
switch (state)
{
default:
break;
case QAbstractSocket::ConnectedState:
Q_EMIT connectedToServer();
break;
case QAbstractSocket::UnconnectedState:
Q_EMIT disconnectFromServer();
break;
}
}
void NetWebSocketConnection::onTextMessageReceived(QString message)
{
if (!message.endsWith("#%"))
{
return;
}
message.chop(2);
QStringList raw_content = message.split('#');
const QString header = raw_content.takeFirst();
for (QString &data : raw_content)
{
data = AOPacket::decode(data);
}
Q_EMIT receivedPacket(AOPacket(header, raw_content));
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "netconnection.h"
class NetworkManager;
#include <QWebSocket>
class NetWebSocketConnection : public NetConnection
{
public:
NetWebSocketConnection(NetworkManager *networkManager);
virtual ~NetWebSocketConnection();
bool isConnected() override;
void connectToServer(ServerInfo &server) override;
void disconnectFromServer() override;
void sendPacket(AOPacket packet) override;
private:
NetworkManager *m_network_manager;
QWebSocket *m_socket;
QAbstractSocket::SocketState m_last_state;
private Q_SLOTS:
void onError();
void onStateChanged(QAbstractSocket::SocketState state);
void onTextMessageReceived(QString message);
};

View File

@ -3,6 +3,8 @@
#include "datatypes.h"
#include "debug_functions.h"
#include "lobby.h"
#include "net/nettcpconnection.h"
#include "net/netwebsocketconnection.h"
#include "options.h"
#include <QAbstractSocket>
@ -77,13 +79,18 @@ void NetworkManager::ms_request_finished(QNetworkReply *reply)
}
ao_app->set_server_list(server_list);
if (ao_app->lobby_constructed)
if (ao_app->is_lobby_constructed())
{
ao_app->w_lobby->list_servers();
}
reply->deleteLater();
}
QString NetworkManager::get_user_agent() const
{
return QStringLiteral("AttorneyOnline/%1 (Desktop)").arg(ao_app->get_version_string());
}
void NetworkManager::send_heartbeat()
{
// Ping the server periodically to tell the MS that you've been playing
@ -137,57 +144,62 @@ void NetworkManager::request_document(MSDocumentType document_type, const std::f
});
}
void NetworkManager::connect_to_server(ServerInfo p_server)
void NetworkManager::connect_to_server(ServerInfo server)
{
disconnect_from_server();
qInfo().nospace().noquote() << "connecting to " << p_server.ip << ":" << p_server.port;
switch (p_server.socket_type)
qInfo().noquote() << QObject::tr("Connecting to %1").arg(server.toString());
switch (server.socket_type)
{
default:
p_server.socket_type = TcpServerConnection;
server.socket_type = TcpServerConnection;
[[fallthrough]];
case TcpServerConnection:
qInfo() << "using TCP backend";
server_socket.tcp = new QTcpSocket(this);
connect(server_socket.tcp, &QAbstractSocket::connected, this, [] { qDebug() << "established connection to server"; });
connect(server_socket.tcp, &QIODevice::readyRead, this, [this] { handle_server_packet(QString::fromUtf8(server_socket.tcp->readAll())); });
connect(server_socket.tcp, &QAbstractSocket::disconnected, ao_app, &AOApplication::server_disconnected);
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
connect(server_socket.tcp, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this, [this] {
#else
connect(server_socket.tcp, &QAbstractSocket::errorOccurred, this, [this] {
#endif
qCritical() << "TCP socket error:" << server_socket.tcp->errorString();
});
server_socket.tcp->connectToHost(p_server.ip, p_server.port);
qInfo() << "Using TCP backend.";
m_connection = new NetTcpConnection(this);
break;
case WebSocketServerConnection:
qInfo() << "using WebSockets backend";
server_socket.ws = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this);
connect(server_socket.ws, &QWebSocket::connected, this, [] { qDebug() << "established connection to server"; });
connect(server_socket.ws, &QWebSocket::textMessageReceived, this, &NetworkManager::handle_server_packet);
connect(server_socket.ws, &QWebSocket::disconnected, ao_app, &AOApplication::server_disconnected);
connect(server_socket.ws, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, [this] { qCritical() << "WebSockets error:" << server_socket.ws->errorString(); });
QUrl url;
url.setScheme("ws");
url.setHost(p_server.ip);
url.setPort(p_server.port);
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, get_user_agent());
server_socket.ws->open(req);
qInfo() << "Using WebSockets backend.";
m_connection = new NetWebSocketConnection(this);
break;
}
connected = true;
active_connection_type = p_server.socket_type;
connect(m_connection, &NetConnection::connectedToServer, this, [] { qInfo() << "Established connection to server."; });
connect(m_connection, &NetConnection::disconnectedFromServer, ao_app, &AOApplication::server_disconnected);
connect(m_connection, &NetConnection::errorOccurred, this, [](QString error) { qCritical() << "Connection error:" << error; });
connect(m_connection, &NetConnection::receivedPacket, this, &NetworkManager::handle_server_packet);
m_connection->connectToServer(server);
}
void NetworkManager::disconnect_from_server()
{
if (m_connection)
{
m_connection->disconnectFromServer();
m_connection->deleteLater();
m_connection = nullptr;
}
}
void NetworkManager::ship_server_packet(AOPacket packet)
{
if (!m_connection)
{
qCritical() << "Failed to ship packet; no connection.";
return;
}
if (!m_connection->isConnected())
{
qCritical() << "Failed to ship packet; not connected.";
return;
}
qInfo().noquote() << "Sending packet:" << packet.toString();
m_connection->sendPacket(packet);
}
void NetworkManager::join_to_server()
@ -195,98 +207,8 @@ void NetworkManager::join_to_server()
ship_server_packet(AOPacket("askchaa").toString());
}
void NetworkManager::disconnect_from_server()
void NetworkManager::handle_server_packet(AOPacket packet)
{
if (!connected)
{
return;
}
switch (active_connection_type)
{
case TcpServerConnection:
server_socket.tcp->close();
server_socket.tcp->deleteLater();
break;
case WebSocketServerConnection:
server_socket.ws->close(QWebSocketProtocol::CloseCodeGoingAway);
server_socket.ws->deleteLater();
break;
}
connected = false;
}
void NetworkManager::ship_server_packet(AOPacket p_packet)
{
QString message = p_packet.toString(true);
switch (active_connection_type)
{
case TcpServerConnection:
server_socket.tcp->write(message.toUtf8());
break;
case WebSocketServerConnection:
server_socket.ws->sendTextMessage(message);
break;
}
}
void NetworkManager::handle_server_packet(const QString &p_data)
{
QString in_data = p_data;
if (!p_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;
}
}
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
const QStringList packet_list = in_data.split("%", QString::SkipEmptyParts);
#else
const QStringList packet_list = in_data.split("%", Qt::SkipEmptyParts);
#endif
for (const QString &packet : packet_list)
{
QStringList f_contents;
// Packet should *always* end with #
if (packet.endsWith("#"))
{
f_contents = packet.chopped(1).split("#");
}
// But, if it somehow doesn't, we should still be able to handle it
else
{
f_contents = packet.split("#");
}
// Empty packets are suspicious!
if (f_contents.isEmpty())
{
qWarning() << "WARNING: Empty packet received from server, skipping...";
continue;
}
// Take the first arg as the command
QString command = f_contents.takeFirst();
for (QString &data : f_contents)
{
data = AOPacket::decode(data);
}
// The rest is contents of the packet
AOPacket f_packet(command, f_contents);
// Ship it to the server!
ao_app->server_packet_received(f_packet);
}
qInfo().noquote() << "Received packet:" << packet.toString();
ao_app->server_packet_received(packet);
}

View File

@ -2,6 +2,7 @@
#include "aoapplication.h"
#include "aopacket.h"
#include "net/netconnection.h"
#include <QDnsLookup>
#include <QNetworkAccessManager>
@ -28,11 +29,13 @@ public:
void connect_to_server(ServerInfo p_server);
void disconnect_from_server();
QString get_user_agent() const;
public Q_SLOTS:
void get_server_list();
void ship_server_packet(AOPacket p_packet);
void ship_server_packet(AOPacket packet);
void join_to_server();
void handle_server_packet(const QString &p_data);
void handle_server_packet(AOPacket packet);
void request_document(MSDocumentType document_type, const std::function<void(QString)> &cb);
void send_heartbeat();
@ -47,13 +50,7 @@ private:
AOApplication *ao_app;
QNetworkAccessManager *http;
union
{
QWebSocket *ws;
QTcpSocket *tcp;
} server_socket;
ServerConnectionType active_connection_type;
bool connected = false;
NetConnection *m_connection = nullptr;
QTimer *heartbeat_timer;
@ -62,10 +59,5 @@ private:
const int heartbeat_interval = 60 * 5 * 1000;
bool partial_packet = false;
QString temp_packet;
unsigned int s_decryptor = 5;
QString get_user_agent() const { return QStringLiteral("AttorneyOnline/%1 (Desktop)").arg(ao_app->get_version_string()); }
};

View File

@ -24,14 +24,10 @@ void AOApplication::append_to_demofile(QString packet_string)
}
}
void AOApplication::server_packet_received(AOPacket p_packet)
void AOApplication::server_packet_received(AOPacket packet)
{
QStringList f_contents_encoded = p_packet.content();
QString f_packet_encoded = p_packet.toString();
QString header = p_packet.header();
QStringList f_contents = p_packet.content();
QString f_packet = p_packet.toString();
QString header = packet.header();
QStringList content = packet.content();
bool log_to_demo = true;
@ -44,7 +40,7 @@ void AOApplication::server_packet_received(AOPacket p_packet)
if (header == "decryptor")
{
if (f_contents.size() == 0)
if (content.size() == 0)
{
return;
}
@ -76,13 +72,13 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "ID")
{
if (f_contents.size() < 2)
if (content.size() < 2)
{
return;
}
client_id = f_contents.at(0).toInt();
server_software = f_contents.at(1);
client_id = content.at(0).toInt();
server_software = content.at(1);
net_manager->server_connected(true);
@ -91,18 +87,18 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "CT")
{
if (!courtroom_constructed || f_contents.size() < 2)
if (!is_courtroom_constructed() || content.size() < 2)
{
return;
}
if (f_contents.size() == 3)
if (content.size() == 3)
{
w_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1), f_contents.at(2));
w_courtroom->append_server_chatmessage(content.at(0), content.at(1), content.at(2));
}
else
{
w_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1), "0");
w_courtroom->append_server_chatmessage(content.at(0), content.at(1), "0");
}
}
else if (header == "FL")
@ -125,104 +121,102 @@ void AOApplication::server_packet_received(AOPacket p_packet)
custom_blips_supported = false;
log_to_demo = false;
if (f_packet.contains("yellowtext", Qt::CaseInsensitive))
if (content.contains("yellowtext", Qt::CaseInsensitive))
{
yellow_text_supported = true;
}
if (f_packet.contains("prezoom", Qt::CaseInsensitive))
if (content.contains("prezoom", Qt::CaseInsensitive))
{
prezoom_supported = true;
}
if (f_packet.contains("flipping", Qt::CaseInsensitive))
if (content.contains("flipping", Qt::CaseInsensitive))
{
flipping_supported = true;
}
if (f_packet.contains("customobjections", Qt::CaseInsensitive))
if (content.contains("customobjections", Qt::CaseInsensitive))
{
custom_objection_supported = true;
}
if (f_packet.contains("deskmod", Qt::CaseInsensitive))
if (content.contains("deskmod", Qt::CaseInsensitive))
{
desk_mod_supported = true;
}
if (f_packet.contains("evidence", Qt::CaseInsensitive))
if (content.contains("evidence", Qt::CaseInsensitive))
{
evidence_supported = true;
}
if (f_packet.contains("cccc_ic_support", Qt::CaseInsensitive))
if (content.contains("cccc_ic_support", Qt::CaseInsensitive))
{
cccc_ic_supported = true;
}
if (f_packet.contains("arup", Qt::CaseInsensitive))
if (content.contains("arup", Qt::CaseInsensitive))
{
arup_supported = true;
}
if (f_packet.contains("casing_alerts", Qt::CaseInsensitive))
if (content.contains("casing_alerts", Qt::CaseInsensitive))
{
casing_alerts_supported = true;
}
if (f_packet.contains("modcall_reason", Qt::CaseInsensitive))
if (content.contains("modcall_reason", Qt::CaseInsensitive))
{
modcall_reason_supported = true;
}
if (f_packet.contains("looping_sfx", Qt::CaseInsensitive))
if (content.contains("looping_sfx", Qt::CaseInsensitive))
{
looping_sfx_supported = true;
}
if (f_packet.contains("additive", Qt::CaseInsensitive))
if (content.contains("additive", Qt::CaseInsensitive))
{
additive_supported = true;
}
if (f_packet.contains("effects", Qt::CaseInsensitive))
if (content.contains("effects", Qt::CaseInsensitive))
{
effects_supported = true;
}
if (f_packet.contains("y_offset", Qt::CaseInsensitive))
if (content.contains("y_offset", Qt::CaseInsensitive))
{
y_offset_supported = true;
}
if (f_packet.contains("expanded_desk_mods", Qt::CaseInsensitive))
if (content.contains("expanded_desk_mods", Qt::CaseInsensitive))
{
expanded_desk_mods_supported = true;
}
if (f_packet.contains("auth_packet", Qt::CaseInsensitive))
if (content.contains("auth_packet", Qt::CaseInsensitive))
{
auth_packet_supported = true;
}
if (f_packet.contains("custom_blips", Qt::CaseInsensitive))
if (content.contains("custom_blips", Qt::CaseInsensitive))
{
custom_blips_supported = true;
}
log_to_demo = false;
}
else if (header == "PN")
{
if (!lobby_constructed || f_contents.size() < 2)
if (!is_lobby_constructed() || content.size() < 2)
{
return;
}
w_lobby->set_player_count(f_contents.at(0).toInt(), f_contents.at(1).toInt());
w_lobby->set_player_count(content.at(0).toInt(), content.at(1).toInt());
if (f_contents.size() >= 3)
if (content.size() >= 3)
{
w_lobby->set_server_description(f_contents.at(2));
w_lobby->set_server_description(content.at(2));
}
log_to_demo = false;
}
else if (header == "SI")
{
if (!lobby_constructed || f_contents.size() != 3)
if (!is_lobby_constructed() || content.size() != 3)
{
return;
}
char_list_size = f_contents.at(0).toInt();
evidence_list_size = f_contents.at(1).toInt();
music_list_size = f_contents.at(2).toInt();
char_list_size = content.at(0).toInt();
evidence_list_size = content.at(1).toInt();
music_list_size = content.at(2).toInt();
if (char_list_size < 0 || evidence_list_size < 0 || music_list_size < 0)
{
@ -272,7 +266,7 @@ void AOApplication::server_packet_received(AOPacket p_packet)
break;
}
if (courtroom_constructed)
if (is_courtroom_constructed())
{
w_courtroom->set_window_title(window_title);
}
@ -301,14 +295,14 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "CharsCheck")
{
if (!courtroom_constructed)
if (!is_courtroom_constructed())
{
return;
}
for (int n_char = 0; n_char < f_contents.size(); ++n_char)
for (int n_char = 0; n_char < content.size(); ++n_char)
{
if (f_contents.at(n_char) == "-1")
if (content.at(n_char) == "-1")
{
w_courtroom->set_taken(n_char, true);
}
@ -322,14 +316,14 @@ void AOApplication::server_packet_received(AOPacket p_packet)
else if (header == "SC")
{
if (!courtroom_constructed)
if (!is_courtroom_constructed())
{
return;
}
w_courtroom->clear_chars();
for (int n_element = 0; n_element < f_contents.size(); ++n_element)
for (int n_element = 0; n_element < content.size(); ++n_element)
{
QStringList sub_elements = f_contents.at(n_element).split("&");
QStringList sub_elements = content.at(n_element).split("&");
for (QString &sub_element : sub_elements)
{
sub_element = AOPacket::decode(sub_element);
@ -359,7 +353,7 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "SM")
{
if (!courtroom_constructed || courtroom_loaded)
if (!is_courtroom_constructed() || courtroom_loaded)
{
return;
}
@ -367,25 +361,25 @@ 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)
for (int n_element = 0; n_element < content.size(); ++n_element)
{
++loaded_music;
if (musics_time)
{
w_courtroom->append_music(f_contents.at(n_element));
w_courtroom->append_music(content.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"))
if (content.at(n_element).endsWith(".wav") || content.at(n_element).endsWith(".mp3") || content.at(n_element).endsWith(".mp4") || content.at(n_element).endsWith(".ogg") || content.at(n_element).endsWith(".opus"))
{
musics_time = true;
w_courtroom->fix_last_area();
w_courtroom->append_music(f_contents.at(n_element));
w_courtroom->append_music(content.at(n_element));
areas--;
}
else
{
w_courtroom->append_area(f_contents.at(n_element));
w_courtroom->append_area(content.at(n_element));
areas++;
}
}
@ -401,16 +395,16 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "FM") // Fetch music ONLY
{
if (!courtroom_constructed)
if (!is_courtroom_constructed())
{
return;
}
w_courtroom->clear_music();
for (int n_element = 0; n_element < f_contents.size(); ++n_element)
for (int n_element = 0; n_element < content.size(); ++n_element)
{
w_courtroom->append_music(f_contents.at(n_element));
w_courtroom->append_music(content.at(n_element));
}
w_courtroom->list_music();
@ -418,7 +412,7 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "FA") // Fetch areas ONLY
{
if (!courtroom_constructed)
if (!is_courtroom_constructed())
{
return;
}
@ -426,9 +420,9 @@ void AOApplication::server_packet_received(AOPacket p_packet)
w_courtroom->clear_areas();
w_courtroom->arup_clear();
for (int n_element = 0; n_element < f_contents.size(); ++n_element)
for (int n_element = 0; n_element < content.size(); ++n_element)
{
w_courtroom->append_area(f_contents.at(n_element));
w_courtroom->append_area(content.at(n_element));
w_courtroom->arup_append(0, "Unknown", "Unknown", "Unknown");
}
@ -437,7 +431,7 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "DONE")
{
if (!courtroom_constructed)
if (!is_courtroom_constructed())
{
return;
}
@ -452,99 +446,99 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "BN")
{
if (!courtroom_constructed || f_contents.isEmpty())
if (!is_courtroom_constructed() || content.isEmpty())
{
return;
}
if (f_contents.size() >= 2)
if (content.size() >= 2)
{
// We have a pos included in the background packet!
if (!f_contents.at(1).isEmpty())
if (!content.at(1).isEmpty())
{
// Not touching it when its empty.
w_courtroom->set_side(f_contents.at(1));
w_courtroom->set_side(content.at(1));
}
}
w_courtroom->set_background(f_contents.at(0), f_contents.size() >= 2);
w_courtroom->set_background(content.at(0), content.size() >= 2);
}
else if (header == "SP")
{
if (!courtroom_constructed || f_contents.isEmpty())
if (!is_courtroom_constructed() || content.isEmpty())
{
return;
}
// We were sent a "set position" packet
w_courtroom->set_side(f_contents.at(0));
w_courtroom->set_side(content.at(0));
}
else if (header == "SD") // Send pos dropdown
{
if (!courtroom_constructed || f_contents.isEmpty())
if (!is_courtroom_constructed() || content.isEmpty())
{
return;
}
w_courtroom->set_pos_dropdown(f_contents.at(0).split("*"));
w_courtroom->set_pos_dropdown(content.at(0).split("*"));
}
// server accepting char request(CC) packet
else if (header == "PV")
{
if (!courtroom_constructed || f_contents.size() < 3)
if (!is_courtroom_constructed() || content.size() < 3)
{
return;
}
// For some reason, args 0 and 1 are not used (from tsu3 they're client ID and a string "CID")
w_courtroom->enter_courtroom();
w_courtroom->set_courtroom_size();
w_courtroom->update_character(f_contents.at(2).toInt());
w_courtroom->update_character(content.at(2).toInt());
}
else if (header == "MS")
{
if (courtroom_constructed && courtroom_loaded)
if (is_courtroom_constructed() && courtroom_loaded)
{
w_courtroom->chatmessage_enqueue(p_packet.content());
w_courtroom->chatmessage_enqueue(packet.content());
}
}
else if (header == "MC")
{
if (courtroom_constructed && courtroom_loaded)
if (is_courtroom_constructed() && courtroom_loaded)
{
w_courtroom->handle_song(&p_packet.content());
w_courtroom->handle_song(&packet.content());
}
}
else if (header == "RT")
{
if (f_contents.isEmpty())
if (content.isEmpty())
{
return;
}
if (courtroom_constructed)
if (is_courtroom_constructed())
{
if (f_contents.size() == 1)
if (content.size() == 1)
{
w_courtroom->handle_wtce(f_contents.at(0), 0);
w_courtroom->handle_wtce(content.at(0), 0);
}
else if (f_contents.size() >= 2)
else if (content.size() >= 2)
{
w_courtroom->handle_wtce(f_contents.at(0), f_contents.at(1).toInt());
w_courtroom->handle_wtce(content.at(0), content.at(1).toInt());
}
}
}
else if (header == "HP")
{
if (courtroom_constructed && f_contents.size() >= 2)
if (is_courtroom_constructed() && content.size() >= 2)
{
w_courtroom->set_hp_bar(f_contents.at(0).toInt(), f_contents.at(1).toInt());
w_courtroom->set_hp_bar(content.at(0).toInt(), content.at(1).toInt());
}
}
else if (header == "LE")
{
if (courtroom_constructed)
if (is_courtroom_constructed())
{
QVector<EvidenceItem> f_evi_list;
for (QString f_string : f_contents_encoded)
for (QString f_string : packet.content())
{
QStringList sub_contents = f_string.split("&");
if (sub_contents.size() < 3)
@ -572,12 +566,12 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "ARUP")
{
if (courtroom_constructed && !f_contents.isEmpty())
if (is_courtroom_constructed() && !content.isEmpty())
{
int arup_type = f_contents.at(0).toInt();
for (int n_element = 1; n_element < f_contents.size(); n_element++)
int arup_type = content.at(0).toInt();
for (int n_element = 1; n_element < content.size(); n_element++)
{
w_courtroom->arup_modify(arup_type, n_element - 1, f_contents.at(n_element));
w_courtroom->arup_modify(arup_type, n_element - 1, content.at(n_element));
}
w_courtroom->list_areas();
}
@ -585,41 +579,41 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "IL")
{
if (courtroom_constructed && !f_contents.isEmpty())
if (is_courtroom_constructed() && !content.isEmpty())
{
w_courtroom->set_ip_list(f_contents.at(0));
w_courtroom->set_ip_list(content.at(0));
}
log_to_demo = false;
}
else if (header == "MU")
{
if (courtroom_constructed && !f_contents.isEmpty())
if (is_courtroom_constructed() && !content.isEmpty())
{
w_courtroom->set_mute(true, f_contents.at(0).toInt());
w_courtroom->set_mute(true, content.at(0).toInt());
}
log_to_demo = false;
}
else if (header == "UM")
{
if (courtroom_constructed && !f_contents.isEmpty())
if (is_courtroom_constructed() && !content.isEmpty())
{
w_courtroom->set_mute(false, f_contents.at(0).toInt());
w_courtroom->set_mute(false, content.at(0).toInt());
log_to_demo = false;
}
}
else if (header == "BB")
{
if (courtroom_constructed && !f_contents.isEmpty())
if (is_courtroom_constructed() && !content.isEmpty())
{
call_notice(f_contents.at(0));
call_notice(content.at(0));
}
log_to_demo = false;
}
else if (header == "KK")
{
if (courtroom_constructed && !f_contents.isEmpty())
if (is_courtroom_constructed() && !content.isEmpty())
{
call_notice(tr("You have been kicked from the server.\nReason: %1").arg(f_contents.at(0)));
call_notice(tr("You have been kicked from the server.\nReason: %1").arg(content.at(0)));
construct_lobby();
destruct_courtroom();
}
@ -627,9 +621,9 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "KB")
{
if (courtroom_constructed && !f_contents.isEmpty())
if (is_courtroom_constructed() && !content.isEmpty())
{
call_notice(tr("You have been banned from the server.\nReason: %1").arg(f_contents.at(0)));
call_notice(tr("You have been banned from the server.\nReason: %1").arg(content.at(0)));
construct_lobby();
destruct_courtroom();
}
@ -637,46 +631,46 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "BD")
{
if (f_contents.isEmpty())
if (content.isEmpty())
{
return;
}
call_notice(tr("You are banned on this server.\nReason: %1").arg(f_contents.at(0)));
call_notice(tr("You are banned on this server.\nReason: %1").arg(content.at(0)));
log_to_demo = false;
}
else if (header == "ZZ")
{
if (courtroom_constructed && !f_contents.isEmpty())
if (is_courtroom_constructed() && !content.isEmpty())
{
w_courtroom->mod_called(f_contents.at(0));
w_courtroom->mod_called(content.at(0));
}
}
else if (header == "TI")
{ // Timer packet
if (!courtroom_constructed || f_contents.size() < 2)
if (!is_courtroom_constructed() || content.size() < 2)
{
return;
}
// Timer ID is reserved as argument 0
int id = f_contents.at(0).toInt();
int id = content.at(0).toInt();
// Type 0 = start/resume/sync timer at time
// Type 1 = pause timer at time
// Type 2 = show timer
// Type 3 = hide timer
int type = f_contents.at(1).toInt();
int type = content.at(1).toInt();
if (type == 0 || type == 1)
{
if (f_contents.size() < 3)
if (content.size() < 3)
{
return;
}
// The time as displayed on the clock, in milliseconds.
// If the number received is negative, stop the timer.
qint64 timer_value = f_contents.at(2).toLongLong();
qint64 timer_value = content.at(2).toLongLong();
if (timer_value > 0)
{
if (type == 0)
@ -706,7 +700,7 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "CHECK")
{
if (!courtroom_constructed)
if (!is_courtroom_constructed())
{
return;
}
@ -722,12 +716,12 @@ void AOApplication::server_packet_received(AOPacket p_packet)
// Subtheme packet
else if (header == "ST")
{
if (!courtroom_constructed || f_contents.isEmpty())
if (!is_courtroom_constructed() || content.isEmpty())
{
return;
}
// Subtheme reserved as argument 0
subtheme = f_contents.at(0);
subtheme = content.at(0);
// Check if we have subthemes set to "server"
if (Options::getInstance().settingsSubTheme().toLower() != "server")
@ -737,7 +731,7 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
// Reload theme request
if (f_contents.size() > 1 && f_contents.at(1) == "1")
if (content.size() > 1 && content.at(1) == "1")
{
Options::getInstance().setServerSubTheme(subtheme);
w_courtroom->on_reload_theme_clicked();
@ -746,15 +740,15 @@ void AOApplication::server_packet_received(AOPacket p_packet)
// Auth packet
else if (header == "AUTH")
{
if (!courtroom_constructed || !auth_packet_supported || f_contents.isEmpty())
if (!is_courtroom_constructed() || !auth_packet_supported || content.isEmpty())
{
return;
}
bool ok;
int authenticated = f_contents.at(0).toInt(&ok);
int authenticated = content.at(0).toInt(&ok);
if (!ok)
{
qWarning() << "Malformed AUTH packet! Contents:" << f_contents.at(0);
qWarning() << "Malformed AUTH packet! Contents:" << content.at(0);
}
w_courtroom->on_authentication_state_received(authenticated);
@ -762,12 +756,12 @@ void AOApplication::server_packet_received(AOPacket p_packet)
}
else if (header == "JD")
{
if (!courtroom_constructed || f_contents.isEmpty())
if (!is_courtroom_constructed() || content.isEmpty())
{
return;
}
bool ok;
Courtroom::JudgeState state = static_cast<Courtroom::JudgeState>(f_contents.at(0).toInt(&ok));
Courtroom::JudgeState state = static_cast<Courtroom::JudgeState>(content.at(0).toInt(&ok));
if (!ok)
{
return; // ignore malformed packet
@ -786,11 +780,11 @@ void AOApplication::server_packet_received(AOPacket p_packet)
// AssetURL Packet
else if (header == "ASS")
{
if (f_contents.size() > 1 || f_contents.isEmpty())
if (content.size() > 1 || content.isEmpty())
{ // This can never be more than one link.
return;
}
QUrl t_asset_url = QUrl::fromPercentEncoding(f_contents.at(0).toUtf8());
QUrl t_asset_url = QUrl::fromPercentEncoding(content.at(0).toUtf8());
if (t_asset_url.isValid())
{
asset_url = t_asset_url.toString();
@ -799,7 +793,7 @@ void AOApplication::server_packet_received(AOPacket p_packet)
if (log_to_demo)
{
append_to_demofile(f_packet_encoded);
append_to_demofile(packet.toString(true));
}
}

View File

@ -66,7 +66,7 @@ VPath AOApplication::get_music_path(QString p_song)
VPath AOApplication::get_background_path(QString p_file)
{
if (courtroom_constructed)
if (is_courtroom_constructed())
{
return VPath("background/" + w_courtroom->get_current_background() + "/" + p_file);
}

View File

@ -447,13 +447,15 @@ QString AOApplication::get_showname(QString p_char, int p_emote)
QString f_result = read_char_ini(p_char, "showname", "Options");
QString f_needed = read_char_ini(p_char, "needs_showname", "Options");
if (p_emote != -1) {
int override_idx =
read_char_ini(p_char, QString::number(p_emote + 1), "OptionsN").toInt();
if (override_idx > 0) {
if (p_emote != -1)
{
int override_idx = read_char_ini(p_char, QString::number(p_emote + 1), "OptionsN").toInt();
if (override_idx > 0)
{
QString override_key = "Options" + QString::number(override_idx);
QString temp_f_result = read_char_ini(p_char, "showname", override_key);
if (!temp_f_result.isEmpty()) {
if (!temp_f_result.isEmpty())
{
f_result = temp_f_result;
}
}
@ -485,30 +487,38 @@ QString AOApplication::get_blipname(QString p_char, int p_emote)
{
QString f_result = read_char_ini(p_char, "blips", "Options");
if (p_emote != -1) {
int override_idx =
read_char_ini(p_char, QString::number(p_emote + 1), "OptionsN").toInt();
if (override_idx > 0) {
if (p_emote != -1)
{
int override_idx = read_char_ini(p_char, QString::number(p_emote + 1), "OptionsN").toInt();
if (override_idx > 0)
{
QString override_key = "Options" + QString::number(override_idx);
QString temp_f_result = read_char_ini(p_char, "blips", override_key);
if (!temp_f_result.isEmpty()) {
if (!temp_f_result.isEmpty())
{
f_result = temp_f_result;
}
}
}
if (f_result == "") {
f_result =
read_char_ini(p_char, "gender", "Options"); // not very PC, FanatSors
if (f_result == "") f_result = "male";
if (f_result == "")
{
f_result = read_char_ini(p_char, "gender", "Options"); // not very PC, FanatSors
if (f_result == "")
{
f_result = "male";
}
}
return f_result;
}
QString AOApplication::get_blips(QString p_blipname)
{
if (!file_exists(get_sfx_suffix(get_sounds_path(p_blipname)))) {
if (!file_exists(get_sfx_suffix(get_sounds_path(p_blipname))))
{
if (file_exists(get_sfx_suffix(get_sounds_path("../blips/" + p_blipname))))
{
return "../blips/" + p_blipname; // Return the cool kids variant
}
return "sfx-blip" + p_blipname; // Return legacy variant
}

View File

@ -4,6 +4,7 @@
#include "gui_utils.h"
#include "options.h"
#include <QDebug>
#include <QFile>
#include <QUiLoader>
#include <QVBoxLayout>