From 2f84055af97859cb877eeafc6d92969e0699fdd9 Mon Sep 17 00:00:00 2001 From: Crystalwarrior Date: Thu, 14 Jan 2021 00:25:24 +0300 Subject: [PATCH] [2.8.6] Demo Recording/Playback (#337) * initial commit with horrible code dupcliation haha lol Set up elapsedtimer to generate wait# stuff between packets recorded * implement groundwork for internal demo server * add core playback functionality * make it work kinda by including SC packet in demo * Add a file dialog for loading a demo file instead of a hardcoded path * Change /play to > in OOC to begin playback or skip to next element Pop up file dialog box *before* establishing the connection, not after. TODO: * Fix having to load the same file *twice* to be able to connect to the demo server for some reason * Fix the segfault caused by calling the playback() function when there's no remaining data, it's almost like demo_data.isEmpty() is ignored for whatever reason??? * Clear demo data when loading a demo file to prevent stacking demos * Properly disconnect the client when sending the request for file browser fails to obtain a good demo file * Fix append_to_file newlining even if file didn't exist prior to calling this function * Add a very scuffed exception to not log or demo record anything that happens in the Demo playback local server * Reduce invalid file spam by checking for non-logging server better Use file_exists() to be more consistent in append_to_file * Fix the client crashing when receiving loading packets etc. at runtime such as SC, SM, CI etc. (TODO: parity???) Remove useless debug stuff * Preserve newlines for demo packets such as "CT", "MS" etc. * Implement /max_wait, /min_wait for adjusting the maximum and minimum wait in milliseconds between wait packets Add /pause or | shorthand to pause playback Re-add /play and keep > as a shorthand Remove clientside restrictions from sending empty OOC messages and sending OOC messages without a name - these should be serverside. * Empty music list Default the character to Spectator char no matter the selection in CSS * Allow -1 character ID or character ID that does not fit into the local Character Select Screen list to still be parsed corectly, using the character folder in the MS packet as reference. Allow servers with no selectable characters to still be properly loaded Bypass the Character Select Screen when joining a server with no character select screen and automatically become a spectator * Properly handle demo files without SC packet to dictate which chars exist Add a /load command letting you load a demo file without rejoining the demo server (the CS packet will not be properly handled but I cannot think of a single tangible problem this causes lol) Make sure all DEMO CT messages are colored properly * Fix logs bleeding into each other if you disabled logging or you joined a demo server after leaving another * Prevent logging even if log_filename is defined because a user might disable auto logging when running the game * Fix custom shownames not appearing in the IC logs * Set up new logic for max_wait, meaning that dead air being skipped is prioritized with MS (IC chat) packets being used as the anchor. * Better logic for min_wait to only affect important packets (IC Chat) * Fix encoding not being performed on packets that are saved to the .demo file, resulting in characters having something like "#1" in their message breaking that specific message * Fix a weird setup in courtroom.cpp that happened out of the merge and bugged the code Fix aopacket being busted up as well * add missing feature flags to the demoserver * use random port * move writing to the demo file to a function * only listen on localhost Co-authored-by: oldmud0 * remove copypasta * add a help to the demo server * fix empty demo disconnecting the server * tell the user how to begin Co-authored-by: scatterflower Co-authored-by: stonedDiscord <10584181+stonedDiscord@users.noreply.github.com> Co-authored-by: stonedDiscord Co-authored-by: oldmud0 --- include/aoapplication.h | 9 ++ include/aopacket.h | 2 +- include/demoserver.h | 55 +++++++ src/aoapplication.cpp | 4 + src/aopacket.cpp | 10 +- src/courtroom.cpp | 26 ++-- src/demoserver.cpp | 298 ++++++++++++++++++++++++++++++++++++ src/lobby.cpp | 11 +- src/packet_distribution.cpp | 48 ++++-- src/text_file_functions.cpp | 11 ++ 10 files changed, 447 insertions(+), 27 deletions(-) create mode 100644 include/demoserver.h create mode 100644 src/demoserver.cpp diff --git a/include/aoapplication.h b/include/aoapplication.h index 0e003a815..e96fefb5a 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -3,6 +3,7 @@ #include "aopacket.h" #include "datatypes.h" +#include "demoserver.h" #include "discord_rich_presence.h" #include "bass.h" @@ -27,6 +28,8 @@ #include #include +#include + class NetworkManager; class Lobby; class Courtroom; @@ -261,6 +264,9 @@ class AOApplication : public QApplication { // directory if it doesn't exist. bool append_to_file(QString p_text, QString p_file, bool make_dir = false); + // Append to the currently open demo file if there is one + void append_to_demofile(QString packet_string); + // Appends the argument string to serverlist.txt void write_to_serverlist_txt(QString p_line); @@ -458,6 +464,9 @@ class AOApplication : public QApplication { void *user); static void doBASSreset(); + QElapsedTimer demo_timer; + DemoServer* demo_server = nullptr; + private: const int RELEASE = 2; const int MAJOR_VERSION = 8; diff --git a/include/aopacket.h b/include/aopacket.h index 6d1debadd..794025c5f 100644 --- a/include/aopacket.h +++ b/include/aopacket.h @@ -12,7 +12,7 @@ class AOPacket { QString get_header() { return m_header; } QStringList &get_contents() { return m_contents; } - QString to_string(); + QString to_string(bool encoded = false); void net_encode(); void net_decode(); diff --git a/include/demoserver.h b/include/demoserver.h new file mode 100644 index 000000000..b21811b72 --- /dev/null +++ b/include/demoserver.h @@ -0,0 +1,55 @@ +#ifndef DEMOSERVER_H +#define DEMOSERVER_H + +#include "aopacket.h" + +#include +#include +#include +#include +#include +#include +#include + +class DemoServer : public QObject +{ + Q_OBJECT +public: + explicit DemoServer(QObject *parent = nullptr); + + bool server_started = false; + int port = 27088; + int max_wait = -1; + int min_wait = -1; + +private: + void handle_packet(AOPacket packet); + void load_demo(QString filename); + + QTcpServer* tcp_server; + QTcpSocket* client_sock = nullptr; + bool client_connected = false; + bool partial_packet = false; + QString temp_packet = ""; + QQueue demo_data; + QString sc_packet; + int num_chars = 0; + QString p_path; + QTimer *timer; + int elapsed_time = 0; + +private slots: + void accept_connection(); + void destroy_connection(); + void recv_data(); + void client_disconnect(); + void playback(); + +public slots: + void start_server(); + +signals: + +}; + +#endif // DEMOSERVER_H diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index fa58ab848..a7f41e3e3 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/aopacket.cpp b/src/aopacket.cpp index bb6ac73bd..a40d2ef7e 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("#", "") + .replaceInStrings("%", "") + .replaceInStrings("$", "") + .replaceInStrings("&", ""); + return m_header + "#" + contents.join("#") + "#%"; } void AOPacket::net_encode() diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 99a330cf0..11bd84303 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1159,13 +1159,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 +1286,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 +1328,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(); @@ -2519,7 +2523,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; @@ -2967,7 +2971,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 && @@ -3132,7 +3136,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; } @@ -3794,9 +3798,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); @@ -4749,7 +4750,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 000000000..9f1e533da --- /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; +} diff --git a/src/lobby.cpp b/src/lobby.cpp index 954c30a8c..f1a61f4cf 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 @@ -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 f21c4acd0..822b2dc98 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -102,6 +102,19 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) 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 1ea27bff4..ffb59c405 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -178,6 +178,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 +253,13 @@ QVector 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; }