aboutsummaryrefslogtreecommitdiff
path: root/src/demoserver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/demoserver.cpp')
-rw-r--r--src/demoserver.cpp298
1 files changed, 298 insertions, 0 deletions
diff --git a/src/demoserver.cpp b/src/demoserver.cpp
new file mode 100644
index 00000000..9f1e533d
--- /dev/null
+++ b/src/demoserver.cpp
@@ -0,0 +1,298 @@
+#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();
+ load_demo(path);
+
+ if (demo_data.isEmpty())
+ destroy_connection();
+
+ 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;
+}