305 lines
9.3 KiB
C++
305 lines
9.3 KiB
C++
#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;
|
|
}
|