diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 7fc0974c..3cec5dd2 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -236,23 +236,23 @@ - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -365,8 +365,8 @@ 0 0 - 543 - 600 + 508 + 665 @@ -893,8 +893,8 @@ 0 0 - 443 - 74 + 442 + 77 @@ -1081,10 +1081,10 @@ - 8 + 0 0 - 431 - 341 + 425 + 419 @@ -1499,8 +1499,8 @@ 0 0 - 430 - 521 + 98 + 28 @@ -1544,8 +1544,8 @@ 0 0 - 430 - 521 + 98 + 28 @@ -1589,8 +1589,8 @@ 0 0 - 430 - 521 + 98 + 28 @@ -1634,8 +1634,8 @@ 0 0 - 430 - 521 + 98 + 28 @@ -1679,8 +1679,8 @@ 0 0 - 430 - 521 + 98 + 28 @@ -1730,8 +1730,8 @@ 0 0 - 430 - 521 + 98 + 28 @@ -2431,8 +2431,8 @@ 0 0 - 118 - 118 + 101 + 101 @@ -2693,7 +2693,7 @@ 0 0 1287 - 22 + 21 @@ -2742,6 +2742,10 @@ + + + + @@ -3022,6 +3026,21 @@ Export Map Stitch Image... + + + Connect to Collab + + + + + Create Collab Session + + + + + Join Collab Session + + @@ -3034,6 +3053,7 @@ AdjustingStackedWidget QStackedWidget
adjustingstackedwidget.h
+ 1 GraphicsView diff --git a/include/collabsession.h b/include/collabsession.h new file mode 100644 index 00000000..d726ddd9 --- /dev/null +++ b/include/collabsession.h @@ -0,0 +1,31 @@ +#ifndef COLLABSESSION_H +#define COLLABSESSION_H + +#include "mainwindow.h" +#include + +#define COMMAND_BLOCKS_CHANGED 0x1 + +#define SERVER_MESSAGE_CREATED_SESSION 0x1 +#define SERVER_MESSAGE_JOINED_SESSION 0x2 +#define SERVER_MESSAGE_BROADCAST_COMMAND 0x3 + +class CollabSession +{ +public: + CollabSession(MainWindow *mainWindow); + static void init(MainWindow *mainWindow); + static bool connect(QString host, int port); + static void createSession(QString sessionName); + static void joinSession(QString sessionName); + static QByteArray prepareSocketMessage(QByteArray message, int messageType); + static void onBlockChanged(int x, int y, Block prevBlock, Block newBlock); + void processMessage(); + MainWindow *mainWindow = nullptr; +private: + static void handleBlocksChangedCommand(QByteArray message); + QTcpSocket *socket = nullptr; + QByteArray messageBuffer; +}; + +#endif // COLLABSESSION_H diff --git a/include/mainwindow.h b/include/mainwindow.h index f6b382c2..a6abd6ca 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -1,6 +1,7 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include #include #include #include @@ -232,6 +233,10 @@ private slots: void on_actionRegion_Map_Editor_triggered(); + void on_actionConnect_to_Collab_triggered(); + void on_actionCreate_Collab_Session_triggered(); + void on_actionJoin_Collab_Session_triggered(); + private: Ui::MainWindow *ui; TilesetEditor *tilesetEditor = nullptr; diff --git a/porymap.pro b/porymap.pro index 04fb94e2..a176c409 100644 --- a/porymap.pro +++ b/porymap.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core gui qml +QT += core gui qml network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -15,6 +15,7 @@ ICON = resources/icons/porymap.icns QMAKE_CXXFLAGS += -std=c++11 -Wall SOURCES += src/core/block.cpp \ + src/collabsession.cpp \ src/core/blockdata.cpp \ src/core/event.cpp \ src/core/heallocation.cpp \ @@ -134,6 +135,7 @@ HEADERS += include/core/block.h \ include/ui/regionmapeditor.h \ include/ui/newmappopup.h \ include/ui/mapimageexporter.h \ + include/collabsession.h \ include/config.h \ include/editor.h \ include/mainwindow.h \ diff --git a/src/collabsession.cpp b/src/collabsession.cpp new file mode 100644 index 00000000..1677ae1c --- /dev/null +++ b/src/collabsession.cpp @@ -0,0 +1,161 @@ +#include "collabsession.h" +#include "log.h" + +CollabSession::CollabSession(MainWindow *mainWindow) { + this->mainWindow = mainWindow; +} + +CollabSession *collabInstance = nullptr; + +void CollabSession::init(MainWindow *mainWindow) { + if (collabInstance) { + delete collabInstance; + } + collabInstance = new CollabSession(mainWindow); +} + +bool CollabSession::connect(QString host, int port) { + if (!collabInstance) return false; + + collabInstance->socket = new QTcpSocket(); + collabInstance->socket->connectToHost(host, port); + + QObject::connect(collabInstance->socket, &QTcpSocket::connected, [=](){ + logInfo("Socket connected to collab server..."); + }); + QObject::connect(collabInstance->socket, &QTcpSocket::disconnected, [=](){ + logInfo("Socket disconnected from collab server..."); + }); + QObject::connect(collabInstance->socket, &QTcpSocket::readyRead, [=](){ + collabInstance->processMessage(); + }); + + if (!collabInstance->socket->waitForConnected(10000)) { + logError(collabInstance->socket->errorString()); + delete collabInstance->socket; + collabInstance->socket = nullptr; + return false; + } + + return true; +} + +void CollabSession::processMessage() { + QByteArray newContent = collabInstance->socket->readAll(); + this->messageBuffer.append(newContent); + // Process all the available messages in the read buffer. + while (true) { + if (this->messageBuffer.size() < 12) { + break; + } + + uint32_t signature = this->messageBuffer.at(0) | + (this->messageBuffer.at(1) << 8) | + (this->messageBuffer.at(2) << 16) | + (this->messageBuffer.at(3) << 24); + if (signature != 0x98765432) { + // Invalid signature from server. Unclear how to recover--just disconnect. + logError("Invalid message signature received from collab server. Disconnecting..."); + this->socket->disconnectFromHost(); + delete this->socket; + this->socket = nullptr; + break; + } + + uint32_t payloadSize = this->messageBuffer.at(4) | + (this->messageBuffer.at(5) << 8) | + (this->messageBuffer.at(6) << 16) | + (this->messageBuffer.at(7) << 24); + int messageSize = 12 + payloadSize; + if (this->messageBuffer.size() < messageSize) { + break; + } + + uint32_t messageType = this->messageBuffer.at(8) | + (this->messageBuffer.at(9) << 8) | + (this->messageBuffer.at(10) << 16) | + (this->messageBuffer.at(11) << 24); + + QByteArray message = this->messageBuffer.left(messageSize); + message.remove(0, 12); + this->messageBuffer = this->messageBuffer.remove(0, messageSize); + if (message.size() < 2) { + break; + } + + switch (messageType) { + case SERVER_MESSAGE_BROADCAST_COMMAND: + int commandType = (message.at(1) << 8) | message.at(0); + switch (commandType) { + case COMMAND_BLOCKS_CHANGED: + this->handleBlocksChangedCommand(message); + break; + } + break; + } + } +} + +void CollabSession::createSession(QString sessionName) { + QByteArray msg = prepareSocketMessage(sessionName.toLocal8Bit(), 0x1); + logInfo(QString("Joining session: %1").arg(collabInstance->socket->write(QByteArray(msg)))); +} + +void CollabSession::joinSession(QString sessionName) { + QByteArray msg = prepareSocketMessage(sessionName.toLocal8Bit(), 0x2); + logInfo(QString("Joining session: %1").arg(collabInstance->socket->write(QByteArray(msg)))); +} + +QByteArray CollabSession::prepareSocketMessage(QByteArray message, int messageType) { + QByteArray header = QByteArray(); + // 4-byte little endian signature is 0x12345678. + header.append(0x78); + header.append(0x56); + header.append(0x34); + header.append(0x12); + // 4-byte little endian payload size. + header.append(message.size() & 0xff); + header.append((message.size() >> 8) & 0xff); + header.append((message.size() >> 16) & 0xff); + header.append((message.size() >> 24) & 0xff); + // 4-byte little endian message type. + header.append(messageType & 0xff); + header.append((messageType >> 8) & 0xff); + header.append((messageType >> 16) & 0xff); + header.append((messageType >> 24) & 0xff); + // Append payload. + return header + message; +} + +void CollabSession::handleBlocksChangedCommand(QByteArray message) { + uint16_t numBlocks = (message.at(3) << 8) | message.at(2); + if (message.size() != 4 + numBlocks * 6) + return; + + for (int i = 0; i < numBlocks; i++) { + int index = 4 + i * 6; + int x = (uint8_t(message.at(index + 1)) << 8) | uint8_t(message.at(index)); + int y = (uint8_t(message.at(index + 3)) << 8) | uint8_t(message.at(index + 2)); + uint16_t metatileId = (uint8_t(message.at(index + 5)) << 8) | uint8_t(message.at(index + 4)); + collabInstance->mainWindow->setBlock(x, y, metatileId, 0, 0, true, false); + } +} + +void CollabSession::onBlockChanged(int x, int y, Block prevBlock, Block newBlock) { + if (!collabInstance || !collabInstance->socket) return; + if (prevBlock.rawValue() == newBlock.rawValue()) return; + + QByteArray message; + message.append(COMMAND_BLOCKS_CHANGED & 0xFF); + message.append((COMMAND_BLOCKS_CHANGED >> 8) & 0xFF); + message.append(0x1); + message.append('\0'); + message.append(x & 0xFF); + message.append((x >> 8) & 0xFF); + message.append(y & 0xFF); + message.append((y >> 8) & 0xFF); + message.append(newBlock.tile & 0xFF); + message.append((newBlock.tile >> 8) & 0xFF); + QByteArray msg = CollabSession::prepareSocketMessage(message, 0x3); + collabInstance->socket->write(QByteArray(msg)); +} diff --git a/src/core/map.cpp b/src/core/map.cpp index 3db6daeb..12cfcff2 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -2,6 +2,7 @@ #include "map.h" #include "imageproviders.h" #include "scripting.h" +#include "collabsession.h" #include "editcommands.h" @@ -383,6 +384,7 @@ void Map::setBlock(int x, int y, Block block, bool enableScriptCallback) { layout->blockdata->blocks->replace(i, block); if (enableScriptCallback) { Scripting::cb_MetatileChanged(x, y, prevBlock, block); + CollabSession::onBlockChanged(x, y, prevBlock, block); } } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d98ed68a..5d987b2f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -10,6 +10,7 @@ #include "currentselectedmetatilespixmapitem.h" #include "customattributestable.h" #include "scripting.h" +#include "collabsession.h" #include "adjustingstackedwidget.h" #include "draggablepixmapitem.h" #include "editcommands.h" @@ -394,6 +395,7 @@ bool MainWindow::openProject(QString dir) { this->setProjectSpecificUIVisibility(); Scripting::init(this); + CollabSession::init(this); bool already_open = isProjectOpen() && (editor->project->root == dir); if (!already_open) { editor->closeProject(); @@ -1430,6 +1432,23 @@ void MainWindow::on_actionMap_Shift_triggered() on_toolButton_Shift_clicked(); } +void MainWindow::on_actionConnect_to_Collab_triggered() +{ + if (!CollabSession::connect("35.223.92.12", 4000)) { + logInfo("Failed to connect to collab session"); + } +} + +void MainWindow::on_actionCreate_Collab_Session_triggered() +{ + CollabSession::createSession("TestSession"); +} + +void MainWindow::on_actionJoin_Collab_Session_triggered() +{ + CollabSession::joinSession("TestSession"); +} + void MainWindow::onWheelZoom(int s) { // Don't zoom the map when the user accidentally scrolls while performing a magic fill. (ctrl + middle button click) if (!(QApplication::mouseButtons() & Qt::MiddleButton)) {