////////////////////////////////////////////////////////////////////// // This file is part of Remere's Map Editor ////////////////////////////////////////////////////////////////////// // Remere's Map Editor is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Remere's Map Editor is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . ////////////////////////////////////////////////////////////////////// #include "main.h" #include "live_client.h" #include "live_tab.h" #include "live_action.h" #include "editor.h" #include LiveClient::LiveClient() : LiveSocket(), readMessage(), queryNodeList(), currentOperation(), resolver(nullptr), socket(nullptr), editor(nullptr), stopped(false) { // } LiveClient::~LiveClient() { // } bool LiveClient::connect(const std::string& address, uint16_t port) { NetworkConnection& connection = NetworkConnection::getInstance(); if (!connection.start()) { setLastError("The previous connection has not been terminated yet."); return false; } auto& service = connection.get_service(); if (!resolver) { resolver = std::make_shared(service); } if (!socket) { socket = std::make_shared(service); } boost::asio::ip::tcp::resolver::query query(address, std::to_string(port)); resolver->async_resolve(query, [this](const boost::system::error_code& error, boost::asio::ip::tcp::resolver::iterator endpoint_iterator) -> void { if (error) { logMessage("Error: " + error.message()); } else { tryConnect(endpoint_iterator); } }); /* if(!client->WaitOnConnect(5, 0)) { if(log) log->Disconnect(); last_err = "Connection timed out."; client->Destroy(); client = nullptr; delete connection; return false; } if(!client->IsConnected()) { if(log) log->Disconnect(); last_err = "Connection refused by peer."; client->Destroy(); client = nullptr; delete connection; return false; } if(log) log->Message("Connection established!"); */ return true; } void LiveClient::tryConnect(boost::asio::ip::tcp::resolver::iterator endpoint_iterator) { if (stopped) { return; } if (endpoint_iterator == boost::asio::ip::tcp::resolver::iterator()) { return; } logMessage("Joining server " + endpoint_iterator->host_name() + ":" + endpoint_iterator->service_name() + "..."); boost::asio::async_connect(*socket, endpoint_iterator, [this](boost::system::error_code error, boost::asio::ip::tcp::resolver::iterator endpoint_iterator) -> void { if (!socket->is_open()) { tryConnect(++endpoint_iterator); } else if (error) { if (handleError(error)) { tryConnect(++endpoint_iterator); } else { wxTheApp->CallAfter([this]() { close(); g_gui.CloseLiveEditors(this); }); } } else { socket->set_option(boost::asio::ip::tcp::no_delay(true), error); if (error) { wxTheApp->CallAfter([this]() { close(); }); return; } sendHello(); receiveHeader(); } }); } void LiveClient::close() { if (resolver) { resolver->cancel(); } if (socket) { socket->close(); } if (log) { log->Message("Disconnected from server."); log->Disconnect(); log = nullptr; } stopped = true; } bool LiveClient::handleError(const boost::system::error_code& error) { if (error == boost::asio::error::eof || error == boost::asio::error::connection_reset) { wxTheApp->CallAfter([this]() { log->Message(wxString() + getHostName() + ": disconnected."); close(); }); return true; } else if (error == boost::asio::error::connection_aborted) { logMessage("You have left the server."); return true; } return false; } std::string LiveClient::getHostName() const { if (!socket) { return "not connected"; } return socket->remote_endpoint().address().to_string(); } void LiveClient::receiveHeader() { readMessage.position = 0; boost::asio::async_read(*socket, boost::asio::buffer(readMessage.buffer, 4), [this](const boost::system::error_code& error, size_t bytesReceived) -> void { if (error) { if (!handleError(error)) { logMessage(wxString() + getHostName() + ": " + error.message()); } } else if (bytesReceived < 4) { logMessage(wxString() + getHostName() + ": Could not receive header[size: " + std::to_string(bytesReceived) + "], disconnecting client."); } else { receive(readMessage.read()); } }); } void LiveClient::receive(uint32_t packetSize) { readMessage.buffer.resize(readMessage.position + packetSize); boost::asio::async_read(*socket, boost::asio::buffer(&readMessage.buffer[readMessage.position], packetSize), [this](const boost::system::error_code& error, size_t bytesReceived) -> void { if (error) { if (!handleError(error)) { logMessage(wxString() + getHostName() + ": " + error.message()); } } else if (bytesReceived < readMessage.buffer.size() - 4) { logMessage(wxString() + getHostName() + ": Could not receive packet[size: " + std::to_string(bytesReceived) + "], disconnecting client."); } else { wxTheApp->CallAfter([this]() { parsePacket(std::move(readMessage)); receiveHeader(); }); } }); } void LiveClient::send(NetworkMessage& message) { memcpy(&message.buffer[0], &message.size, 4); boost::asio::async_write(*socket, boost::asio::buffer(message.buffer, message.size + 4), [this](const boost::system::error_code& error, size_t bytesTransferred) -> void { if (error) { logMessage(wxString() + getHostName() + ": " + error.message()); } }); } void LiveClient::updateCursor(const Position& position) { LiveCursor cursor; cursor.id = 77; // Unimportant, server fixes it for us cursor.pos = position; cursor.color = wxColor( g_settings.getInteger(Config::CURSOR_RED), g_settings.getInteger(Config::CURSOR_GREEN), g_settings.getInteger(Config::CURSOR_BLUE), g_settings.getInteger(Config::CURSOR_ALPHA) ); NetworkMessage message; message.write(PACKET_CLIENT_UPDATE_CURSOR); writeCursor(message, cursor); send(message); } LiveLogTab* LiveClient::createLogWindow(wxWindow* parent) { MapTabbook* mtb = dynamic_cast(parent); ASSERT(mtb); log = newd LiveLogTab(mtb, this); log->Message("New Live mapping session started."); return log; } MapTab* LiveClient::createEditorWindow() { MapTabbook* mtb = dynamic_cast(g_gui.tabbook); ASSERT(mtb); MapTab* edit = newd MapTab(mtb, editor); edit->OnSwitchEditorMode(g_gui.IsSelectionMode() ? SELECTION_MODE : DRAWING_MODE); return edit; } void LiveClient::sendHello() { NetworkMessage message; message.write(PACKET_HELLO_FROM_CLIENT); message.write(__RME_VERSION_ID__); message.write(__LIVE_NET_VERSION__); message.write(g_gui.GetCurrentVersionID()); message.write(nstr(name)); message.write(nstr(password)); send(message); } void LiveClient::sendNodeRequests() { if (queryNodeList.empty()) { return; } NetworkMessage message; message.write(PACKET_REQUEST_NODES); message.write(queryNodeList.size()); for (uint32_t node : queryNodeList) { message.write(node); } send(message); queryNodeList.clear(); } void LiveClient::sendChanges(DirtyList& dirtyList) { ChangeList& changeList = dirtyList.GetChanges(); if (changeList.empty()) { return; } mapWriter.reset(); for (Change* change : changeList) { switch (change->getType()) { case CHANGE_TILE: { const Position& position = static_cast(change->getData())->getPosition(); sendTile(mapWriter, editor->map.getTile(position), &position); break; } default: break; } } mapWriter.endNode(); NetworkMessage message; message.write(PACKET_CHANGE_LIST); std::string data(reinterpret_cast(mapWriter.getMemory()), mapWriter.getSize()); message.write(data); send(message); } void LiveClient::sendChat(const wxString& chatMessage) { NetworkMessage message; message.write(PACKET_CLIENT_TALK); message.write(nstr(chatMessage)); send(message); } void LiveClient::sendReady() { NetworkMessage message; message.write(PACKET_READY_CLIENT); send(message); } void LiveClient::queryNode(int32_t ndx, int32_t ndy, bool underground) { uint32_t nd = 0; nd |= ((ndx >> 2) << 18); nd |= ((ndy >> 2) << 4); nd |= (underground ? 1 : 0); queryNodeList.insert(nd); } void LiveClient::parsePacket(NetworkMessage message) { uint8_t packetType; while (message.position < message.buffer.size()) { packetType = message.read(); switch (packetType) { case PACKET_HELLO_FROM_SERVER: parseHello(message); break; case PACKET_KICK: parseKick(message); break; case PACKET_ACCEPTED_CLIENT: parseClientAccepted(message); break; case PACKET_CHANGE_CLIENT_VERSION: parseChangeClientVersion(message); break; case PACKET_SERVER_TALK: parseServerTalk(message); break; case PACKET_NODE: parseNode(message); break; case PACKET_CURSOR_UPDATE: parseCursorUpdate(message); break; case PACKET_START_OPERATION: parseStartOperation(message); break; case PACKET_UPDATE_OPERATION: parseUpdateOperation(message); break; default: { log->Message("Unknown packet receieved!"); close(); break; } } } } void LiveClient::parseHello(NetworkMessage& message) { ASSERT(editor == nullptr); editor = newd Editor(g_gui.copybuffer, this); Map& map = editor->map; map.setName("Live Map - " + message.read()); map.setWidth(message.read()); map.setHeight(message.read()); createEditorWindow(); } void LiveClient::parseKick(NetworkMessage& message) { const std::string& kickMessage = message.read(); close(); g_gui.PopupDialog("Disconnected", wxstr(kickMessage), wxOK); } void LiveClient::parseClientAccepted(NetworkMessage& message) { sendReady(); } void LiveClient::parseChangeClientVersion(NetworkMessage& message) { ClientVersionID clientVersion = static_cast(message.read()); if (!g_gui.CloseAllEditors()) { close(); return; } wxString error; wxArrayString warnings; g_gui.LoadVersion(clientVersion, error, warnings); sendReady(); } void LiveClient::parseServerTalk(NetworkMessage& message) { const std::string& speaker = message.read(); const std::string& chatMessage = message.read(); log->Chat( wxstr(speaker), wxstr(chatMessage) ); } void LiveClient::parseNode(NetworkMessage& message) { uint32_t ind = message.read(); // Extract node position int32_t ndx = ind >> 18; int32_t ndy = (ind >> 4) & 0x3FFF; bool underground = ind & 1; Action* action = editor->actionQueue->createAction(ACTION_REMOTE); receiveNode(message, *editor, action, ndx, ndy, underground); editor->actionQueue->addAction(action); g_gui.RefreshView(); g_gui.UpdateMinimap(); } void LiveClient::parseCursorUpdate(NetworkMessage& message) { LiveCursor cursor = readCursor(message); cursors[cursor.id] = cursor; g_gui.RefreshView(); } void LiveClient::parseStartOperation(NetworkMessage& message) { const std::string& operation = message.read(); currentOperation = wxstr(operation); g_gui.SetStatusText("Server Operation in Progress: " + currentOperation + "... (0%)"); } void LiveClient::parseUpdateOperation(NetworkMessage& message) { int32_t percent = message.read(); if (percent >= 100) { g_gui.SetStatusText("Server Operation Finished."); } else { g_gui.SetStatusText("Server Operation in Progress: " + currentOperation + "... (" + std::to_string(percent) + "%)"); } }