aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/aoapplication.cpp4
-rw-r--r--src/aooptionsdialog.cpp16
-rw-r--r--src/aopacket.cpp10
-rw-r--r--src/courtroom.cpp165
-rw-r--r--src/demoserver.cpp304
-rw-r--r--src/lobby.cpp11
-rw-r--r--src/packet_distribution.cpp48
-rw-r--r--src/text_file_functions.cpp17
8 files changed, 463 insertions, 112 deletions
diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp
index fa58ab84..a7f41e3e 100644
--- a/src/aoapplication.cpp
+++ b/src/aoapplication.cpp
@@ -45,6 +45,10 @@ void AOApplication::construct_lobby()
if (is_discord_enabled())
discord->state_lobby();
+ if (demo_server)
+ demo_server->deleteLater();
+ demo_server = new DemoServer();
+
w_lobby->show();
}
diff --git a/src/aooptionsdialog.cpp b/src/aooptionsdialog.cpp
index 7b425207..0fbee478 100644
--- a/src/aooptionsdialog.cpp
+++ b/src/aooptionsdialog.cpp
@@ -182,7 +182,7 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app)
ui_stay_time_lbl->setText(tr("Text Stay Time:"));
ui_stay_time_lbl->setToolTip(tr(
"Minimum amount of time (in miliseconds) an IC message must stay on screen before "
- "the next IC message is shown, acting as a 'queue'. Set to 0 to disable this behaivor."));
+ "the next IC message is shown, acting as a 'queue'. Set to 0 to disable this behavior."));
ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_stay_time_lbl);
@@ -219,6 +219,19 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app)
ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_instant_objection_cb);
row += 1;
+ ui_chat_ratelimit_lbl = new QLabel(ui_form_layout_widget);
+ ui_chat_ratelimit_lbl->setText(tr("Chat Rate Limit:"));
+ ui_chat_ratelimit_lbl->setToolTip(tr(
+ "Minimum amount of time (in miliseconds) that must pass before the next Enter key press will send your IC message."));
+
+ ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_chat_ratelimit_lbl);
+
+ ui_chat_ratelimit_spinbox = new QSpinBox(ui_form_layout_widget);
+ ui_chat_ratelimit_spinbox->setMaximum(5000);
+ ui_chat_ratelimit_spinbox->setValue(p_ao_app->get_chat_ratelimit());
+
+ ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_chat_ratelimit_spinbox);
+ row += 1;
ui_log_names_divider = new QFrame(ui_form_layout_widget);
ui_log_names_divider->setFrameShape(QFrame::HLine);
ui_log_names_divider->setFrameShadow(QFrame::Sunken);
@@ -824,6 +837,7 @@ void AOOptionsDialog::save_pressed()
configini->setValue("desync_logs", ui_desync_logs_cb->isChecked());
configini->setValue("stay_time", ui_stay_time_spinbox->value());
configini->setValue("instant_objection", ui_instant_objection_cb->isChecked());
+ configini->setValue("chat_ratelimit", ui_chat_ratelimit_spinbox->value());
configini->setValue("default_username", ui_username_textbox->text());
configini->setValue("show_custom_shownames", ui_showname_cb->isChecked());
configini->setValue("master", ui_ms_textbox->text());
diff --git a/src/aopacket.cpp b/src/aopacket.cpp
index bb6ac73b..a40d2ef7 100644
--- a/src/aopacket.cpp
+++ b/src/aopacket.cpp
@@ -8,9 +8,15 @@ AOPacket::AOPacket(QString p_packet_string)
m_contents = packet_contents.mid(1, packet_contents.size()-2); // trims %
}
-QString AOPacket::to_string()
+QString AOPacket::to_string(bool encoded)
{
- return m_header + "#" + m_contents.join("#") + "#%";
+ QStringList contents = m_contents;
+ if (encoded)
+ contents.replaceInStrings("#", "<num>")
+ .replaceInStrings("%", "<percent>")
+ .replaceInStrings("$", "<dollar>")
+ .replaceInStrings("&", "<and>");
+ return m_header + "#" + contents.join("#") + "#%";
}
void AOPacket::net_encode()
diff --git a/src/courtroom.cpp b/src/courtroom.cpp
index 443fb305..5f5f0b90 100644
--- a/src/courtroom.cpp
+++ b/src/courtroom.cpp
@@ -299,6 +299,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow()
connect(ui_sfx_dropdown, SIGNAL(currentIndexChanged(int)), this,
SLOT(on_sfx_dropdown_changed(int)));
+ connect(ui_sfx_dropdown, SIGNAL(editTextChanged(QString)), this,
+ SLOT(on_sfx_dropdown_custom(QString)));
connect(ui_sfx_dropdown, SIGNAL(customContextMenuRequested(QPoint)), this,
SLOT(on_sfx_context_menu_requested(QPoint)));
connect(ui_sfx_remove, SIGNAL(clicked()), this,
@@ -731,7 +733,7 @@ void Courtroom::set_widgets()
set_size_and_pos(ui_sfx_dropdown, "sfx_dropdown");
ui_sfx_dropdown->setEditable(true);
- ui_sfx_dropdown->setInsertPolicy(QComboBox::InsertAtBottom);
+ ui_sfx_dropdown->setInsertPolicy(QComboBox::NoInsert);
ui_sfx_dropdown->setToolTip(
tr("Set a sound effect to play on your next 'Preanim'. Leaving it on "
"Default will use the emote-defined sound (if any).\n"
@@ -1159,13 +1161,20 @@ void Courtroom::done_received()
objection_player->set_volume(0);
blip_player->set_volume(0);
- set_char_select_page();
+ if (char_list.size() > 0)
+ {
+ set_char_select_page();
+ set_char_select();
+ }
+ else
+ {
+ update_character(m_cid);
+ enter_courtroom();
+ }
set_mute_list();
set_pair_list();
- set_char_select();
-
show();
ui_spectator->show();
@@ -1279,8 +1288,6 @@ void Courtroom::set_pos_dropdown(QStringList pos_dropdowns)
ui_pos_dropdown->addItems(pos_dropdown_list);
// Unblock the signals so the element can be used for setting pos again
ui_pos_dropdown->blockSignals(false);
-
- qDebug() << pos_dropdown_list;
}
void Courtroom::update_character(int p_cid)
@@ -1323,7 +1330,6 @@ void Courtroom::update_character(int p_cid)
set_sfx_dropdown();
set_effects_dropdown();
- qDebug() << "update_character called";
if (newchar) // Avoid infinite loop of death and suffering
set_iniswap_dropdown();
@@ -1621,7 +1627,7 @@ void Courtroom::on_chat_return_pressed()
return;
ui_ic_chat_message->blockSignals(true);
- QTimer::singleShot(200, this,
+ QTimer::singleShot(ao_app->get_chat_ratelimit(), this,
[=] { ui_ic_chat_message->blockSignals(false); });
// MS#
// deskmod#
@@ -1680,12 +1686,6 @@ void Courtroom::on_chat_return_pressed()
packet_contents.append(current_side);
packet_contents.append(get_char_sfx());
- if (ui_pre->isChecked() && !ao_app->is_stickysounds_enabled() && ui_sfx_dropdown->currentIndex() > 1) {
- ui_sfx_dropdown->blockSignals(true);
- ui_sfx_dropdown->setCurrentIndex(0);
- ui_sfx_dropdown->blockSignals(false);
- ui_sfx_remove->hide();
- }
int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote);
@@ -1861,8 +1861,6 @@ void Courtroom::reset_ui()
realization_state = 0;
screenshake_state = 0;
is_presenting_evidence = false;
- if (!ao_app->is_stickypres_enabled())
- ui_pre->setChecked(false);
ui_hold_it->set_image("holdit");
ui_objection->set_image("objection");
ui_take_that->set_image("takethat");
@@ -1870,6 +1868,14 @@ void Courtroom::reset_ui()
ui_realization->set_image("realization");
ui_screenshake->set_image("screenshake");
ui_evidence_present->set_image("present");
+
+ if (ui_pre->isChecked() && !ao_app->is_stickysounds_enabled()) {
+ ui_sfx_dropdown->setCurrentIndex(0);
+ ui_sfx_remove->hide();
+ custom_sfx = "";
+ }
+ if (!ao_app->is_stickypres_enabled())
+ ui_pre->setChecked(false);
}
void Courtroom::chatmessage_enqueue(QStringList p_contents)
@@ -2520,7 +2526,7 @@ void Courtroom::play_char_sfx(QString sfx_name)
void Courtroom::initialize_chatbox()
{
int f_charid = m_chatmessage[CHAR_ID].toInt();
- if (f_charid >= 0 &&
+ if (f_charid >= 0 && f_charid < char_list.size() &&
(m_chatmessage[SHOWNAME].isEmpty() || !ui_showname_enable->isChecked())) {
QString real_name = char_list.at(f_charid).name;
@@ -2968,7 +2974,7 @@ void Courtroom::log_ic_text(QString p_name, QString p_showname,
{
chatlogpiece log_entry(p_name, p_showname, p_message, p_action, p_color);
ic_chatlog_history.append(log_entry);
- if (ao_app->get_auto_logging_enabled())
+ if (ao_app->get_auto_logging_enabled() && !ao_app->log_filename.isEmpty())
ao_app->append_to_file(log_entry.get_full(), ao_app->log_filename, true);
while (ic_chatlog_history.size() > log_maximum_blocks &&
@@ -3133,7 +3139,7 @@ void Courtroom::play_preanim(bool immediate)
else
anim_state = 1;
preanim_done();
- qDebug() << "could not find " + anim_to_find;
+ qDebug() << "W: could not find " + anim_to_find;
return;
}
@@ -3795,9 +3801,6 @@ void Courtroom::on_ooc_return_pressed()
{
QString ooc_message = ui_ooc_chat_message->text();
- if (ooc_message == "" || ui_ooc_chat_name->text() == "")
- return;
-
if (ooc_message.startsWith("/pos")) {
if (ooc_message == "/pos jud") {
toggle_judge_buttons(true);
@@ -4322,28 +4325,28 @@ void Courtroom::set_sfx_dropdown()
ui_sfx_remove->hide();
return;
}
- QStringList soundlist = ao_app->get_list_file(
+ // Initialzie character sound list first. Will be empty if not found.
+ sound_list = ao_app->get_list_file(
ao_app->get_character_path(current_char, "soundlist.ini"));
- if (soundlist.size() <= 0) {
- soundlist = ao_app->get_list_file(
- ao_app->get_theme_path("character_soundlist.ini"));
- if (soundlist.size() <= 0) {
- soundlist = ao_app->get_list_file(
- ao_app->get_default_theme_path("character_soundlist.ini"));
- }
- }
+ // Append default sound list after the character sound list.
+ sound_list += ao_app->get_list_file(
+ ao_app->get_base_path() + "soundlist.ini");
- if (soundlist.size() <= 0) {
- ui_sfx_dropdown->hide();
- ui_sfx_remove->hide();
- return;
+ QStringList display_sounds;
+ for (QString sound : sound_list) {
+ QStringList unpacked = sound.split("=");
+ QString display = unpacked[0].trimmed();
+ if (unpacked.size() > 1)
+ display = unpacked[1].trimmed();
+
+ display_sounds.append(display);
}
- soundlist.prepend("Nothing");
- soundlist.prepend("Default");
+ display_sounds.prepend("Nothing");
+ display_sounds.prepend("Default");
ui_sfx_dropdown->show();
- ui_sfx_dropdown->addItems(soundlist);
+ ui_sfx_dropdown->addItems(display_sounds);
ui_sfx_dropdown->setCurrentIndex(0);
ui_sfx_remove->hide();
ui_sfx_dropdown->blockSignals(false);
@@ -4352,36 +4355,14 @@ void Courtroom::set_sfx_dropdown()
void Courtroom::on_sfx_dropdown_changed(int p_index)
{
ui_ic_chat_message->setFocus();
+ ui_sfx_remove->hide();
+ custom_sfx = "";
+}
- QStringList soundlist;
- for (int i = 2; i < ui_sfx_dropdown->count(); ++i) {
- QString entry = ui_sfx_dropdown->itemText(i);
- if (!soundlist.contains(entry))
- soundlist.append(entry);
- }
-
- QStringList defaultlist =
- ao_app->get_list_file(ao_app->get_theme_path("character_soundlist.ini"));
- if (defaultlist.size() <= 0) {
- defaultlist = ao_app->get_list_file(
- ao_app->get_default_theme_path("character_soundlist.ini"));
- }
-
- if (defaultlist.size() > 0 &&
- defaultlist.toSet().subtract(soundlist.toSet()).size() >
- 0) // There's a difference from the default configuration
- ao_app->write_to_file(
- soundlist.join("\n"),
- ao_app->get_character_path(current_char,
- "soundlist.ini")); // Create a new sound list
-
- ui_sfx_dropdown->blockSignals(true);
- ui_sfx_dropdown->setCurrentIndex(p_index);
- ui_sfx_dropdown->blockSignals(false);
- if (p_index > 1)
- ui_sfx_remove->show();
- else
- ui_sfx_remove->hide();
+void Courtroom::on_sfx_dropdown_custom(QString p_sfx)
+{
+ ui_sfx_remove->show();
+ custom_sfx = p_sfx;
}
void Courtroom::on_sfx_context_menu_requested(const QPoint &pos)
@@ -4394,40 +4375,27 @@ void Courtroom::on_sfx_context_menu_requested(const QPoint &pos)
menu->addAction(QString("Edit " + current_char + "/soundlist.ini"), this,
SLOT(on_sfx_edit_requested()));
else
- menu->addAction(QString("Edit theme's character_soundlist.ini"), this,
+ menu->addAction(QString("Edit global soundlist.ini"), this,
SLOT(on_sfx_edit_requested()));
- if (ui_sfx_dropdown->currentIndex() > 1)
- menu->addAction(QString("Remove " + ui_sfx_dropdown->itemText(
- ui_sfx_dropdown->currentIndex())),
- this, SLOT(on_sfx_remove_clicked()));
+ if (!custom_sfx.isEmpty())
+ menu->addAction(QString("Clear Edit Text"), this, SLOT(on_sfx_remove_clicked()));
menu->popup(ui_sfx_dropdown->mapToGlobal(pos));
}
+
void Courtroom::on_sfx_edit_requested()
{
QString p_path = ao_app->get_character_path(current_char, "soundlist.ini");
if (!file_exists(p_path)) {
- p_path = ao_app->get_theme_path("character_soundlist.ini");
- if (!file_exists(p_path)) {
- p_path = ao_app->get_default_theme_path("character_soundlist.ini");
- if (!file_exists(p_path)) {
- return;
- }
+ p_path = ao_app->get_base_path() + "soundlist.ini";
}
- }
QDesktopServices::openUrl(QUrl::fromLocalFile(p_path));
}
void Courtroom::on_sfx_remove_clicked()
{
- if (ui_sfx_dropdown->count() <= 0) {
- ui_sfx_remove->hide(); // We're not supposed to see it. Do this or the
- // client will crash
- return;
- }
- if (ui_sfx_dropdown->currentIndex() > 1) {
- ui_sfx_dropdown->removeItem(ui_sfx_dropdown->currentIndex());
- on_sfx_dropdown_changed(0); // Reset back to original
- }
+ ui_sfx_remove->hide();
+ ui_sfx_dropdown->setCurrentIndex(0);
+ custom_sfx = "";
}
void Courtroom::set_effects_dropdown()
@@ -4530,19 +4498,21 @@ bool Courtroom::effects_dropdown_find_and_set(QString effect)
QString Courtroom::get_char_sfx()
{
- QString sfx = ui_sfx_dropdown->itemText(ui_sfx_dropdown->currentIndex());
- if (sfx == "Nothing")
- return "1";
- if (sfx != "" && sfx != "Default")
- return sfx;
- return ao_app->get_sfx_name(current_char, current_emote);
+ if (!custom_sfx.isEmpty())
+ return custom_sfx;
+ int index = ui_sfx_dropdown->currentIndex();
+ if (index == 0) // Default
+ return ao_app->get_sfx_name(current_char, current_emote);
+ if (index == 1) // Nothing
+ return "1";
+ QString sfx = sound_list[index-2].split("=")[0].trimmed();
+ if (sfx == "")
+ return "1";
+ return sfx;
}
int Courtroom::get_char_sfx_delay()
{
- // QString sfx = ui_sfx_dropdown->itemText(ui_sfx_dropdown->currentIndex());
- // if (sfx != "" && sfx != "Default")
- // return 0; //todo: a way to define this
return ao_app->get_sfx_delay(current_char, current_emote);
}
@@ -4750,7 +4720,6 @@ void Courtroom::on_area_list_double_clicked(QTreeWidgetItem *p_item, int column)
QStringList packet_contents;
packet_contents.append(p_area);
packet_contents.append(QString::number(m_cid));
- qDebug() << packet_contents;
ao_app->send_server_packet(new AOPacket("MC", packet_contents), false);
}
diff --git a/src/demoserver.cpp b/src/demoserver.cpp
new file mode 100644
index 00000000..fcb5003d
--- /dev/null
+++ b/src/demoserver.cpp
@@ -0,0 +1,304 @@
+#include "demoserver.h"
+#include "lobby.h"
+
+DemoServer::DemoServer(QObject *parent) : QObject(parent)
+{
+ timer = new QTimer(this);
+ timer->setTimerType(Qt::PreciseTimer);
+ timer->setSingleShot(true);
+
+ tcp_server = new QTcpServer(this);
+ connect(tcp_server, &QTcpServer::newConnection, this, &DemoServer::accept_connection);
+ connect(timer, &QTimer::timeout, this, &DemoServer::playback);
+}
+
+void DemoServer::start_server()
+{
+ if (server_started) return;
+ if (!tcp_server->listen(QHostAddress::LocalHost, 0)) {
+ qCritical() << "Could not start demo playback server...";
+ qDebug() << tcp_server->errorString();
+ return;
+ }
+ this->port = tcp_server->serverPort();
+ qDebug() << "Server started";
+ server_started = true;
+}
+
+void DemoServer::destroy_connection()
+{
+ QTcpSocket* temp_socket = tcp_server->nextPendingConnection();
+ connect(temp_socket, &QAbstractSocket::disconnected, temp_socket, &QObject::deleteLater);
+ temp_socket->disconnectFromHost();
+ return;
+}
+
+void DemoServer::accept_connection()
+{
+ QString path = QFileDialog::getOpenFileName(nullptr, tr("Load Demo"), "logs/", tr("Demo Files (*.demo)"));
+ if (path.isEmpty())
+ {
+ destroy_connection();
+ return;
+ }
+ load_demo(path);
+
+ if (demo_data.isEmpty())
+ {
+ destroy_connection();
+ return;
+ }
+
+ if (demo_data.head().startsWith("SC#"))
+ {
+ sc_packet = demo_data.dequeue();
+ AOPacket sc(sc_packet);
+ num_chars = sc.get_contents().length();
+ }
+ else
+ {
+ sc_packet = "SC#%";
+ num_chars = 0;
+ }
+
+ if (client_sock) {
+ // Client is already connected...
+ qDebug() << "Multiple connections to demo server disallowed.";
+ QTcpSocket* temp_socket = tcp_server->nextPendingConnection();
+ connect(temp_socket, &QAbstractSocket::disconnected, temp_socket, &QObject::deleteLater);
+ temp_socket->disconnectFromHost();
+ return;
+ }
+ client_sock = tcp_server->nextPendingConnection();
+ connect(client_sock, &QAbstractSocket::disconnected, this, &DemoServer::client_disconnect);
+ connect(client_sock, &QAbstractSocket::readyRead, this, &DemoServer::recv_data);
+ client_sock->write("decryptor#NOENCRYPT#%");
+}
+
+void DemoServer::recv_data()
+{
+ QString in_data = QString::fromUtf8(client_sock->readAll());
+
+ // Copypasted from NetworkManager
+ if (!in_data.endsWith("%")) {
+ partial_packet = true;
+ temp_packet += in_data;
+ return;
+ }
+
+ else {
+ if (partial_packet) {
+ in_data = temp_packet + in_data;
+ temp_packet = "";
+ partial_packet = false;
+ }
+ }
+
+ QStringList packet_list =
+ in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts));
+
+ for (QString packet : packet_list) {
+ AOPacket ao_packet(packet);
+ handle_packet(ao_packet);
+ }
+}
+
+void DemoServer::handle_packet(AOPacket packet)
+{
+ packet.net_decode();
+
+ // This code is literally a barebones AO server
+ // It is wise to do it this way, because I can
+ // avoid touching any of this disgusting shit
+ // related to hardcoding this stuff in.
+
+ // Also, at some point, I will make akashit
+ // into a shared library.
+
+ QString header = packet.get_header();
+ QStringList contents = packet.get_contents();
+
+ if (header == "HI") {
+ client_sock->write("ID#0#DEMOINTERNAL#0#%");
+ }
+ else if (header == "ID") {
+ QStringList feature_list = {
+ "noencryption", "yellowtext", "prezoom",
+ "flipping", "customobjections", "fastloading",
+ "deskmod", "evidence", "cccc_ic_support",
+ "arup", "casing_alerts", "modcall_reason",
+ "looping_sfx", "additive", "effects",
+ "y_offset", "expanded_desk_mods"};
+ client_sock->write("PN#0#1#%");
+ client_sock->write("FL#");
+ client_sock->write(feature_list.join('#').toUtf8());
+ client_sock->write("#%");
+ }
+ else if (header == "askchaa") {
+ client_sock->write("SI#");
+ client_sock->write(QString::number(num_chars).toUtf8());
+ client_sock->write("#0#1#%");
+ }
+ else if (header == "RC") {
+ client_sock->write(sc_packet.toUtf8());
+ }
+ else if (header == "RM") {
+ client_sock->write("SM#%");
+ }
+ else if (header == "RD") {
+ client_sock->write("DONE#%");
+ }
+ else if (header == "CC") {
+ client_sock->write("PV#0#CID#-1#%");
+ client_sock->write("CT#DEMO#Demo file loaded. Send /play or > in OOC to begin playback.#1#%");
+ }
+ else if (header == "CT") {
+ if (contents[1].startsWith("/load"))
+ {
+ QString path = QFileDialog::getOpenFileName(nullptr, tr("Load Demo"), "logs/", tr("Demo Files (*.demo)"));
+ if (path.isEmpty())
+ return;
+ load_demo(path);
+ client_sock->write("CT#DEMO#Demo file loaded. Send /play or > in OOC to begin playback.#1#%");
+ }
+ else if (contents[1].startsWith("/play") || contents[1] == ">")
+ {
+ if (timer->interval() != 0 && !timer->isActive())
+ {
+ timer->start();
+ client_sock->write("CT#DEMO#Resuming playback.#1#%");
+ }
+ else
+ {
+ if (demo_data.isEmpty() && p_path != "")
+ load_demo(p_path);
+ playback();
+ }
+ }
+ else if (contents[1].startsWith("/pause") || contents[1] == "|")
+ {
+ int timeleft = timer->remainingTime();
+ timer->stop();
+ timer->setInterval(timeleft);
+ client_sock->write("CT#DEMO#Pausing playback.#1#%");
+ }
+ else if (contents[1].startsWith("/max_wait"))
+ {
+ QStringList args = contents[1].split(" ");
+ if (args.size() > 1)
+ {
+ bool ok;
+ int p_max_wait = args.at(1).toInt(&ok);
+ if (ok)
+ {
+ if (p_max_wait < 0)
+ p_max_wait = -1;
+ max_wait = p_max_wait;
+ client_sock->write("CT#DEMO#Setting max_wait to ");
+ client_sock->write(QString::number(max_wait).toUtf8());
+ client_sock->write(" milliseconds.#1#%");
+ }
+ else
+ {
+ client_sock->write("CT#DEMO#Not a valid integer!#1#%");
+ }
+ }
+ else
+ {
+ client_sock->write("CT#DEMO#Current max_wait is ");
+ client_sock->write(QString::number(max_wait).toUtf8());
+ client_sock->write(" milliseconds.#1#%");
+ }
+ }
+ else if (contents[1].startsWith("/min_wait"))
+ {
+ QStringList args = contents[1].split(" ");
+ if (args.size() > 1)
+ {
+ bool ok;
+ int p_min_wait = args.at(1).toInt(&ok);
+ if (ok)
+ {
+ if (p_min_wait < 0)
+ p_min_wait = -1;
+ min_wait = p_min_wait;
+ client_sock->write("CT#DEMO#Setting min_wait to ");
+ client_sock->write(QString::number(min_wait).toUtf8());
+ client_sock->write(" milliseconds.#1#%");
+ }
+ else
+ {
+ client_sock->write("CT#DEMO#Not a valid integer!#1#%");
+ }
+ }
+ else
+ {
+ client_sock->write("CT#DEMO#Current min_wait is ");
+ client_sock->write(QString::number(min_wait).toUtf8());
+ client_sock->write(" milliseconds.#1#%");
+ }
+ }
+ else if (contents[1].startsWith("/help"))
+ {
+ client_sock->write("CT#DEMO#Available commands:\nload, play, pause, max_wait, min_wait, help#1#%");
+ }
+ }
+}
+
+void DemoServer::load_demo(QString filename)
+{
+ QFile demo_file(filename);
+ demo_file.open(QIODevice::ReadOnly);
+ if (!demo_file.isOpen())
+ return;
+ demo_data.clear();
+ p_path = filename;
+ QTextStream demo_stream(&demo_file);
+ QString line = demo_stream.readLine();
+ while (!line.isNull()) {
+ if (!line.endsWith("%")) {
+ line += "\n";
+ }
+ demo_data.enqueue(line);
+ line = demo_stream.readLine();
+ }
+}
+
+void DemoServer::playback()
+{
+ if (demo_data.isEmpty())
+ return;
+
+ QString current_packet = demo_data.dequeue();
+ // We reset the elapsed time with this packet
+ if (current_packet.startsWith("MS#"))
+ elapsed_time = 0;
+
+ while (!current_packet.startsWith("wait") && !demo_data.isEmpty()) {
+ client_sock->write(current_packet.toUtf8());
+ current_packet = demo_data.dequeue();
+ }
+ if (!demo_data.isEmpty()) {
+ AOPacket wait_packet = AOPacket(current_packet);
+
+ int duration = wait_packet.get_contents().at(0).toInt();
+ if (max_wait != -1 && duration + elapsed_time > max_wait)
+ duration = qMax(0, max_wait - elapsed_time);
+ // We use elapsed_time to make sure that the packet we're using min_wait on is "priority" (e.g. IC)
+ if (elapsed_time == 0 && min_wait != -1 && duration < min_wait)
+ duration = min_wait;
+ elapsed_time += duration;
+ timer->start(duration);
+ }
+ else
+ {
+ client_sock->write("CT#DEMO#Reached the end of the demo file. Send /play or > in OOC to restart, or /load to open a new file.#1#%");
+ timer->setInterval(0);
+ }
+}
+
+void DemoServer::client_disconnect()
+{
+ client_sock->deleteLater();
+ client_sock = nullptr;
+}
diff --git a/src/lobby.cpp b/src/lobby.cpp
index 954c30a8..f1a61f4c 100644
--- a/src/lobby.cpp
+++ b/src/lobby.cpp
@@ -3,6 +3,7 @@
#include "aoapplication.h"
#include "aosfxplayer.h"
#include "debug_functions.h"
+#include "demoserver.h"
#include "networkmanager.h"
#include <QImageReader>
@@ -438,7 +439,15 @@ void Lobby::on_server_list_clicked(QTreeWidgetItem *p_item, int column)
ui_connect->setEnabled(false);
- ao_app->net_manager->connect_to_server(f_server);
+ if (f_server.port == 99999 && f_server.ip == "127.0.0.1") {
+ // Demo playback server selected
+ ao_app->demo_server->start_server();
+ server_type demo_server;
+ demo_server.ip = "127.0.0.1";
+ demo_server.port = ao_app->demo_server->port;
+ ao_app->net_manager->connect_to_server(demo_server);
+ }
+ else ao_app->net_manager->connect_to_server(f_server);
}
}
diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp
index f21c4acd..822b2dc9 100644
--- a/src/packet_distribution.cpp
+++ b/src/packet_distribution.cpp
@@ -102,6 +102,19 @@ end:
delete p_packet;
}
+void AOApplication::append_to_demofile(QString packet_string)
+{
+ if (get_auto_logging_enabled() && !log_filename.isEmpty())
+ {
+ QString path = log_filename.left(log_filename.size()).replace(".log", ".demo");
+ append_to_file(packet_string, path, true);
+ if (!demo_timer.isValid())
+ demo_timer.start();
+ else
+ append_to_file("wait#"+ QString::number(demo_timer.restart()) + "#%", path, true);
+ }
+}
+
void AOApplication::server_packet_received(AOPacket *p_packet)
{
p_packet->net_decode();
@@ -164,6 +177,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
else
w_courtroom->append_server_chatmessage(f_contents.at(0),
f_contents.at(1), "0");
+
+ append_to_demofile(p_packet->to_string(true));
}
}
else if (header == "FL") {
@@ -232,7 +247,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
evidence_list_size = f_contents.at(1).toInt();
music_list_size = f_contents.at(2).toInt();
- if (char_list_size < 1 || evidence_list_size < 0 || music_list_size < 0)
+ if (char_list_size < 0 || evidence_list_size < 0 || music_list_size < 0)
goto end;
loaded_chars = 0;
@@ -255,7 +270,6 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
server_name = info.name;
server_address =
QString("%1:%2").arg(info.ip, QString::number(info.port));
- qDebug() << server_address;
window_title += ": " + server_name;
}
}
@@ -265,7 +279,6 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
server_name = info.name;
server_address =
QString("%1:%2").arg(info.ip, QString::number(info.port));
- qDebug() << server_address;
window_title += ": " + server_name;
}
}
@@ -283,7 +296,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
// Remove any characters not accepted in folder names for the server_name
// here
- if (AOApplication::get_auto_logging_enabled()) {
+ if (AOApplication::get_auto_logging_enabled() && server_name != "Demo playback") {
this->log_filename = QDateTime::currentDateTime().toUTC().toString(
"'logs/" + server_name.remove(QRegExp("[\\\\/:*?\"<>|\']")) +
"/'yyyy-MM-dd hh-mm-ss t'.log'");
@@ -292,6 +305,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
QDateTime::currentDateTime().toUTC().toString(),
log_filename, true);
}
+ else
+ this->log_filename = "";
QCryptographicHash hash(QCryptographicHash::Algorithm::Sha256);
hash.addData(server_address.toUtf8());
@@ -312,7 +327,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
}
else if (header == "SC") {
- if (!courtroom_constructed)
+ if (!courtroom_constructed || courtroom_loaded)
goto end;
for (int n_element = 0; n_element < f_contents.size(); ++n_element) {
@@ -344,9 +359,10 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
}
send_server_packet(new AOPacket("RM#%"));
+ append_to_demofile(p_packet->to_string(true));
}
else if (header == "SM") {
- if (!courtroom_constructed)
+ if (!courtroom_constructed || courtroom_loaded)
goto end;
bool musics_time = false;
@@ -445,6 +461,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
2) // We have a pos included in the background packet!
w_courtroom->set_side(f_contents.at(1));
w_courtroom->set_background(f_contents.at(0), f_contents.size() >= 2);
+ append_to_demofile(p_packet->to_string(true));
}
}
else if (header == "SP") {
@@ -454,6 +471,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
if (courtroom_constructed) // We were sent a "set position" packet
{
w_courtroom->set_side(f_contents.at(0));
+ append_to_demofile(p_packet->to_string(true));
}
}
else if (header == "SD") // Send pos dropdown
@@ -475,27 +493,37 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
}
else if (header == "MS") {
if (courtroom_constructed && courtroom_loaded)
+ {
w_courtroom->chatmessage_enqueue(p_packet->get_contents());
+ append_to_demofile(p_packet->to_string(true));
+ }
}
else if (header == "MC") {
if (courtroom_constructed && courtroom_loaded)
+ {
w_courtroom->handle_song(&p_packet->get_contents());
+ append_to_demofile(p_packet->to_string(true));
+ }
}
else if (header == "RT") {
if (f_contents.size() < 1)
goto end;
if (courtroom_constructed) {
- if (f_contents.size() == 1)
- w_courtroom->handle_wtce(f_contents.at(0), 0);
- else if (f_contents.size() == 2) {
- w_courtroom->handle_wtce(f_contents.at(0), f_contents.at(1).toInt());
+ if (f_contents.size() == 1)
+ w_courtroom->handle_wtce(f_contents.at(0), 0);
+ else if (f_contents.size() == 2) {
+ w_courtroom->handle_wtce(f_contents.at(0), f_contents.at(1).toInt());
+ append_to_demofile(p_packet->to_string(true));
}
}
}
else if (header == "HP") {
if (courtroom_constructed && f_contents.size() > 1)
+ {
w_courtroom->set_hp_bar(f_contents.at(0).toInt(),
f_contents.at(1).toInt());
+ append_to_demofile(p_packet->to_string(true));
+ }
}
else if (header == "LE") {
if (courtroom_constructed) {
diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp
index 1ea27bff..e79411a2 100644
--- a/src/text_file_functions.cpp
+++ b/src/text_file_functions.cpp
@@ -52,6 +52,12 @@ int AOApplication::stay_time()
return result;
}
+int AOApplication::get_chat_ratelimit()
+{
+ int result = configini->value("chat_ratelimit", 300).toInt();
+ return result;
+}
+
bool AOApplication::get_log_goes_downwards()
{
QString result =
@@ -178,6 +184,10 @@ bool AOApplication::write_to_file(QString p_text, QString p_file, bool make_dir)
bool AOApplication::append_to_file(QString p_text, QString p_file,
bool make_dir)
{
+ if(!file_exists(p_file)) //Don't create a newline if file didn't exist before now
+ {
+ return write_to_file(p_text, p_file, make_dir);
+ }
QString path = QFileInfo(p_file).path();
// Create the dir if it doesn't exist yet
if (make_dir) {
@@ -249,6 +259,13 @@ QVector<server_type> AOApplication::read_serverlist_txt()
f_server_list.append(f_server);
}
+ server_type demo_server;
+ demo_server.ip = "127.0.0.1";
+ demo_server.port = 99999;
+ demo_server.name = "Demo playback";
+ demo_server.desc = "Play back demos you have previously recorded";
+ f_server_list.append(demo_server);
+
return f_server_list;
}