diff options
Diffstat (limited to 'src/demoserver.cpp')
| -rw-r--r-- | src/demoserver.cpp | 298 |
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; +} |
